From 080bb2ec12bca4d7ede255ca33b5016d9483b78e Mon Sep 17 00:00:00 2001 From: Stefaan Lippens Date: Tue, 7 Mar 2023 18:27:49 +0100 Subject: [PATCH] Issue #134 globally require at least 1.0.0 in Connection --- CHANGELOG.md | 10 +++++++--- openeo/capabilities.py | 7 +++++++ openeo/rest/auth/cli.py | 6 +++--- openeo/rest/connection.py | 34 +++++----------------------------- tests/rest/auth/test_cli.py | 6 +++++- tests/test_capabilities.py | 9 ++++++++- tests/test_rest_session.py | 9 +++++---- tests/test_usecase1.py | 6 ++++-- 8 files changed, 44 insertions(+), 43 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 18ad98d5b..88e26da6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,9 +20,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Removed -- Remove `ImageCollectionClient` (and related helpers), - a now unused leftover from the pre-1.0.0 versions of the openEO API - ([#134](https://github.com/Open-EO/openeo-python-client/issues/134), [#100](https://github.com/Open-EO/openeo-python-client/issues/100)) +- Dropped support for pre-1.0.0 versions of the openEO API + ([#134](https://github.com/Open-EO/openeo-python-client/issues/134)): + - Remove `ImageCollectionClient` and related helpers + (now unused leftovers from version 0.4.0 and earlier). + ([#134](https://github.com/Open-EO/openeo-python-client/issues/134), [#100](https://github.com/Open-EO/openeo-python-client/issues/100)) + - Require at least version 1.0.0 of the openEO API for a back-end in `Connection` + ([#134](https://github.com/Open-EO/openeo-python-client/issues/134)) ### Fixed diff --git a/openeo/capabilities.py b/openeo/capabilities.py index e8fa5bcf1..71c6fc1cb 100644 --- a/openeo/capabilities.py +++ b/openeo/capabilities.py @@ -173,6 +173,13 @@ def accept_higher(self, other: Union[str, 'ComparableVersion']): """Other is higher than self.""" return ComparableVersion(other) > self + def require_at_least(self, other: Union[str, "ComparableVersion"]): + """Raise exception if self is not at least other.""" + if not self.at_least(other): + raise ApiVersionException( + f"openEO API version should be at least {other!s}, but got {self!s}." + ) + class ApiVersionException(RuntimeError): pass diff --git a/openeo/rest/auth/cli.py b/openeo/rest/auth/cli.py index 4933e3431..1c11d3c6a 100644 --- a/openeo/rest/auth/cli.py +++ b/openeo/rest/auth/cli.py @@ -9,6 +9,7 @@ from typing import List, Tuple from openeo import connect, Connection +from openeo.capabilities import ApiVersionException from openeo.rest.auth.config import AuthConfig, RefreshTokenStore from openeo.rest.auth.oidc import OidcProviderInfo @@ -250,9 +251,8 @@ def main_add_oidc(args): print("to config file: {c!r}".format(c=str(config.path))) con = connect(backend) - api_version = con.capabilities().api_version_check - if api_version < "1.0.0": - raise CliToolException("Backend API version is too low: {v} < 1.0.0".format(v=api_version)) + assert con.capabilities().api_version_check.at_least("1.0.0") + # Find provider ID oidc_info = con.get("/credentials/oidc", expected_status=200).json() providers = OrderedDict((p["id"], OidcProviderInfo.from_dict(p)) for p in oidc_info["providers"]) diff --git a/openeo/rest/connection.py b/openeo/rest/connection.py index 1beffbfcd..49eb1d40c 100644 --- a/openeo/rest/connection.py +++ b/openeo/rest/connection.py @@ -214,7 +214,7 @@ class Connection(RestApiConnection): Connection to an openEO backend. """ - _MINIMUM_API_VERSION = ComparableVersion("0.4.0") + _MINIMUM_API_VERSION = ComparableVersion("1.0.0") def __init__( self, url: str, auth: AuthBase = None, session: requests.Session = None, default_timeout: int = None, @@ -237,10 +237,7 @@ def __init__( self._capabilities_cache = LazyLoadCache() # Initial API version check. - if self._api_version.below(self._MINIMUM_API_VERSION): - raise ApiVersionException("OpenEO API version should be at least {m!s}, but got {v!s}".format( - m=self._MINIMUM_API_VERSION, v=self._api_version) - ) + self._api_version.require_at_least(self._MINIMUM_API_VERSION) self._auth_config = auth_config self._refresh_token_store = refresh_token_store @@ -298,7 +295,6 @@ def authenticate_basic(self, username: str = None, password: str = None) -> 'Con auth=HTTPBasicAuth(username, password) ).json() # Switch to bearer based authentication in further requests. - assert self._api_version.at_least("1.0.0") self.auth = BasicBearerAuth(access_token=resp["access_token"]) return self @@ -310,7 +306,6 @@ def _get_oidc_provider(self, provider_id: Union[str, None] = None) -> Tuple[str, Can be None if there is just one provider. :return: updated provider_id and provider info object """ - assert self._api_version.at_least("1.0.0") oidc_info = self.get("/credentials/oidc", expected_status=200).json() providers = OrderedDict((p["id"], p) for p in oidc_info["providers"]) if len(providers) < 1: @@ -413,7 +408,6 @@ def _authenticate_oidc( else: _log.warning("No OIDC refresh token to store.") token = tokens.access_token - assert self._api_version.at_least("1.0.0") if refreshable: refresh_data = OidcRefreshInfo( provider_id=provider_id, @@ -674,10 +668,7 @@ def capabilities(self) -> RESTCapabilities: ) def list_output_formats(self) -> dict: - if self._api_version.at_least("1.0.0"): - return self.list_file_formats()["output"] - else: - return self.get('/output_formats', expected_status=200).json() + return self.list_file_formats().get("output", {}) list_file_types = legacy_alias(list_output_formats, "list_file_types") @@ -928,13 +919,8 @@ def datacube_from_process(self, process_id: str, namespace: str = None, **kwargs :param kwargs: The arguments of the custom process :return: A :py:class:`DataCube`, without valid metadata, as the client is not aware of this custom process. """ - - if self._api_version.at_least("1.0.0"): - graph = PGNode(process_id, namespace=namespace, arguments=kwargs) - return DataCube(graph=graph, connection=self) - else: - raise OpenEoClientException( - "This method requires support for at least version 1.0.0 in the openEO backend.") + graph = PGNode(process_id, namespace=namespace, arguments=kwargs) + return DataCube(graph=graph, connection=self) def datacube_from_flat_graph(self, flat_graph: dict, parameters: dict = None) -> DataCube: """ @@ -945,10 +931,6 @@ def datacube_from_flat_graph(self, flat_graph: dict, parameters: dict = None) -> (and optionally parameter metadata under a "parameters" field). :return: A :py:class:`DataCube` corresponding with the operations encoded in the process graph """ - if self._api_version.below("1.0.0"): - raise OpenEoClientException( - "This method requires support for at least version 1.0.0 in the openEO backend.") - parameters = parameters or {} if "process_graph" in flat_graph: @@ -996,7 +978,6 @@ def load_collection( .. versionadded:: 0.13.0 added the ``max_cloud_cover`` argument. """ - assert self._api_version.at_least("1.0.0") return DataCube.load_collection( collection_id=collection_id, connection=self, spatial_extent=spatial_extent, temporal_extent=temporal_extent, bands=bands, properties=properties, @@ -1025,9 +1006,6 @@ def load_result( :return: a :py:class:`DataCube` """ # TODO: add check that back-end supports `load_result` process? - if self._api_version.below("1.0.0"): - raise OpenEoClientException( - "This method requires support for at least version 1.0.0 in the openEO backend.") metadata = CollectionMetadata({}, dimensions=[ SpatialDimension(name="x", extent=[]), SpatialDimension(name="y", extent=[]), @@ -1128,7 +1106,6 @@ def _build_request_with_process_graph(self, process_graph: Union[dict, Any], **k """ result = kwargs process_graph = as_flat_graph(process_graph) - assert self._api_version.at_least("1.0.0") if "process_graph" not in process_graph: process_graph = {"process_graph": process_graph} result["process"] = process_graph @@ -1244,7 +1221,6 @@ def load_disk_collection( :param options: options specific to the file format :return: the data as an ImageCollection """ - assert self._api_version.at_least("1.0.0") return DataCube.load_disk_collection( self, format, glob_pattern, **(options or {}) ) diff --git a/tests/rest/auth/test_cli.py b/tests/rest/auth/test_cli.py index 3e774b4dc..d8a12bdd7 100644 --- a/tests/rest/auth/test_cli.py +++ b/tests/rest/auth/test_cli.py @@ -4,6 +4,7 @@ import pytest +from openeo.capabilities import ApiVersionException from openeo.rest.auth import cli from openeo.rest.auth.cli import CliToolException from openeo.rest.auth.config import AuthConfig, RefreshTokenStore @@ -225,7 +226,10 @@ def test_add_oidc_use_default_client_overwrite(auth_config, requests_mock, caplo def test_add_oidc_04(auth_config, requests_mock): requests_mock.get("https://oeo.test/", json={"api_version": "0.4.0"}) - with pytest.raises(CliToolException, match="Backend API version is too low"): + with pytest.raises( + ApiVersionException, + match="openEO API version should be at least 1.0.0, but got 0.4.0", + ): cli.main(["add-oidc", "https://oeo.test"]) diff --git a/tests/test_capabilities.py b/tests/test_capabilities.py index 09372e3ed..556153c6c 100644 --- a/tests/test_capabilities.py +++ b/tests/test_capabilities.py @@ -1,6 +1,6 @@ import pytest -from openeo.capabilities import ComparableVersion +from openeo.capabilities import ComparableVersion, ApiVersionException class TestComparableVersion: @@ -135,3 +135,10 @@ def test_left_referencing(self): assert v.accept_lower("1.2.2") is True assert v.accept_lower("1.2.3") is False assert v.accept_lower("1.2.4") is False + + def test_require_at_least(self): + v = ComparableVersion("1.2.3") + v.require_at_least("1.0.0") + v.require_at_least("1.2.0") + with pytest.raises(ApiVersionException): + v.require_at_least("1.2.4") diff --git a/tests/test_rest_session.py b/tests/test_rest_session.py index cd2895a53..b89232cfc 100644 --- a/tests/test_rest_session.py +++ b/tests/test_rest_session.py @@ -56,6 +56,7 @@ @requests_mock.mock() class TestUserFiles(TestCase): + # TODO: review this test class (looks very dated), most things are probably already covered elsewhere def setUp(self): # configuration phase: define username, endpoint, parameters? @@ -121,7 +122,7 @@ def test_user_delete_file(self, m): def test_list_capabilities(self, m): capabilities = { - "api_version": "0.4.0", + "api_version": "1.0.0", "endpoints": [ {"path": "/collections", "methods": ["GET"]}, ] @@ -170,7 +171,7 @@ def test_capabilities_api_version_check(self, m): assert capabilities.api_version_check.above('1.2.2') def test_list_collections(self, m): - m.get("http://localhost:8000/api/", json={"api_version": "0.4.0"}) + m.get("http://localhost:8000/api/", json={"api_version": "1.0.0"}) con = openeo.connect(self.endpoint) collection_url = "{}/collections".format(self.endpoint) @@ -179,7 +180,7 @@ def test_list_collections(self, m): assert collections == COLLECTIONS def test_get_collection(self, m): - m.get("http://localhost:8000/api/", json={"api_version": "0.4.0"}) + m.get("http://localhost:8000/api/", json={"api_version": "1.0.0"}) con = openeo.connect(self.endpoint) collection_org = COLLECTIONS[0] @@ -190,7 +191,7 @@ def test_get_collection(self, m): assert collection == collection_org def test_get_all_processes(self, m): - m.get("http://localhost:8000/api/", json={"api_version": "0.4.0"}) + m.get("http://localhost:8000/api/", json={"api_version": "1.0.0"}) con = openeo.connect(self.endpoint) processes_url = "{}/processes".format(self.endpoint) diff --git a/tests/test_usecase1.py b/tests/test_usecase1.py index 4f3e1bcd2..d184ae03d 100644 --- a/tests/test_usecase1.py +++ b/tests/test_usecase1.py @@ -9,6 +9,8 @@ @requests_mock.mock() class TestUsecase1(TestCase): + # TODO: review this test class (looks very dated), most things are probably already covered elsewhere + _capabilities = { "api_version": "1.0.0", "endpoints": [{"path": "/credentials/basic", "methods": ["GET"]}], @@ -41,7 +43,7 @@ def test_viewing_list_jobs(self, m): assert jobs == [job_info] def test_viewing_data(self, m): - m.get("http://localhost:8000/api/", json={"api_version": "0.4.0"}) + m.get("http://localhost:8000/api/", json={"api_version": "1.0.0"}) m.get("http://localhost:8000/api/collections", json={"collections": [{"product_id": "sentinel2_subset"}]}) m.get("http://localhost:8000/api/collections/sentinel2_subset", json={"product_id": "sentinel2_subset"}) @@ -55,7 +57,7 @@ def test_viewing_data(self, m): self.assertEqual(data_info["product_id"], self.data_id) def test_viewing_processes(self, m): - m.get("http://localhost:8000/api/", json={"api_version": "0.4.0"}) + m.get("http://localhost:8000/api/", json={"api_version": "1.0.0"}) con = openeo.connect(self.endpoint) m.get("http://localhost:8000/api/processes", json={"processes": [{"process_id": "calculate_ndvi"}]})