diff --git a/app/models.py b/app/models.py index 841d57e..b01eddf 100644 --- a/app/models.py +++ b/app/models.py @@ -63,6 +63,27 @@ 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 +156,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 +164,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..79a6394 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", @@ -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 @@ -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):