ecmwf-datastores-client 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of ecmwf-datastores-client might be problematic. Click here for more details.

@@ -0,0 +1,275 @@
1
+ # Copyright 2022, European Union.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from __future__ import annotations
16
+
17
+ import collections
18
+ import logging
19
+ import typing
20
+ import warnings
21
+ from types import TracebackType
22
+ from typing import Any, Callable, TypeVar, overload
23
+
24
+ import cdsapi.api
25
+ import multiurl
26
+ import requests
27
+
28
+ from ecmwf import datastores
29
+
30
+ LOGGER = logging.getLogger(__name__)
31
+ F = TypeVar("F", bound=Callable[..., Any])
32
+
33
+
34
+ class LoggingContext:
35
+ def __init__(self, logger: logging.Logger, quiet: bool, debug: bool) -> None:
36
+ self.old_level = logger.level
37
+ if quiet:
38
+ logger.setLevel(logging.WARNING)
39
+ else:
40
+ logger.setLevel(logging.DEBUG if debug else logging.INFO)
41
+
42
+ self.new_handlers = []
43
+ if not logger.handlers:
44
+ formatter = logging.Formatter("%(asctime)s %(levelname)s %(message)s")
45
+ handler = logging.StreamHandler()
46
+ handler.setFormatter(formatter)
47
+ logger.addHandler(handler)
48
+ self.new_handlers.append(handler)
49
+
50
+ self.logger = logger
51
+
52
+ def __enter__(self) -> logging.Logger:
53
+ return self.logger
54
+
55
+ def __exit__(
56
+ self,
57
+ exc_type: type[BaseException] | None,
58
+ exc_val: BaseException | None,
59
+ exc_tb: TracebackType | None,
60
+ ) -> None:
61
+ self.logger.setLevel(self.old_level)
62
+ for handler in self.new_handlers:
63
+ self.logger.removeHandler(handler)
64
+
65
+
66
+ class LegacyClient(cdsapi.api.Client): # type: ignore[misc]
67
+ def __init__(
68
+ self,
69
+ url: str | None = None,
70
+ key: str | None = None,
71
+ quiet: bool = False,
72
+ debug: bool = False,
73
+ verify: bool | int | None = None,
74
+ timeout: int = 60,
75
+ progress: bool = True,
76
+ full_stack: None = None,
77
+ delete: bool = False,
78
+ retry_max: int = 500,
79
+ sleep_max: float = 120,
80
+ wait_until_complete: bool = True,
81
+ info_callback: Callable[..., None] | None = None,
82
+ warning_callback: Callable[..., None] | None = None,
83
+ error_callback: Callable[..., None] | None = None,
84
+ debug_callback: Callable[..., None] | None = None,
85
+ metadata: None = None,
86
+ forget: None = None,
87
+ session: requests.Session | None = None,
88
+ ) -> None:
89
+ self.issue_deprecated_kwargs_warning(
90
+ full_stack=full_stack, metadata=metadata, forget=forget
91
+ )
92
+
93
+ self.url, self.key, verify = cdsapi.api.get_url_key_verify(url, key, verify)
94
+ self.verify = bool(verify)
95
+ self.quiet = quiet
96
+ self._debug = debug
97
+ self.timeout = timeout
98
+ self.progress = progress
99
+ self.delete = delete
100
+ self.retry_max = retry_max
101
+ self.sleep_max = sleep_max
102
+ self.wait_until_complete = wait_until_complete
103
+ self.info_callback = info_callback
104
+ self.warning_callback = warning_callback
105
+ self.error_callback = error_callback
106
+ self.debug_callback = debug_callback
107
+ self.session = requests.Session() if session is None else session
108
+
109
+ self.client = datastores.Client(
110
+ url=self.url,
111
+ key=self.key,
112
+ verify=self.verify,
113
+ timeout=self.timeout,
114
+ progress=self.progress,
115
+ cleanup=self.delete,
116
+ sleep_max=self.sleep_max,
117
+ retry_after=self.sleep_max,
118
+ maximum_tries=self.retry_max,
119
+ session=self.session,
120
+ log_callback=self.log,
121
+ )
122
+ self.debug(
123
+ "CDSAPI %s",
124
+ {
125
+ "url": self.url,
126
+ "key": self.key,
127
+ "quiet": self.quiet,
128
+ "verify": self.verify,
129
+ "timeout": self.timeout,
130
+ "progress": self.progress,
131
+ "sleep_max": self.sleep_max,
132
+ "retry_max": self.retry_max,
133
+ "delete": self.delete,
134
+ "datastores_version": datastores.__version__,
135
+ },
136
+ )
137
+
138
+ @classmethod
139
+ def issue_deprecated_kwargs_warning(self, **kwargs: Any) -> None:
140
+ if kwargs := {k: v for k, v in kwargs.items() if v is not None}:
141
+ warnings.warn(
142
+ f"The following parameters are deprecated: {kwargs}."
143
+ " Set them to None to silence this warning.",
144
+ UserWarning,
145
+ )
146
+
147
+ @classmethod
148
+ def raise_toolbox_error(self) -> None:
149
+ raise NotImplementedError(
150
+ "Legacy CDS Toolbox is now discontinued."
151
+ " Watch for announcements/updates on new CDS improved capabilities on our Forum (https://forum.ecmwf.int/)."
152
+ )
153
+
154
+ @overload
155
+ def retrieve(self, name: str, request: dict[str, Any], target: str) -> str: ...
156
+
157
+ @overload
158
+ def retrieve(
159
+ self, name: str, request: dict[str, Any], target: None = ...
160
+ ) -> datastores.Results: ...
161
+
162
+ def retrieve(
163
+ self, name: str, request: dict[str, Any], target: str | None = None
164
+ ) -> str | datastores.Remote | datastores.Results:
165
+ submitted: datastores.Remote | datastores.Results
166
+ if self.wait_until_complete:
167
+ submitted = self.client.submit_and_wait_on_results(
168
+ collection_id=name,
169
+ request=request,
170
+ )
171
+ else:
172
+ submitted = self.client.submit(
173
+ collection_id=name,
174
+ request=request,
175
+ )
176
+
177
+ return submitted if target is None else submitted.download(target)
178
+
179
+ def log(self, level: int, *args: Any, **kwargs: Any) -> None:
180
+ with LoggingContext(
181
+ logger=LOGGER, quiet=self.quiet, debug=self._debug
182
+ ) as logger:
183
+ if level == logging.INFO and self.info_callback is not None:
184
+ self.info_callback(*args, **kwargs)
185
+ elif level == logging.WARNING and self.warning_callback is not None:
186
+ self.warning_callback(*args, **kwargs)
187
+ elif level == logging.ERROR and self.error_callback is not None:
188
+ self.error_callback(*args, **kwargs)
189
+ elif level == logging.DEBUG and self.debug_callback is not None:
190
+ self.debug_callback(*args, **kwargs)
191
+ else:
192
+ logger.log(level, *args, **kwargs)
193
+
194
+ def info(self, *args: Any, **kwargs: Any) -> None:
195
+ self.log(logging.INFO, *args, **kwargs)
196
+
197
+ def warning(self, *args: Any, **kwargs: Any) -> None:
198
+ self.log(logging.WARNING, *args, **kwargs)
199
+
200
+ def error(self, *args: Any, **kwargs: Any) -> None:
201
+ self.log(logging.ERROR, *args, **kwargs)
202
+
203
+ def debug(self, *args: Any, **kwargs: Any) -> None:
204
+ self.log(logging.DEBUG, *args, **kwargs)
205
+
206
+ @typing.no_type_check
207
+ def service(self, name, *args, **kwargs):
208
+ self.raise_toolbox_error()
209
+
210
+ @typing.no_type_check
211
+ def workflow(self, code, *args, **kwargs):
212
+ self.raise_toolbox_error()
213
+
214
+ def status(self, context: Any = None) -> dict[str, list[str]]:
215
+ status = collections.defaultdict(list)
216
+ messages = self.client._catalogue_api.messages._json_dict.get("messages", [])
217
+ for message in messages:
218
+ status[message["severity"]].append(message["content"])
219
+ return dict(status)
220
+
221
+ @typing.no_type_check
222
+ def _download(self, results, targets=None):
223
+ if isinstance(
224
+ results, (cdsapi.api.Result, datastores.Remote, datastores.Results)
225
+ ):
226
+ if targets:
227
+ path = targets.pop(0)
228
+ else:
229
+ path = None
230
+ return results.download(path)
231
+
232
+ if isinstance(results, (list, tuple)):
233
+ return [self._download(x, targets) for x in results]
234
+
235
+ if isinstance(results, dict):
236
+ if "location" in results and "contentLength" in results:
237
+ reply = dict(
238
+ location=results["location"],
239
+ content_length=results["contentLength"],
240
+ content_type=results.get("contentType"),
241
+ )
242
+
243
+ if targets:
244
+ path = targets.pop(0)
245
+ else:
246
+ path = None
247
+
248
+ return cdsapi.api.Result(self, reply).download(path)
249
+
250
+ r = {}
251
+ for v in results.values():
252
+ r[v] = self._download(v, targets)
253
+ return r
254
+
255
+ return results
256
+
257
+ @typing.no_type_check
258
+ def download(self, results, targets=None):
259
+ if targets:
260
+ # Make a copy
261
+ targets = [t for t in targets]
262
+ return self._download(results, targets)
263
+
264
+ def remote(self, url: str) -> cdsapi.api.Result:
265
+ r = requests.head(url)
266
+ reply = dict(
267
+ location=url,
268
+ content_length=r.headers["Content-Length"],
269
+ content_type=r.headers["Content-Type"],
270
+ )
271
+ return cdsapi.api.Result(self, reply)
272
+
273
+ def robust(self, call: F) -> F:
274
+ robust: F = multiurl.robust(call, **self.client._retry_options)
275
+ return robust