Skip to content

Commit 760a5f4

Browse files
committed
Merge branch 'jupyter-federation-support'
2 parents c9d135f + 3616f8e commit 760a5f4

File tree

11 files changed

+220
-38
lines changed

11 files changed

+220
-38
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
### Added
1111

1212
- More extensive band detection for `load_stac` use cases, including the common `bands` metadata introduced with STAC 1.1 ([#699](https://github.com/Open-EO/openeo-python-client/issues/699), [#692](https://github.com/Open-EO/openeo-python-client/issues/692), [#586](https://github.com/Open-EO/openeo-python-client/issues/586)).
13+
- Improved support for Federation Extension in Jupyter notebook context ([#668](https://github.com/Open-EO/openeo-python-client/issues/668))
1314

1415
### Changed
1516

openeo/metadata.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -519,13 +519,14 @@ class CollectionMetadata(CubeMetadata):
519519
520520
"""
521521

522-
def __init__(self, metadata: dict, dimensions: List[Dimension] = None):
522+
def __init__(self, metadata: dict, dimensions: List[Dimension] = None, _federation: Optional[dict] = None):
523523
self._orig_metadata = metadata
524524
if dimensions is None:
525525
dimensions = self._parse_dimensions(self._orig_metadata)
526-
527526
super().__init__(dimensions=dimensions)
528527

528+
self._federation = _federation
529+
529530
@classmethod
530531
def _parse_dimensions(cls, spec: dict, complain: Callable[[str], None] = warnings.warn) -> List[Dimension]:
531532
"""
@@ -640,7 +641,7 @@ def extent(self) -> dict:
640641
return self._orig_metadata.get("extent")
641642

642643
def _repr_html_(self):
643-
return render_component("collection", data=self._orig_metadata)
644+
return render_component("collection", data=self._orig_metadata, parameters={"federation": self._federation})
644645

645646
def __str__(self) -> str:
646647
bands = self.band_names if self.has_band_dimension() else "no bands dimension"

openeo/rest/connection.py

Lines changed: 52 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
JobListingResponse,
7474
ProcessListingResponse,
7575
ValidationResponse,
76+
federation_extension,
7677
)
7778
from openeo.rest.result import SaveResult
7879
from openeo.rest.service import Service
@@ -734,7 +735,7 @@ def list_collections(self) -> CollectionListingResponse:
734735
# TODO: add caching #383, but reset cache on auth change #254
735736
# TODO #677 add pagination support?
736737
data = self.get("/collections", expected_status=200).json()
737-
return CollectionListingResponse(response_data=data)
738+
return CollectionListingResponse(response_data=data, connection=self)
738739

739740
def list_collection_ids(self) -> List[str]:
740741
"""
@@ -776,7 +777,13 @@ def list_file_formats(self) -> dict:
776777
key="file_formats",
777778
load=lambda: self.get('/file_formats', expected_status=200).json()
778779
)
779-
return VisualDict("file-formats", data=formats)
780+
federation_missing = federation_extension.get_federation_missing(data=formats, resource_name="file_formats")
781+
federation = self.capabilities().ext_federation_backend_details()
782+
return VisualDict(
783+
"file-formats",
784+
data=formats,
785+
parameters={"missing": federation_missing, "federation": federation},
786+
)
780787

781788
def list_service_types(self) -> dict:
782789
"""
@@ -800,17 +807,24 @@ def list_udf_runtimes(self) -> dict:
800807
key="udf_runtimes",
801808
load=lambda: self.get('/udf_runtimes', expected_status=200).json()
802809
)
803-
return VisualDict("udf-runtimes", data=runtimes)
810+
federation = self.capabilities().ext_federation_backend_details()
811+
return VisualDict("udf-runtimes", data=runtimes, parameters={"federation": federation})
804812

805-
def list_services(self) -> dict:
813+
def list_services(self) -> list:
806814
"""
807815
Loads all available services of the authenticated user.
808816
809817
:return: data_dict: Dict All available services
810818
"""
811819
# TODO return parsed service objects
812820
services = self.get('/services', expected_status=200).json()["services"]
813-
return VisualList("data-table", data=services, parameters={'columns': 'services'})
821+
federation_missing = federation_extension.get_federation_missing(data=services, resource_name="services")
822+
federation = self.capabilities().ext_federation_backend_details()
823+
return VisualList(
824+
"data-table",
825+
data=services,
826+
parameters={"columns": "services", "missing": federation_missing, "federation": federation},
827+
)
814828

815829
def describe_collection(self, collection_id: str) -> dict:
816830
"""
@@ -827,7 +841,8 @@ def describe_collection(self, collection_id: str) -> dict:
827841
# TODO: duplication with `Connection.collection_metadata`: deprecate one or the other?
828842
# TODO: add caching #383
829843
data = self.get(f"/collections/{collection_id}", expected_status=200).json()
830-
return VisualDict("collection", data=data)
844+
federation = self.capabilities().ext_federation_backend_details()
845+
return VisualDict("collection", data=data, parameters={"federation": federation})
831846

832847
def collection_items(
833848
self,
@@ -865,11 +880,22 @@ def collection_items(
865880
if limit is not None and limit > 0:
866881
params['limit'] = limit
867882

868-
return paginate(self, url, params, lambda response, page: VisualDict("items", data = response, parameters = {'show-map': True, 'heading': 'Page {} - Items'.format(page)}))
883+
federation = self.capabilities().ext_federation_backend_details()
884+
return paginate(
885+
self,
886+
url,
887+
params,
888+
lambda response, page: VisualDict(
889+
"items",
890+
data=response,
891+
parameters={"show-map": True, "heading": "Page {} - Items".format(page), "federation": federation},
892+
),
893+
)
869894

870895
def collection_metadata(self, name) -> CollectionMetadata:
871896
# TODO: duplication with `Connection.describe_collection`: deprecate one or the other?
872-
return CollectionMetadata(metadata=self.describe_collection(name))
897+
federation = self.capabilities().ext_federation_backend_details()
898+
return CollectionMetadata(metadata=self.describe_collection(name), _federation=federation)
873899

874900
def list_processes(self, namespace: Optional[str] = None) -> ProcessListingResponse:
875901
"""
@@ -891,7 +917,7 @@ def list_processes(self, namespace: Optional[str] = None) -> ProcessListingRespo
891917
)
892918
else:
893919
response = self.get("/processes/" + namespace, expected_status=200).json()
894-
return ProcessListingResponse(response_data=response)
920+
return ProcessListingResponse(response_data=response, connection=self)
895921

896922
def describe_process(self, id: str, namespace: Optional[str] = None) -> dict:
897923
"""
@@ -904,9 +930,14 @@ def describe_process(self, id: str, namespace: Optional[str] = None) -> dict:
904930
"""
905931

906932
processes = self.list_processes(namespace)
933+
federation = self.capabilities().ext_federation_backend_details()
907934
for process in processes:
908935
if process["id"] == id:
909-
return VisualDict("process", data=process, parameters={'show-graph': True, 'provide-download': False})
936+
return VisualDict(
937+
"process",
938+
data=process,
939+
parameters={"show-graph": True, "provide-download": False, "federation": federation},
940+
)
910941

911942
raise OpenEoClientException("Process does not exist.")
912943

@@ -933,7 +964,7 @@ def list_jobs(self, limit: Union[int, None] = 100) -> JobListingResponse:
933964
# TODO: Parse the result so that Job classes returned?
934965
# TODO: when pagination is enabled: how to expose link to next page?
935966
resp = self.get("/jobs", params={"limit": limit}, expected_status=200).json()
936-
return JobListingResponse(response_data=resp)
967+
return JobListingResponse(response_data=resp, connection=self)
937968

938969
def assert_user_defined_process_support(self):
939970
"""
@@ -996,7 +1027,7 @@ def list_user_defined_processes(self) -> ProcessListingResponse:
9961027
# TODO #677 add pagination support?
9971028
self.assert_user_defined_process_support()
9981029
data = self.get("/process_graphs", expected_status=200).json()
999-
return ProcessListingResponse(response_data=data)
1030+
return ProcessListingResponse(response_data=data, connection=self)
10001031

10011032
def user_defined_process(self, user_defined_process_id: str) -> RESTUserDefinedProcess:
10021033
"""
@@ -1496,9 +1527,15 @@ def list_files(self) -> List[UserFile]:
14961527
14971528
:return: List of the user-uploaded files.
14981529
"""
1499-
files = self.get('/files', expected_status=200).json()['files']
1500-
files = [UserFile.from_metadata(metadata=f, connection=self) for f in files]
1501-
return VisualList("data-table", data=files, parameters={'columns': 'files'})
1530+
data = self.get("/files", expected_status=200).json()
1531+
files = [UserFile.from_metadata(metadata=f, connection=self) for f in data.get("files", [])]
1532+
federation_missing = federation_extension.get_federation_missing(data=data, resource_name="files")
1533+
federation = self.capabilities().ext_federation_backend_details()
1534+
return VisualList(
1535+
"data-table",
1536+
data=files,
1537+
parameters={"columns": "files", "missing": federation_missing, "federation": federation},
1538+
)
15021539

15031540
def get_file(
15041541
self, path: Union[str, PurePosixPath], metadata: Optional[dict] = None

openeo/rest/job.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,15 @@ def __repr__(self):
8181

8282
def _repr_html_(self):
8383
data = self.describe()
84-
currency = self.connection.capabilities().currency()
85-
return render_component('job', data=data, parameters={'currency': currency})
84+
capabilities = self.connection.capabilities()
85+
return render_component(
86+
"job",
87+
data=data,
88+
parameters={
89+
"currency": capabilities.currency(),
90+
"federation": capabilities.ext_federation_backend_details(),
91+
},
92+
)
8693

8794
@openeo_endpoint("GET /jobs/{job_id}")
8895
def describe(self) -> dict:
@@ -235,7 +242,7 @@ def logs(self, offset: Optional[str] = None, level: Union[str, int, None] = None
235242
if level is not None:
236243
params["level"] = log_level_name(level)
237244
response_data = self.connection.get(url, params=params, expected_status=200).json()
238-
return LogsResponse(response_data=response_data, log_level=level)
245+
return LogsResponse(response_data=response_data, log_level=level, connection=self.connection)
239246

240247
@deprecated("Use start_and_wait instead", version="0.39.0")
241248
def run_synchronous(

openeo/rest/models/general.py

Lines changed: 58 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@
22

33
import functools
44
from dataclasses import dataclass
5-
from typing import List, Optional, Union
5+
from typing import TYPE_CHECKING, List, Optional, Union
66

77
from openeo.internal.jupyter import render_component
88
from openeo.rest.models import federation_extension
99
from openeo.rest.models.logs import LogEntry, normalize_log_level
1010

11+
if TYPE_CHECKING:
12+
from openeo.rest.connection import Connection
1113

1214
@dataclass(frozen=True)
1315
class Link:
@@ -43,23 +45,33 @@ class CollectionListingResponse(list):
4345
but now also provides methods/properties to access additional response data.
4446
4547
:param response_data: response data from a ``GET /collections`` request
48+
:param connection: optional connection object to use for federation extension
4649
4750
.. seealso:: :py:meth:`openeo.rest.connection.Connection.list_collections()`
4851
4952
.. versionadded:: 0.38.0
5053
"""
5154

52-
__slots__ = ["_data"]
55+
__slots__ = ["_data", "_connection"]
5356

54-
def __init__(self, response_data: dict):
57+
def __init__(self, response_data: dict, connection: Optional[Connection] = None):
5558
self._data = response_data
5659
# Mimic original list of collection metadata dictionaries
5760
super().__init__(response_data["collections"])
5861

62+
self._connection = connection
5963
self.ext_federation_missing(auto_warn=True)
6064

6165
def _repr_html_(self):
62-
return render_component(component="collections", data=self)
66+
federation = self._connection.capabilities().ext_federation_backend_details() if self._connection else None
67+
return render_component(
68+
component="collections",
69+
data=self,
70+
parameters={
71+
"missing": self.ext_federation_missing(),
72+
"federation": federation,
73+
},
74+
)
6375

6476
@property
6577
def links(self) -> List[Link]:
@@ -94,24 +106,34 @@ class ProcessListingResponse(list):
94106
but now also provides methods/properties to access additional response data.
95107
96108
:param response_data: response data from a ``GET /processes`` request
109+
:param connection: optional connection object to use for federation extension
97110
98111
.. seealso:: :py:meth:`openeo.rest.connection.Connection.list_processes()`
99112
100113
.. versionadded:: 0.38.0
101114
"""
102115

103-
__slots__ = ["_data"]
116+
__slots__ = ["_data", "_connection"]
104117

105-
def __init__(self, response_data: dict):
118+
def __init__(self, response_data: dict, connection: Optional[Connection] = None):
106119
self._data = response_data
107120
# Mimic original list of process metadata dictionaries
108121
super().__init__(response_data["processes"])
109122

123+
self._connection = connection
110124
self.ext_federation_missing(auto_warn=True)
111125

112126
def _repr_html_(self):
127+
federation = self._connection.capabilities().ext_federation_backend_details() if self._connection else None
113128
return render_component(
114-
component="processes", data=self, parameters={"show-graph": True, "provide-download": False}
129+
component="processes",
130+
data=self,
131+
parameters={
132+
"show-graph": True,
133+
"provide-download": False,
134+
"missing": self.ext_federation_missing(),
135+
"federation": federation,
136+
},
115137
)
116138

117139
@property
@@ -148,23 +170,34 @@ class JobListingResponse(list):
148170
but now also provides methods/properties to access additional response data.
149171
150172
:param response_data: response data from a ``GET /jobs`` request
173+
:param connection: optional connection object to use for federation extension
151174
152175
.. seealso:: :py:meth:`openeo.rest.connection.Connection.list_jobs()`
153176
154177
.. versionadded:: 0.38.0
155178
"""
156179

157-
__slots__ = ["_data"]
180+
__slots__ = ["_data", "_connection"]
158181

159-
def __init__(self, response_data: dict):
182+
def __init__(self, response_data: dict, connection: Optional[Connection] = None):
160183
self._data = response_data
161184
# Mimic original list of process metadata dictionaries
162185
super().__init__(response_data["jobs"])
163186

187+
self._connection = connection
164188
self.ext_federation_missing(auto_warn=True)
165189

166190
def _repr_html_(self):
167-
return render_component(component="data-table", data=self, parameters={"columns": "jobs"})
191+
federation = self._connection.capabilities().ext_federation_backend_details() if self._connection else None
192+
return render_component(
193+
component="data-table",
194+
data=self,
195+
parameters={
196+
"columns": "jobs",
197+
"missing": self.ext_federation_missing(),
198+
"federation": federation,
199+
},
200+
)
168201

169202
@property
170203
def links(self) -> List[Link]:
@@ -202,16 +235,19 @@ class LogsResponse(list):
202235
203236
:param response_data: response data from a ``GET /jobs/{job_id}/logs``
204237
or ``GET /services/{service_id}/logs`` request.
238+
:param connection: optional connection object to use for federation extension
205239
206240
.. seealso:: :py:meth:`~openeo.rest.job.BatchJob.logs()`
207241
and :py:meth:`~openeo.rest.service.Service.logs()`
208242
209243
.. versionadded:: 0.38.0
210244
"""
211245

212-
__slots__ = ["_data"]
246+
__slots__ = ["_data", "_connection"]
213247

214-
def __init__(self, response_data: dict, *, log_level: Optional[str] = None):
248+
def __init__(
249+
self, response_data: dict, *, log_level: Optional[str] = None, connection: Optional[Connection] = None
250+
):
215251
self._data = response_data
216252

217253
logs = response_data.get("logs", [])
@@ -234,10 +270,19 @@ def accept_level(level: str) -> bool:
234270
# Mimic original list of process metadata dictionaries
235271
super().__init__(logs)
236272

273+
self._connection = connection
237274
self.ext_federation_missing(auto_warn=True)
238275

239276
def _repr_html_(self):
240-
return render_component(component="logs", data=self)
277+
federation = self._connection.capabilities().ext_federation_backend_details() if self._connection else None
278+
return render_component(
279+
component="logs",
280+
data=self,
281+
parameters={
282+
"missing": self.ext_federation_missing(),
283+
"federation": federation,
284+
},
285+
)
241286

242287
@property
243288
def logs(self) -> List[LogEntry]:

0 commit comments

Comments
 (0)