From c48c9f789114c62ef12d999c3881f5f0bb4aeeeb Mon Sep 17 00:00:00 2001 From: Matteo Westerwinter <m.westerwinter@fz-juelich.de> Date: Tue, 18 Feb 2025 17:00:12 +0100 Subject: [PATCH] "/search/a?" is now handeled like "/search/?" refactored aggregation into static method instead of using sum --- tests/test_search_aggregations.py | 227 ++++++++++++++++++++++++++++++ toardb/timeseries/crud.py | 35 ++--- toardb/timeseries/timeseries.py | 13 +- 3 files changed, 255 insertions(+), 20 deletions(-) diff --git a/tests/test_search_aggregations.py b/tests/test_search_aggregations.py index 426d0a7..9ead2be 100644 --- a/tests/test_search_aggregations.py +++ b/tests/test_search_aggregations.py @@ -176,6 +176,233 @@ class TestApps: ) db.execute("COMMIT") + def test_search_base(self, client, db): + response = client.get("/search/a?") + expected_status_code = 200 + assert response.status_code == expected_status_code + expected_resp = [ + { + "id": 1, + "label": "CMA", + "order": 1, + "sampling_frequency": "hourly", + "aggregation": "mean", + "data_start_date": "2003-09-07T15:30:00+00:00", + "data_end_date": "2016-12-31T14:30:00+00:00", + "data_origin": "instrument", + "data_origin_type": "measurement", + "provider_version": "N/A", + "sampling_height": 7.0, + "additional_metadata": {}, + "doi": "", + "coverage": -1.0, + "station": { + "id": 2, + "codes": ["SDZ54421"], + "name": "Shangdianzi", + "coordinates": {"lat": 40.65, "lng": 117.106, "alt": 293.9}, + "coordinate_validation_status": "not checked", + "country": "China", + "state": "Beijing Shi", + "type": "unknown", + "type_of_area": "unknown", + "timezone": "Asia/Shanghai", + "additional_metadata": {"dummy_info": "Here is some more information about the station"}, + "roles": [], + "annotations": [], + "aux_images": [], + "aux_docs": [], + "aux_urls": [], + "globalmeta": { + "mean_topography_srtm_alt_90m_year1994": -999.0, + "mean_topography_srtm_alt_1km_year1994": -999.0, + "max_topography_srtm_relative_alt_5km_year1994": -999.0, + "min_topography_srtm_relative_alt_5km_year1994": -999.0, + "stddev_topography_srtm_relative_alt_5km_year1994": -999.0, + "climatic_zone_year2016": "6 (warm temperate dry)", + "htap_region_tier1_year2010": "11 (MDE Middle East: S. Arabia, Oman, etc, Iran, Iraq)", + "dominant_landcover_year2012": "11 (Cropland, rainfed, herbaceous cover)", + "landcover_description_25km_year2012": "", + "dominant_ecoregion_year2017": "-1 (undefined)", + "ecoregion_description_25km_year2017": "", + "distance_to_major_road_year2020": -999.0, + "mean_stable_nightlights_1km_year2013": -999.0, + "mean_stable_nightlights_5km_year2013": -999.0, + "max_stable_nightlights_25km_year2013": -999.0, + "max_stable_nightlights_25km_year1992": -999.0, + "mean_population_density_250m_year2015": -1.0, + "mean_population_density_5km_year2015": -1.0, + "max_population_density_25km_year2015": -1.0, + "mean_population_density_250m_year1990": -1.0, + "mean_population_density_5km_year1990": -1.0, + "max_population_density_25km_year1990": -1.0, + "mean_nox_emissions_10km_year2015": -999.0, + "mean_nox_emissions_10km_year2000": -999.0, + "toar1_category": "unclassified", + }, + "changelog": [ + { + "datetime": "2023-07-15T19:27:09.463245+00:00", + "description": "station created", + "old_value": "", + "new_value": "", + "station_id": 2, + "author_id": 1, + "type_of_change": "created", + } + ], + }, + "variable": { + "name": "toluene", + "longname": "toluene", + "displayname": "Toluene", + "cf_standardname": "mole_fraction_of_toluene_in_air", + "units": "nmol mol-1", + "chemical_formula": "C7H8", + "id": 7, + }, + "programme": {"id": 0, "name": "", "longname": "", "homepage": "", "description": ""}, + "roles": [ + { + "id": 2, + "role": "resource provider", + "status": "active", + "contact": { + "id": 4, + "organisation": { + "id": 1, + "name": "UBA", + "longname": "Umweltbundesamt", + "kind": "government", + "city": "Dessau-Roßlau", + "postcode": "06844", + "street_address": "Wörlitzer Platz 1", + "country": "Germany", + "homepage": "https://www.umweltbundesamt.de", + "contact_url": "mailto:immission@uba.de", + }, + }, + } + ], + }, + { + "id": 2, + "label": "CMA", + "order": 1, + "sampling_frequency": "hourly", + "aggregation": "mean", + "data_start_date": "2003-09-07T15:30:00+00:00", + "data_end_date": "2016-12-31T14:30:00+00:00", + "data_origin": "instrument", + "data_origin_type": "measurement", + "provider_version": "N/A", + "sampling_height": 7.0, + "additional_metadata": { + "original_units": {"since_19740101000000": "nmol/mol"}, + "measurement_method": "uv_abs", + "absorption_cross_section": "Hearn 1961", + "ebas_metadata_19740101000000_29y": { + "Submitter": "Unknown, Lady, lady.unknown@unknown.com, some long division name, SHORT, , 111 Streetname, , zipcode, Boulder, CO, USA", + "Data level": "2", + "Frameworks": "GAW-WDCRG NOAA-ESRL", + "Station code": "XXX", + "Station name": "Secret", + }, + }, + "doi": "", + "coverage": -1.0, + "station": { + "id": 3, + "codes": ["China_test8"], + "name": "Test_China", + "coordinates": {"lat": 36.256, "lng": 117.106, "alt": 1534.0}, + "coordinate_validation_status": "not checked", + "country": "China", + "state": "Shandong Sheng", + "type": "unknown", + "type_of_area": "unknown", + "timezone": "Asia/Shanghai", + "additional_metadata": {}, + "roles": [], + "annotations": [], + "aux_images": [], + "aux_docs": [], + "aux_urls": [], + "globalmeta": { + "mean_topography_srtm_alt_90m_year1994": -999.0, + "mean_topography_srtm_alt_1km_year1994": -999.0, + "max_topography_srtm_relative_alt_5km_year1994": -999.0, + "min_topography_srtm_relative_alt_5km_year1994": -999.0, + "stddev_topography_srtm_relative_alt_5km_year1994": -999.0, + "climatic_zone_year2016": "6 (warm temperate dry)", + "htap_region_tier1_year2010": "10 (SAF Sub Saharan/sub Sahel Africa)", + "dominant_landcover_year2012": "10 (Cropland, rainfed)", + "landcover_description_25km_year2012": "", + "dominant_ecoregion_year2017": "-1 (undefined)", + "ecoregion_description_25km_year2017": "", + "distance_to_major_road_year2020": -999.0, + "mean_stable_nightlights_1km_year2013": -999.0, + "mean_stable_nightlights_5km_year2013": -999.0, + "max_stable_nightlights_25km_year2013": -999.0, + "max_stable_nightlights_25km_year1992": -999.0, + "mean_population_density_250m_year2015": -1.0, + "mean_population_density_5km_year2015": -1.0, + "max_population_density_25km_year2015": -1.0, + "mean_population_density_250m_year1990": -1.0, + "mean_population_density_5km_year1990": -1.0, + "max_population_density_25km_year1990": -1.0, + "mean_nox_emissions_10km_year2015": -999.0, + "mean_nox_emissions_10km_year2000": -999.0, + "toar1_category": "unclassified", + }, + "changelog": [ + { + "datetime": "2023-08-15T21:16:20.596545+00:00", + "description": "station created", + "old_value": "", + "new_value": "", + "station_id": 3, + "author_id": 1, + "type_of_change": "created", + } + ], + }, + "variable": { + "name": "o3", + "longname": "ozone", + "displayname": "Ozone", + "cf_standardname": "mole_fraction_of_ozone_in_air", + "units": "nmol mol-1", + "chemical_formula": "O3", + "id": 5, + }, + "programme": {"id": 0, "name": "", "longname": "", "homepage": "", "description": ""}, + "roles": [ + { + "id": 1, + "role": "resource provider", + "status": "active", + "contact": { + "id": 5, + "organisation": { + "id": 2, + "name": "FZJ", + "longname": "Forschungszentrum Jülich", + "kind": "research", + "city": "Jülich", + "postcode": "52425", + "street_address": "Wilhelm-Johnen-Straße", + "country": "Germany", + "homepage": "https://www.fz-juelich.de", + "contact_url": "mailto:toar-data@fz-juelich.de", + }, + }, + } + ], + }, + ] + assert response.json() == expected_resp + def test_search_single(self, client, db): response = client.get("/search/a?id=2") expected_status_code = 200 diff --git a/toardb/timeseries/crud.py b/toardb/timeseries/crud.py index f5a13a1..c1ad7c2 100644 --- a/toardb/timeseries/crud.py +++ b/toardb/timeseries/crud.py @@ -204,21 +204,24 @@ class TimeseriesQuery: self.query = query self.fields = fields self.lconstr_roles = lconstr_roles - - def __add__(self, other): - if self.fields != other.fields: - raise ValueError("Fields of subquerys are diffrent") - - return TimeseriesQuery( - True, - self.query.union(other.query) if other.sign else self.query.except_(other.query), - self.fields, - self.lconstr_roles or other.lconstr_roles, - ) - - def __radd__(self, other): - return self if other == 0 else self + other # To make sum() work + @staticmethod + def aggregate(querys): + aggregated_query = next(querys) + for query in querys: + if aggregated_query.fields != query.fields: + raise ValueError("Fields of subquerys are diffrent") + + aggregated_query = TimeseriesQuery( + True, + aggregated_query.query.union(query.query) + if query.sign + else aggregated_query.query.except_(query.query), + aggregated_query.fields, + aggregated_query.lconstr_roles or query.lconstr_roles, + ) + return aggregated_query + @staticmethod def from_query_params(query_params, db, endpoint="timeseries", sign=True): limit, offset, fields, format, filters = create_filter(query_params, endpoint) @@ -303,11 +306,11 @@ def search_all_aggreagtion(db, path_params, signs, query_params_list, lts=False) endpoint = "timeseries" if lts else "search" try: - return sum( + return TimeseriesQuery.aggregate( TimeseriesQuery.from_query_params(query_params, db, endpoint, sign) for sign, query_params in zip(signs, query_params_list) ).adapt_objects(db) - + except (KeyError, ValueError) as e: status_code = 400 return JSONResponse(status_code=status_code, content=str(e)) diff --git a/toardb/timeseries/timeseries.py b/toardb/timeseries/timeseries.py index 445cefd..91df21b 100644 --- a/toardb/timeseries/timeseries.py +++ b/toardb/timeseries/timeseries.py @@ -50,10 +50,15 @@ def search_all_timeseries(request: Request, db: Session = Depends(get_db)): @router.get('/search/a', response_model=List[schemas.Timeseries] | List[schemas.TimeseriesFields], response_model_exclude_none=True, response_model_exclude_unset=True) def search_all_timeseries_aggregations(request: Request, db: Session = Depends(get_db)): urls = re.split(r"(?=[+-]\D)", "+" + request.url.query)[1:] - - signs = [ url.startswith("+") for url in urls] - query_params = [get_query_params(url[1:]) for url in urls] - return crud.search_all_aggreagtion(db, path_params=request.path_params, signs=signs, query_params_list=query_params) + if urls: + signs = [url.startswith("+") for url in urls] + query_params = [get_query_params(url[1:]) for url in urls] + return crud.search_all_aggreagtion( + db, path_params=request.path_params, signs=signs, query_params_list=query_params + ) + else: + updated_query_params = get_query_params(request.url.query) + return crud.search_all(db, path_params=request.path_params, query_params=updated_query_params) -- GitLab