Skip to content

Commit 75cbe2b

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 99969e1 commit 75cbe2b

File tree

8 files changed

+74
-91
lines changed

8 files changed

+74
-91
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: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,14 @@ Backend details
2929

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

3434
.. code-block:: python
3535
3636
import openeo
3737
connection = openeo.connect(url=...)
3838
capabilities = connection.capabilities()
39-
print("Federated backends:", capabilities.get_federation())
39+
print("Federated backends:", capabilities.ext_federation_backend_details())
4040
4141
4242
Unavailable backends (``federation:missing``)
@@ -57,12 +57,11 @@ and can be inspected as follows:
5757
connection = openeo.connect(url=...)
5858
collections = connection.list_collections()
5959
print("Number of collections:", len(collections))
60-
print("Missing federation components:", collections.ext_federation.missing)
60+
print("Missing federation components:", collections.ext_federation_missing)
6161
6262
6363
Note that the ``collections`` object in this example, returned by
6464
:py:meth:`Connection.list_collections() <openeo.rest.connection.Connection.list_collections>`,
6565
acts at the surface as a simple list of dictionaries with collection metadata,
6666
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>`)
67+
: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: 27 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,39 @@
11
import logging
2-
from typing import List, Union
2+
import textwrap
3+
from typing import Dict, List, Union
34

45
_log = logging.getLogger(__name__)
56

6-
class FederationExtension:
7-
"""
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`
12-
"""
137

14-
__slots__ = ["_data"]
158

16-
def __init__(self, data: dict):
17-
self._data = data
9+
def get_backend_details(data: dict) -> Union[Dict[str, dict], None]:
10+
"""
11+
Get federated backend details from capabilities document (``GET /``)
12+
at "federation" field
13+
"""
14+
# TODO: return a richer object instead of raw dicts?
15+
return data.get("federation", None)
1816

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.
2417

25-
Example usage with collection listing request
26-
(using :py:meth:`~openeo.rest.connection.Connection.list_collections()`):
18+
def get_federation_missing(data: dict, *, resource_name: str, auto_warn: bool = True) -> Union[List[str], None]:
19+
missing = data.get("federation:missing", None)
20+
if auto_warn and missing:
21+
_log.warning(f"Partial {resource_name}: missing federation components: {missing!r}.")
22+
return missing
2723

28-
.. code-block:: pycon
2924

30-
>>> collections = connection.list_collections()
31-
>>> collections.ext_federation.missing
32-
["backend1"]
25+
def get_federation_missing_doc(attribute_name: str = "ext_federation_missing", prefix: str = " ") -> str:
26+
# TODO: is there a cleaner way to append to doc strings, using some standard Sphinx API?
27+
doc = f"""
28+
.. py:attribute:: {attribute_name}
29+
:type: Union[None, List[str]]
3330
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)
31+
List of backends IDs (from the federation)
32+
that were not available during the resource listing request.
3833
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}.")
34+
.. seealso:: :ref:`federation-extension`
35+
"""
36+
return textwrap.indent(
37+
"\n\n" + textwrap.dedent(doc).strip() + "\n\n",
38+
prefix=prefix,
39+
)

openeo/rest/models/general.py

Lines changed: 33 additions & 38 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

@@ -51,14 +51,18 @@ class CollectionListingResponse(list):
5151
.. versionadded:: 0.38.0
5252
"""
5353

54-
__slots__ = ["_data"]
54+
__doc__ += federation_extension.get_federation_missing_doc()
5555

56-
def __init__(self, response_data: dict, *, warn_on_federation_missing: bool = True):
56+
__slots__ = ["_data", "ext_federation_missing"]
57+
58+
def __init__(self, response_data: dict):
5759
self._data = response_data
5860
# Mimic original list of collection metadata dictionaries
5961
super().__init__(response_data["collections"])
60-
if warn_on_federation_missing:
61-
self.ext_federation.warn_on_missing(resource_name="collection listing")
62+
63+
self.ext_federation_missing = federation_extension.get_federation_missing(
64+
data=response_data, resource_name="collection listing"
65+
)
6266

6367
def _repr_html_(self):
6468
return render_component(component="collections", data=self)
@@ -68,11 +72,6 @@ def links(self) -> List[Link]:
6872
"""Get links related to this resource."""
6973
return [Link.from_dict(d) for d in self._data.get("links", [])]
7074

71-
@property
72-
def ext_federation(self) -> FederationExtension:
73-
"""Accessor for federation extension data related to this resource."""
74-
return FederationExtension(self._data)
75-
7675

7776
class ProcessListingResponse(list):
7877
"""
@@ -94,14 +93,18 @@ class ProcessListingResponse(list):
9493
.. versionadded:: 0.38.0
9594
"""
9695

97-
__slots__ = ["_data"]
96+
__doc__ += federation_extension.get_federation_missing_doc()
9897

99-
def __init__(self, response_data: dict, *, warn_on_federation_missing: bool = True):
98+
__slots__ = ["_data", "ext_federation_missing"]
99+
100+
def __init__(self, response_data: dict):
100101
self._data = response_data
101102
# Mimic original list of process metadata dictionaries
102103
super().__init__(response_data["processes"])
103-
if warn_on_federation_missing:
104-
self.ext_federation.warn_on_missing(resource_name="process listing")
104+
105+
self.ext_federation_missing = federation_extension.get_federation_missing(
106+
data=response_data, resource_name="process listing"
107+
)
105108

106109
def _repr_html_(self):
107110
return render_component(
@@ -113,10 +116,6 @@ def links(self) -> List[Link]:
113116
"""Get links related to this resource."""
114117
return [Link.from_dict(d) for d in self._data.get("links", [])]
115118

116-
@property
117-
def ext_federation(self) -> FederationExtension:
118-
"""Accessor for federation extension data related to this resource."""
119-
return FederationExtension(self._data)
120119

121120

122121
class JobListingResponse(list):
@@ -140,14 +139,19 @@ class JobListingResponse(list):
140139
.. versionadded:: 0.38.0
141140
"""
142141

143-
__slots__ = ["_data"]
142+
__doc__ += federation_extension.get_federation_missing_doc()
143+
144144

145-
def __init__(self, response_data: dict, *, warn_on_federation_missing: bool = True):
145+
__slots__ = ["_data", "ext_federation_missing"]
146+
147+
def __init__(self, response_data: dict):
146148
self._data = response_data
147149
# Mimic original list of process metadata dictionaries
148150
super().__init__(response_data["jobs"])
149-
if warn_on_federation_missing:
150-
self.ext_federation.warn_on_missing(resource_name="job listing")
151+
152+
self.ext_federation_missing = federation_extension.get_federation_missing(
153+
data=response_data, resource_name="job listing"
154+
)
151155

152156
def _repr_html_(self):
153157
return render_component(component="data-table", data=self, parameters={"columns": "jobs"})
@@ -157,11 +161,6 @@ def links(self) -> List[Link]:
157161
"""Get links related to this resource."""
158162
return [Link.from_dict(d) for d in self._data.get("links", [])]
159163

160-
@property
161-
def ext_federation(self) -> FederationExtension:
162-
"""Accessor for federation extension data related to this resource."""
163-
return FederationExtension(self._data)
164-
165164

166165
class LogsResponse(list):
167166
"""
@@ -187,11 +186,11 @@ class LogsResponse(list):
187186
.. versionadded:: 0.38.0
188187
"""
189188

190-
__slots__ = ["_data"]
189+
__doc__ += federation_extension.get_federation_missing_doc()
191190

192-
def __init__(
193-
self, response_data: dict, *, log_level: Optional[str] = None, warn_on_federation_missing: bool = True
194-
):
191+
__slots__ = ["_data", "ext_federation_missing"]
192+
193+
def __init__(self, response_data: dict, *, log_level: Optional[str] = None):
195194
self._data = response_data
196195

197196
logs = response_data.get("logs", [])
@@ -214,8 +213,9 @@ def accept_level(level: str) -> bool:
214213
# Mimic original list of process metadata dictionaries
215214
super().__init__(logs)
216215

217-
if warn_on_federation_missing:
218-
self.ext_federation.warn_on_missing(resource_name="log listing")
216+
self.ext_federation_missing = federation_extension.get_federation_missing(
217+
data=response_data, resource_name="log listing"
218+
)
219219

220220
def _repr_html_(self):
221221
return render_component(component="logs", data=self)
@@ -229,8 +229,3 @@ def logs(self) -> List[LogEntry]:
229229
def links(self) -> List[Link]:
230230
"""Get links related to this resource."""
231231
return [Link.from_dict(d) for d in self._data.get("links", [])]
232-
233-
@property
234-
def ext_federation(self) -> FederationExtension:
235-
"""Accessor for federation extension data related to this resource."""
236-
return FederationExtension(self._data)

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_connection.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3325,7 +3325,7 @@ def test_list_collections_extra_metadata(requests_mock, caplog):
33253325
collections = con.list_collections()
33263326
assert collections == [{"id": "S2"}, {"id": "NDVI"}]
33273327
assert collections.links == [Link(rel="next", href="https://oeo.test/collections?page=2", type=None, title=None)]
3328-
assert collections.ext_federation.missing == ["oeob"]
3328+
assert collections.ext_federation_missing == ["oeob"]
33293329
assert "Partial collection listing: missing federation components: ['oeob']." in caplog.text
33303330

33313331

@@ -3403,7 +3403,7 @@ def test_list_processes_extra_metadata(requests_mock, caplog):
34033403
processes = conn.list_processes()
34043404
assert processes == [{"id": "add"}, {"id": "mask"}]
34053405
assert processes.links == [Link(rel="next", href="https://oeo.test/processes?page=2", type=None, title=None)]
3406-
assert processes.ext_federation.missing == ["oeob"]
3406+
assert processes.ext_federation_missing == ["oeob"]
34073407
assert "Partial process listing: missing federation components: ['oeob']." in caplog.text
34083408

34093409

@@ -3715,7 +3715,7 @@ def test_list_udps_extra_metadata(self, requests_mock, test_data, caplog):
37153715
udps = conn.list_user_defined_processes()
37163716
assert udps == [{"id": "myevi"}]
37173717
assert udps.links == [Link(rel="about", href="https://oeo.test/my-evi")]
3718-
assert udps.ext_federation.missing == ["oeob"]
3718+
assert udps.ext_federation_missing == ["oeob"]
37193719
assert "Partial process listing: missing federation components: ['oeob']." in caplog.text
37203720

37213721

tests/rest/test_job.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -393,7 +393,7 @@ def test_get_job_logs_extra_metadata(con100, requests_mock, log_generator):
393393
assert logs.links == [
394394
Link(rel="next", href="https://oeo.test/jobs/f00ba5/logs?offset=123abc"),
395395
]
396-
assert logs.ext_federation.missing == ["eoeb"]
396+
assert logs.ext_federation_missing == ["eoeb"]
397397

398398

399399
def test_get_job_logs_level_handling_default(con100, requests_mock, log_generator):
@@ -878,5 +878,5 @@ def get_jobs(request, context):
878878
{"id": "job456", "status": "created", "created": "2021-03-22T10:00:00Z"},
879879
]
880880
assert jobs.links == [Link(rel="next", href="https://oeo.test/jobs?limit=2&offset=2")]
881-
assert jobs.ext_federation.missing == ["oeob"]
881+
assert jobs.ext_federation_missing == ["oeob"]
882882
assert "Partial job listing: missing federation components: ['oeob']." in caplog.text

0 commit comments

Comments
 (0)