freva-client 2411.0.0__tar.gz → 2505.0.0__tar.gz
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.
- {freva_client-2411.0.0 → freva_client-2505.0.0}/PKG-INFO +4 -3
- {freva_client-2411.0.0 → freva_client-2505.0.0}/pyproject.toml +3 -2
- {freva_client-2411.0.0 → freva_client-2505.0.0}/src/freva_client/__init__.py +1 -1
- {freva_client-2411.0.0 → freva_client-2505.0.0}/src/freva_client/cli/auth_cli.py +1 -0
- {freva_client-2411.0.0 → freva_client-2505.0.0}/src/freva_client/cli/cli_app.py +1 -0
- {freva_client-2411.0.0 → freva_client-2505.0.0}/src/freva_client/cli/cli_parser.py +2 -0
- {freva_client-2411.0.0 → freva_client-2505.0.0}/src/freva_client/cli/cli_utils.py +2 -1
- {freva_client-2411.0.0 → freva_client-2505.0.0}/src/freva_client/cli/databrowser_cli.py +287 -34
- {freva_client-2411.0.0 → freva_client-2505.0.0}/src/freva_client/query.py +204 -25
- {freva_client-2411.0.0 → freva_client-2505.0.0}/src/freva_client/utils/databrowser_utils.py +62 -26
- {freva_client-2411.0.0 → freva_client-2505.0.0}/MANIFEST.in +0 -0
- {freva_client-2411.0.0 → freva_client-2505.0.0}/README.md +0 -0
- {freva_client-2411.0.0 → freva_client-2505.0.0}/assets/share/freva/freva.toml +0 -0
- {freva_client-2411.0.0 → freva_client-2505.0.0}/src/freva_client/__main__.py +0 -0
- {freva_client-2411.0.0 → freva_client-2505.0.0}/src/freva_client/auth.py +0 -0
- {freva_client-2411.0.0 → freva_client-2505.0.0}/src/freva_client/cli/__init__.py +0 -0
- {freva_client-2411.0.0 → freva_client-2505.0.0}/src/freva_client/py.typed +0 -0
- {freva_client-2411.0.0 → freva_client-2505.0.0}/src/freva_client/utils/__init__.py +0 -0
- {freva_client-2411.0.0 → freva_client-2505.0.0}/src/freva_client/utils/logger.py +0 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: freva-client
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2505.0.0
|
|
4
4
|
Summary: Search for climate data based on key-value pairs
|
|
5
5
|
Author-email: "DKRZ, Clint" <freva@dkrz.de>
|
|
6
6
|
Requires-Python: >=3.8
|
|
7
7
|
Description-Content-Type: text/markdown
|
|
8
|
-
Classifier: Development Status ::
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
9
|
Classifier: Environment :: Console
|
|
10
10
|
Classifier: Intended Audience :: Developers
|
|
11
11
|
Classifier: Intended Audience :: Science/Research
|
|
@@ -16,6 +16,7 @@ Classifier: Programming Language :: Python :: 3.9
|
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.10
|
|
17
17
|
Classifier: Programming Language :: Python :: 3.11
|
|
18
18
|
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
20
|
Requires-Dist: appdirs
|
|
20
21
|
Requires-Dist: pyyaml
|
|
21
22
|
Requires-Dist: authlib
|
|
@@ -8,7 +8,7 @@ description = "Search for climate data based on key-value pairs"
|
|
|
8
8
|
authors = [{name = "DKRZ, Clint", email = "freva@dkrz.de"}]
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
classifiers = [
|
|
11
|
-
"Development Status ::
|
|
11
|
+
"Development Status :: 4 - Beta",
|
|
12
12
|
"Environment :: Console",
|
|
13
13
|
"Intended Audience :: Developers",
|
|
14
14
|
"Intended Audience :: Science/Research",
|
|
@@ -19,6 +19,7 @@ classifiers = [
|
|
|
19
19
|
"Programming Language :: Python :: 3.10",
|
|
20
20
|
"Programming Language :: Python :: 3.11",
|
|
21
21
|
"Programming Language :: Python :: 3.12",
|
|
22
|
+
"Programming Language :: Python :: 3.13",
|
|
22
23
|
]
|
|
23
24
|
requires-python = ">=3.8"
|
|
24
25
|
dependencies = [
|
|
@@ -48,4 +49,4 @@ include = ["assets/*"]
|
|
|
48
49
|
[tool.flit.external-data]
|
|
49
50
|
directory = "assets"
|
|
50
51
|
[package-data]
|
|
51
|
-
|
|
52
|
+
freva_client = ["py.typed"]
|
|
@@ -7,10 +7,11 @@ import json
|
|
|
7
7
|
from enum import Enum
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
from tempfile import NamedTemporaryFile
|
|
10
|
-
from typing import Dict, List, Literal, Optional, Union, cast
|
|
10
|
+
from typing import Dict, List, Literal, Optional, Tuple, Union, cast
|
|
11
11
|
|
|
12
12
|
import typer
|
|
13
13
|
import xarray as xr
|
|
14
|
+
|
|
14
15
|
from freva_client import databrowser
|
|
15
16
|
from freva_client.auth import Auth
|
|
16
17
|
from freva_client.utils import exception_handler, logger
|
|
@@ -31,41 +32,67 @@ def _auth(url: str, token: Optional[str]) -> None:
|
|
|
31
32
|
class UniqKeys(str, Enum):
|
|
32
33
|
"""Literal implementation for the cli."""
|
|
33
34
|
|
|
34
|
-
file
|
|
35
|
-
uri
|
|
35
|
+
file = "file"
|
|
36
|
+
uri = "uri"
|
|
36
37
|
|
|
37
38
|
|
|
38
39
|
class Flavours(str, Enum):
|
|
39
40
|
"""Literal implementation for the cli."""
|
|
40
41
|
|
|
41
|
-
freva
|
|
42
|
-
cmip6
|
|
43
|
-
cmip5
|
|
44
|
-
cordex
|
|
45
|
-
nextgems
|
|
46
|
-
user
|
|
42
|
+
freva = "freva"
|
|
43
|
+
cmip6 = "cmip6"
|
|
44
|
+
cmip5 = "cmip5"
|
|
45
|
+
cordex = "cordex"
|
|
46
|
+
nextgems = "nextgems"
|
|
47
|
+
user = "user"
|
|
47
48
|
|
|
48
49
|
|
|
49
|
-
class
|
|
50
|
+
class SelectMethod(str, Enum):
|
|
50
51
|
"""Literal implementation for the cli."""
|
|
51
52
|
|
|
52
|
-
strict
|
|
53
|
-
flexible
|
|
54
|
-
file
|
|
53
|
+
strict = "strict"
|
|
54
|
+
flexible = "flexible"
|
|
55
|
+
file = "file"
|
|
55
56
|
|
|
56
57
|
@staticmethod
|
|
57
|
-
def get_help() -> str:
|
|
58
|
-
"""Generate the help string.
|
|
58
|
+
def get_help(context: str) -> str:
|
|
59
|
+
"""Generate the help string for time or bbox selection methods.
|
|
60
|
+
|
|
61
|
+
Parameters
|
|
62
|
+
----------
|
|
63
|
+
context: str, default: "time"
|
|
64
|
+
Either "time" or "bbox" to generate appropriate help text.
|
|
65
|
+
"""
|
|
66
|
+
examples = {
|
|
67
|
+
"time": ("2000 to 2012", "2010 to 2020"),
|
|
68
|
+
"bbox": ("-10 10 -10 10", "0 5 0 5")
|
|
69
|
+
}
|
|
70
|
+
descriptions = {
|
|
71
|
+
"time": {
|
|
72
|
+
"unit": "time period",
|
|
73
|
+
"start_end": "start or end period",
|
|
74
|
+
"subset": "time period"
|
|
75
|
+
},
|
|
76
|
+
"bbox": {
|
|
77
|
+
"unit": "spatial extent",
|
|
78
|
+
"start_end": "any part of the extent",
|
|
79
|
+
"subset": "spatial extent"
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
context_info = descriptions.get(context, descriptions["time"])
|
|
84
|
+
example = examples.get(context, examples["time"])
|
|
85
|
+
|
|
59
86
|
return (
|
|
60
|
-
"Operator that specifies how the
|
|
87
|
+
f"Operator that specifies how the {context_info['unit']} is selected. "
|
|
61
88
|
"Choose from flexible (default), strict or file. "
|
|
62
89
|
"``strict`` returns only those files that have the *entire* "
|
|
63
|
-
"
|
|
64
|
-
"not select files containing data from
|
|
65
|
-
"the ``strict`` method. ``flexible`` will select those files "
|
|
66
|
-
"
|
|
67
|
-
"
|
|
68
|
-
"
|
|
90
|
+
f"{context_info['unit']} covered. The {context} search ``{example[0]}`` "
|
|
91
|
+
f"will not select files containing data from {example[1]} with "
|
|
92
|
+
"the ``strict`` method. ``flexible`` will select those files as "
|
|
93
|
+
f"``flexible`` returns those files that have {context_info['start_end']} "
|
|
94
|
+
f"covered. ``file`` will only return files where the entire "
|
|
95
|
+
f"{context_info['subset']} is contained within *one single* file."
|
|
69
96
|
)
|
|
70
97
|
|
|
71
98
|
|
|
@@ -123,11 +150,11 @@ def metadata_search(
|
|
|
123
150
|
"of climate datasets to query."
|
|
124
151
|
),
|
|
125
152
|
),
|
|
126
|
-
time_select:
|
|
153
|
+
time_select: SelectMethod = typer.Option(
|
|
127
154
|
"flexible",
|
|
128
155
|
"-ts",
|
|
129
156
|
"--time-select",
|
|
130
|
-
help=
|
|
157
|
+
help=SelectMethod.get_help("time"),
|
|
131
158
|
),
|
|
132
159
|
time: Optional[str] = typer.Option(
|
|
133
160
|
None,
|
|
@@ -143,6 +170,24 @@ def metadata_search(
|
|
|
143
170
|
" valid."
|
|
144
171
|
),
|
|
145
172
|
),
|
|
173
|
+
bbox: Optional[Tuple[float, float, float, float]] = typer.Option(
|
|
174
|
+
None,
|
|
175
|
+
"-b",
|
|
176
|
+
"--bbox",
|
|
177
|
+
help=(
|
|
178
|
+
"Special search facet to refine/subset search results by spatial "
|
|
179
|
+
"extent. This can be a string representation of a bounding box. "
|
|
180
|
+
"The bounding box has to follow the format ``min_lon max_lon "
|
|
181
|
+
"min_lat,max_lat``. Valid strings are ``-10 10 -10 10`` to "
|
|
182
|
+
"``0 5 0 5``."
|
|
183
|
+
),
|
|
184
|
+
),
|
|
185
|
+
bbox_select: SelectMethod = typer.Option(
|
|
186
|
+
"flexible",
|
|
187
|
+
"-bs",
|
|
188
|
+
"--bbox-select",
|
|
189
|
+
help=SelectMethod.get_help("bbox"),
|
|
190
|
+
),
|
|
146
191
|
extended_search: bool = typer.Option(
|
|
147
192
|
False,
|
|
148
193
|
"-e",
|
|
@@ -188,6 +233,8 @@ def metadata_search(
|
|
|
188
233
|
*(facets or []),
|
|
189
234
|
time=time or "",
|
|
190
235
|
time_select=cast(Literal["file", "flexible", "strict"], time_select.value),
|
|
236
|
+
bbox=bbox or None,
|
|
237
|
+
bbox_select=cast(Literal["file", "flexible", "strict"], bbox_select.value),
|
|
191
238
|
flavour=cast(
|
|
192
239
|
Literal["freva", "cmip6", "cmip5", "cordex", "nextgems", "user"],
|
|
193
240
|
flavour.value,
|
|
@@ -245,11 +292,11 @@ def data_search(
|
|
|
245
292
|
"of climate datasets to query."
|
|
246
293
|
),
|
|
247
294
|
),
|
|
248
|
-
time_select:
|
|
295
|
+
time_select: SelectMethod = typer.Option(
|
|
249
296
|
"flexible",
|
|
250
297
|
"-ts",
|
|
251
298
|
"--time-select",
|
|
252
|
-
help=
|
|
299
|
+
help=SelectMethod.get_help("time"),
|
|
253
300
|
),
|
|
254
301
|
zarr: bool = typer.Option(False, "--zarr", help="Create zarr stream files."),
|
|
255
302
|
access_token: Optional[str] = typer.Option(
|
|
@@ -274,6 +321,24 @@ def data_search(
|
|
|
274
321
|
" valid."
|
|
275
322
|
),
|
|
276
323
|
),
|
|
324
|
+
bbox: Optional[Tuple[float, float, float, float]] = typer.Option(
|
|
325
|
+
None,
|
|
326
|
+
"-b",
|
|
327
|
+
"--bbox",
|
|
328
|
+
help=(
|
|
329
|
+
"Special search facet to refine/subset search results by spatial "
|
|
330
|
+
"extent. This can be a string representation of a bounding box. "
|
|
331
|
+
"The bounding box has to follow the format ``min_lon max_lon "
|
|
332
|
+
"min_lat max_lat``. Valid strings are ``-10 10 -10 10`` to "
|
|
333
|
+
"``0 5 0 5``."
|
|
334
|
+
),
|
|
335
|
+
),
|
|
336
|
+
bbox_select: SelectMethod = typer.Option(
|
|
337
|
+
"flexible",
|
|
338
|
+
"-bs",
|
|
339
|
+
"--bbox-select",
|
|
340
|
+
help=SelectMethod.get_help("bbox"),
|
|
341
|
+
),
|
|
277
342
|
parse_json: bool = typer.Option(
|
|
278
343
|
False, "-j", "--json", help="Parse output in json format."
|
|
279
344
|
),
|
|
@@ -311,6 +376,8 @@ def data_search(
|
|
|
311
376
|
*(facets or []),
|
|
312
377
|
time=time or "",
|
|
313
378
|
time_select=cast(Literal["file", "flexible", "strict"], time_select),
|
|
379
|
+
bbox=bbox or None,
|
|
380
|
+
bbox_select=cast(Literal["file", "flexible", "strict"], bbox_select),
|
|
314
381
|
flavour=cast(
|
|
315
382
|
Literal["freva", "cmip6", "cmip5", "cordex", "nextgems", "user"],
|
|
316
383
|
flavour.value,
|
|
@@ -371,11 +438,11 @@ def intake_catalogue(
|
|
|
371
438
|
"of climate datasets to query."
|
|
372
439
|
),
|
|
373
440
|
),
|
|
374
|
-
time_select:
|
|
441
|
+
time_select: SelectMethod = typer.Option(
|
|
375
442
|
"flexible",
|
|
376
443
|
"-ts",
|
|
377
444
|
"--time-select",
|
|
378
|
-
help=
|
|
445
|
+
help=SelectMethod.get_help("time"),
|
|
379
446
|
),
|
|
380
447
|
time: Optional[str] = typer.Option(
|
|
381
448
|
None,
|
|
@@ -391,6 +458,24 @@ def intake_catalogue(
|
|
|
391
458
|
" valid."
|
|
392
459
|
),
|
|
393
460
|
),
|
|
461
|
+
bbox: Optional[Tuple[float, float, float, float]] = typer.Option(
|
|
462
|
+
None,
|
|
463
|
+
"-b",
|
|
464
|
+
"--bbox",
|
|
465
|
+
help=(
|
|
466
|
+
"Special search facet to refine/subset search results by spatial "
|
|
467
|
+
"extent. This can be a string representation of a bounding box. "
|
|
468
|
+
"The bounding box has to follow the format ``min_lon max_lon "
|
|
469
|
+
"min_lat max_lat``. Valid strings are ``-10 10 -10 10`` to "
|
|
470
|
+
"``0 5 0 5``."
|
|
471
|
+
),
|
|
472
|
+
),
|
|
473
|
+
bbox_select: SelectMethod = typer.Option(
|
|
474
|
+
"flexible",
|
|
475
|
+
"-bs",
|
|
476
|
+
"--bbox-select",
|
|
477
|
+
help=SelectMethod.get_help("bbox"),
|
|
478
|
+
),
|
|
394
479
|
zarr: bool = typer.Option(
|
|
395
480
|
False, "--zarr", help="Create zarr stream files, as catalogue targets."
|
|
396
481
|
),
|
|
@@ -442,6 +527,8 @@ def intake_catalogue(
|
|
|
442
527
|
*(facets or []),
|
|
443
528
|
time=time or "",
|
|
444
529
|
time_select=cast(Literal["file", "flexible", "strict"], time_select),
|
|
530
|
+
bbox=bbox or None,
|
|
531
|
+
bbox_select=cast(Literal["file", "flexible", "strict"], bbox_select),
|
|
445
532
|
flavour=cast(
|
|
446
533
|
Literal["freva", "cmip6", "cmip5", "cordex", "nextgems", "user"],
|
|
447
534
|
flavour.value,
|
|
@@ -461,7 +548,145 @@ def intake_catalogue(
|
|
|
461
548
|
print(Path(temp_f.name).read_text())
|
|
462
549
|
|
|
463
550
|
|
|
464
|
-
@databrowser_app.command(
|
|
551
|
+
@databrowser_app.command(
|
|
552
|
+
name="stac-catalogue",
|
|
553
|
+
help="Create a static STAC catalogue from the search."
|
|
554
|
+
)
|
|
555
|
+
@exception_handler
|
|
556
|
+
def stac_catalogue(
|
|
557
|
+
search_keys: Optional[List[str]] = typer.Argument(
|
|
558
|
+
default=None,
|
|
559
|
+
help="Refine your data search with this `key=value` pair search "
|
|
560
|
+
"parameters. The parameters could be, depending on the DRS standard, "
|
|
561
|
+
"flavour product, project model etc.",
|
|
562
|
+
),
|
|
563
|
+
facets: Optional[List[str]] = typer.Option(
|
|
564
|
+
None,
|
|
565
|
+
"--facet",
|
|
566
|
+
help=(
|
|
567
|
+
"If you are not sure about the correct search key's you can use"
|
|
568
|
+
" the ``--facet`` flag to search of any matching entries. For "
|
|
569
|
+
"example --facet 'era5' would allow you to search for any entries"
|
|
570
|
+
" containing era5, regardless of project, product etc."
|
|
571
|
+
),
|
|
572
|
+
),
|
|
573
|
+
uniq_key: UniqKeys = typer.Option(
|
|
574
|
+
"file",
|
|
575
|
+
"--uniq-key",
|
|
576
|
+
"-u",
|
|
577
|
+
help=(
|
|
578
|
+
"The type of search result, which can be either “file” "
|
|
579
|
+
"or “uri”. This parameter determines whether the search will be "
|
|
580
|
+
"based on file paths or Uniform Resource Identifiers"
|
|
581
|
+
),
|
|
582
|
+
),
|
|
583
|
+
flavour: Flavours = typer.Option(
|
|
584
|
+
"freva",
|
|
585
|
+
"--flavour",
|
|
586
|
+
"-f",
|
|
587
|
+
help=(
|
|
588
|
+
"The Data Reference Syntax (DRS) standard specifying the type "
|
|
589
|
+
"of climate datasets to query."
|
|
590
|
+
),
|
|
591
|
+
),
|
|
592
|
+
time_select: SelectMethod = typer.Option(
|
|
593
|
+
"flexible",
|
|
594
|
+
"-ts",
|
|
595
|
+
"--time-select",
|
|
596
|
+
help=SelectMethod.get_help("time"),
|
|
597
|
+
),
|
|
598
|
+
time: Optional[str] = typer.Option(
|
|
599
|
+
None,
|
|
600
|
+
"-t",
|
|
601
|
+
"--time",
|
|
602
|
+
help=(
|
|
603
|
+
"Special search facet to refine/subset search results by time. "
|
|
604
|
+
"This can be a string representation of a time range or a single "
|
|
605
|
+
"time step. The time steps have to follow ISO-8601. Valid strings "
|
|
606
|
+
"are ``%Y-%m-%dT%H:%M`` to ``%Y-%m-%dT%H:%M`` for time ranges and "
|
|
607
|
+
"``%Y-%m-%dT%H:%M``. **Note**: You don't have to give the full "
|
|
608
|
+
"string format to subset time steps ``%Y``, ``%Y-%m`` etc are also"
|
|
609
|
+
" valid."
|
|
610
|
+
),
|
|
611
|
+
),
|
|
612
|
+
bbox: Optional[Tuple[float, float, float, float]] = typer.Option(
|
|
613
|
+
None,
|
|
614
|
+
"-b",
|
|
615
|
+
"--bbox",
|
|
616
|
+
help=(
|
|
617
|
+
"Special search facet to refine/subset search results by spatial "
|
|
618
|
+
"extent. This can be a string representation of a bounding box. "
|
|
619
|
+
"The bounding box has to follow the format ``min_lon max_lon "
|
|
620
|
+
"min_lat max_lat``. Valid strings are ``-10 10 -10 10`` to "
|
|
621
|
+
"``0 5 0 5``."
|
|
622
|
+
),
|
|
623
|
+
),
|
|
624
|
+
bbox_select: SelectMethod = typer.Option(
|
|
625
|
+
"flexible",
|
|
626
|
+
"-bs",
|
|
627
|
+
"--bbox-select",
|
|
628
|
+
help=SelectMethod.get_help("bbox"),
|
|
629
|
+
),
|
|
630
|
+
host: Optional[str] = typer.Option(
|
|
631
|
+
None,
|
|
632
|
+
"--host",
|
|
633
|
+
help=(
|
|
634
|
+
"Set the hostname of the databrowser, if not set (default) "
|
|
635
|
+
"the hostname is read from a config file"
|
|
636
|
+
),
|
|
637
|
+
),
|
|
638
|
+
verbose: int = typer.Option(0, "-v", help="Increase verbosity", count=True),
|
|
639
|
+
multiversion: bool = typer.Option(
|
|
640
|
+
False,
|
|
641
|
+
"--multi-version",
|
|
642
|
+
help="Select all versions and not just the latest version (default).",
|
|
643
|
+
),
|
|
644
|
+
version: Optional[bool] = typer.Option(
|
|
645
|
+
False,
|
|
646
|
+
"-V",
|
|
647
|
+
"--version",
|
|
648
|
+
help="Show version an exit",
|
|
649
|
+
callback=version_callback,
|
|
650
|
+
),
|
|
651
|
+
filename: Optional[Path] = typer.Option(
|
|
652
|
+
None,
|
|
653
|
+
"-o",
|
|
654
|
+
"--filename",
|
|
655
|
+
help=(
|
|
656
|
+
"Path to the file where the static STAC catalogue, "
|
|
657
|
+
"should be written to. If you don't specify or the path "
|
|
658
|
+
"does not exist, the file will be created in the current "
|
|
659
|
+
"working directory. "
|
|
660
|
+
),
|
|
661
|
+
),
|
|
662
|
+
) -> None:
|
|
663
|
+
"""Create a STAC catalogue for climate datasets based on the specified
|
|
664
|
+
Data Reference Syntax (DRS) standard (flavour) and the type of search
|
|
665
|
+
result (uniq_key), which can be either "file" or "uri"."""
|
|
666
|
+
logger.set_verbosity(verbose)
|
|
667
|
+
result = databrowser(
|
|
668
|
+
*(facets or []),
|
|
669
|
+
time=time or "",
|
|
670
|
+
time_select=cast(Literal["file", "flexible", "strict"], time_select),
|
|
671
|
+
bbox=bbox or None,
|
|
672
|
+
bbox_select=cast(Literal["file", "flexible", "strict"], bbox_select),
|
|
673
|
+
flavour=cast(
|
|
674
|
+
Literal["freva", "cmip6", "cmip5", "cordex", "nextgems", "user"],
|
|
675
|
+
flavour.value,
|
|
676
|
+
),
|
|
677
|
+
uniq_key=cast(Literal["uri", "file"], uniq_key.value),
|
|
678
|
+
host=host,
|
|
679
|
+
fail_on_error=False,
|
|
680
|
+
multiversion=multiversion,
|
|
681
|
+
stream_zarr=False,
|
|
682
|
+
**(parse_cli_args(search_keys or [])),
|
|
683
|
+
)
|
|
684
|
+
print(result.stac_catalogue(filename=filename))
|
|
685
|
+
|
|
686
|
+
|
|
687
|
+
@databrowser_app.command(
|
|
688
|
+
name="data-count", help="Count the databrowser search results"
|
|
689
|
+
)
|
|
465
690
|
@exception_handler
|
|
466
691
|
def count_values(
|
|
467
692
|
search_keys: Optional[List[str]] = typer.Argument(
|
|
@@ -495,11 +720,11 @@ def count_values(
|
|
|
495
720
|
"of climate datasets to query."
|
|
496
721
|
),
|
|
497
722
|
),
|
|
498
|
-
time_select:
|
|
723
|
+
time_select: SelectMethod = typer.Option(
|
|
499
724
|
"flexible",
|
|
500
725
|
"-ts",
|
|
501
726
|
"--time-select",
|
|
502
|
-
help=
|
|
727
|
+
help=SelectMethod.get_help("time"),
|
|
503
728
|
),
|
|
504
729
|
time: Optional[str] = typer.Option(
|
|
505
730
|
None,
|
|
@@ -515,6 +740,24 @@ def count_values(
|
|
|
515
740
|
" valid."
|
|
516
741
|
),
|
|
517
742
|
),
|
|
743
|
+
bbox: Optional[Tuple[float, float, float, float]] = typer.Option(
|
|
744
|
+
None,
|
|
745
|
+
"-b",
|
|
746
|
+
"--bbox",
|
|
747
|
+
help=(
|
|
748
|
+
"Special search facet to refine/subset search results by spatial "
|
|
749
|
+
"extent. This can be a string representation of a bounding box. "
|
|
750
|
+
"The bounding box has to follow the format ``min_lon max_lon "
|
|
751
|
+
"min_lat max_lat``. Valid strings are ``-10 10 -10 10`` to "
|
|
752
|
+
"``0 5 0 5``."
|
|
753
|
+
),
|
|
754
|
+
),
|
|
755
|
+
bbox_select: SelectMethod = typer.Option(
|
|
756
|
+
"flexible",
|
|
757
|
+
"-bs",
|
|
758
|
+
"--bbox-select",
|
|
759
|
+
help=SelectMethod.get_help("bbox"),
|
|
760
|
+
),
|
|
518
761
|
extended_search: bool = typer.Option(
|
|
519
762
|
False,
|
|
520
763
|
"-e",
|
|
@@ -559,12 +802,16 @@ def count_values(
|
|
|
559
802
|
result: Union[int, Dict[str, Dict[str, int]]] = 0
|
|
560
803
|
search_kws = parse_cli_args(search_keys or [])
|
|
561
804
|
time = cast(str, time or search_kws.pop("time", ""))
|
|
805
|
+
bbox = cast(Optional[Tuple[float, float, float, float]],
|
|
806
|
+
bbox or search_kws.pop("bbox", None))
|
|
562
807
|
facets = facets or []
|
|
563
808
|
if detail:
|
|
564
809
|
result = databrowser.count_values(
|
|
565
810
|
*facets,
|
|
566
811
|
time=time or "",
|
|
567
812
|
time_select=cast(Literal["file", "flexible", "strict"], time_select),
|
|
813
|
+
bbox=bbox or None,
|
|
814
|
+
bbox_select=cast(Literal["file", "flexible", "strict"], bbox_select),
|
|
568
815
|
flavour=cast(
|
|
569
816
|
Literal["freva", "cmip6", "cmip5", "cordex", "nextgems", "user"],
|
|
570
817
|
flavour.value,
|
|
@@ -581,8 +828,12 @@ def count_values(
|
|
|
581
828
|
*facets,
|
|
582
829
|
time=time or "",
|
|
583
830
|
time_select=cast(Literal["file", "flexible", "strict"], time_select),
|
|
831
|
+
bbox=bbox or None,
|
|
832
|
+
bbox_select=cast(Literal["file", "flexible", "strict"], bbox_select),
|
|
584
833
|
flavour=cast(
|
|
585
|
-
Literal[
|
|
834
|
+
Literal[
|
|
835
|
+
"freva", "cmip6", "cmip5", "cordex", "nextgems", "user"
|
|
836
|
+
],
|
|
586
837
|
flavour.value,
|
|
587
838
|
),
|
|
588
839
|
host=host,
|
|
@@ -662,11 +913,13 @@ def user_data_add(
|
|
|
662
913
|
action="add",
|
|
663
914
|
userdata_items=cast(List[Union[str, xr.Dataset]], paths),
|
|
664
915
|
metadata=facet_dict,
|
|
665
|
-
host=host
|
|
916
|
+
host=host,
|
|
666
917
|
)
|
|
667
918
|
|
|
668
919
|
|
|
669
|
-
@user_data_app.command(
|
|
920
|
+
@user_data_app.command(
|
|
921
|
+
name="delete", help="Delete user data from the databrowser."
|
|
922
|
+
)
|
|
670
923
|
@exception_handler
|
|
671
924
|
def user_data_delete(
|
|
672
925
|
search_keys: List[str] = typer.Option(
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
"""Query climate data sets by using-key value pair search queries."""
|
|
2
2
|
|
|
3
|
-
|
|
4
3
|
import sys
|
|
5
4
|
from collections import defaultdict
|
|
6
5
|
from fnmatch import fnmatch
|
|
@@ -64,8 +63,10 @@ class databrowser:
|
|
|
64
63
|
timestamp. The timestamps has to follow ISO-8601. Valid strings are
|
|
65
64
|
``%Y-%m-%dT%H:%M to %Y-%m-%dT%H:%M`` for time ranges or
|
|
66
65
|
``%Y-%m-%dT%H:%M`` for single time stamps.
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
|
|
67
|
+
.. note:: You don't have to give the full string format to subset time
|
|
68
|
+
steps ``%Y``, ``%Y-%m`` etc are also valid.
|
|
69
|
+
|
|
69
70
|
time_select: str, default: flexible
|
|
70
71
|
Operator that specifies how the time period is selected. Choose from
|
|
71
72
|
flexible (default), strict or file. ``strict`` returns only those files
|
|
@@ -75,11 +76,25 @@ class databrowser:
|
|
|
75
76
|
``flexible`` returns those files that have either start or end period
|
|
76
77
|
covered. ``file`` will only return files where the entire time
|
|
77
78
|
period is contained within `one single` file.
|
|
79
|
+
bbox: str, default: ""
|
|
80
|
+
Special search facet to refine/subset search results by spatial extent.
|
|
81
|
+
This can be a list representation of a bounding box or a WKT polygon.
|
|
82
|
+
Valid lists are ``min_lon max_lon min_lat max_lat`` for bounding
|
|
83
|
+
boxes and Well-Known Text (WKT) format for polygons.
|
|
84
|
+
|
|
85
|
+
bbox_select: str, default: flexible
|
|
86
|
+
Operator that specifies how the spatial extent is selected. Choose from
|
|
87
|
+
flexible (default), strict or file. ``strict`` returns only those files
|
|
88
|
+
that fully contain the query extent. The bbox search ``-10 10 -10 10``
|
|
89
|
+
will not select files covering only ``0 5 0 5`` with the ``strict``
|
|
90
|
+
method. ``flexible`` will select those files as it returns files that
|
|
91
|
+
have any overlap with the query extent. ``file`` will only return files
|
|
92
|
+
where the entire spatial extent is contained by the query geometry.
|
|
78
93
|
uniq_key: str, default: file
|
|
79
94
|
Chose if the solr search query should return paths to files or
|
|
80
95
|
uris, uris will have the file path along with protocol of the storage
|
|
81
|
-
system.
|
|
82
|
-
|
|
96
|
+
system. URIs are useful when working with libraries like fsspec, which
|
|
97
|
+
require protocol information.
|
|
83
98
|
host: str, default: None
|
|
84
99
|
Override the host name of the databrowser server. This is usually the
|
|
85
100
|
url where the freva web site can be found. Such as www.freva.dkrz.de.
|
|
@@ -215,6 +230,8 @@ class databrowser:
|
|
|
215
230
|
time: Optional[str] = None,
|
|
216
231
|
host: Optional[str] = None,
|
|
217
232
|
time_select: Literal["flexible", "strict", "file"] = "flexible",
|
|
233
|
+
bbox: Optional[Tuple[float, float, float, float]] = None,
|
|
234
|
+
bbox_select: Literal["flexible", "strict", "file"] = "flexible",
|
|
218
235
|
stream_zarr: bool = False,
|
|
219
236
|
multiversion: bool = False,
|
|
220
237
|
fail_on_error: bool = False,
|
|
@@ -239,6 +256,10 @@ class databrowser:
|
|
|
239
256
|
if time:
|
|
240
257
|
self._params["time"] = time
|
|
241
258
|
self._params["time_select"] = time_select
|
|
259
|
+
if bbox:
|
|
260
|
+
bbox_str = ",".join(map(str, bbox))
|
|
261
|
+
self._params["bbox"] = bbox_str
|
|
262
|
+
self._params["bbox_select"] = bbox_select
|
|
242
263
|
if facets:
|
|
243
264
|
self._add_search_keyword_args_from_facet(facets, facet_search)
|
|
244
265
|
|
|
@@ -246,7 +267,8 @@ class databrowser:
|
|
|
246
267
|
self, facets: Tuple[str, ...], search_kw: Dict[str, List[str]]
|
|
247
268
|
) -> None:
|
|
248
269
|
metadata = {
|
|
249
|
-
k: v[::2]
|
|
270
|
+
k: v[::2]
|
|
271
|
+
for (k, v) in self._facet_search(extended_search=True).items()
|
|
250
272
|
}
|
|
251
273
|
primary_key = list(metadata.keys() or ["project"])[0]
|
|
252
274
|
num_facets = 0
|
|
@@ -304,7 +326,9 @@ class databrowser:
|
|
|
304
326
|
|
|
305
327
|
# Create a table-like structure for available flavors and search facets
|
|
306
328
|
style = 'style="text-align: left"'
|
|
307
|
-
facet_heading =
|
|
329
|
+
facet_heading = (
|
|
330
|
+
f"Available search facets for <em>{self._flavour}</em> flavour"
|
|
331
|
+
)
|
|
308
332
|
html_repr = (
|
|
309
333
|
"<table>"
|
|
310
334
|
f"<tr><th colspan='2' {style}>{self.__class__.__name__}"
|
|
@@ -345,7 +369,9 @@ class databrowser:
|
|
|
345
369
|
if self._stream_zarr:
|
|
346
370
|
token = self._auth.check_authentication(auth_url=self._cfg.auth_url)
|
|
347
371
|
url = self._cfg.zarr_loader_url
|
|
348
|
-
kwargs["headers"] = {
|
|
372
|
+
kwargs["headers"] = {
|
|
373
|
+
"Authorization": f"Bearer {token['access_token']}"
|
|
374
|
+
}
|
|
349
375
|
kwargs["params"] = {"catalogue-type": "intake"}
|
|
350
376
|
result = self._request("GET", url, **kwargs)
|
|
351
377
|
if result is None:
|
|
@@ -357,7 +383,9 @@ class databrowser:
|
|
|
357
383
|
for content in result.iter_content(decode_unicode=False):
|
|
358
384
|
stream.write(content)
|
|
359
385
|
except Exception as error:
|
|
360
|
-
raise ValueError(
|
|
386
|
+
raise ValueError(
|
|
387
|
+
f"Couldn't write catalogue content: {error}"
|
|
388
|
+
) from None
|
|
361
389
|
|
|
362
390
|
def intake_catalogue(self) -> intake_esm.core.esm_datastore:
|
|
363
391
|
"""Create an intake esm catalogue object from the search.
|
|
@@ -388,7 +416,88 @@ class databrowser:
|
|
|
388
416
|
"""
|
|
389
417
|
with NamedTemporaryFile(suffix=".json") as temp_f:
|
|
390
418
|
self._create_intake_catalogue_file(temp_f.name)
|
|
391
|
-
return
|
|
419
|
+
return cast(
|
|
420
|
+
intake_esm.core.esm_datastore,
|
|
421
|
+
intake.open_esm_datastore(temp_f.name),
|
|
422
|
+
)
|
|
423
|
+
|
|
424
|
+
def stac_catalogue(
|
|
425
|
+
self,
|
|
426
|
+
filename: Optional[Union[str, Path]] = None,
|
|
427
|
+
**kwargs: Any,
|
|
428
|
+
) -> str:
|
|
429
|
+
"""Create a static STAC catalogue from
|
|
430
|
+
the search.
|
|
431
|
+
|
|
432
|
+
Parameters
|
|
433
|
+
~~~~~~~~~~
|
|
434
|
+
filename: str, default: None
|
|
435
|
+
The filename of the STAC catalogue. If not given
|
|
436
|
+
or doesn't exist the STAC catalogue will be saved
|
|
437
|
+
to the current working directory.
|
|
438
|
+
**kwargs: Any
|
|
439
|
+
Additional keyword arguments to be passed to the request.
|
|
440
|
+
|
|
441
|
+
Returns
|
|
442
|
+
~~~~~~~
|
|
443
|
+
BinaryIO
|
|
444
|
+
A zip file stream
|
|
445
|
+
|
|
446
|
+
Raises
|
|
447
|
+
~~~~~~
|
|
448
|
+
ValueError: If stac-catalogue creation failed.
|
|
449
|
+
|
|
450
|
+
Example
|
|
451
|
+
~~~~~~~
|
|
452
|
+
Let's create a static STAC catalogue:
|
|
453
|
+
|
|
454
|
+
.. execute_code::
|
|
455
|
+
|
|
456
|
+
from tempfile import mktemp
|
|
457
|
+
temp_path = mktemp(suffix=".zip")
|
|
458
|
+
|
|
459
|
+
from freva_client import databrowser
|
|
460
|
+
db = databrowser(dataset="cmip6-hsm")
|
|
461
|
+
db.stac_catalogue(filename=temp_path)
|
|
462
|
+
print(f"STAC catalog saved to: {temp_path}")
|
|
463
|
+
|
|
464
|
+
"""
|
|
465
|
+
|
|
466
|
+
kwargs.update({"stream": True})
|
|
467
|
+
stac_url = self._cfg.stac_url
|
|
468
|
+
pprint("[b][green]Downloading the STAC catalog started ...[green][b]")
|
|
469
|
+
result = self._request("GET", stac_url, **kwargs)
|
|
470
|
+
if result is None or result.status_code == 404:
|
|
471
|
+
raise ValueError( # pragma: no cover
|
|
472
|
+
"No STAC catalog found. Please check if you have any search results."
|
|
473
|
+
)
|
|
474
|
+
default_filename = (
|
|
475
|
+
result.headers.get("Content-Disposition", "")
|
|
476
|
+
.split("filename=")[-1]
|
|
477
|
+
.strip('"')
|
|
478
|
+
)
|
|
479
|
+
|
|
480
|
+
if filename is None:
|
|
481
|
+
save_path = Path.cwd() / default_filename
|
|
482
|
+
else:
|
|
483
|
+
save_path = Path(cast(str, filename))
|
|
484
|
+
if save_path.is_dir() and save_path.exists():
|
|
485
|
+
save_path = save_path / default_filename
|
|
486
|
+
|
|
487
|
+
save_path.parent.mkdir(parents=True, exist_ok=True)
|
|
488
|
+
|
|
489
|
+
total_size = 0
|
|
490
|
+
with open(save_path, "wb") as f:
|
|
491
|
+
for chunk in result.iter_content(chunk_size=8192):
|
|
492
|
+
if chunk:
|
|
493
|
+
f.write(chunk)
|
|
494
|
+
total_size += len(chunk)
|
|
495
|
+
|
|
496
|
+
return (
|
|
497
|
+
f"STAC catalog saved to: {save_path} "
|
|
498
|
+
f"(size: {total_size / 1024 / 1024:.2f} MB). "
|
|
499
|
+
f"Or simply download from: {result.url}"
|
|
500
|
+
)
|
|
392
501
|
|
|
393
502
|
@classmethod
|
|
394
503
|
def count_values(
|
|
@@ -400,6 +509,8 @@ class databrowser:
|
|
|
400
509
|
time: Optional[str] = None,
|
|
401
510
|
host: Optional[str] = None,
|
|
402
511
|
time_select: Literal["flexible", "strict", "file"] = "flexible",
|
|
512
|
+
bbox: Optional[Tuple[float, float, float, float]] = None,
|
|
513
|
+
bbox_select: Literal["flexible", "strict", "file"] = "flexible",
|
|
403
514
|
multiversion: bool = False,
|
|
404
515
|
fail_on_error: bool = False,
|
|
405
516
|
extended_search: bool = False,
|
|
@@ -423,8 +534,11 @@ class databrowser:
|
|
|
423
534
|
This can be a string representation of a time range or a single
|
|
424
535
|
timestamp. The timestamp has to follow ISO-8601. Valid strings are
|
|
425
536
|
``%Y-%m-%dT%H:%M`` to ``%Y-%m-%dT%H:%M`` for time ranges and
|
|
426
|
-
``%Y-%m-%dT%H:%M``.
|
|
427
|
-
|
|
537
|
+
``%Y-%m-%dT%H:%M``.
|
|
538
|
+
|
|
539
|
+
.. note:: You don't have to give the full string format to subset time
|
|
540
|
+
steps ``%Y``, ``%Y-%m`` etc are also valid.
|
|
541
|
+
|
|
428
542
|
time_select: str, default: flexible
|
|
429
543
|
Operator that specifies how the time period is selected. Choose from
|
|
430
544
|
flexible (default), strict or file. ``strict`` returns only those files
|
|
@@ -434,6 +548,20 @@ class databrowser:
|
|
|
434
548
|
``flexible`` returns those files that have either start or end period
|
|
435
549
|
covered. ``file`` will only return files where the entire time
|
|
436
550
|
period is contained within `one single` file.
|
|
551
|
+
bbox: str, default: ""
|
|
552
|
+
Special search facet to refine/subset search results by spatial extent.
|
|
553
|
+
This can be a list representation of a bounding box or a WKT polygon.
|
|
554
|
+
Valid lists are ``min_lon max_lon min_lat max_lat`` for bounding
|
|
555
|
+
boxes and Well-Known Text (WKT) format for polygons.
|
|
556
|
+
|
|
557
|
+
bbox_select: str, default: flexible
|
|
558
|
+
Operator that specifies how the spatial extent is selected. Choose from
|
|
559
|
+
flexible (default), strict or file. ``strict`` returns only those files
|
|
560
|
+
that fully contain the query extent. The bbox search ``-10 10 -10 10``
|
|
561
|
+
will not select files covering only ``0 5 0 5`` with the ``strict``
|
|
562
|
+
method. ``flexible`` will select those files as it returns files that
|
|
563
|
+
have any overlap with the query extent. ``file`` will only return files
|
|
564
|
+
where the entire spatial extent is contained by the query geometry.
|
|
437
565
|
extended_search: bool, default: False
|
|
438
566
|
Retrieve information on additional search keys.
|
|
439
567
|
host: str, default: None
|
|
@@ -485,6 +613,8 @@ class databrowser:
|
|
|
485
613
|
flavour=flavour,
|
|
486
614
|
time=time,
|
|
487
615
|
time_select=time_select,
|
|
616
|
+
bbox=bbox,
|
|
617
|
+
bbox_select=bbox_select,
|
|
488
618
|
host=host,
|
|
489
619
|
multiversion=multiversion,
|
|
490
620
|
fail_on_error=fail_on_error,
|
|
@@ -495,7 +625,9 @@ class databrowser:
|
|
|
495
625
|
result = this._facet_search(extended_search=extended_search)
|
|
496
626
|
counts = {}
|
|
497
627
|
for facet, value_counts in result.items():
|
|
498
|
-
counts[facet] = dict(
|
|
628
|
+
counts[facet] = dict(
|
|
629
|
+
zip(value_counts[::2], map(int, value_counts[1::2]))
|
|
630
|
+
)
|
|
499
631
|
return counts
|
|
500
632
|
|
|
501
633
|
@cached_property
|
|
@@ -520,7 +652,8 @@ class databrowser:
|
|
|
520
652
|
|
|
521
653
|
"""
|
|
522
654
|
return {
|
|
523
|
-
k: v[::2]
|
|
655
|
+
k: v[::2]
|
|
656
|
+
for (k, v) in self._facet_search(extended_search=True).items()
|
|
524
657
|
}
|
|
525
658
|
|
|
526
659
|
@classmethod
|
|
@@ -533,6 +666,8 @@ class databrowser:
|
|
|
533
666
|
time: Optional[str] = None,
|
|
534
667
|
host: Optional[str] = None,
|
|
535
668
|
time_select: Literal["flexible", "strict", "file"] = "flexible",
|
|
669
|
+
bbox: Optional[Tuple[float, float, float, float]] = None,
|
|
670
|
+
bbox_select: Literal["flexible", "strict", "file"] = "flexible",
|
|
536
671
|
multiversion: bool = False,
|
|
537
672
|
fail_on_error: bool = False,
|
|
538
673
|
extended_search: bool = False,
|
|
@@ -559,8 +694,11 @@ class databrowser:
|
|
|
559
694
|
This can be a string representation of a time range or a single
|
|
560
695
|
timestamp. The timestamp has to follow ISO-8601. Valid strings are
|
|
561
696
|
``%Y-%m-%dT%H:%M`` to ``%Y-%m-%dT%H:%M`` for time ranges and
|
|
562
|
-
``%Y-%m-%dT%H:%M``.
|
|
563
|
-
|
|
697
|
+
``%Y-%m-%dT%H:%M``.
|
|
698
|
+
|
|
699
|
+
.. note:: You don't have to give the full string format to subset time
|
|
700
|
+
steps ``%Y``, ``%Y-%m`` etc are also valid.
|
|
701
|
+
|
|
564
702
|
time_select: str, default: flexible
|
|
565
703
|
Operator that specifies how the time period is selected. Choose from
|
|
566
704
|
flexible (default), strict or file. ``strict`` returns only those files
|
|
@@ -570,6 +708,20 @@ class databrowser:
|
|
|
570
708
|
``flexible`` returns those files that have either start or end period
|
|
571
709
|
covered. ``file`` will only return files where the entire time
|
|
572
710
|
period is contained within *one single* file.
|
|
711
|
+
bbox: str, default: ""
|
|
712
|
+
Special search facet to refine/subset search results by spatial extent.
|
|
713
|
+
This can be a list representation of a bounding box or a WKT polygon.
|
|
714
|
+
Valid lists are ``min_lon max_lon min_lat max_lat`` for bounding
|
|
715
|
+
boxes and Well-Known Text (WKT) format for polygons.
|
|
716
|
+
|
|
717
|
+
bbox_select: str, default: flexible
|
|
718
|
+
Operator that specifies how the spatial extent is selected. Choose from
|
|
719
|
+
flexible (default), strict or file. ``strict`` returns only those files
|
|
720
|
+
that fully contain the query extent. The bbox search ``-10 10 -10 10``
|
|
721
|
+
will not select files covering only ``0 5 0 5`` with the ``strict``
|
|
722
|
+
method. ``flexible`` will select those files as it returns files that
|
|
723
|
+
have any overlap with the query extent. ``file`` will only return files
|
|
724
|
+
where the entire spatial extent is contained by the query geometry.
|
|
573
725
|
extended_search: bool, default: False
|
|
574
726
|
Retrieve information on additional search keys.
|
|
575
727
|
multiversion: bool, default: False
|
|
@@ -638,12 +790,30 @@ class databrowser:
|
|
|
638
790
|
from freva_client import databrowser
|
|
639
791
|
print(databrowser.metadata_search("reana*", realm="ocean", flavour="cmip6"))
|
|
640
792
|
|
|
793
|
+
In datasets with multiple versions only the `latest` version (i.e.
|
|
794
|
+
`highest` version number) is returned by default. Querying a specific
|
|
795
|
+
version from a multi versioned datasets requires the ``multiversion``
|
|
796
|
+
flag in combination with the ``version`` special attribute:
|
|
797
|
+
|
|
798
|
+
.. execute_code::
|
|
799
|
+
|
|
800
|
+
from freva_client import databrowser
|
|
801
|
+
res = databrowser.metadata_search(dataset="cmip6-fs",
|
|
802
|
+
model="access-cm2", version="v20191108", extended_search=True,
|
|
803
|
+
multiversion=True)
|
|
804
|
+
print(res)
|
|
805
|
+
|
|
806
|
+
If no particular ``version`` is requested, information of all versions
|
|
807
|
+
will be returned.
|
|
808
|
+
|
|
641
809
|
"""
|
|
642
810
|
this = cls(
|
|
643
811
|
*facets,
|
|
644
812
|
flavour=flavour,
|
|
645
813
|
time=time,
|
|
646
814
|
time_select=time_select,
|
|
815
|
+
bbox=bbox,
|
|
816
|
+
bbox_select=bbox_select,
|
|
647
817
|
host=host,
|
|
648
818
|
multiversion=multiversion,
|
|
649
819
|
fail_on_error=fail_on_error,
|
|
@@ -653,7 +823,9 @@ class databrowser:
|
|
|
653
823
|
)
|
|
654
824
|
return {
|
|
655
825
|
k: v[::2]
|
|
656
|
-
for (k, v) in this._facet_search(
|
|
826
|
+
for (k, v) in this._facet_search(
|
|
827
|
+
extended_search=extended_search
|
|
828
|
+
).items()
|
|
657
829
|
}
|
|
658
830
|
|
|
659
831
|
@classmethod
|
|
@@ -782,11 +954,11 @@ class databrowser:
|
|
|
782
954
|
import xarray as xr
|
|
783
955
|
token_info = authenticate(username="janedoe")
|
|
784
956
|
filenames = (
|
|
785
|
-
"../freva-rest/src/databrowser_api/mock/data/model/regional/cordex/output/EUR-11/"
|
|
957
|
+
"../freva-rest/src/freva_rest/databrowser_api/mock/data/model/regional/cordex/output/EUR-11/"
|
|
786
958
|
"GERICS/NCC-NorESM1-M/rcp85/r1i1p1/GERICS-REMO2015/v1/3hr/pr/v20181212/*.nc"
|
|
787
959
|
)
|
|
788
960
|
filename1 = (
|
|
789
|
-
"../freva-rest/src/databrowser_api/mock/data/model/regional/cordex/output/EUR-11/"
|
|
961
|
+
"../freva-rest/src/freva_rest/databrowser_api/mock/data/model/regional/cordex/output/EUR-11/"
|
|
790
962
|
"CLMcom/MPI-M-MPI-ESM-LR/historical/r0i0p0/CLMcom-CCLM4-8-17/v1/fx/orog/v20140515/"
|
|
791
963
|
"orog_EUR-11_MPI-M-MPI-ESM-LR_historical_r1i1p1_CLMcom-CCLM4-8-17_v1_fx.nc"
|
|
792
964
|
)
|
|
@@ -856,7 +1028,7 @@ class databrowser:
|
|
|
856
1028
|
method: Literal["GET", "POST", "PUT", "PATCH", "DELETE"],
|
|
857
1029
|
url: str,
|
|
858
1030
|
data: Optional[Dict[str, Any]] = None,
|
|
859
|
-
**kwargs: Any
|
|
1031
|
+
**kwargs: Any,
|
|
860
1032
|
) -> Optional[requests.models.Response]:
|
|
861
1033
|
"""Request method to handle CRUD operations (GET, POST, PUT, PATCH, DELETE)."""
|
|
862
1034
|
method_upper = method.upper()
|
|
@@ -864,8 +1036,13 @@ class databrowser:
|
|
|
864
1036
|
params = kwargs.pop("params", {})
|
|
865
1037
|
stream = kwargs.pop("stream", False)
|
|
866
1038
|
|
|
867
|
-
logger.debug(
|
|
868
|
-
|
|
1039
|
+
logger.debug(
|
|
1040
|
+
"%s request to %s with data: %s and parameters: %s",
|
|
1041
|
+
method_upper,
|
|
1042
|
+
url,
|
|
1043
|
+
data,
|
|
1044
|
+
{**self._params, **params},
|
|
1045
|
+
)
|
|
869
1046
|
|
|
870
1047
|
try:
|
|
871
1048
|
req = requests.Request(
|
|
@@ -873,7 +1050,7 @@ class databrowser:
|
|
|
873
1050
|
url=url,
|
|
874
1051
|
params={**self._params, **params},
|
|
875
1052
|
json=None if method_upper in "GET" else data,
|
|
876
|
-
**kwargs
|
|
1053
|
+
**kwargs,
|
|
877
1054
|
)
|
|
878
1055
|
with requests.Session() as session:
|
|
879
1056
|
prepared = session.prepare_request(req)
|
|
@@ -883,8 +1060,10 @@ class databrowser:
|
|
|
883
1060
|
|
|
884
1061
|
except KeyboardInterrupt:
|
|
885
1062
|
pprint("[red][b]User interrupt: Exit[/red][/b]", file=sys.stderr)
|
|
886
|
-
except (
|
|
887
|
-
|
|
1063
|
+
except (
|
|
1064
|
+
requests.exceptions.ConnectionError,
|
|
1065
|
+
requests.exceptions.HTTPError,
|
|
1066
|
+
) as error:
|
|
888
1067
|
msg = f"{method_upper} request failed with {error}"
|
|
889
1068
|
if self._fail_on_error:
|
|
890
1069
|
raise ValueError(msg) from None
|
|
@@ -53,7 +53,7 @@ class Config:
|
|
|
53
53
|
ini_parser.read_string(path.read_text())
|
|
54
54
|
config = ini_parser["evaluation_system"]
|
|
55
55
|
scheme, host = self._split_url(
|
|
56
|
-
config.get("databrowser.host"
|
|
56
|
+
config.get("databrowser.host", config.get("solr.host", ""))
|
|
57
57
|
)
|
|
58
58
|
host, _, port = (host or "").partition(":")
|
|
59
59
|
port = port or config.get("databrowser.port", "")
|
|
@@ -88,7 +88,9 @@ class Config:
|
|
|
88
88
|
try:
|
|
89
89
|
res = requests.get(f"{self.databrowser_url}/overview", timeout=15)
|
|
90
90
|
except requests.exceptions.ConnectionError:
|
|
91
|
-
raise ValueError(
|
|
91
|
+
raise ValueError(
|
|
92
|
+
f"Could not connect to {self.databrowser_url}"
|
|
93
|
+
) from None
|
|
92
94
|
return cast(Dict[str, Any], res.json())
|
|
93
95
|
|
|
94
96
|
def _get_databrowser_host_from_config(self) -> str:
|
|
@@ -103,7 +105,9 @@ class Config:
|
|
|
103
105
|
Path(appdirs.user_config_dir("freva")) / "freva.toml": "toml",
|
|
104
106
|
Path(self.get_dirs(user=True)) / "freva.toml": "toml",
|
|
105
107
|
freva_config: "toml",
|
|
106
|
-
Path(
|
|
108
|
+
Path(
|
|
109
|
+
os.environ.get("EVALUATION_SYSTEM_CONFIG_FILE") or eval_conf
|
|
110
|
+
): "ini",
|
|
107
111
|
}
|
|
108
112
|
for config_path, config_type in paths.items():
|
|
109
113
|
if config_path.is_file():
|
|
@@ -130,7 +134,9 @@ class Config:
|
|
|
130
134
|
@property
|
|
131
135
|
def search_url(self) -> str:
|
|
132
136
|
"""Define the data search endpoint."""
|
|
133
|
-
return
|
|
137
|
+
return (
|
|
138
|
+
f"{self.databrowser_url}/data-search/{self.flavour}/{self.uniq_key}"
|
|
139
|
+
)
|
|
134
140
|
|
|
135
141
|
@property
|
|
136
142
|
def zarr_loader_url(self) -> str:
|
|
@@ -140,13 +146,19 @@ class Config:
|
|
|
140
146
|
@property
|
|
141
147
|
def intake_url(self) -> str:
|
|
142
148
|
"""Define the url for creating intake catalogues."""
|
|
143
|
-
return f"{self.databrowser_url}/
|
|
149
|
+
return f"{self.databrowser_url}/intake-catalogue/{self.flavour}/{self.uniq_key}"
|
|
150
|
+
|
|
151
|
+
@property
|
|
152
|
+
def stac_url(self) -> str:
|
|
153
|
+
"""Define the url for creating stac catalogue."""
|
|
154
|
+
return f"{self.databrowser_url}/stac-catalogue/{self.flavour}/{self.uniq_key}"
|
|
144
155
|
|
|
145
156
|
@property
|
|
146
157
|
def metadata_url(self) -> str:
|
|
147
158
|
"""Define the endpoint for the metadata search."""
|
|
148
159
|
return (
|
|
149
|
-
f"{self.databrowser_url}/
|
|
160
|
+
f"{self.databrowser_url}/metadata-search/"
|
|
161
|
+
f"{self.flavour}/{self.uniq_key}"
|
|
150
162
|
)
|
|
151
163
|
|
|
152
164
|
@staticmethod
|
|
@@ -166,7 +178,7 @@ class Config:
|
|
|
166
178
|
if port:
|
|
167
179
|
hostname = f"{hostname}:{port}"
|
|
168
180
|
hostname = hostname.partition("/")[0]
|
|
169
|
-
return f"{scheme}://{hostname}/api"
|
|
181
|
+
return f"{scheme}://{hostname}/api/freva-nextgen"
|
|
170
182
|
|
|
171
183
|
@staticmethod
|
|
172
184
|
def get_dirs(user: bool = True) -> Path:
|
|
@@ -199,9 +211,12 @@ class UserDataHandler:
|
|
|
199
211
|
This class is used for processing user data and extracting metadata
|
|
200
212
|
from the data files.
|
|
201
213
|
"""
|
|
214
|
+
|
|
202
215
|
def __init__(self, userdata_items: List[Union[str, xr.Dataset]]) -> None:
|
|
203
216
|
self._suffixes = [".nc", ".nc4", ".grb", ".grib", ".zarr", "zar"]
|
|
204
|
-
self.user_metadata: List[
|
|
217
|
+
self.user_metadata: List[
|
|
218
|
+
Dict[str, Union[str, List[str], Dict[str, str]]]
|
|
219
|
+
] = []
|
|
205
220
|
self._metadata_collection: List[Dict[str, Union[str, List[str]]]] = []
|
|
206
221
|
try:
|
|
207
222
|
self._executor = concurrent.futures.ThreadPoolExecutor(
|
|
@@ -238,28 +253,37 @@ class UserDataHandler:
|
|
|
238
253
|
validated_xarray_datasets.append(data)
|
|
239
254
|
|
|
240
255
|
if not validated_paths and not validated_xarray_datasets:
|
|
241
|
-
raise FileNotFoundError(
|
|
256
|
+
raise FileNotFoundError(
|
|
257
|
+
"No valid file paths or xarray datasets found."
|
|
258
|
+
)
|
|
242
259
|
return {
|
|
243
260
|
"validated_user_paths": validated_paths,
|
|
244
261
|
"validated_user_xrdatasets": validated_xarray_datasets,
|
|
245
262
|
}
|
|
246
263
|
|
|
247
|
-
def _process_user_data(
|
|
248
|
-
|
|
264
|
+
def _process_user_data(
|
|
265
|
+
self,
|
|
266
|
+
userdata_items: List[Union[str, xr.Dataset]],
|
|
267
|
+
) -> None:
|
|
249
268
|
"""Process xarray datasets and file paths using thread pool."""
|
|
250
269
|
futures = []
|
|
251
|
-
validated_userdata: Dict[str, Union[List[Path], List[xr.Dataset]]] =
|
|
270
|
+
validated_userdata: Dict[str, Union[List[Path], List[xr.Dataset]]] = (
|
|
252
271
|
self._validate_user_data(userdata_items)
|
|
272
|
+
)
|
|
253
273
|
if validated_userdata["validated_user_xrdatasets"]:
|
|
254
274
|
futures.append(
|
|
255
|
-
self._executor.submit(
|
|
256
|
-
|
|
275
|
+
self._executor.submit(
|
|
276
|
+
self._process_userdata_in_executor,
|
|
277
|
+
validated_userdata["validated_user_xrdatasets"],
|
|
278
|
+
)
|
|
257
279
|
)
|
|
258
280
|
|
|
259
281
|
if validated_userdata["validated_user_paths"]:
|
|
260
282
|
futures.append(
|
|
261
|
-
self._executor.submit(
|
|
262
|
-
|
|
283
|
+
self._executor.submit(
|
|
284
|
+
self._process_userdata_in_executor,
|
|
285
|
+
validated_userdata["validated_user_paths"],
|
|
286
|
+
)
|
|
263
287
|
)
|
|
264
288
|
for future in futures:
|
|
265
289
|
try:
|
|
@@ -309,11 +333,11 @@ class UserDataHandler:
|
|
|
309
333
|
|
|
310
334
|
try:
|
|
311
335
|
dset = (
|
|
312
|
-
path
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
336
|
+
path
|
|
337
|
+
if isinstance(path, xr.Dataset)
|
|
338
|
+
else xr.open_mfdataset(
|
|
339
|
+
str(path), parallel=False, use_cftime=True, lock=False
|
|
340
|
+
)
|
|
317
341
|
)
|
|
318
342
|
time_freq = dset.attrs.get("frequency", "")
|
|
319
343
|
data_vars = list(map(str, dset.data_vars))
|
|
@@ -328,16 +352,26 @@ class UserDataHandler:
|
|
|
328
352
|
return {}
|
|
329
353
|
if len(times) > 0:
|
|
330
354
|
try:
|
|
331
|
-
time_str =
|
|
332
|
-
|
|
355
|
+
time_str = (
|
|
356
|
+
f"[{times[0].isoformat()}Z TO {times[-1].isoformat()}Z]"
|
|
357
|
+
)
|
|
358
|
+
dt = (
|
|
359
|
+
abs((times[1] - times[0]).total_seconds())
|
|
360
|
+
if len(times) > 1
|
|
361
|
+
else 0
|
|
362
|
+
)
|
|
333
363
|
except Exception as non_cftime:
|
|
334
|
-
logger.info(
|
|
364
|
+
logger.info(
|
|
365
|
+
"The time var is not based on the cftime: %s", non_cftime
|
|
366
|
+
)
|
|
335
367
|
time_str = (
|
|
336
368
|
f"[{np.datetime_as_string(times[0], unit='s')}Z TO "
|
|
337
369
|
f"{np.datetime_as_string(times[-1], unit='s')}Z]"
|
|
338
370
|
)
|
|
339
371
|
dt = (
|
|
340
|
-
abs(
|
|
372
|
+
abs(
|
|
373
|
+
(times[1] - times[0]).astype("timedelta64[s]").astype(int)
|
|
374
|
+
)
|
|
341
375
|
if len(times) > 1
|
|
342
376
|
else 0
|
|
343
377
|
)
|
|
@@ -357,7 +391,9 @@ class UserDataHandler:
|
|
|
357
391
|
|
|
358
392
|
_data: Dict[str, Union[str, List[str], Dict[str, str]]] = {}
|
|
359
393
|
_data.setdefault("variable", variables[0])
|
|
360
|
-
_data.setdefault(
|
|
394
|
+
_data.setdefault(
|
|
395
|
+
"time_frequency", self._get_time_frequency(dt, time_freq)
|
|
396
|
+
)
|
|
361
397
|
_data["time"] = time_str
|
|
362
398
|
_data.setdefault("cmor_table", _data["time_frequency"])
|
|
363
399
|
_data.setdefault("version", "")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|