From b5b4e733bb4f516e75257324c2b5975c4b01f9b1 Mon Sep 17 00:00:00 2001 From: Andrea Orlandi Date: Fri, 12 Dec 2025 16:29:39 +0100 Subject: [PATCH 1/2] [api] Add alpha 'get_user_info' function --- src/picterra/forge_client.py | 11 +++++++++++ src/picterra/tracer_client.py | 10 ++++++++++ tests/test_forge_client.py | 13 +++++++++++++ tests/test_tracer_client.py | 13 +++++++++++++ 4 files changed, 47 insertions(+) diff --git a/src/picterra/forge_client.py b/src/picterra/forge_client.py index 93ed14c..1eb8f49 100644 --- a/src/picterra/forge_client.py +++ b/src/picterra/forge_client.py @@ -38,6 +38,17 @@ class ForgeClient(BaseAPIClient): def __init__(self, **kwargs): super().__init__("public/api/v2/", **kwargs) + def get_user_info(self) -> dict: + """ + Get information about the current user + + This endpoint is in alpha stage and may change without warning. + """ + resp = self.sess.get(self._full_url("users/me/")) + if not resp.ok: + raise APIError(resp.text) + return resp.json() + def upload_raster( self, filename: str, diff --git a/src/picterra/tracer_client.py b/src/picterra/tracer_client.py index a036aa3..1788d21 100644 --- a/src/picterra/tracer_client.py +++ b/src/picterra/tracer_client.py @@ -56,6 +56,16 @@ def _upload_plot_ids(self, plot_ids: List[str]) -> str: _check_resp_is_ok(resp, "Failure uploading plots file") return upload_id + def get_user_info(self) -> dict: + """ + Get information about the current user + + This endpoint is in alpha stage and may change without warning. + """ + resp = self.sess.get(self._full_url("users/me/")) + _check_resp_is_ok(resp, "Failed to get user info") + return resp.json() + def list_methodologies( self, search: Optional[str] = None, diff --git a/tests/test_forge_client.py b/tests/test_forge_client.py index ae7d527..9a83376 100644 --- a/tests/test_forge_client.py +++ b/tests/test_forge_client.py @@ -1180,3 +1180,16 @@ def test_folder_creation(monkeypatch): client = _client(monkeypatch) add_mock_folder_creation_response("folder-id", "folder-name") assert client.create_folder("folder-name") == "folder-id" + + +@responses.activate +def test_get_user_info(monkeypatch): + client = _client(monkeypatch) + data = { + "organization_id": "user-id", + "user_id": "user-id", + "organization_name": "organization-name", + } + _add_api_response(detector_api_url("users/me/"), json=data) + assert client.get_user_info() == data + assert len(responses.calls) == 1 diff --git a/tests/test_tracer_client.py b/tests/test_tracer_client.py index 139e673..3485ada 100644 --- a/tests/test_tracer_client.py +++ b/tests/test_tracer_client.py @@ -529,3 +529,16 @@ def test_set_authorization_grants(monkeypatch): ) grants = client.set_authorization_grants("plots_group", "a-plots-group-id", {"grants": new_grants}) assert grants == {"grants": new_grants} and len(responses.calls) == 1 + + +@responses.activate +def test_get_user_info(monkeypatch): + client: TracerClient = _client(monkeypatch, platform="plots_analysis") + data = { + "organization_id": "user-id", + "user_id": "user-id", + "organization_name": "organization-name", + } + _add_api_response(plots_analysis_api_url("users/me/"), json=data) + assert client.get_user_info() == data + assert len(responses.calls) == 1 From f74ec670f3fd0a4169af20caa8ef34d43e75e4eb Mon Sep 17 00:00:00 2001 From: Andrea Orlandi Date: Fri, 12 Dec 2025 16:37:35 +0100 Subject: [PATCH 2/2] [style] Format with black --- examples/forge/detect_on_project.py | 1 + examples/forge/raster_management.py | 15 +- examples/tracer/plots_analysis.py | 2 +- tests/test_base_client.py | 24 +- tests/test_forge_client.py | 125 ++++++--- tests/test_tracer_client.py | 377 +++++++++++++++++----------- tests/utils.py | 5 +- 7 files changed, 347 insertions(+), 202 deletions(-) diff --git a/examples/forge/detect_on_project.py b/examples/forge/detect_on_project.py index aba697f..0228e1b 100644 --- a/examples/forge/detect_on_project.py +++ b/examples/forge/detect_on_project.py @@ -1,6 +1,7 @@ """ Demonstrate running a detector on all images within a project """ + from picterra import APIClient # Set the PICTERRA_API_KEY environment variable to define your API key diff --git a/examples/forge/raster_management.py b/examples/forge/raster_management.py index 29103d0..c2fab39 100644 --- a/examples/forge/raster_management.py +++ b/examples/forge/raster_management.py @@ -25,14 +25,13 @@ local_raster_id = client.upload_raster("data/raster1.tif", name="A short-lived raster") print("Uploaded a second local raster=", local_raster_id) # Editing the image's band specification. See https://docs.picterra.ch/imagery/#Multispectral -client.edit_raster(local_raster_id, multispectral_band_specification={ - "ranges": [ - [0, 128], [0, 128], [0, 128] - ], - "display_bands": [ - {"type": "multiband", "name": "default", "bands": [2, 1, 0]} - ] -}) +client.edit_raster( + local_raster_id, + multispectral_band_specification={ + "ranges": [[0, 128], [0, 128], [0, 128]], + "display_bands": [{"type": "multiband", "name": "default", "bands": [2, 1, 0]}], + }, +) # Deleting the image client.delete_raster(local_raster_id) print("Deleted raster=", local_raster_id) diff --git a/examples/tracer/plots_analysis.py b/examples/tracer/plots_analysis.py index c9a5156..bf0949f 100644 --- a/examples/tracer/plots_analysis.py +++ b/examples/tracer/plots_analysis.py @@ -13,7 +13,7 @@ "New analysis", ["plotid_1", "plotid_2", "plotid_3"], datetime.date.fromisoformat("2022-01-01"), - datetime.date.fromisoformat("2024-01-01") + datetime.date.fromisoformat("2024-01-01"), ) url = client.get_plots_analysis(analysis_id, plots_group_id)["url"] diff --git a/tests/test_base_client.py b/tests/test_base_client.py index a555a7a..3684c3d 100644 --- a/tests/test_base_client.py +++ b/tests/test_base_client.py @@ -65,7 +65,9 @@ def request_callback(request, uri, response_headers): time.sleep(2) return [200, response_headers, json.dumps([])] - httpretty.register_uri(httpretty.GET, detector_api_url("rasters/"), body=request_callback) + httpretty.register_uri( + httpretty.GET, detector_api_url("rasters/"), body=request_callback + ) timeout = 1 client = _client(monkeypatch, timeout=timeout) with pytest.raises(requests.exceptions.ConnectionError) as e: @@ -79,7 +81,9 @@ def request_callback(request, uri, response_headers): @responses.activate def test_headers_api_key(monkeypatch): - _add_api_response(detector_api_url("detectors/"), responses.POST, json={"id": "foobar"}) + _add_api_response( + detector_api_url("detectors/"), responses.POST, json={"id": "foobar"} + ) client = _client(monkeypatch) client.create_detector() assert len(responses.calls) == 1 @@ -88,7 +92,9 @@ def test_headers_api_key(monkeypatch): @responses.activate def test_headers_user_agent_version(monkeypatch): - _add_api_response(detector_api_url("detectors/"), responses.POST, json={"id": "foobar"}) + _add_api_response( + detector_api_url("detectors/"), responses.POST, json={"id": "foobar"} + ) client = _client(monkeypatch) client.create_detector() assert len(responses.calls) == 1 @@ -99,8 +105,12 @@ def test_headers_user_agent_version(monkeypatch): @responses.activate def test_headers_user_agent_version__fallback(monkeypatch): - _add_api_response(detector_api_url("detectors/"), responses.POST, json={"id": "foobar"},) - monkeypatch.setattr(base_client, '_get_distr_name', lambda: 'foobar') + _add_api_response( + detector_api_url("detectors/"), + responses.POST, + json={"id": "foobar"}, + ) + monkeypatch.setattr(base_client, "_get_distr_name", lambda: "foobar") client = _client(monkeypatch) client.create_detector() assert len(responses.calls) == 1 @@ -126,7 +136,9 @@ def test_results_page(): method=responses.GET, url="http://example.com/page/2", json={ - "count": 1, "next": None, "previous": "http://example.com/page/1", + "count": 1, + "next": None, + "previous": "http://example.com/page/1", "results": ["three"], }, status=200, diff --git a/tests/test_forge_client.py b/tests/test_forge_client.py index 9a83376..ee3ce85 100644 --- a/tests/test_forge_client.py +++ b/tests/test_forge_client.py @@ -79,7 +79,9 @@ def add_mock_rasters_in_folder_list_response(folder_id): } qs = {"folder": folder_id, "page_number": "1"} _add_api_response( - detector_api_url("rasters/"), json=data, match=responses.matchers.query_param_matcher(qs) + detector_api_url("rasters/"), + json=data, + match=responses.matchers.query_param_matcher(qs), ) @@ -110,7 +112,9 @@ def add_mock_rasters_in_filtered_list_response( if has_layers is not None: qs["has_vector_layers"] = bool(has_layers) _add_api_response( - detector_api_url("rasters/"), match=responses.matchers.query_param_matcher(qs), json=data + detector_api_url("rasters/"), + match=responses.matchers.query_param_matcher(qs), + json=data, ) @@ -186,21 +190,32 @@ def add_mock_detectors_list_response(string=None, tag=None, shared=None): def add_mock_detector_creation_response(**kwargs): match = responses.json_params_matcher({"configuration": kwargs}) if kwargs else None - _add_api_response(detector_api_url("detectors/"), responses.POST, json={"id": "foobar"}, match=match) + _add_api_response( + detector_api_url("detectors/"), + responses.POST, + json={"id": "foobar"}, + match=match, + ) def add_mock_detector_edit_response(d_id, **kwargs): match = responses.json_params_matcher({"configuration": kwargs}) if kwargs else None - _add_api_response(detector_api_url("detectors/%s/" % d_id), responses.PUT, status=204, match=match) + _add_api_response( + detector_api_url("detectors/%s/" % d_id), responses.PUT, status=204, match=match + ) def add_mock_detector_train_responses(detector_id): - _add_api_response(detector_api_url("detectors/%s/train/" % detector_id), responses.POST, OP_RESP) + _add_api_response( + detector_api_url("detectors/%s/train/" % detector_id), responses.POST, OP_RESP + ) def add_mock_run_dataset_recommendation_responses(detector_id): _add_api_response( - detector_api_url("detectors/%s/dataset_recommendation/" % detector_id), responses.POST, OP_RESP + detector_api_url("detectors/%s/dataset_recommendation/" % detector_id), + responses.POST, + OP_RESP, ) @@ -287,7 +302,9 @@ def add_mock_raster_upload_responses(identity_key, multispectral, cloud_coverage # Storage PUT responses.add(responses.PUT, "http://storage.example.com", status=200) # Commit - _add_api_response(detector_api_url("rasters/%s/commit/" % raster_id), responses.POST, OP_RESP) + _add_api_response( + detector_api_url("rasters/%s/commit/" % raster_id), responses.POST, OP_RESP + ) # Status, first check data = {"id": raster_id, "name": "raster1", "status": "processing"} _add_api_response(detector_api_url("rasters/%s/" % raster_id), json=data) @@ -302,13 +319,17 @@ def add_mock_detection_areas_upload_responses(raster_id): # Upload initiation data = {"upload_url": "http://storage.example.com", "upload_id": upload_id} _add_api_response( - detector_api_url("rasters/%s/detection_areas/upload/file/" % raster_id), responses.POST, data + detector_api_url("rasters/%s/detection_areas/upload/file/" % raster_id), + responses.POST, + data, ) # Storage PUT responses.add(responses.PUT, "http://storage.example.com", status=200) # Commit _add_api_response( - detector_api_url("rasters/%s/detection_areas/upload/%s/commit/" % (raster_id, upload_id)), + detector_api_url( + "rasters/%s/detection_areas/upload/%s/commit/" % (raster_id, upload_id) + ), responses.POST, OP_RESP, status=200, @@ -316,12 +337,18 @@ def add_mock_detection_areas_upload_responses(raster_id): # Status, first check data = {"status": "processing"} _add_api_response( - detector_api_url("rasters/%s/detection_areas/upload/%s/" % (raster_id, upload_id)), json=data + detector_api_url( + "rasters/%s/detection_areas/upload/%s/" % (raster_id, upload_id) + ), + json=data, ) # Status, second check data = {"status": "ready"} _add_api_response( - detector_api_url("rasters/%s/detection_areas/upload/%s/" % (raster_id, upload_id)), json=data + detector_api_url( + "rasters/%s/detection_areas/upload/%s/" % (raster_id, upload_id) + ), + json=data, ) @@ -390,7 +417,9 @@ def add_mock_vector_layer_download_responses(layer_id, polygons_num): } add_mock_operations_responses("success", results=results) url = results["download_url"] - polygons_fc = multipolygon_to_polygon_feature_collection(make_geojson_multipolygon(polygons_num)) + polygons_fc = multipolygon_to_polygon_feature_collection( + make_geojson_multipolygon(polygons_num) + ) assert len(polygons_fc["features"]) == polygons_num responses.add( responses.GET, @@ -399,13 +428,19 @@ def add_mock_vector_layer_download_responses(layer_id, polygons_num): ) return polygons_fc + def add_mock_folder_creation_response(id, name): match = responses.matchers.json_params_matcher({"name": name}) - _add_api_response(detector_api_url("folders/"), responses.POST, json={"id": id}, match=match) + _add_api_response( + detector_api_url("folders/"), responses.POST, json={"id": id}, match=match + ) def make_geojson_polygon(base=1): - return {"type": "Polygon", "coordinates": [[[0, 0], [base, 0], [base, base], [0, base], [0, 0]]]} + return { + "type": "Polygon", + "coordinates": [[[0, 0], [base, 0], [base, base], [0, base], [0, 0]]], + } def make_geojson_multipolygon(npolygons=1): @@ -424,9 +459,10 @@ def add_mock_download_result_response(op_id, num_classes): "class": {"name": f"class_{i + 1}"}, "result": { "url": f"http://storage.example.com/result_for_class_{i + 1}.geojson", - "vector_layer_id": f"layer_{i + 1}" + "vector_layer_id": f"layer_{i + 1}", }, - } for i in range(num_classes) + } + for i in range(num_classes) ], }, } @@ -479,7 +515,9 @@ def add_mock_delete_raster_response(raster_id): def add_mock_delete_detectionarea_response(raster_id): - _add_api_response(detector_api_url("rasters/%s/detection_areas/" % raster_id), responses.DELETE) + _add_api_response( + detector_api_url("rasters/%s/detection_areas/" % raster_id), responses.DELETE + ) def add_mock_delete_detector_response(detector_id): @@ -487,7 +525,9 @@ def add_mock_delete_detector_response(detector_id): def add_mock_delete_vector_layer_response(layer_id): - _add_api_response(detector_api_url("vector_layers/%s/" % layer_id), responses.DELETE) + _add_api_response( + detector_api_url("vector_layers/%s/" % layer_id), responses.DELETE + ) def add_mock_edit_vector_layer_response(layer_id, **kwargs): @@ -536,7 +576,9 @@ def add_mock_marker_creation_response(marker_id, raster_id, detector_id, coords, "text": text, } match = responses.matchers.json_params_matcher(body) - _add_api_response(detector_api_url(url), responses.POST, json={"id": marker_id}, match=match) + _add_api_response( + detector_api_url(url), responses.POST, json={"id": marker_id}, match=match + ) def add_mock_folder_detector_response(folder_id: str): @@ -598,27 +640,30 @@ def test_multipolygon_to_polygon_feature_collection(): "type": "MultiPolygon", "coordinates": [ [[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]], - [[[1, 1], [1, 2], [2, 2], [2, 1], [1, 1]]] - ] + [[[1, 1], [1, 2], [2, 2], [2, 1], [1, 1]]], + ], } fc = multipolygon_to_polygon_feature_collection(mp) assert fc == { "type": "FeatureCollection", - "features": [{ - "type": "Feature", - "properties": {}, - "geometry": { - "type": "Polygon", - "coordinates": [[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]] - } - }, { - "type": "Feature", - "properties": {}, - "geometry": { - "type": "Polygon", - "coordinates": [[[1, 1], [1, 2], [2, 2], [2, 1], [1, 1]]] - } - }] + "features": [ + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Polygon", + "coordinates": [[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]], + }, + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Polygon", + "coordinates": [[[1, 1], [1, 2], [2, 2], [2, 1], [1, 1]]], + }, + }, + ], } @@ -953,7 +998,9 @@ def test_download_result_to_feature_collection(monkeypatch): assert len(feat1["geometry"]["coordinates"]) == 10 assert isinstance(feat1["geometry"]["coordinates"][0][0][0][0], (int, float)) feat2 = fc["features"][(class_1_index + 1) % 2] - assert feat2["type"] == "Feature" and feat2["geometry"]["type"] == "MultiPolygon" + assert ( + feat2["type"] == "Feature" and feat2["geometry"]["type"] == "MultiPolygon" + ) assert feat2["properties"]["class_name"] == "class_2" assert len(feat2["geometry"]["coordinates"]) == 20 assert isinstance(feat2["geometry"]["coordinates"][0][0][0][0], (int, float)) @@ -1060,7 +1107,9 @@ def test_download_vector_layer_to_file(monkeypatch): assert fc["type"] == "FeatureCollection" assert fc == polygons_fc and len(fc["features"]) == 2 assert fc["features"][0]["geometry"]["type"] == "Polygon" - assert isinstance(fc["features"][1]["geometry"]["coordinates"][0][0][0], (int, float)) + assert isinstance( + fc["features"][1]["geometry"]["coordinates"][0][0][0], (int, float) + ) assert len(responses.calls) == 3 # POST /download, GET /operations, GET url diff --git a/tests/test_tracer_client.py b/tests/test_tracer_client.py index 3485ada..784a578 100644 --- a/tests/test_tracer_client.py +++ b/tests/test_tracer_client.py @@ -18,7 +18,10 @@ def make_geojson_polygon(base=1): - return {"type": "Polygon", "coordinates": [[[0, 0], [base, 0], [base, base], [0, base], [0, 0]]]} + return { + "type": "Polygon", + "coordinates": [[[0, 0], [base, 0], [base, base], [0, base], [0, 0]]], + } def make_geojson_multipolygon(npolygons=1): @@ -27,6 +30,7 @@ def make_geojson_multipolygon(npolygons=1): coords.append(make_geojson_polygon(i + 1)["coordinates"]) return {"type": "MultiPolygon", "coordinates": coords} + def test_plots_analysis_platform_client_base_url(monkeypatch): """ Sanity-check that the client defaults to the correct base url @@ -51,40 +55,48 @@ def test_create_plots_group(monkeypatch): plots_analysis_api_url("plots_groups/"), responses.POST, OP_RESP, - match=responses.matchers.json_params_matcher({ - "name": "name of my plot group", - "methodology_id": "eudr-cocoa-id", - "custom_columns_values": {"foo": "bar"} - }), - ) - _add_api_response(plots_analysis_api_url(f"operations/{OPERATION_ID}/"), responses.GET, { - "status": "success", - "results": {"plots_group_id": "a-plots-group"} - }) + match=responses.matchers.json_params_matcher( + { + "name": "name of my plot group", + "methodology_id": "eudr-cocoa-id", + "custom_columns_values": {"foo": "bar"}, + } + ), + ) + _add_api_response( + plots_analysis_api_url(f"operations/{OPERATION_ID}/"), + responses.GET, + {"status": "success", "results": {"plots_group_id": "a-plots-group"}}, + ) client: TracerClient = _client(monkeypatch, platform="plots_analysis") with tempfile.NamedTemporaryFile() as tmp: - _add_api_response(plots_analysis_api_url( - "plots_groups/a-plots-group/upload/commit/"), + _add_api_response( + plots_analysis_api_url("plots_groups/a-plots-group/upload/commit/"), responses.POST, OP_RESP, - match=responses.matchers.json_params_matcher({ - "files": [ - { - "upload_id": "an-upload", - "filename": os.path.basename(tmp.name), - } - ], - "overwrite": False, - }), + match=responses.matchers.json_params_matcher( + { + "files": [ + { + "upload_id": "an-upload", + "filename": os.path.basename(tmp.name), + } + ], + "overwrite": False, + } + ), ) with open(tmp.name, "w") as f: json.dump({"type": "FeatureCollection", "features": []}, f) - assert client.create_plots_group( - "name of my plot group", - "eudr-cocoa-id", - [tmp.name], - {"foo": "bar"}, - ) == "a-plots-group" + assert ( + client.create_plots_group( + "name of my plot group", + "eudr-cocoa-id", + [tmp.name], + {"foo": "bar"}, + ) + == "a-plots-group" + ) @responses.activate @@ -98,27 +110,33 @@ def test_update_plots_group_plots(monkeypatch): }, ) responses.put("https://upload.example.com/") - _add_api_response(plots_analysis_api_url(f"operations/{OPERATION_ID}/"), responses.GET, { - "status": "success", - "results": { - "plots_group_id": "group-id", - } - }) + _add_api_response( + plots_analysis_api_url(f"operations/{OPERATION_ID}/"), + responses.GET, + { + "status": "success", + "results": { + "plots_group_id": "group-id", + }, + }, + ) client: TracerClient = _client(monkeypatch, platform="plots_analysis") with tempfile.NamedTemporaryFile() as tmp: - _add_api_response(plots_analysis_api_url( - "plots_groups/group-id/upload/commit/"), + _add_api_response( + plots_analysis_api_url("plots_groups/group-id/upload/commit/"), responses.POST, OP_RESP, - match=responses.matchers.json_params_matcher({ - "files": [ - { - "filename": os.path.basename(tmp.name), - "upload_id": "an-upload", - } - ], - "overwrite": False, - }), + match=responses.matchers.json_params_matcher( + { + "files": [ + { + "filename": os.path.basename(tmp.name), + "upload_id": "an-upload", + } + ], + "overwrite": False, + } + ), ) with open(tmp.name, "w") as f: json.dump({"type": "FeatureCollection", "features": []}, f) @@ -135,35 +153,48 @@ def test_analyse_plots(monkeypatch): "upload_url": "https://upload.example.com/", }, ) - responses.put("https://upload.example.com/", match=[responses.matchers.json_params_matcher({ - "plot_ids": ["uno", "dos"], - })]) - _add_api_response(plots_analysis_api_url( - "plots_groups/a-group-id/analysis/"), + responses.put( + "https://upload.example.com/", + match=[ + responses.matchers.json_params_matcher( + { + "plot_ids": ["uno", "dos"], + } + ) + ], + ) + _add_api_response( + plots_analysis_api_url("plots_groups/a-group-id/analysis/"), responses.POST, OP_RESP, - match=responses.matchers.json_params_matcher({ - "analysis_name": "foobar", - "upload_id": "an-upload-id", - "date_from": "2023-01-01", - "date_to": "2025-01-01", - }), - ) - _add_api_response(plots_analysis_api_url(f"operations/{OPERATION_ID}/"), responses.GET, { - "status": "success", - "results": {"analysis_id": "an-analysis-id"} - }) + match=responses.matchers.json_params_matcher( + { + "analysis_name": "foobar", + "upload_id": "an-upload-id", + "date_from": "2023-01-01", + "date_to": "2025-01-01", + } + ), + ) + _add_api_response( + plots_analysis_api_url(f"operations/{OPERATION_ID}/"), + responses.GET, + {"status": "success", "results": {"analysis_id": "an-analysis-id"}}, + ) client: TracerClient = _client(monkeypatch, platform="plots_analysis") with tempfile.NamedTemporaryFile() as tmp: with open(tmp.name, "w") as f: json.dump({"type": "FeatureCollection", "features": []}, f) - assert client.analyze_plots( - "a-group-id", - "foobar", - ["uno", "dos"], - datetime.date.fromisoformat("2023-01-01"), - datetime.date.fromisoformat("2025-01-01") - ) == "an-analysis-id" + assert ( + client.analyze_plots( + "a-group-id", + "foobar", + ["uno", "dos"], + datetime.date.fromisoformat("2023-01-01"), + datetime.date.fromisoformat("2025-01-01"), + ) + == "an-analysis-id" + ) @responses.activate @@ -176,24 +207,37 @@ def test_analyse_precheck(monkeypatch): "upload_url": "https://upload.example.com/", }, ) - responses.put("https://upload.example.com/", match=[responses.matchers.json_params_matcher({ - "plot_ids": ["uno", "dos"], - })]) - _add_api_response(plots_analysis_api_url( - "plots_groups/a-group-id/analysis/precheck/"), + responses.put( + "https://upload.example.com/", + match=[ + responses.matchers.json_params_matcher( + { + "plot_ids": ["uno", "dos"], + } + ) + ], + ) + _add_api_response( + plots_analysis_api_url("plots_groups/a-group-id/analysis/precheck/"), responses.POST, OP_RESP, - match=responses.matchers.json_params_matcher({ - "analysis_name": "foobar", - "upload_id": "an-upload-id", - "date_from": "2023-01-01", - "date_to": "2025-01-01", - }), - ) - _add_api_response(plots_analysis_api_url(f"operations/{OPERATION_ID}/"), responses.GET, { - "status": "success", - "results": {"precheck_data_url": "https://precheck_data_url.example.com/"} - }) + match=responses.matchers.json_params_matcher( + { + "analysis_name": "foobar", + "upload_id": "an-upload-id", + "date_from": "2023-01-01", + "date_to": "2025-01-01", + } + ), + ) + _add_api_response( + plots_analysis_api_url(f"operations/{OPERATION_ID}/"), + responses.GET, + { + "status": "success", + "results": {"precheck_data_url": "https://precheck_data_url.example.com/"}, + }, + ) precheck = { "status": "failed", "errors": {"critical": [], "high": []}, @@ -205,13 +249,16 @@ def test_analyse_precheck(monkeypatch): with tempfile.NamedTemporaryFile() as tmp: with open(tmp.name, "w") as f: json.dump({"type": "FeatureCollection", "features": []}, f) - assert client.analyze_plots_precheck( - "a-group-id", - "foobar", - ["uno", "dos"], - datetime.date.fromisoformat("2023-01-01"), - datetime.date.fromisoformat("2025-01-01") - ) == precheck + assert ( + client.analyze_plots_precheck( + "a-group-id", + "foobar", + ["uno", "dos"], + datetime.date.fromisoformat("2023-01-01"), + datetime.date.fromisoformat("2025-01-01"), + ) + == precheck + ) @responses.activate @@ -273,17 +320,23 @@ def test_list_plots_analyses(monkeypatch): @responses.activate def test_download_plots_group(monkeypatch): - _add_api_response(plots_analysis_api_url( - "plots_groups/a-group-id/export/"), + _add_api_response( + plots_analysis_api_url("plots_groups/a-group-id/export/"), responses.POST, OP_RESP, match=responses.matchers.json_params_matcher({"format": "geojson"}), ) - _add_api_response(plots_analysis_api_url(f"operations/{OPERATION_ID}/"), responses.GET, { - "status": "success", - "results": {"download_url": "https://a-group-id.example.com/geojson"} - }) - polygons_fc = multipolygon_to_polygon_feature_collection(make_geojson_multipolygon()) + _add_api_response( + plots_analysis_api_url(f"operations/{OPERATION_ID}/"), + responses.GET, + { + "status": "success", + "results": {"download_url": "https://a-group-id.example.com/geojson"}, + }, + ) + polygons_fc = multipolygon_to_polygon_feature_collection( + make_geojson_multipolygon() + ) responses.add( responses.GET, "https://a-group-id.example.com/geojson", @@ -306,8 +359,12 @@ def test_list_plots_analysis_reports(monkeypatch): assert len(reports) == 3 assert reports[0]["name"] == "a_1" and reports[-1]["name"] == "a_3" # test search and filter - add_mock_paginated_list_response(url, qs={"report_type": "type_1", "search": "spam"}) - reports = client.list_plots_analysis_reports("my-analysis-id", search="spam", report_type="type_1") + add_mock_paginated_list_response( + url, qs={"report_type": "type_1", "search": "spam"} + ) + reports = client.list_plots_analysis_reports( + "my-analysis-id", search="spam", report_type="type_1" + ) assert len(reports) == 2 @@ -333,7 +390,7 @@ def test_list_plots_analysis_report_types(monkeypatch): {"report_type": "type_1", "name": "a_1"}, {"report_type": "type_2", "name": "a_2"}, ], - match=responses.matchers.query_param_matcher({"search": "spam"}) + match=responses.matchers.query_param_matcher({"search": "spam"}), ) reports = client.list_plots_analysis_report_types("my-analysis-id", search="spam") assert len(reports) == 2 @@ -349,24 +406,37 @@ def test_create_plots_analysis_report_precheck(monkeypatch): "upload_url": "https://upload.example.com/", }, ) - responses.put("https://upload.example.com/", match=[responses.matchers.json_params_matcher({ - "plot_ids": ["uno", "dos"], - })]) - _add_api_response(plots_analysis_api_url( - "plots_analyses/an-analysis-id/reports/precheck/"), + responses.put( + "https://upload.example.com/", + match=[ + responses.matchers.json_params_matcher( + { + "plot_ids": ["uno", "dos"], + } + ) + ], + ) + _add_api_response( + plots_analysis_api_url("plots_analyses/an-analysis-id/reports/precheck/"), responses.POST, OP_RESP, - match=responses.matchers.json_params_matcher({ - "name": "foobar", - "upload_id": "an-upload-id", - "report_type": "a-report-type", - "metadata": {"foo": "bar"}, - }), - ) - _add_api_response(plots_analysis_api_url(f"operations/{OPERATION_ID}/"), responses.GET, { - "status": "success", - "results": {"precheck_data_url": "https://precheck_data_url.example.com/"} - }) + match=responses.matchers.json_params_matcher( + { + "name": "foobar", + "upload_id": "an-upload-id", + "report_type": "a-report-type", + "metadata": {"foo": "bar"}, + } + ), + ) + _add_api_response( + plots_analysis_api_url(f"operations/{OPERATION_ID}/"), + responses.GET, + { + "status": "success", + "results": {"precheck_data_url": "https://precheck_data_url.example.com/"}, + }, + ) client: TracerClient = _client(monkeypatch, platform="plots_analysis") with tempfile.NamedTemporaryFile() as tmp: with open(tmp.name, "w") as f: @@ -376,7 +446,7 @@ def test_create_plots_analysis_report_precheck(monkeypatch): "foobar", ["uno", "dos"], "a-report-type", - metadata={"foo": "bar"} + metadata={"foo": "bar"}, ) == {"status": "passed"} @@ -390,35 +460,48 @@ def test_create_plots_analysis_report(monkeypatch): "upload_url": "https://upload.example.com/", }, ) - responses.put("https://upload.example.com/", match=[responses.matchers.json_params_matcher({ - "plot_ids": ["uno", "dos"], - })]) - _add_api_response(plots_analysis_api_url( - "plots_analyses/an-analysis-id/reports/"), + responses.put( + "https://upload.example.com/", + match=[ + responses.matchers.json_params_matcher( + { + "plot_ids": ["uno", "dos"], + } + ) + ], + ) + _add_api_response( + plots_analysis_api_url("plots_analyses/an-analysis-id/reports/"), responses.POST, OP_RESP, - match=responses.matchers.json_params_matcher({ - "name": "foobar", - "upload_id": "an-upload-id", - "report_type": "a-report-type", - "metadata": {"foo": "bar"}, - }), - ) - _add_api_response(plots_analysis_api_url(f"operations/{OPERATION_ID}/"), responses.GET, { - "status": "success", - "results": {"plots_analysis_report_id": "a-report-id"} - }) + match=responses.matchers.json_params_matcher( + { + "name": "foobar", + "upload_id": "an-upload-id", + "report_type": "a-report-type", + "metadata": {"foo": "bar"}, + } + ), + ) + _add_api_response( + plots_analysis_api_url(f"operations/{OPERATION_ID}/"), + responses.GET, + {"status": "success", "results": {"plots_analysis_report_id": "a-report-id"}}, + ) client: TracerClient = _client(monkeypatch, platform="plots_analysis") with tempfile.NamedTemporaryFile() as tmp: with open(tmp.name, "w") as f: json.dump({"type": "FeatureCollection", "features": []}, f) - assert client.create_plots_analysis_report( - "an-analysis-id", - "foobar", - ["uno", "dos"], - "a-report-type", - metadata={"foo": "bar"} - ) == "a-report-id" + assert ( + client.create_plots_analysis_report( + "an-analysis-id", + "foobar", + ["uno", "dos"], + "a-report-type", + metadata={"foo": "bar"}, + ) + == "a-report-id" + ) @responses.activate @@ -432,7 +515,7 @@ def test_get_plots_group(monkeypatch): "name": "My Plots Group", "created_at": "2025-09-29T10:04:08.143098Z", "methodology": "Coffee - EUDR", - } + }, ) plots_group = client.get_plots_group("a-plots-group") assert plots_group["id"] == "a-plots-group" @@ -450,7 +533,7 @@ def test_get_plots_analysis(monkeypatch): "name": "My Analysis", "date_from": "2023-06-06", "date_to": "2025-02-08", - "url": "https://app.picterra.ch/plots_analysis/plots_groups/136b812e-8d9c-418f-b317-8be5c7c6281d/analysis/cda443d7-5baf-483d-bb5e-fa1190180b0d/" # noqa[E501] + "url": "https://app.picterra.ch/plots_analysis/plots_groups/136b812e-8d9c-418f-b317-8be5c7c6281d/analysis/cda443d7-5baf-483d-bb5e-fa1190180b0d/", # noqa[E501] }, ) plots_analysis = client.get_plots_analysis("an-analysis-id") @@ -461,9 +544,7 @@ def test_get_plots_analysis(monkeypatch): @responses.activate def test_get_plots_analysis_report(monkeypatch): _add_api_response( - plots_analysis_api_url( - "plots_analysis_reports/a-report-id/" - ), + plots_analysis_api_url("plots_analysis_reports/a-report-id/"), responses.GET, { "id": "a-report-id", @@ -527,7 +608,9 @@ def test_set_authorization_grants(monkeypatch): {"grants": new_grants}, match=responses.matchers.json_params_matcher({"grants": new_grants}), ) - grants = client.set_authorization_grants("plots_group", "a-plots-group-id", {"grants": new_grants}) + grants = client.set_authorization_grants( + "plots_group", "a-plots-group-id", {"grants": new_grants} + ) assert grants == {"grants": new_grants} and len(responses.calls) == 1 diff --git a/tests/utils.py b/tests/utils.py index 6a063ba..a8ee98f 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -60,7 +60,7 @@ def add_mock_paginated_list_response( name_prefix: str = "a", num_results: int = 2, include_archived: Optional[bool] = None, - qs: Optional[dict] = None + qs: Optional[dict] = None, ): curr, next = str(page), str(page + 1) data1 = { @@ -69,7 +69,8 @@ def add_mock_paginated_list_response( "previous": None, "page_size": num_results, "results": [ - {"id": str(i), "name": name_prefix + "_" + str(i)} for i in range(1, num_results + 1) + {"id": str(i), "name": name_prefix + "_" + str(i)} + for i in range(1, num_results + 1) ], } qs_params = {"page_number": curr}