Skip to content

Commit e577200

Browse files
committed
Issue #668 eliminate FederationExtension abstraction
- for user API that's simpler to navigate - revert to methods iso properties (to allow future tweaks, e.g. return parsed object instead of raw dicts)
1 parent ebe9f9c commit e577200

9 files changed

+106
-98
lines changed

docs/api.rst

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,6 @@ openeo.rest.models
7676
.. automodule:: openeo.rest.models.general
7777
:members:
7878

79-
.. automodule:: openeo.rest.models.federation_extension
80-
:members: FederationExtension
81-
8279
.. automodule:: openeo.rest.models.logs
8380
:members: LogEntry, normalize_log_level
8481

docs/federation-extension.rst

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,22 @@ in a couple of resources.
2424
.. versionadded:: 0.38.0
2525
initial support to access federation extension related metadata.
2626

27+
.. warning:: this API is experimental and subject to change.
28+
29+
2730
Backend details
2831
---------------
2932

3033
Participating backends in a federation are listed under the ``federation`` field
3134
of the capabilities document (``GET /``) and can be inspected
32-
using :py:meth:`OpenEoCapabilities.get_federation() <openeo.rest.capabilities.OpenEoCapabilities.get_federation>`:
35+
using :py:meth:`OpenEoCapabilities.ext_federation_backend_details() <openeo.rest.capabilities.OpenEoCapabilities.ext_federation_backend_details>`:
3336

3437
.. code-block:: python
3538
3639
import openeo
3740
connection = openeo.connect(url=...)
3841
capabilities = connection.capabilities()
39-
print("Federated backends:", capabilities.get_federation())
42+
print("Federated backends:", capabilities.ext_federation_backend_details())
4043
4144
4245
Unavailable backends (``federation:missing``)
@@ -57,12 +60,11 @@ and can be inspected as follows:
5760
connection = openeo.connect(url=...)
5861
collections = connection.list_collections()
5962
print("Number of collections:", len(collections))
60-
print("Missing federation components:", collections.ext_federation.missing)
63+
print("Missing federation components:", collections.ext_federation_missing())
6164
6265
6366
Note that the ``collections`` object in this example, returned by
6467
:py:meth:`Connection.list_collections() <openeo.rest.connection.Connection.list_collections>`,
6568
acts at the surface as a simple list of dictionaries with collection metadata,
6669
but also provides additional properties/methods like
67-
:py:attr:`ext_federation <openeo.rest.models.general.CollectionListingResponse.ext_federation>`.
68-
This is an accessor (an instance of :py:class:`FederationExtension <openeo.rest.models.federation_extension.FederationExtension>`)
70+
:py:attr:`ext_federation_missing() <openeo.rest.models.general.CollectionListingResponse.ext_federation_missing>`.

openeo/rest/capabilities.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from typing import Dict, List, Optional, Union
22

33
from openeo.internal.jupyter import render_component
4+
from openeo.rest.models import federation_extension
45
from openeo.util import deep_get
56
from openeo.utils.version import ApiVersionException, ComparableVersion
67

@@ -54,16 +55,13 @@ def list_plans(self) -> List[dict]:
5455
def _repr_html_(self):
5556
return render_component("capabilities", data=self.capabilities, parameters={"url": self.url})
5657

57-
def get_federation(self) -> Union[Dict[str, dict], None]:
58+
def ext_federation_backend_details(self) -> Union[Dict[str, dict], None]:
5859
"""
5960
Lists all back-ends (with details, such as URL) that are part of the federation
6061
if this backend acts as a federated backend,
6162
as specified in the openEO Federation Extension.
62-
Returns ``None`` otherwise
63+
Returns ``None`` otherwise.
6364
6465
.. versionadded:: 0.38.0
6566
"""
66-
# TODO: also check related conformance class in `/conformance`?
67-
# TODO: refactor into FederationExtension
68-
# TODO: return a richer object instead of raw dicts?
69-
return self.get("federation")
67+
return federation_extension.get_backend_details(data=self.capabilities)
Lines changed: 18 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,26 @@
11
import logging
2-
from typing import List, Union
2+
from typing import Dict, List, Union
33

44
_log = logging.getLogger(__name__)
55

6-
class FederationExtension:
6+
def get_backend_details(data: dict) -> Union[Dict[str, dict], None]:
77
"""
8-
Wrapper the openEO Federation extension as defined by
9-
https://github.com/Open-EO/openeo-api/tree/master/extensions/federation
10-
11-
.. seealso:: :ref:`federation-extension`
8+
Get federated backend details from capabilities document (``GET /``)
9+
at "federation" field
1210
"""
11+
# TODO: return a richer object instead of raw dicts?
12+
return data.get("federation", None)
1313

14-
__slots__ = ["_data"]
15-
16-
def __init__(self, data: dict):
17-
self._data = data
18-
19-
@property
20-
def missing(self) -> Union[List[str], None]:
21-
"""
22-
Get the ``federation:missing`` property (if any) of the resource,
23-
which lists back-ends that were not available during the request.
24-
25-
Example usage with collection listing request
26-
(using :py:meth:`~openeo.rest.connection.Connection.list_collections()`):
27-
28-
.. code-block:: pycon
2914

30-
>>> collections = connection.list_collections()
31-
>>> collections.ext_federation.missing
32-
["backend1"]
33-
34-
:return: list of back-end IDs that were not available.
35-
Or None, when ``federation:missing`` is not present in response.
36-
"""
37-
return self._data.get("federation:missing", None)
38-
39-
def warn_on_missing(self, resource_name: str) -> None:
40-
"""
41-
Warn about presence of non-empty ``federation:missing`` in the resource.
42-
"""
43-
missing = self.missing
44-
if missing:
45-
_log.warning(f"Partial {resource_name}: missing federation components: {missing!r}.")
15+
def get_federation_missing(data: dict, *, resource_name: str, auto_warn: bool = False) -> Union[List[str], None]:
16+
"""
17+
Get "federation:missing" field from response data, if present.
18+
:param data: response data
19+
:param resource_name: name of the requested resource (listing)
20+
:param auto_warn: whether to automatically log a warning if missing federation components are detected.
21+
"""
22+
# TODO: options to return richer objects (e.g. resolve backend id to URL/title)
23+
missing = data.get("federation:missing", None)
24+
if auto_warn and missing:
25+
_log.warning(f"Partial {resource_name}: missing federation components: {missing!r}.")
26+
return missing

openeo/rest/models/general.py

Lines changed: 69 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from typing import List, Optional, Union
66

77
from openeo.internal.jupyter import render_component
8-
from openeo.rest.models.federation_extension import FederationExtension
8+
from openeo.rest.models import federation_extension
99
from openeo.rest.models.logs import LogEntry, normalize_log_level
1010

1111

@@ -43,8 +43,6 @@ class CollectionListingResponse(list):
4343
but now also provides methods/properties to access additional response data.
4444
4545
:param response_data: response data from a ``GET /collections`` request
46-
:param warn_on_federation_missing: whether to automatically warn
47-
about missing federation components.
4846
4947
.. seealso:: :py:meth:`openeo.rest.connection.Connection.list_collections()`
5048
@@ -53,12 +51,12 @@ class CollectionListingResponse(list):
5351

5452
__slots__ = ["_data"]
5553

56-
def __init__(self, response_data: dict, *, warn_on_federation_missing: bool = True):
54+
def __init__(self, response_data: dict):
5755
self._data = response_data
5856
# Mimic original list of collection metadata dictionaries
5957
super().__init__(response_data["collections"])
60-
if warn_on_federation_missing:
61-
self.ext_federation.warn_on_missing(resource_name="collection listing")
58+
59+
self.ext_federation_missing(auto_warn=True)
6260

6361
def _repr_html_(self):
6462
return render_component(component="collections", data=self)
@@ -68,11 +66,20 @@ def links(self) -> List[Link]:
6866
"""Get links related to this resource."""
6967
return [Link.from_dict(d) for d in self._data.get("links", [])]
7068

71-
@property
72-
def ext_federation(self) -> FederationExtension:
73-
"""Accessor for federation extension data related to this resource."""
74-
return FederationExtension(self._data)
69+
def ext_federation_missing(self, *, auto_warn: bool = False) -> Union[None, List[str]]:
70+
"""
71+
List of backends IDs (from the federation)
72+
that were not available during the resource listing request.
73+
74+
:param auto_warn: whether to automatically log a warning if missing federation components are detected.
7575
76+
.. seealso:: :ref:`federation-extension`
77+
78+
.. warning:: this API is experimental and subject to change.
79+
"""
80+
return federation_extension.get_federation_missing(
81+
data=self._data, resource_name="collection listing", auto_warn=auto_warn
82+
)
7683

7784
class ProcessListingResponse(list):
7885
"""
@@ -86,22 +93,21 @@ class ProcessListingResponse(list):
8693
but now also provides methods/properties to access additional response data.
8794
8895
:param response_data: response data from a ``GET /processes`` request
89-
:param warn_on_federation_missing: whether to automatically warn
90-
about missing federation components.
9196
9297
.. seealso:: :py:meth:`openeo.rest.connection.Connection.list_processes()`
9398
9499
.. versionadded:: 0.38.0
95100
"""
96101

102+
97103
__slots__ = ["_data"]
98104

99-
def __init__(self, response_data: dict, *, warn_on_federation_missing: bool = True):
105+
def __init__(self, response_data: dict):
100106
self._data = response_data
101107
# Mimic original list of process metadata dictionaries
102108
super().__init__(response_data["processes"])
103-
if warn_on_federation_missing:
104-
self.ext_federation.warn_on_missing(resource_name="process listing")
109+
110+
self.ext_federation_missing(auto_warn=True)
105111

106112
def _repr_html_(self):
107113
return render_component(
@@ -113,11 +119,20 @@ def links(self) -> List[Link]:
113119
"""Get links related to this resource."""
114120
return [Link.from_dict(d) for d in self._data.get("links", [])]
115121

116-
@property
117-
def ext_federation(self) -> FederationExtension:
118-
"""Accessor for federation extension data related to this resource."""
119-
return FederationExtension(self._data)
122+
def ext_federation_missing(self, *, auto_warn: bool = False) -> Union[None, List[str]]:
123+
"""
124+
List of backends IDs (from the federation)
125+
that were not available during the resource listing request.
126+
127+
:param auto_warn: whether to automatically log a warning if missing federation components are detected.
120128
129+
.. seealso:: :ref:`federation-extension`
130+
131+
.. warning:: this API is experimental and subject to change.
132+
"""
133+
return federation_extension.get_federation_missing(
134+
data=self._data, resource_name="process listing", auto_warn=auto_warn
135+
)
121136

122137
class JobListingResponse(list):
123138
"""
@@ -132,22 +147,21 @@ class JobListingResponse(list):
132147
but now also provides methods/properties to access additional response data.
133148
134149
:param response_data: response data from a ``GET /jobs`` request
135-
:param warn_on_federation_missing: whether to automatically warn
136-
about missing federation components.
137150
138151
.. seealso:: :py:meth:`openeo.rest.connection.Connection.list_jobs()`
139152
140153
.. versionadded:: 0.38.0
141154
"""
142155

156+
143157
__slots__ = ["_data"]
144158

145-
def __init__(self, response_data: dict, *, warn_on_federation_missing: bool = True):
159+
def __init__(self, response_data: dict):
146160
self._data = response_data
147161
# Mimic original list of process metadata dictionaries
148162
super().__init__(response_data["jobs"])
149-
if warn_on_federation_missing:
150-
self.ext_federation.warn_on_missing(resource_name="job listing")
163+
164+
self.ext_federation_missing(auto_warn=True)
151165

152166
def _repr_html_(self):
153167
return render_component(component="data-table", data=self, parameters={"columns": "jobs"})
@@ -157,11 +171,20 @@ def links(self) -> List[Link]:
157171
"""Get links related to this resource."""
158172
return [Link.from_dict(d) for d in self._data.get("links", [])]
159173

160-
@property
161-
def ext_federation(self) -> FederationExtension:
162-
"""Accessor for federation extension data related to this resource."""
163-
return FederationExtension(self._data)
174+
def ext_federation_missing(self, *, auto_warn: bool = False) -> Union[None, List[str]]:
175+
"""
176+
List of backends IDs (from the federation)
177+
that were not available during the resource listing request.
178+
179+
:param auto_warn: whether to automatically log a warning if missing federation components are detected.
164180
181+
.. seealso:: :ref:`federation-extension`
182+
183+
.. warning:: this API is experimental and subject to change.
184+
"""
185+
return federation_extension.get_federation_missing(
186+
data=self._data, resource_name="job listing", auto_warn=auto_warn
187+
)
165188

166189
class LogsResponse(list):
167190
"""
@@ -178,20 +201,17 @@ class LogsResponse(list):
178201
179202
:param response_data: response data from a ``GET /jobs/{job_id}/logs``
180203
or ``GET /services/{service_id}/logs`` request.
181-
:param warn_on_federation_missing: whether to automatically warn
182-
about missing federation components.
183204
184205
.. seealso:: :py:meth:`~openeo.rest.job.BatchJob.logs()`
185206
and :py:meth:`~openeo.rest.service.Service.logs()`
186207
187208
.. versionadded:: 0.38.0
188209
"""
189210

211+
190212
__slots__ = ["_data"]
191213

192-
def __init__(
193-
self, response_data: dict, *, log_level: Optional[str] = None, warn_on_federation_missing: bool = True
194-
):
214+
def __init__(self, response_data: dict, *, log_level: Optional[str] = None):
195215
self._data = response_data
196216

197217
logs = response_data.get("logs", [])
@@ -214,8 +234,8 @@ def accept_level(level: str) -> bool:
214234
# Mimic original list of process metadata dictionaries
215235
super().__init__(logs)
216236

217-
if warn_on_federation_missing:
218-
self.ext_federation.warn_on_missing(resource_name="log listing")
237+
self.ext_federation_missing(auto_warn=True)
238+
219239

220240
def _repr_html_(self):
221241
return render_component(component="logs", data=self)
@@ -230,7 +250,17 @@ def links(self) -> List[Link]:
230250
"""Get links related to this resource."""
231251
return [Link.from_dict(d) for d in self._data.get("links", [])]
232252

233-
@property
234-
def ext_federation(self) -> FederationExtension:
235-
"""Accessor for federation extension data related to this resource."""
236-
return FederationExtension(self._data)
253+
def ext_federation_missing(self, *, auto_warn: bool = False) -> Union[None, List[str]]:
254+
"""
255+
List of backends IDs (from the federation)
256+
that were not available during the resource listing request.
257+
258+
:param auto_warn: whether to automatically log a warning if missing federation components are detected.
259+
260+
.. seealso:: :ref:`federation-extension`
261+
262+
.. warning:: this API is experimental and subject to change.
263+
"""
264+
return federation_extension.get_federation_missing(
265+
data=self._data, resource_name="log listing", auto_warn=auto_warn
266+
)

tests/rest/models/test_general.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,4 +59,4 @@ def test_links(self):
5959
)
6060
def test_federation_missing(self, data, expected):
6161
collections = CollectionListingResponse(data)
62-
assert collections.ext_federation.missing == expected
62+
assert collections.ext_federation_missing() == expected

tests/rest/test_capabilities.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ def test_list_plans(self):
5151
assert OpenEoCapabilities({"billing": {"plans": [{"name": "free"}]}}).list_plans() == [{"name": "free"}]
5252

5353
def test_federation_absent(self):
54-
assert OpenEoCapabilities({}).get_federation() is None
54+
assert OpenEoCapabilities({}).ext_federation_backend_details() is None
5555

5656
def test_federation_present(self):
5757
data = {
@@ -62,7 +62,7 @@ def test_federation_present(self):
6262
},
6363
}
6464
capabilities = OpenEoCapabilities(data)
65-
assert capabilities.get_federation() == {
65+
assert capabilities.ext_federation_backend_details() == {
6666
"a": {"url": "https://a.test/openeo/v2", "title": "A backend"},
6767
"bb": {"url": "https://openeo.b.test/v9"},
6868
}

0 commit comments

Comments
 (0)