vortex-nwp 2.0.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.
- vortex/__init__.py +159 -0
- vortex/algo/__init__.py +13 -0
- vortex/algo/components.py +2462 -0
- vortex/algo/mpitools.py +1953 -0
- vortex/algo/mpitools_templates/__init__.py +1 -0
- vortex/algo/mpitools_templates/envelope_wrapper_default.tpl +27 -0
- vortex/algo/mpitools_templates/envelope_wrapper_mpiauto.tpl +29 -0
- vortex/algo/mpitools_templates/wrapstd_wrapper_default.tpl +18 -0
- vortex/algo/serversynctools.py +171 -0
- vortex/config.py +112 -0
- vortex/data/__init__.py +19 -0
- vortex/data/abstractstores.py +1510 -0
- vortex/data/containers.py +835 -0
- vortex/data/contents.py +622 -0
- vortex/data/executables.py +275 -0
- vortex/data/flow.py +119 -0
- vortex/data/geometries.ini +2689 -0
- vortex/data/geometries.py +799 -0
- vortex/data/handlers.py +1230 -0
- vortex/data/outflow.py +67 -0
- vortex/data/providers.py +487 -0
- vortex/data/resources.py +207 -0
- vortex/data/stores.py +1390 -0
- vortex/data/sync_templates/__init__.py +0 -0
- vortex/gloves.py +309 -0
- vortex/layout/__init__.py +20 -0
- vortex/layout/contexts.py +577 -0
- vortex/layout/dataflow.py +1220 -0
- vortex/layout/monitor.py +969 -0
- vortex/nwp/__init__.py +14 -0
- vortex/nwp/algo/__init__.py +21 -0
- vortex/nwp/algo/assim.py +537 -0
- vortex/nwp/algo/clim.py +1086 -0
- vortex/nwp/algo/coupling.py +831 -0
- vortex/nwp/algo/eda.py +840 -0
- vortex/nwp/algo/eps.py +785 -0
- vortex/nwp/algo/forecasts.py +886 -0
- vortex/nwp/algo/fpserver.py +1303 -0
- vortex/nwp/algo/ifsnaming.py +463 -0
- vortex/nwp/algo/ifsroot.py +404 -0
- vortex/nwp/algo/monitoring.py +263 -0
- vortex/nwp/algo/mpitools.py +694 -0
- vortex/nwp/algo/odbtools.py +1258 -0
- vortex/nwp/algo/oopsroot.py +916 -0
- vortex/nwp/algo/oopstests.py +220 -0
- vortex/nwp/algo/request.py +660 -0
- vortex/nwp/algo/stdpost.py +1641 -0
- vortex/nwp/data/__init__.py +30 -0
- vortex/nwp/data/assim.py +380 -0
- vortex/nwp/data/boundaries.py +314 -0
- vortex/nwp/data/climfiles.py +521 -0
- vortex/nwp/data/configfiles.py +153 -0
- vortex/nwp/data/consts.py +954 -0
- vortex/nwp/data/ctpini.py +149 -0
- vortex/nwp/data/diagnostics.py +209 -0
- vortex/nwp/data/eda.py +147 -0
- vortex/nwp/data/eps.py +432 -0
- vortex/nwp/data/executables.py +1045 -0
- vortex/nwp/data/fields.py +111 -0
- vortex/nwp/data/gridfiles.py +380 -0
- vortex/nwp/data/logs.py +584 -0
- vortex/nwp/data/modelstates.py +363 -0
- vortex/nwp/data/monitoring.py +193 -0
- vortex/nwp/data/namelists.py +696 -0
- vortex/nwp/data/obs.py +840 -0
- vortex/nwp/data/oopsexec.py +74 -0
- vortex/nwp/data/providers.py +207 -0
- vortex/nwp/data/query.py +206 -0
- vortex/nwp/data/stores.py +160 -0
- vortex/nwp/data/surfex.py +337 -0
- vortex/nwp/syntax/__init__.py +9 -0
- vortex/nwp/syntax/stdattrs.py +437 -0
- vortex/nwp/tools/__init__.py +10 -0
- vortex/nwp/tools/addons.py +40 -0
- vortex/nwp/tools/agt.py +67 -0
- vortex/nwp/tools/bdap.py +59 -0
- vortex/nwp/tools/bdcp.py +41 -0
- vortex/nwp/tools/bdm.py +24 -0
- vortex/nwp/tools/bdmp.py +54 -0
- vortex/nwp/tools/conftools.py +1661 -0
- vortex/nwp/tools/drhook.py +66 -0
- vortex/nwp/tools/grib.py +294 -0
- vortex/nwp/tools/gribdiff.py +104 -0
- vortex/nwp/tools/ifstools.py +203 -0
- vortex/nwp/tools/igastuff.py +273 -0
- vortex/nwp/tools/mars.py +68 -0
- vortex/nwp/tools/odb.py +657 -0
- vortex/nwp/tools/partitioning.py +258 -0
- vortex/nwp/tools/satrad.py +71 -0
- vortex/nwp/util/__init__.py +6 -0
- vortex/nwp/util/async.py +212 -0
- vortex/nwp/util/beacon.py +40 -0
- vortex/nwp/util/diffpygram.py +447 -0
- vortex/nwp/util/ens.py +279 -0
- vortex/nwp/util/hooks.py +139 -0
- vortex/nwp/util/taskdeco.py +85 -0
- vortex/nwp/util/usepygram.py +697 -0
- vortex/nwp/util/usetnt.py +101 -0
- vortex/proxy.py +6 -0
- vortex/sessions.py +374 -0
- vortex/syntax/__init__.py +9 -0
- vortex/syntax/stdattrs.py +867 -0
- vortex/syntax/stddeco.py +185 -0
- vortex/toolbox.py +1117 -0
- vortex/tools/__init__.py +20 -0
- vortex/tools/actions.py +523 -0
- vortex/tools/addons.py +316 -0
- vortex/tools/arm.py +96 -0
- vortex/tools/compression.py +325 -0
- vortex/tools/date.py +27 -0
- vortex/tools/ddhpack.py +10 -0
- vortex/tools/delayedactions.py +782 -0
- vortex/tools/env.py +541 -0
- vortex/tools/folder.py +834 -0
- vortex/tools/grib.py +738 -0
- vortex/tools/lfi.py +953 -0
- vortex/tools/listings.py +423 -0
- vortex/tools/names.py +637 -0
- vortex/tools/net.py +2124 -0
- vortex/tools/odb.py +10 -0
- vortex/tools/parallelism.py +368 -0
- vortex/tools/prestaging.py +210 -0
- vortex/tools/rawfiles.py +10 -0
- vortex/tools/schedulers.py +480 -0
- vortex/tools/services.py +940 -0
- vortex/tools/storage.py +996 -0
- vortex/tools/surfex.py +61 -0
- vortex/tools/systems.py +3976 -0
- vortex/tools/targets.py +440 -0
- vortex/util/__init__.py +9 -0
- vortex/util/config.py +1122 -0
- vortex/util/empty.py +24 -0
- vortex/util/helpers.py +216 -0
- vortex/util/introspection.py +69 -0
- vortex/util/iosponge.py +80 -0
- vortex/util/roles.py +49 -0
- vortex/util/storefunctions.py +129 -0
- vortex/util/structs.py +26 -0
- vortex/util/worker.py +162 -0
- vortex_nwp-2.0.0.dist-info/METADATA +67 -0
- vortex_nwp-2.0.0.dist-info/RECORD +144 -0
- vortex_nwp-2.0.0.dist-info/WHEEL +5 -0
- vortex_nwp-2.0.0.dist-info/licenses/LICENSE +517 -0
- vortex_nwp-2.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Various Resources for executables used by the OOPS software.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from vortex.data.executables import NWPModel
|
|
6
|
+
from ..syntax.stdattrs import gvar, arpifs_cycle, executable_flavour_deco
|
|
7
|
+
from ..syntax.stdattrs import oops_run, known_oops_testcomponent_runs
|
|
8
|
+
from vortex.syntax.stddeco import namebuilding_append
|
|
9
|
+
|
|
10
|
+
#: No automatic export
|
|
11
|
+
__all__ = []
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@namebuilding_append("src", lambda self: self.run)
|
|
15
|
+
class OOPSBinary(NWPModel):
|
|
16
|
+
"""Yet an other OOPS Binary."""
|
|
17
|
+
|
|
18
|
+
_footprint = [
|
|
19
|
+
arpifs_cycle,
|
|
20
|
+
gvar,
|
|
21
|
+
oops_run,
|
|
22
|
+
executable_flavour_deco,
|
|
23
|
+
dict(
|
|
24
|
+
info="OOPS Binary: an OOPS binary, dedicated to a task (a run in OOPS namespace).",
|
|
25
|
+
attr=dict(
|
|
26
|
+
kind=dict(
|
|
27
|
+
values=[
|
|
28
|
+
"oopsbinary",
|
|
29
|
+
],
|
|
30
|
+
),
|
|
31
|
+
gvar=dict(
|
|
32
|
+
default="master_[run]",
|
|
33
|
+
),
|
|
34
|
+
run=dict(
|
|
35
|
+
outcast=known_oops_testcomponent_runs,
|
|
36
|
+
),
|
|
37
|
+
),
|
|
38
|
+
),
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def realkind(self):
|
|
43
|
+
return "oopsbinary"
|
|
44
|
+
|
|
45
|
+
def command_line(self, configfile):
|
|
46
|
+
"""
|
|
47
|
+
Build command line for execution as a single string.
|
|
48
|
+
"""
|
|
49
|
+
cmdline = "{}".format(configfile)
|
|
50
|
+
return cmdline
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class OOPSTestComponent(OOPSBinary):
|
|
54
|
+
"""Binary for OOPS Tests of components."""
|
|
55
|
+
|
|
56
|
+
_footprint = dict(
|
|
57
|
+
info="OOPS Component Test: can run a sub-test or a family of sub-tests",
|
|
58
|
+
attr=dict(
|
|
59
|
+
run=dict(
|
|
60
|
+
values=known_oops_testcomponent_runs,
|
|
61
|
+
outcast=[],
|
|
62
|
+
),
|
|
63
|
+
),
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
def command_line(self, configfile, test_type=None):
|
|
67
|
+
"""
|
|
68
|
+
Build command line for execution as a single string.
|
|
69
|
+
"""
|
|
70
|
+
cmdline = ""
|
|
71
|
+
if test_type is not None:
|
|
72
|
+
cmdline += "-t {} ".format(test_type)
|
|
73
|
+
cmdline += super().command_line(configfile)
|
|
74
|
+
return cmdline
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Common Providers.
|
|
3
|
+
|
|
4
|
+
For now, only the BDPE access is available here (Base de Donnée des Produits Élaborés).
|
|
5
|
+
This provider should work both on Soprano servers and on HPC, be it experimentally for
|
|
6
|
+
certain parameters combinations.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from bronx.fancies import loggers
|
|
10
|
+
from bronx.stdtypes.date import Time
|
|
11
|
+
from vortex.data.providers import Provider
|
|
12
|
+
from vortex.syntax.stdattrs import DelayedEnvValue, Namespace, namespacefp
|
|
13
|
+
from vortex.util.config import GenericConfigParser
|
|
14
|
+
|
|
15
|
+
#: No automatic export
|
|
16
|
+
__all__ = []
|
|
17
|
+
|
|
18
|
+
logger = loggers.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class BdpeError(Exception):
|
|
22
|
+
"""Base for Bdpe errors."""
|
|
23
|
+
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class BdpeConfigurationError(BdpeError):
|
|
28
|
+
"""Missing BDPE product description."""
|
|
29
|
+
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class BdpeMismatchError(BdpeError):
|
|
34
|
+
"""BDPE product description does not match ressource description."""
|
|
35
|
+
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class BdpeProvider(Provider):
|
|
40
|
+
"""
|
|
41
|
+
Provider to resources stored in the BDPE database.
|
|
42
|
+
|
|
43
|
+
The BDPE only knows about product ids, base datetime, and terms.
|
|
44
|
+
A dedicated ini file describes the relationship between such ids and
|
|
45
|
+
Vortex resources. This link could be used to deduce the BDPE id from
|
|
46
|
+
the resource (a la footprints). For now, it is only checked that the
|
|
47
|
+
resource attributes are compatible with the product description.
|
|
48
|
+
|
|
49
|
+
Canvas of a complete uri:
|
|
50
|
+
bdpe://bdpe.archive.fr/EXPE/date/BDPE_num+term
|
|
51
|
+
|
|
52
|
+
The EXPE part is built from the footprint attributes that correspond
|
|
53
|
+
to env variables used by the underlying tool we use: preferred_target,
|
|
54
|
+
forbidden_target, aso., so that the BDPE store can retrieve them from
|
|
55
|
+
the uri (See :class:`BdpeStore`).
|
|
56
|
+
|
|
57
|
+
When a resource has no ``date`` attribute, the most recent data is
|
|
58
|
+
extracted from the BDPE (this feature may be used for Alert Models).
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
_footprint = [
|
|
62
|
+
namespacefp,
|
|
63
|
+
dict(
|
|
64
|
+
info="BDPE provider",
|
|
65
|
+
attr=dict(
|
|
66
|
+
namespace=dict(
|
|
67
|
+
default=Namespace("bdpe.archive.fr"),
|
|
68
|
+
values=["bdpe.archive.fr"],
|
|
69
|
+
),
|
|
70
|
+
bdpeid=dict(),
|
|
71
|
+
preferred_target=dict(
|
|
72
|
+
info="The database we'd like to get the data from - See the BDPE documentation.",
|
|
73
|
+
optional=True,
|
|
74
|
+
default=DelayedEnvValue("BDPE_CIBLE_PREFEREE", "SEC"),
|
|
75
|
+
values=[
|
|
76
|
+
"OPER",
|
|
77
|
+
"INT",
|
|
78
|
+
"SEC",
|
|
79
|
+
"oper",
|
|
80
|
+
"int",
|
|
81
|
+
"sec",
|
|
82
|
+
],
|
|
83
|
+
),
|
|
84
|
+
forbidden_target=dict(
|
|
85
|
+
info="The database we don't want to access - See the BDPE documentation.",
|
|
86
|
+
optional=True,
|
|
87
|
+
default=DelayedEnvValue("BDPE_CIBLE_INTERDITE", "OPER"),
|
|
88
|
+
values=[
|
|
89
|
+
"OPER",
|
|
90
|
+
"INT",
|
|
91
|
+
"SEC",
|
|
92
|
+
"oper",
|
|
93
|
+
"int",
|
|
94
|
+
"sec",
|
|
95
|
+
],
|
|
96
|
+
),
|
|
97
|
+
soprano_domain=dict(
|
|
98
|
+
info="Databases priorities profile - See the BDPE documentation.",
|
|
99
|
+
optional=True,
|
|
100
|
+
default=DelayedEnvValue("DOMAINE_SOPRA", "dev"),
|
|
101
|
+
values=["oper", "int", "dev"],
|
|
102
|
+
),
|
|
103
|
+
allow_archive=dict(
|
|
104
|
+
info="Allow the use of the archive version of the BDPE databases.",
|
|
105
|
+
optional=True,
|
|
106
|
+
type=bool,
|
|
107
|
+
default=False,
|
|
108
|
+
),
|
|
109
|
+
bdpe_timeout=dict(
|
|
110
|
+
info="Seconds before abandoning a request.",
|
|
111
|
+
optional=True,
|
|
112
|
+
type=int,
|
|
113
|
+
default=10,
|
|
114
|
+
),
|
|
115
|
+
bdpe_retries=dict(
|
|
116
|
+
info="Number of retries when a request fails.",
|
|
117
|
+
optional=True,
|
|
118
|
+
type=int,
|
|
119
|
+
default=3,
|
|
120
|
+
),
|
|
121
|
+
config=dict(
|
|
122
|
+
info="A ready to use configuration file object for this storage place.",
|
|
123
|
+
type=GenericConfigParser,
|
|
124
|
+
optional=True,
|
|
125
|
+
default=None,
|
|
126
|
+
),
|
|
127
|
+
inifile=dict(
|
|
128
|
+
info=(
|
|
129
|
+
"The name of the configuration file that will be used (if "
|
|
130
|
+
+ "**config** is not provided."
|
|
131
|
+
),
|
|
132
|
+
optional=True,
|
|
133
|
+
default="@bdpe-map-resources.ini",
|
|
134
|
+
),
|
|
135
|
+
),
|
|
136
|
+
fastkeys={"bdpeid"},
|
|
137
|
+
),
|
|
138
|
+
]
|
|
139
|
+
|
|
140
|
+
def __init__(self, *args, **kw):
|
|
141
|
+
logger.debug("BDPE provider init %s", self.__class__)
|
|
142
|
+
super().__init__(*args, **kw)
|
|
143
|
+
self._actual_config = self.config
|
|
144
|
+
if self._actual_config is None:
|
|
145
|
+
self._actual_config = GenericConfigParser(inifile=self.inifile)
|
|
146
|
+
|
|
147
|
+
@property
|
|
148
|
+
def realkind(self):
|
|
149
|
+
return "bdpe"
|
|
150
|
+
|
|
151
|
+
def scheme(self, resource):
|
|
152
|
+
"""A dedicated scheme."""
|
|
153
|
+
return "bdpe"
|
|
154
|
+
|
|
155
|
+
def netloc(self, resource):
|
|
156
|
+
"""The actual netloc is the ``namespace`` attribute of the current provider."""
|
|
157
|
+
return self.namespace.netloc
|
|
158
|
+
|
|
159
|
+
def basename(self, resource):
|
|
160
|
+
"""Something like 'BDPE_num+term'."""
|
|
161
|
+
myterm = getattr(resource, "term", Time(0))
|
|
162
|
+
if int(myterm) < 0:
|
|
163
|
+
myterm = Time(9000) - myterm
|
|
164
|
+
return "BDPE_{}+{!s}".format(self.bdpeid, myterm)
|
|
165
|
+
|
|
166
|
+
def pathname(self, resource):
|
|
167
|
+
"""Something like 'PREFERRED_FORBIDDEN_DOMAIN_ARCHIVE_TIMEOUT_RETRIES/date/'."""
|
|
168
|
+
try:
|
|
169
|
+
requested_date = resource.date.vortex()
|
|
170
|
+
except AttributeError:
|
|
171
|
+
requested_date = "most_recent"
|
|
172
|
+
return "{}_{}_{}_{}_{}_{}/{}".format(
|
|
173
|
+
self.preferred_target,
|
|
174
|
+
self.forbidden_target,
|
|
175
|
+
self.soprano_domain,
|
|
176
|
+
self.allow_archive,
|
|
177
|
+
self.bdpe_timeout,
|
|
178
|
+
self.bdpe_retries,
|
|
179
|
+
requested_date,
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
def uri(self, resource):
|
|
183
|
+
"""
|
|
184
|
+
Overridden to check the resource attributes against
|
|
185
|
+
the BDPE product description from the .ini file.
|
|
186
|
+
"""
|
|
187
|
+
# check that the product is described in the configuration file
|
|
188
|
+
if not self._actual_config.has_section(self.bdpeid):
|
|
189
|
+
fmt = 'Missing product n°{} in BDPE configuration file\n"{}"'
|
|
190
|
+
raise BdpeConfigurationError(
|
|
191
|
+
fmt.format(self.bdpeid, self.config.file)
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
# resource description: rely on the footprint_export (also used to JSONise resources).
|
|
195
|
+
rsrcdict = {k: str(v) for k, v in resource.footprint_export().items()}
|
|
196
|
+
|
|
197
|
+
# check the BDPE pairs against the resource's
|
|
198
|
+
for k, v in self._actual_config.items(self.bdpeid):
|
|
199
|
+
if k not in rsrcdict:
|
|
200
|
+
raise BdpeMismatchError(
|
|
201
|
+
'Missing key "{}" in resource'.format(k)
|
|
202
|
+
)
|
|
203
|
+
if rsrcdict[k] != v:
|
|
204
|
+
fmt = 'Bad value for key "{}": rsrc="{}" bdpe="{}"'
|
|
205
|
+
raise BdpeMismatchError(fmt.format(k, rsrcdict[k], v))
|
|
206
|
+
|
|
207
|
+
return super().uri(resource)
|
vortex/nwp/data/query.py
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Resources for query files used for extractions in various databases.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
|
|
7
|
+
from bronx.fancies import loggers
|
|
8
|
+
|
|
9
|
+
from vortex.data.outflow import StaticResource
|
|
10
|
+
from ..syntax.stdattrs import gvar
|
|
11
|
+
from vortex.data.contents import AlmostListContent, DataTemplate
|
|
12
|
+
|
|
13
|
+
#: No automatic export
|
|
14
|
+
__all__ = []
|
|
15
|
+
|
|
16
|
+
logger = loggers.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Query(StaticResource):
|
|
20
|
+
"""Class to deal with queries."""
|
|
21
|
+
|
|
22
|
+
_abstract = True
|
|
23
|
+
_footprint = [
|
|
24
|
+
gvar,
|
|
25
|
+
dict(
|
|
26
|
+
info="Abstract class for queries.",
|
|
27
|
+
attr=dict(
|
|
28
|
+
gvar=dict(values=["extract_stuff"], default="extract_stuff"),
|
|
29
|
+
source=dict(),
|
|
30
|
+
origin=dict(),
|
|
31
|
+
),
|
|
32
|
+
),
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
def gget_urlquery(self):
|
|
36
|
+
"""GGET specific query : ``extract``."""
|
|
37
|
+
return "extract=" + self.source
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class BDAPQuery(Query):
|
|
41
|
+
"""Class to deal with BDAP queries."""
|
|
42
|
+
|
|
43
|
+
_footprint = dict(
|
|
44
|
+
info="BDAP query",
|
|
45
|
+
attr=dict(
|
|
46
|
+
kind=dict(values=["bdap_query"]),
|
|
47
|
+
origin=dict(default="bdap", values=["bdap"], optional=True),
|
|
48
|
+
),
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def realkind(self):
|
|
53
|
+
return "bdap_query"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class BDMPQuery(Query):
|
|
57
|
+
"""Class to deal with BDMP queries."""
|
|
58
|
+
|
|
59
|
+
_footprint = dict(
|
|
60
|
+
info="BDMP query",
|
|
61
|
+
attr=dict(
|
|
62
|
+
kind=dict(values=["bdmp_query"]),
|
|
63
|
+
origin=dict(default="bdmp", values=["bdmp"], optional=True),
|
|
64
|
+
),
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
def realkind(self):
|
|
69
|
+
return "bdmp_query"
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class BDCPQuery(Query):
|
|
73
|
+
"""Class to deal with BDCP queries."""
|
|
74
|
+
|
|
75
|
+
_footprint = dict(
|
|
76
|
+
info="BDCP query",
|
|
77
|
+
attr=dict(
|
|
78
|
+
kind=dict(values=["bdcp_query"]),
|
|
79
|
+
origin=dict(default="bdcp", values=["bdcp"], optional=True),
|
|
80
|
+
),
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def realkind(self):
|
|
85
|
+
return "bdcp_query"
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class StaticCutoffDispenser:
|
|
89
|
+
"""
|
|
90
|
+
From a dictionary of cutoff times, for a given *obstype*, find the
|
|
91
|
+
best suited cutoff time.
|
|
92
|
+
|
|
93
|
+
The __call__ method takes a unique *obstype* argument. It will return the
|
|
94
|
+
best suited cutoff time for this particular *obstype*. N.B: If no exact
|
|
95
|
+
match is found, the default cutoff time will be used.
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
def __init__(self, default_cutoff, obstype_cutoffs=None):
|
|
99
|
+
self._default_cutoff = default_cutoff
|
|
100
|
+
if obstype_cutoffs:
|
|
101
|
+
self._cutoffs = {
|
|
102
|
+
k: {o.lower() for o in v} for k, v in obstype_cutoffs.items()
|
|
103
|
+
}
|
|
104
|
+
else:
|
|
105
|
+
self._cutoffs = {}
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def max_cutoff(self):
|
|
109
|
+
return self._default_cutoff
|
|
110
|
+
|
|
111
|
+
def __call__(self, obstype):
|
|
112
|
+
"""Find the best suited cutoff time for *obstype*."""
|
|
113
|
+
obstype = obstype.lower()
|
|
114
|
+
found = None
|
|
115
|
+
for k, v in self._cutoffs.items():
|
|
116
|
+
if obstype in v:
|
|
117
|
+
found = k
|
|
118
|
+
if found:
|
|
119
|
+
return found
|
|
120
|
+
else:
|
|
121
|
+
return self._default_cutoff
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class BDMQueryContent(AlmostListContent):
|
|
125
|
+
"""Read the content of BDM query file."""
|
|
126
|
+
|
|
127
|
+
_RE_OBSTYPE = re.compile(r"^(\s*)(OBS\s+TYPE\s*):(\s+)(\w+)$")
|
|
128
|
+
|
|
129
|
+
def add_cutoff_info(self, cutoffs_dispenser):
|
|
130
|
+
"""
|
|
131
|
+
Using a :class:`vortex.tools.listings.ListBasedCutoffDispenser` or
|
|
132
|
+
:class:`StaticCutoffDispenser` object, add the cutoff related
|
|
133
|
+
information in the BDM query.
|
|
134
|
+
"""
|
|
135
|
+
if cutoffs_dispenser.max_cutoff is None:
|
|
136
|
+
logger.warning(
|
|
137
|
+
"The cutoffs_dispenser is empty. No cutoff data can be retrieved"
|
|
138
|
+
)
|
|
139
|
+
else:
|
|
140
|
+
xdata = list()
|
|
141
|
+
for line in self:
|
|
142
|
+
xdata.append(line)
|
|
143
|
+
l_match = self._RE_OBSTYPE.match(line)
|
|
144
|
+
if l_match:
|
|
145
|
+
cutoff_fmt = (
|
|
146
|
+
"{0:s}{1:<"
|
|
147
|
+
+ str(len(l_match.group(2)))
|
|
148
|
+
+ "s}:{2:s}{3.ymdhms:s}\n"
|
|
149
|
+
)
|
|
150
|
+
cutoff_date = cutoffs_dispenser(l_match.group(4))
|
|
151
|
+
xdata.append(
|
|
152
|
+
cutoff_fmt.format(
|
|
153
|
+
l_match.group(1),
|
|
154
|
+
"CUTOFF",
|
|
155
|
+
l_match.group(3),
|
|
156
|
+
cutoff_date,
|
|
157
|
+
)
|
|
158
|
+
)
|
|
159
|
+
logger.info(
|
|
160
|
+
"CUTOFF=%s added for obstype < %s >.",
|
|
161
|
+
cutoff_date.ymdhms,
|
|
162
|
+
l_match.group(4),
|
|
163
|
+
)
|
|
164
|
+
self._data = xdata
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
class BDMQuery(Query):
|
|
168
|
+
"""Class to deal with BDM queries."""
|
|
169
|
+
|
|
170
|
+
_footprint = dict(
|
|
171
|
+
info="BDM query",
|
|
172
|
+
attr=dict(
|
|
173
|
+
kind=dict(values=["bdm_query"]),
|
|
174
|
+
origin=dict(default="bdm", values=["bdm"], optional=True),
|
|
175
|
+
clscontents=dict(
|
|
176
|
+
default=BDMQueryContent,
|
|
177
|
+
),
|
|
178
|
+
),
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
@property
|
|
182
|
+
def realkind(self):
|
|
183
|
+
return "bdm_query"
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
class MarsQuery(Query):
|
|
187
|
+
"""Class to deal with Mars queries"""
|
|
188
|
+
|
|
189
|
+
_footprint = dict(
|
|
190
|
+
info="Mars query",
|
|
191
|
+
attr=dict(
|
|
192
|
+
kind=dict(values=["mars_query"]),
|
|
193
|
+
origin=dict(
|
|
194
|
+
default="mars",
|
|
195
|
+
values=[
|
|
196
|
+
"mars",
|
|
197
|
+
],
|
|
198
|
+
optional=True,
|
|
199
|
+
),
|
|
200
|
+
clscontents=dict(default=DataTemplate),
|
|
201
|
+
),
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
@property
|
|
205
|
+
def realkind(self):
|
|
206
|
+
return "mars_query"
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# pylint: disable=unused-argument
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Common stores.
|
|
5
|
+
|
|
6
|
+
For now, only the BDPE store is available here, dedicated to BDPE extraction.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import footprints
|
|
10
|
+
import vortex
|
|
11
|
+
from bronx.fancies import loggers
|
|
12
|
+
from bronx.stdtypes import date
|
|
13
|
+
from vortex.data.abstractstores import Store
|
|
14
|
+
from vortex.syntax.stdattrs import compressionpipeline
|
|
15
|
+
|
|
16
|
+
#: No automatic export
|
|
17
|
+
__all__ = []
|
|
18
|
+
|
|
19
|
+
logger = loggers.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class BdpeStore(Store):
|
|
23
|
+
"""Access items stored in the BDPE database (get only)."""
|
|
24
|
+
|
|
25
|
+
_footprint = [
|
|
26
|
+
compressionpipeline,
|
|
27
|
+
dict(
|
|
28
|
+
info="Access the BDPE database",
|
|
29
|
+
attr=dict(
|
|
30
|
+
scheme=dict(
|
|
31
|
+
values=["bdpe"],
|
|
32
|
+
),
|
|
33
|
+
netloc=dict(
|
|
34
|
+
values=["bdpe.archive.fr"],
|
|
35
|
+
),
|
|
36
|
+
),
|
|
37
|
+
priority=dict(level=footprints.priorities.top.DEFAULT),
|
|
38
|
+
),
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def realkind(self):
|
|
43
|
+
return "bdpe"
|
|
44
|
+
|
|
45
|
+
def bdpelocate(self, remote, options):
|
|
46
|
+
"""Reasonably close to whatever 'remote location' could mean.
|
|
47
|
+
|
|
48
|
+
e.g.: ``bdpe://bdpe.archive.fr/EXPE/date/BDPE_num+term``
|
|
49
|
+
"""
|
|
50
|
+
return self.scheme + "://" + self.netloc + remote["path"]
|
|
51
|
+
|
|
52
|
+
def bdpecheck(self, remote, options):
|
|
53
|
+
"""Cannot check a BDPE call a priori."""
|
|
54
|
+
logger.warning("A BdpeStore is not able to perform CHECKs.")
|
|
55
|
+
return False
|
|
56
|
+
|
|
57
|
+
def bdpeput(self, local, remote, options):
|
|
58
|
+
"""Cannot write to the BDPE (See :class:`BdpeService`)."""
|
|
59
|
+
logger.error("A BdpeStore is not able to perform PUTs.")
|
|
60
|
+
return False
|
|
61
|
+
|
|
62
|
+
def bdpedelete(self, remote, options):
|
|
63
|
+
"""Cannot delete a BDPE product."""
|
|
64
|
+
logger.error("A BdpeStore is not able to perform DELETEs.")
|
|
65
|
+
return False
|
|
66
|
+
|
|
67
|
+
def bdpeget(self, remote, local, options):
|
|
68
|
+
"""Real extraction from the BDPE database."""
|
|
69
|
+
|
|
70
|
+
# Check that local is a file (i.e not a virtual container)
|
|
71
|
+
if not isinstance(local, str):
|
|
72
|
+
raise TypeError(
|
|
73
|
+
"The BDPE provider can not deal with virtual containers"
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
# remote['path'] looks like '/OPER_SEC_DEV_True_10_3/20151105T0000P/BDPE_42+06:00'
|
|
77
|
+
_, targetmix, str_date, more = remote["path"].split("/")
|
|
78
|
+
p_target, f_target, domain, s_archive, timeout, retries = (
|
|
79
|
+
targetmix.split("_")
|
|
80
|
+
)
|
|
81
|
+
productid, str_term = more[5:].split("+")
|
|
82
|
+
|
|
83
|
+
# the 'oper' domain is allowed only to the operational suite
|
|
84
|
+
if domain == "oper":
|
|
85
|
+
if not vortex.ticket().glove.profile == "oper":
|
|
86
|
+
logger.warning(
|
|
87
|
+
"Only profile 'oper' can use 'soprano_domain=oper'. Using 'dev' instead."
|
|
88
|
+
)
|
|
89
|
+
domain = "dev"
|
|
90
|
+
|
|
91
|
+
if str_date == "most_recent":
|
|
92
|
+
bdpe_date = "/"
|
|
93
|
+
else:
|
|
94
|
+
bdpe_date = date.Date(str_date).ymdhms
|
|
95
|
+
bdpe_term = date.Time(str_term).fmtraw
|
|
96
|
+
args = [
|
|
97
|
+
productid, # id
|
|
98
|
+
bdpe_date, # date: yyyymmddhhmmss
|
|
99
|
+
bdpe_term, # term: HHHHmm
|
|
100
|
+
local, # local filename
|
|
101
|
+
]
|
|
102
|
+
extraenv = dict(
|
|
103
|
+
BDPE_CIBLE_PREFEREE=p_target,
|
|
104
|
+
BDPE_CIBLE_INTERDITE=f_target,
|
|
105
|
+
DOMAINE_SOPRA=domain,
|
|
106
|
+
BDPE_TIMEOUT=timeout,
|
|
107
|
+
BDPE_RETRYS=retries,
|
|
108
|
+
)
|
|
109
|
+
if s_archive == "True":
|
|
110
|
+
extraenv["BDPE_LECTURE_ARCHIVE_AUTORISEE"] = "oui"
|
|
111
|
+
|
|
112
|
+
wsinterpreter = self.system.default_target.get(
|
|
113
|
+
"bdpe:wsclient_interpreter", None
|
|
114
|
+
)
|
|
115
|
+
wscommand = self.system.default_target.get("bdpe:wsclient_path", None)
|
|
116
|
+
if wscommand is None:
|
|
117
|
+
raise RuntimeError(
|
|
118
|
+
"bdpe:wsclient_path has to be set in the target config"
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
args.insert(0, wscommand)
|
|
122
|
+
if wsinterpreter is not None:
|
|
123
|
+
args.insert(0, wsinterpreter)
|
|
124
|
+
|
|
125
|
+
logger.debug("lirepe_cmd: %s", " ".join(args))
|
|
126
|
+
|
|
127
|
+
with self.system.env.delta_context(**extraenv):
|
|
128
|
+
rc = self.system.spawn(args, output=False, fatal=False)
|
|
129
|
+
rc = rc and self.system.path.exists(local)
|
|
130
|
+
|
|
131
|
+
diagfile = local + ".diag"
|
|
132
|
+
if not rc:
|
|
133
|
+
logger.warning(
|
|
134
|
+
"Something went wrong with the following command: %s",
|
|
135
|
+
" ".join(args),
|
|
136
|
+
)
|
|
137
|
+
if not rc or bdpe_date == "/":
|
|
138
|
+
if self.system.path.exists(diagfile):
|
|
139
|
+
logger.warning("The %s file is:", diagfile)
|
|
140
|
+
self.system.cat(diagfile)
|
|
141
|
+
if rc and self._actual_cpipeline:
|
|
142
|
+
# Deal with compressed files in the BDPE using the optional attribute
|
|
143
|
+
# store_compressed of the BDPE store.
|
|
144
|
+
tempfile = local + self._actual_cpipeline.suffix
|
|
145
|
+
rc = rc and self.system.mv(local, tempfile)
|
|
146
|
+
self._actual_cpipeline.file2uncompress(tempfile, local)
|
|
147
|
+
rc = rc and self.system.path.exists(local)
|
|
148
|
+
if not rc:
|
|
149
|
+
logger.warning(
|
|
150
|
+
"Something went wrong while uncompressing the file %s.",
|
|
151
|
+
tempfile,
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
# Final step : deal with format specific packing
|
|
155
|
+
rc = rc and self.system.forceunpack(local, fmt=options.get("fmt"))
|
|
156
|
+
|
|
157
|
+
if self.system.path.exists(diagfile):
|
|
158
|
+
self.system.remove(diagfile)
|
|
159
|
+
|
|
160
|
+
return rc
|