ERA-12680: Add v2 subject tracks and async track methods#40
Open
JoshuaVulcan wants to merge 4 commits intomainfrom
Open
ERA-12680: Add v2 subject tracks and async track methods#40JoshuaVulcan wants to merge 4 commits intomainfrom
JoshuaVulcan wants to merge 4 commits intomainfrom
Conversation
- Add `version` parameter to `ERClient.get_subject_tracks()` supporting
both v1 (flat coordinates, default) and v2 (segmented GeoJSON
FeatureCollection at /api/v2.0/subject/{id}/tracks/).
- Add `_er_url_versioned()` helper to both clients for building
versioned API URLs.
- Add async `get_subject_tracks()` with identical v1/v2 support.
- Add async `get_subject_source_tracks()` for per-source track retrieval.
- Fix async `_call()` to handle full URLs (skip `_er_url()` prepend).
- v2 forwards extra query params: show_excluded, group_by_flags,
max_speed_kmh, max_gap_ms, max_gap_seconds, max_gap_minutes.
- Add comprehensive sync and async test suites (22 new tests).
Co-authored-by: Cursor <cursoragent@cursor.com>
…rl pattern Replace the brittle `_er_url_versioned()` method (which uses regex substitution on service_root) with the cleaner `_api_root(version)` + `base_url` parameter pattern from PR #23. This adds `_api_root()`, updates `_er_url()` to accept a `base_url` param, and threads `base_url` through `_get`, `_post`, `_patch`, and `_call` on both sync and async clients. Updates get_subject_tracks(version='2.0') to use `base_url=self._api_root('v2.0')` instead of building versioned URLs via regex. Updates tests to use `_api_root()` instead of manual string replacement. Co-authored-by: Cursor <cursoragent@cursor.com>
Contributor
Author
V2 URL Convention AlignmentReplaced the Changes:
This PR is now compatible with PR #23's infrastructure and will merge cleanly once #23 lands. |
… test URL base Co-authored-by: Cursor <cursoragent@cursor.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Adds v2 (segmented) subject track retrieval support alongside the existing v1 endpoint, and expands async client parity, with new sync/async test coverage for URL construction and filtering behavior.
Changes:
- Extend sync
ERClient.get_subject_tracks()to support a v2 tracks endpoint and forward v2-specific filter params. - Add async
get_subject_tracks()(v1/v2) and asyncget_subject_source_tracks()toAsyncERClient. - Add new sync + async test modules covering v1/v2 behavior, date filtering, extra params, and 404 handling.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 11 comments.
| File | Description |
|---|---|
erclient/client.py |
Adds sync+async subject track methods with v2 endpoint support and new async per-source track retrieval. |
tests/sync_client/test_get_subject_tracks.py |
New sync tests validating v1 default behavior, v2 routing, params forwarding, and _api_root. |
tests/async_client/test_get_subject_tracks.py |
New async tests for v1/v2 tracks, per-source tracks, query params, and 404 error mapping. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
Comment on lines
+1
to
+4
| import httpx | ||
| import pytest | ||
| import respx | ||
| from datetime import datetime, timezone |
Comment on lines
+173
to
+179
| result = await er_client.get_subject_tracks( | ||
| subject_id=SUBJECT_ID, | ||
| version="2.0", | ||
| show_excluded="true", | ||
| max_speed_kmh=120.0, | ||
| max_gap_minutes=30, | ||
| ) |
Comment on lines
+133
to
+135
| result = await er_client.get_subject_tracks( | ||
| subject_id=SUBJECT_ID, version="2.0" | ||
| ) |
Comment on lines
+889
to
+892
| def get_subject_tracks(self, subject_id='', start=None, end=None, version='1.0', **kwargs): | ||
| """ | ||
| Get the latest tracks for the Subject having the given subject_id. | ||
|
|
Comment on lines
+889
to
921
| def get_subject_tracks(self, subject_id='', start=None, end=None, version='1.0', **kwargs): | ||
| """ | ||
| Get the latest tracks for the Subject having the given subject_id. | ||
|
|
||
| :param subject_id: The UUID of the subject. | ||
| :param start: datetime lower-bound filter (sent as ``since``). | ||
| :param end: datetime upper-bound filter (sent as ``until``). | ||
| :param version: API version string, either '1.0' (default, legacy flat | ||
| coordinates) or '2.0' (segmented GeoJSON FeatureCollection). | ||
| :param kwargs: Extra query params forwarded to the v2 endpoint such as | ||
| ``show_excluded``, ``group_by_flags``, ``max_speed_kmh``, | ||
| ``max_gap_ms``, ``max_gap_seconds``, ``max_gap_minutes``. | ||
| :return: Track data (format varies by version). | ||
| """ | ||
| p = {} | ||
| if start is not None and isinstance(start, datetime): | ||
| p['since'] = start.isoformat() | ||
| if end is not None and isinstance(end, datetime): | ||
| p['until'] = end.isoformat() | ||
|
|
||
| if version == '2.0': | ||
| # v2 supports additional filter params | ||
| for key in ('show_excluded', 'group_by_flags', 'max_speed_kmh', | ||
| 'max_gap_ms', 'max_gap_seconds', 'max_gap_minutes'): | ||
| if key in kwargs: | ||
| p[key] = kwargs[key] | ||
| return self._get( | ||
| path=f'subject/{subject_id}/tracks/', | ||
| base_url=self._api_root('v2.0'), | ||
| params=p, | ||
| ) | ||
|
|
||
| return self._get(path='subject/{0}/tracks'.format(subject_id), params=p) |
Comment on lines
+153
to
+156
| result = await er_client.get_subject_tracks( | ||
| subject_id=SUBJECT_ID, start=start, end=end, version="2.0" | ||
| ) | ||
|
|
| ) | ||
|
|
||
| start = datetime(2025, 1, 1, tzinfo=timezone.utc) | ||
| result = await er_client.get_subject_source_tracks( |
|
|
||
| import pytest | ||
|
|
||
| from erclient.client import ERClient |
Comment on lines
+126
to
+128
| result = er_client.get_subject_tracks( | ||
| subject_id=SUBJECT_ID, version="2.0" | ||
| ) |
Comment on lines
+1646
to
+1658
| if version == '2.0': | ||
| for key in ('show_excluded', 'group_by_flags', 'max_speed_kmh', | ||
| 'max_gap_ms', 'max_gap_seconds', 'max_gap_minutes'): | ||
| if key in kwargs: | ||
| p[key] = kwargs[key] | ||
| return await self._get( | ||
| path=f'subject/{subject_id}/tracks/', | ||
| base_url=self._api_root('v2.0'), | ||
| params=p, | ||
| ) | ||
|
|
||
| return await self._get(path=f'subject/{subject_id}/tracks', params=p) | ||
|
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
versionparameter toERClient.get_subject_tracks()to support both the v1 (flat coordinates, default) and v2 (segmented GeoJSON FeatureCollection at/api/v2.0/subject/{id}/tracks/) endpoints._er_url_versioned()helper to both sync and async clients for building version-specific API URLs.get_subject_tracks()with identical v1/v2 support toAsyncERClient.get_subject_source_tracks()for per-source track retrieval._call()to properly handle full URLs (skips_er_url()prepend when path already starts withhttp).show_excluded,group_by_flags,max_speed_kmh,max_gap_ms,max_gap_seconds,max_gap_minutes.Jira
ERA-12680
Test plan
get_subject_tracks()v1 default behavior preservedget_subject_tracks(version='2.0')hits/api/v2.0/endpointget_subject_tracks()v1 and v2 both work correctlyget_subject_source_tracks()hits correct endpointERClientNotFoundfor both versions_er_url_versioned()correctly swaps version in URL