Skip to content

Commit 4bfd2a6

Browse files
committed
Issue #668 support federation extension on list_collections
1 parent 5df1b2b commit 4bfd2a6

File tree

8 files changed

+174
-3
lines changed

8 files changed

+174
-3
lines changed

docs/api.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,16 @@ openeo.rest.capabilities
7070
:members: OpenEoCapabilities
7171

7272

73+
openeo.rest.models
74+
-------------------
75+
76+
.. automodule:: openeo.rest.models.general
77+
:members:
78+
79+
.. automodule:: openeo.rest.models.federation_extension
80+
:members: FederationExtension
81+
82+
7383
openeo.api.process
7484
--------------------
7585

openeo/rest/connection.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
from openeo.rest.graph_building import CollectionProperty
6767
from openeo.rest.job import BatchJob, RESTJob
6868
from openeo.rest.mlmodel import MlModel
69+
from openeo.rest.models.general import CollectionListingResponse
6970
from openeo.rest.service import Service
7071
from openeo.rest.udp import Parameter, RESTUserDefinedProcess
7172
from openeo.rest.userfile import UserFile
@@ -671,7 +672,7 @@ def describe_account(self) -> dict:
671672
def user_jobs(self) -> List[dict]:
672673
return self.list_jobs()
673674

674-
def list_collections(self) -> List[dict]:
675+
def list_collections(self) -> CollectionListingResponse:
675676
"""
676677
List basic metadata of all collections provided by the back-end.
677678
@@ -684,8 +685,8 @@ def list_collections(self) -> List[dict]:
684685
:return: list of dictionaries with basic collection metadata.
685686
"""
686687
# TODO: add caching #383, but reset cache on auth change #254
687-
data = self.get('/collections', expected_status=200).json()["collections"]
688-
return VisualList("collections", data=data)
688+
data = self.get("/collections", expected_status=200).json()
689+
return CollectionListingResponse(data)
689690

690691
def list_collection_ids(self) -> List[str]:
691692
"""

openeo/rest/models/__init__.py

Whitespace-only changes.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from typing import List, Union
2+
3+
4+
class FederationExtension:
5+
"""
6+
Wrapper the openEO Federation extension as defined by
7+
https://github.com/Open-EO/openeo-api/tree/draft/extensions/federation
8+
"""
9+
10+
__slots__ = ["_data"]
11+
12+
def __init__(self, data: dict):
13+
self._data = data
14+
15+
@property
16+
def missing(self) -> Union[List[str], None]:
17+
"""
18+
Get the ``federation:missing`` property (if any) of the resource,
19+
which lists back-ends that were not available during the request.
20+
21+
:return: list of back-end IDs that were not available.
22+
Or None, when ``federation:missing`` is not present in response.
23+
"""
24+
return self._data.get("federation:missing", None)

openeo/rest/models/general.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
from dataclasses import dataclass
2+
from typing import List, Optional, Union
3+
4+
from openeo.internal.jupyter import render_component
5+
from openeo.rest.models.federation_extension import FederationExtension
6+
7+
8+
@dataclass(frozen=True)
9+
class Link:
10+
"""
11+
Container for (web) link data, used throughout the openEO API,
12+
to point to alternate representations, a license, extra detailed information, and more.
13+
"""
14+
15+
rel: str
16+
href: str
17+
type: Optional[str] = None
18+
title: Optional[str] = None
19+
20+
@classmethod
21+
def from_dict(cls, data: dict):
22+
return cls(rel=data["rel"], href=data["href"], type=data.get("type"), title=data.get("title"))
23+
24+
# TODO: add _html_repr_ for Jupyter integration
25+
26+
27+
class CollectionListingResponse(list):
28+
"""
29+
Container for collection metadata listing received from a ``GET /collections`` request.
30+
31+
This object mimics a list of collection metadata dictionaries,
32+
which was the original return API of :py:meth:`~openeo.rest.connection.Connection.list_collections()`,
33+
but now also includes additional metadata like links and extensions.
34+
35+
:param data: response data from a ``GET /collections`` request
36+
"""
37+
38+
__slots__ = ["_data"]
39+
40+
def __init__(self, data: dict):
41+
self._data = data
42+
# Mimic original list of collection metadata dictionaries
43+
super().__init__(data["collections"])
44+
45+
def _repr_html_(self):
46+
return render_component(component="collections", data=self)
47+
48+
@property
49+
def links(self) -> List[Link]:
50+
"""Get links from collections response."""
51+
return [Link.from_dict(d) for d in self._data.get("links", [])]
52+
53+
@property
54+
def ext_federation(self) -> FederationExtension:
55+
"""Accessor for federation extension data."""
56+
return FederationExtension(self._data)

tests/rest/models/__init__.py

Whitespace-only changes.

tests/rest/models/test_general.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import pytest
2+
3+
from openeo.rest.models.general import CollectionListingResponse, Link
4+
5+
6+
class TestLink:
7+
def test_basic(self):
8+
link = Link(rel="about", href="https://example.com/about")
9+
assert link.rel == "about"
10+
assert link.href == "https://example.com/about"
11+
assert link.title is None
12+
assert link.type is None
13+
14+
def test_full(self):
15+
link = Link(rel="about", href="https://example.com/about", type="text/html", title="About example")
16+
assert link.rel == "about"
17+
assert link.href == "https://example.com/about"
18+
assert link.title == "About example"
19+
assert link.type == "text/html"
20+
21+
def test_repr(self):
22+
link = Link(rel="about", href="https://example.com/about")
23+
assert repr(link) == "Link(rel='about', href='https://example.com/about', type=None, title=None)"
24+
25+
26+
class TestCollectionListingResponse:
27+
def test_basic(self):
28+
data = {"collections": [{"id": "S2"}, {"id": "S3"}]}
29+
collections = CollectionListingResponse(data)
30+
assert collections == [{"id": "S2"}, {"id": "S3"}]
31+
assert repr(collections) == "[{'id': 'S2'}, {'id': 'S3'}]"
32+
33+
def test_links(self):
34+
data = {
35+
"collections": [{"id": "S2"}, {"id": "S3"}],
36+
"links": [
37+
{"rel": "self", "href": "https://openeo.test/collections"},
38+
{"rel": "next", "href": "https://openeo.test/collections?page=2"},
39+
],
40+
}
41+
collections = CollectionListingResponse(data)
42+
assert collections.links == [
43+
Link(rel="self", href="https://openeo.test/collections"),
44+
Link(rel="next", href="https://openeo.test/collections?page=2"),
45+
]
46+
47+
@pytest.mark.parametrize(
48+
["data", "expected"],
49+
[
50+
(
51+
{"collections": [{"id": "S2"}], "federation:missing": ["wwu"]},
52+
["wwu"],
53+
),
54+
(
55+
{"collections": [{"id": "S2"}]},
56+
None,
57+
),
58+
],
59+
)
60+
def test_federation_missing(self, data, expected):
61+
collections = CollectionListingResponse(data)
62+
assert collections.ext_federation.missing == expected

tests/rest/test_connection.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
extract_connections,
4141
paginate,
4242
)
43+
from openeo.rest.models.general import Link
4344
from openeo.rest.vectorcube import VectorCube
4445
from openeo.testing.stac import StacDummyBuilder
4546
from openeo.util import ContextTimer, deep_get, dict_no_none
@@ -3329,6 +3330,23 @@ def test_list_collections(requests_mock):
33293330
assert con.list_collections() == collections
33303331

33313332

3333+
def test_list_collections_extra_metadata(requests_mock):
3334+
requests_mock.get(API_URL, json={"api_version": "1.0.0"})
3335+
requests_mock.get(
3336+
API_URL + "collections",
3337+
json={
3338+
"collections": [{"id": "S2"}, {"id": "NDVI"}],
3339+
"links": [{"rel": "next", "href": "https://oeo.test/collections?page=2"}],
3340+
"federation:missing": ["oeob"],
3341+
},
3342+
)
3343+
con = Connection(API_URL)
3344+
collections = con.list_collections()
3345+
assert collections == [{"id": "S2"}, {"id": "NDVI"}]
3346+
assert collections.links == [Link(rel="next", href="https://oeo.test/collections?page=2", type=None, title=None)]
3347+
assert collections.ext_federation.missing == ["oeob"]
3348+
3349+
33323350
def test_describe_collection(requests_mock):
33333351
requests_mock.get(API_URL, json={"api_version": "1.0.0"})
33343352
requests_mock.get(

0 commit comments

Comments
 (0)