From e1112e7934a7646d695ded6ef05f89721bf0b7ae Mon Sep 17 00:00:00 2001 From: Fernanda Carles Date: Sun, 22 Mar 2026 16:14:34 -0300 Subject: [PATCH 1/3] Add optional recorded sorting to measurements endpoint --- app/models.py | 22 ++++++++++++++++++++-- app/schemas.py | 6 ++++++ tests/service.py | 38 ++++++++++++++++++++++++++++++++++++-- 3 files changed, 62 insertions(+), 4 deletions(-) diff --git a/app/models.py b/app/models.py index 841d57e..2069411 100644 --- a/app/models.py +++ b/app/models.py @@ -63,6 +63,24 @@ class Measurement: + @staticmethod + def default_order(): + return [ + sqlalchemy.asc(measurements.c.description).nulls_last(), + sqlalchemy.asc(measurements.c.source), + sqlalchemy.asc(measurements.c.sensor), + ] + + @staticmethod + def sort_order(sort): + if sort is None: + return Measurement.default_order() + + if sort == "-recorded": + return [sqlalchemy.desc(measurements.c.recorded), *Measurement.default_order()] + + return [sqlalchemy.asc(measurements.c.recorded), *Measurement.default_order()] + @staticmethod async def store(db, measurements_): insert = measurements.insert() @@ -135,7 +153,7 @@ async def stats(db, query): measurements.c.latitude, measurements.c.longitude, ) - select = select.order_by(sqlalchemy.asc(measurements.c.description)) + select = select.order_by(*Measurement.default_order()) select = Measurement.filter(select, query) return await db.fetch_all(select) @@ -143,7 +161,7 @@ async def stats(db, query): @staticmethod async def retrieve(db, query): select = measurements.select() - select = select.order_by(sqlalchemy.asc(measurements.c.description)) + select = select.order_by(*Measurement.sort_order(query.sort)) select = Measurement.filter(select, query) return await db.fetch_all(select) diff --git a/app/schemas.py b/app/schemas.py index 24904d0..f596be4 100644 --- a/app/schemas.py +++ b/app/schemas.py @@ -159,6 +159,12 @@ class QueryParams: title="Distance", description="Include measurements that are this kilometers far from the target", ) + sort: Optional[str] = Query( + None, + title="Sort", + description="Sort measurements by recorded time. Use '-recorded' for descending order", + regex="^-?recorded$", + ) @validator("start") def only_recent(cls, v): diff --git a/tests/service.py b/tests/service.py index 86b3886..5022d88 100644 --- a/tests/service.py +++ b/tests/service.py @@ -43,7 +43,7 @@ "co2": 1000.0, "longitude": -57.521369, "latitude": -25.194156, - "recorded": "2020-10-24T20:47:57.370721Z", + "recorded": "2020-10-24T20:49:57.370721Z", }, { "sensor": "nullable", @@ -59,7 +59,7 @@ "co2": None, "longitude": -57.521369, "latitude": -25.194156, - "recorded": "2020-10-24T20:47:57.370721Z", + "recorded": "2020-10-24T20:48:57.370721Z", }, { "sensor": "test", @@ -299,6 +299,38 @@ def test_status(client): assert response.json() == status +@pytest.mark.dependency(depends=["test_record"]) +def test_query_sort_recorded_ascending(client): + query = { + "start": "1984-04-24T00:00:00", + "sort": "recorded", + } + + response = client.get(f"/api/v1/measurements?{urlencode(query)}") + assert response.status_code == 200 + assert response.json() == [ + measurements[2], + measurements[1], + measurements[0], + ] + + +@pytest.mark.dependency(depends=["test_record"]) +def test_query_sort_recorded_descending(client): + query = { + "start": "1984-04-24T00:00:00", + "sort": "-recorded", + } + + response = client.get(f"/api/v1/measurements?{urlencode(query)}") + assert response.status_code == 200 + assert response.json() == [ + measurements[0], + measurements[1], + measurements[2], + ] + + @pytest.mark.dependency( depends=[ "test_query", @@ -306,6 +338,8 @@ def test_status(client): "test_distance_query", "test_aqi", "test_stats", + "test_query_sort_recorded_ascending", + "test_query_sort_recorded_descending", ] ) def test_delete_provider(client): From d130a3cd054e3d751564886d9612615c724edf8b Mon Sep 17 00:00:00 2001 From: Fernanda Carles Date: Thu, 7 May 2026 20:34:44 -0300 Subject: [PATCH 2/3] apply black formatting to app/models.py --- app/models.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/models.py b/app/models.py index 2069411..b01eddf 100644 --- a/app/models.py +++ b/app/models.py @@ -77,7 +77,10 @@ def sort_order(sort): return Measurement.default_order() if sort == "-recorded": - return [sqlalchemy.desc(measurements.c.recorded), *Measurement.default_order()] + return [ + sqlalchemy.desc(measurements.c.recorded), + *Measurement.default_order(), + ] return [sqlalchemy.asc(measurements.c.recorded), *Measurement.default_order()] From 2c87d074290ffe59d19c851545f08c39e4f1321f Mon Sep 17 00:00:00 2001 From: Fernanda Carles Date: Fri, 8 May 2026 15:50:44 -0300 Subject: [PATCH 3/3] tests: sync test_enforce_utc literals with measurement[0].recorded --- tests/service.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/service.py b/tests/service.py index 5022d88..79a6394 100644 --- a/tests/service.py +++ b/tests/service.py @@ -253,13 +253,13 @@ def test_enforce_utc(): original = measurements[0] future = copy.deepcopy(original) - future["recorded"] = "2020-10-24T21:47:57.370721+01:00" + future["recorded"] = "2020-10-24T21:49:57.370721+01:00" present = copy.deepcopy(original) - present["recorded"] = "2020-10-24T20:47:57.370721" + present["recorded"] = "2020-10-24T20:49:57.370721" past = copy.deepcopy(original) - past["recorded"] = "2020-10-24T19:47:57.370721-01:00" + past["recorded"] = "2020-10-24T19:49:57.370721-01:00" from app.schemas import Measurement