vortex-nwp 2.0.0b2__py3-none-any.whl → 2.1.1__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 +21 -5
- vortex/algo/components.py +6 -6
- vortex/algo/mpitools.py +12 -12
- vortex/algo/mpitools_templates/__init__.py +1 -0
- vortex/config.py +11 -7
- vortex/data/handlers.py +102 -6
- vortex/data/stores.py +15 -25
- vortex/data/sync_templates/__init__.py +0 -0
- vortex/nwp/algo/forecasts.py +7 -15
- vortex/nwp/algo/ifsroot.py +5 -0
- vortex/nwp/algo/monitoring.py +5 -0
- vortex/nwp/algo/mpitools.py +12 -2
- vortex/nwp/algo/odbtools.py +20 -0
- vortex/nwp/algo/oopsroot.py +5 -0
- vortex/toolbox.py +81 -21
- vortex/tools/actions.py +5 -82
- vortex/tools/grib.py +48 -31
- vortex/tools/net.py +27 -5
- vortex/tools/services.py +2 -2
- vortex/tools/storage.py +3 -1
- vortex/tools/systems.py +25 -14
- vortex/util/config.py +2 -1
- {vortex_nwp-2.0.0b2.dist-info → vortex_nwp-2.1.1.dist-info}/METADATA +3 -2
- {vortex_nwp-2.0.0b2.dist-info → vortex_nwp-2.1.1.dist-info}/RECORD +27 -25
- {vortex_nwp-2.0.0b2.dist-info → vortex_nwp-2.1.1.dist-info}/WHEEL +1 -1
- {vortex_nwp-2.0.0b2.dist-info → vortex_nwp-2.1.1.dist-info/licenses}/LICENSE +0 -0
- {vortex_nwp-2.0.0b2.dist-info → vortex_nwp-2.1.1.dist-info}/top_level.txt +0 -0
vortex/toolbox.py
CHANGED
|
@@ -485,15 +485,39 @@ def add_section(section, args, kw):
|
|
|
485
485
|
|
|
486
486
|
# noinspection PyShadowingBuiltins
|
|
487
487
|
def input(*args, **kw): # @ReservedAssignment
|
|
488
|
-
"""
|
|
488
|
+
r"""Declare one or more input resources.
|
|
489
|
+
|
|
490
|
+
This function takes an abitrary of keyword arguments forming the resource
|
|
491
|
+
description.
|
|
492
|
+
|
|
493
|
+
:return: A list of :py:class:`Handler <vortex.data.handlers.Handler>` objects.
|
|
494
|
+
|
|
495
|
+
**Example:**
|
|
496
|
+
|
|
497
|
+
The following call to ``input`` returns a list of 6
|
|
498
|
+
:py:class:`Handler <vortex.data.handlers.Handler>` objects, one
|
|
499
|
+
for each date and member:
|
|
500
|
+
|
|
501
|
+
.. code:: python
|
|
502
|
+
|
|
503
|
+
rhandlers = vortex.input(
|
|
504
|
+
kind='gridpoint',
|
|
505
|
+
term=1,
|
|
506
|
+
geometry='eurw1s40',
|
|
507
|
+
nativefmt='grib',
|
|
508
|
+
model='arome',
|
|
509
|
+
cutoff='production',
|
|
510
|
+
date=['2024060121', '2024060122'],
|
|
511
|
+
origin='historic',
|
|
512
|
+
vapp='arome',
|
|
513
|
+
vconf='pefrance',
|
|
514
|
+
member=[1,2,5],
|
|
515
|
+
experiment='myexp',
|
|
516
|
+
block='forecast',
|
|
517
|
+
local='gribfile_[member].grib',
|
|
518
|
+
format='grib',
|
|
519
|
+
)
|
|
489
520
|
|
|
490
|
-
Relies on the :func:`add_section` function (see its documentation), with:
|
|
491
|
-
|
|
492
|
-
* It's ``section`` attribute is automatically set to 'input';
|
|
493
|
-
* The ``kw``'s *insitu* item is set to :data:`active_insitu` by default.
|
|
494
|
-
|
|
495
|
-
:return: A list of :class:`vortex.data.handlers.Handler` objects (associated
|
|
496
|
-
with the newly created class:`~vortex.layout.dataflow.Section` objects).
|
|
497
521
|
"""
|
|
498
522
|
kw.setdefault("insitu", active_insitu)
|
|
499
523
|
kw.setdefault("batch", active_batchinputs)
|
|
@@ -532,14 +556,39 @@ def show_inputs(context=None):
|
|
|
532
556
|
|
|
533
557
|
|
|
534
558
|
def output(*args, **kw):
|
|
535
|
-
"""
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
559
|
+
r"""Declare one or more output resources.
|
|
560
|
+
|
|
561
|
+
This function takes an abitrary of keyword arguments forming the resource
|
|
562
|
+
description.
|
|
563
|
+
|
|
564
|
+
:return: A list of :py:class:`Handler <vortex.data.handlers.Handler>` objects.
|
|
565
|
+
|
|
566
|
+
**Example:**
|
|
567
|
+
|
|
568
|
+
The following call to ``output`` returns a list of 6
|
|
569
|
+
:py:class:`Handler <vortex.data.handlers.Handler>` objects, one
|
|
570
|
+
for each date and member:
|
|
571
|
+
|
|
572
|
+
.. code:: python
|
|
573
|
+
|
|
574
|
+
rhandlers = vortex.output(
|
|
575
|
+
kind='gridpoint',
|
|
576
|
+
term=1,
|
|
577
|
+
geometry='eurw1s40',
|
|
578
|
+
nativefmt='grib',
|
|
579
|
+
model='arome',
|
|
580
|
+
cutoff='production',
|
|
581
|
+
date=['2024060121', '2024060122'],
|
|
582
|
+
origin='historic',
|
|
583
|
+
vapp='arome',
|
|
584
|
+
vconf='pefrance',
|
|
585
|
+
member=[1,2,5],
|
|
586
|
+
experiment='myexp',
|
|
587
|
+
block='forecast',
|
|
588
|
+
local='gribfile_[member].grib',
|
|
589
|
+
format='grib',
|
|
590
|
+
)
|
|
540
591
|
|
|
541
|
-
:return: A list of :class:`vortex.data.handlers.Handler` objects (associated
|
|
542
|
-
with the newly created class:`~vortex.layout.dataflow.Section` objects).
|
|
543
592
|
"""
|
|
544
593
|
# Strip the metadatacheck option depending on active_metadatacheck
|
|
545
594
|
if not active_promise:
|
|
@@ -607,15 +656,26 @@ def promise(*args, **kw):
|
|
|
607
656
|
|
|
608
657
|
|
|
609
658
|
def executable(*args, **kw):
|
|
610
|
-
"""
|
|
659
|
+
r"""Declare one or more executable resources.
|
|
611
660
|
|
|
612
|
-
|
|
661
|
+
This function takes an abitrary of keyword arguments forming the
|
|
662
|
+
executable resource description.
|
|
613
663
|
|
|
614
|
-
|
|
615
|
-
|
|
664
|
+
:return: A list of :py:class:`Handler <vortex.data.handlers.Handler>` objects.
|
|
665
|
+
|
|
666
|
+
**Example:**
|
|
667
|
+
|
|
668
|
+
The following call to ``input`` returns a list of one
|
|
669
|
+
:py:class:`Handler <vortex.data.handlers.Handler>` object:
|
|
670
|
+
|
|
671
|
+
.. code:: python
|
|
672
|
+
|
|
673
|
+
rhandlers = vortex.executable(
|
|
674
|
+
kind="mfmodel",
|
|
675
|
+
local="ARPEGE",
|
|
676
|
+
remote="/path/to/binaries/ARPEGE.EX",
|
|
677
|
+
)
|
|
616
678
|
|
|
617
|
-
:return: A list of :class:`vortex.data.handlers.Handler` objects (associated
|
|
618
|
-
with the newly created class:`~vortex.layout.dataflow.Section` objects).
|
|
619
679
|
"""
|
|
620
680
|
kw.setdefault("insitu", active_insitu)
|
|
621
681
|
return add_section("executable", args, kw)
|
vortex/tools/actions.py
CHANGED
|
@@ -10,8 +10,6 @@ to be processed: e.g. mail, routing, alarm.
|
|
|
10
10
|
import bronx.stdtypes.catalog
|
|
11
11
|
import footprints
|
|
12
12
|
from bronx.fancies import loggers
|
|
13
|
-
from bronx.fancies.display import dict_as_str
|
|
14
|
-
from vortex import sessions
|
|
15
13
|
|
|
16
14
|
#: Export nothing
|
|
17
15
|
__all__ = []
|
|
@@ -134,105 +132,30 @@ class Action:
|
|
|
134
132
|
class TunableAction(Action):
|
|
135
133
|
"""An Action that may be tuned
|
|
136
134
|
|
|
137
|
-
|
|
138
|
-
- accepts the syntax `ad.action_tune(key=value)` (which has priority)
|
|
135
|
+
accepts the syntax `ad.action_tune(key=value)` (which has priority)
|
|
139
136
|
"""
|
|
140
137
|
|
|
141
138
|
def __init__(self, configuration=None, **kwargs):
|
|
142
139
|
super().__init__(**kwargs)
|
|
143
140
|
self._tuning = dict()
|
|
144
|
-
self._conf_section = configuration
|
|
145
|
-
self._conf_dict = None
|
|
146
|
-
|
|
147
|
-
@property
|
|
148
|
-
def _shtarget(self):
|
|
149
|
-
"""Warning: this may be a `vortex.syntax.stdattrs.DelayedInit` object
|
|
150
|
-
during Vortex initialization and may not have a `sections()` method
|
|
151
|
-
nor a `config` property.
|
|
152
|
-
"""
|
|
153
|
-
return sessions.current().sh.default_target
|
|
154
|
-
|
|
155
|
-
@property
|
|
156
|
-
def _conf_items(self):
|
|
157
|
-
"""Check and return the configuration: a section in the target-xxx.ini file.
|
|
158
|
-
|
|
159
|
-
If the configuration is None, an attempt is made to use the Action's kind.
|
|
160
|
-
Don't use before Vortex initialization is done (see `_shtarget`).
|
|
161
|
-
"""
|
|
162
|
-
if self._conf_dict is None:
|
|
163
|
-
if self._conf_section is None:
|
|
164
|
-
if self.kind in self._shtarget.sections():
|
|
165
|
-
self._conf_section = self.kind
|
|
166
|
-
else:
|
|
167
|
-
if self._conf_section not in self._shtarget.sections():
|
|
168
|
-
raise KeyError(
|
|
169
|
-
'No section "{}" in "{}"'.format(
|
|
170
|
-
self._conf_section, self._shtarget.config.file
|
|
171
|
-
)
|
|
172
|
-
)
|
|
173
|
-
if self._conf_section is None:
|
|
174
|
-
self._conf_dict = dict()
|
|
175
|
-
else:
|
|
176
|
-
self._conf_dict = self._shtarget.items(self._conf_section)
|
|
177
|
-
return self._conf_dict
|
|
178
141
|
|
|
179
142
|
def service_info(self, **kw):
|
|
180
|
-
for k, v in self.
|
|
143
|
+
for k, v in self._tuning.items():
|
|
181
144
|
kw.setdefault(k, v)
|
|
182
145
|
return super().service_info(**kw)
|
|
183
146
|
|
|
184
|
-
def tune(self,
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
``section`` is a specific section name, or ``None`` for all.
|
|
188
|
-
"""
|
|
189
|
-
if section is None or section == self._conf_section:
|
|
190
|
-
self._tuning.update(kw)
|
|
191
|
-
|
|
192
|
-
def _get_config_dict(self):
|
|
193
|
-
final_dict = dict()
|
|
194
|
-
final_dict.update(self._conf_items)
|
|
195
|
-
final_dict.update(self._tuning)
|
|
196
|
-
return final_dict
|
|
197
|
-
|
|
198
|
-
def info(self):
|
|
199
|
-
"""Informative string (may serve debugging purposes)."""
|
|
200
|
-
s = super().info() + " - tunable\n"
|
|
201
|
-
mix = dict()
|
|
202
|
-
mix.update(self._conf_items)
|
|
203
|
-
mix.update(self._tuning)
|
|
204
|
-
prt = dict()
|
|
205
|
-
for k, v in mix.items():
|
|
206
|
-
if k in self._tuning:
|
|
207
|
-
prt["++ " + k] = "{} (was: {})".format(
|
|
208
|
-
v,
|
|
209
|
-
str(self._conf_items[k])
|
|
210
|
-
if k in self._conf_items
|
|
211
|
-
else "<not set>",
|
|
212
|
-
)
|
|
213
|
-
else:
|
|
214
|
-
prt[" " + k] = v
|
|
215
|
-
if self._conf_section is not None:
|
|
216
|
-
s += " " * 4 + "configuration: " + self._conf_section + "\n"
|
|
217
|
-
s += dict_as_str(prt, prefix=4)
|
|
218
|
-
return s.strip()
|
|
147
|
+
def tune(self, **kw):
|
|
148
|
+
self._tuning.update(kw)
|
|
219
149
|
|
|
220
150
|
def getx(self, key, *args, **kw):
|
|
221
151
|
"""Shortcut to access the configuration overridden by the tuning."""
|
|
222
152
|
if key in self._tuning:
|
|
223
153
|
return self._tuning[key]
|
|
224
154
|
|
|
225
|
-
if self._conf_section is not None:
|
|
226
|
-
return self._shtarget.getx(
|
|
227
|
-
key=self._conf_section + ":" + key, *args, **kw
|
|
228
|
-
)
|
|
229
|
-
|
|
230
155
|
if "default" in kw:
|
|
231
156
|
return kw["default"]
|
|
232
157
|
|
|
233
|
-
raise KeyError(
|
|
234
|
-
'The "{:s}" entry was not found in any configuration'.format(key)
|
|
235
|
-
)
|
|
158
|
+
raise KeyError('The "{:s}" entry was not found'.format(key))
|
|
236
159
|
|
|
237
160
|
|
|
238
161
|
class SendMail(Action):
|
vortex/tools/grib.py
CHANGED
|
@@ -10,6 +10,7 @@ It also provdes an AlgoComponent's Mixin to properly setup the environment
|
|
|
10
10
|
when using the grib_api or ecCodes libraries.
|
|
11
11
|
"""
|
|
12
12
|
|
|
13
|
+
from pathlib import Path
|
|
13
14
|
from urllib import parse as urlparse
|
|
14
15
|
|
|
15
16
|
import re
|
|
@@ -563,37 +564,41 @@ class EcGribDecoMixin(AlgoComponentDecoMixin):
|
|
|
563
564
|
"After gribapi_setup %s = %s", a_var, self.env.getvar(a_var)
|
|
564
565
|
)
|
|
565
566
|
|
|
566
|
-
def _eccodes_envsetup(
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
567
|
+
def _eccodes_envsetup(
|
|
568
|
+
self,
|
|
569
|
+
eccodes_lib,
|
|
570
|
+
envvar="ECCODES_DEFINITIONS_PATH",
|
|
571
|
+
tgt_path="definitions",
|
|
572
|
+
):
|
|
573
|
+
"""Export envirionment variables required by ECCODES
|
|
574
|
+
|
|
575
|
+
Value is
|
|
576
|
+
|
|
577
|
+
/path/to/eccodes-X.Y.Z/share/eccodes/<target_path>
|
|
578
|
+
|
|
579
|
+
eccodes_lib: Absolute path to the eccodes so file
|
|
580
|
+
envvar: Name of the environment variable to export
|
|
581
|
+
tgt_path: Name of the eccodes install subdirectory to appear
|
|
582
|
+
in the value
|
|
583
|
+
"""
|
|
584
|
+
if envvar in self.env:
|
|
585
|
+
return envvar
|
|
586
|
+
if envvar.replace("ECCODES", "GRIB") in self.env:
|
|
587
|
+
logger.warning(
|
|
588
|
+
(
|
|
589
|
+
"%s is left unconfigured because the old grib_api's"
|
|
590
|
+
"variable is defined. ",
|
|
591
|
+
"Please remove that!",
|
|
592
|
+
),
|
|
593
|
+
envvar,
|
|
594
|
+
)
|
|
595
|
+
return envvar.replace("ECCODES", "GRIB")
|
|
596
|
+
eccodes_root = Path(eccodes_lib).parent.parent
|
|
597
|
+
self.env.setgenericpath(
|
|
598
|
+
envvar,
|
|
599
|
+
str(eccodes_root / "share" / "eccodes" / tgt_path),
|
|
571
600
|
)
|
|
572
|
-
|
|
573
|
-
eccodes_root = self.system.path.split(eccodes_root)[0]
|
|
574
|
-
eccodes_share = self.system.path.join(eccodes_root, "share", "eccodes")
|
|
575
|
-
defvar = "ECCODES_DEFINITION_PATH"
|
|
576
|
-
if defvar not in self.env:
|
|
577
|
-
if "GRIB_DEFINITION_PATH" in self.env:
|
|
578
|
-
logger.warning(dep_warn, defvar)
|
|
579
|
-
defvar = "GRIB_DEFINITION_PATH"
|
|
580
|
-
else:
|
|
581
|
-
self.env.setgenericpath(
|
|
582
|
-
defvar, self.system.path.join(eccodes_share, "definitions")
|
|
583
|
-
)
|
|
584
|
-
samplevar = "ECCODES_SAMPLES_PATH"
|
|
585
|
-
if samplevar not in self.env:
|
|
586
|
-
if "GRIB_SAMPLES_PATH" in self.env:
|
|
587
|
-
logger.warning(dep_warn, samplevar)
|
|
588
|
-
samplevar = "GRIB_SAMPLES_PATH"
|
|
589
|
-
else:
|
|
590
|
-
self.env.setgenericpath(
|
|
591
|
-
samplevar,
|
|
592
|
-
self.system.path.join(
|
|
593
|
-
eccodes_share, "ifs_samples", "grib1"
|
|
594
|
-
),
|
|
595
|
-
)
|
|
596
|
-
return defvar, samplevar
|
|
601
|
+
return envvar
|
|
597
602
|
|
|
598
603
|
def eccodes_setup(self, rh, opts, compat=False, fatal=True):
|
|
599
604
|
"""Setup the grib_api related stuff.
|
|
@@ -605,7 +610,19 @@ class EcGribDecoMixin(AlgoComponentDecoMixin):
|
|
|
605
610
|
# Detect the library's path and setup appropriate variables
|
|
606
611
|
eccodes_lib, gribapi_lib = self._ecgrib_libs_detext(rh)
|
|
607
612
|
if eccodes_lib is not None:
|
|
608
|
-
defvar
|
|
613
|
+
defvar = self._eccodes_envsetup(
|
|
614
|
+
eccodes_lib,
|
|
615
|
+
envvar="ECCODES_DEFINITIONS_PATH",
|
|
616
|
+
tgt_path="definitions",
|
|
617
|
+
)
|
|
618
|
+
subdir = Path("ifs_samples") / (
|
|
619
|
+
"grib1" if rh.resource.cycle < "cy49" else "grib1_mlgrib2"
|
|
620
|
+
)
|
|
621
|
+
samplevar = self._eccodes_envsetup(
|
|
622
|
+
eccodes_lib,
|
|
623
|
+
envvar="ECCODES_SAMPLES_PATH",
|
|
624
|
+
tgt_path=subdir,
|
|
625
|
+
)
|
|
609
626
|
elif compat:
|
|
610
627
|
defvar, samplevar = self._gribapi_envsetup(gribapi_lib)
|
|
611
628
|
else:
|
vortex/tools/net.py
CHANGED
|
@@ -26,6 +26,8 @@ from bronx.fancies import loggers
|
|
|
26
26
|
from bronx.net.netrc import netrc
|
|
27
27
|
from bronx.syntax.decorators import nicedeco, secure_getattr
|
|
28
28
|
|
|
29
|
+
from vortex.config import get_from_config_w_default, ConfigurationError
|
|
30
|
+
|
|
29
31
|
#: No automatic export
|
|
30
32
|
__all__ = []
|
|
31
33
|
|
|
@@ -1192,15 +1194,35 @@ class Ssh:
|
|
|
1192
1194
|
self._logname = logname
|
|
1193
1195
|
self._remote = hostname
|
|
1194
1196
|
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1197
|
+
def _get_ssh_config(key, default):
|
|
1198
|
+
config = get_from_config_w_default(
|
|
1199
|
+
section="ssh", key=key, default=default
|
|
1200
|
+
)
|
|
1201
|
+
try:
|
|
1202
|
+
val = config.pop("default")
|
|
1203
|
+
except AttributeError:
|
|
1204
|
+
assert isinstance(config, str)
|
|
1205
|
+
return config
|
|
1206
|
+
except KeyError:
|
|
1207
|
+
msg = (
|
|
1208
|
+
"A default value must be specified for configuration option"
|
|
1209
|
+
f" {key}. See vortex-nwp.readthedocs.io/en/latest/user-guide/configuration.html#ssh"
|
|
1210
|
+
)
|
|
1211
|
+
raise ConfigurationError(msg)
|
|
1212
|
+
|
|
1213
|
+
for k, v in config.items():
|
|
1214
|
+
if re.match(k, socket.gethostname()):
|
|
1215
|
+
val = v
|
|
1216
|
+
return val
|
|
1217
|
+
|
|
1218
|
+
self._sshcmd = _get_ssh_config(key="sshcmd", default="ssh")
|
|
1219
|
+
self._scpcmd = _get_ssh_config(key="scpcmd", default="scp")
|
|
1198
1220
|
self._sshopts = (
|
|
1199
|
-
|
|
1221
|
+
_get_ssh_config(key="sshopts", default="").split()
|
|
1200
1222
|
+ (sshopts or "").split()
|
|
1201
1223
|
)
|
|
1202
1224
|
self._scpopts = (
|
|
1203
|
-
|
|
1225
|
+
_get_ssh_config(key="scpopts", default="").split()
|
|
1204
1226
|
+ (scpopts or "").split()
|
|
1205
1227
|
)
|
|
1206
1228
|
|
vortex/tools/services.py
CHANGED
|
@@ -20,7 +20,6 @@ from bronx.stdtypes.dictionaries import UpperCaseDict
|
|
|
20
20
|
from bronx.syntax.pretty import EncodedPrettyPrinter
|
|
21
21
|
from vortex import sessions
|
|
22
22
|
from vortex.util.config import (
|
|
23
|
-
GenericConfigParser,
|
|
24
23
|
load_template,
|
|
25
24
|
LegacyTemplatingAdapter,
|
|
26
25
|
)
|
|
@@ -563,7 +562,8 @@ class Directory:
|
|
|
563
562
|
|
|
564
563
|
def __init__(self, inifile, domain="meteo.fr", encoding=None):
|
|
565
564
|
"""Keep aliases in memory, as a dict of sets."""
|
|
566
|
-
config =
|
|
565
|
+
config = configparser.ConfigParser()
|
|
566
|
+
config.read(inifile, encoding=encoding)
|
|
567
567
|
try:
|
|
568
568
|
self.domain = config.get("general", "default_domain")
|
|
569
569
|
except configparser.NoOptionError:
|
vortex/tools/storage.py
CHANGED
|
@@ -636,9 +636,11 @@ class Archive(AbstractArchive):
|
|
|
636
636
|
|
|
637
637
|
def __init__(self, *kargs, **kwargs):
|
|
638
638
|
super().__init__(*kargs, **kwargs)
|
|
639
|
-
|
|
639
|
+
|
|
640
|
+
self.default_usejeeves = config.get_from_config_w_default(
|
|
640
641
|
section="storage",
|
|
641
642
|
key="usejeeves",
|
|
643
|
+
default=False,
|
|
642
644
|
)
|
|
643
645
|
|
|
644
646
|
@property
|
vortex/tools/systems.py
CHANGED
|
@@ -68,6 +68,7 @@ from vortex.tools.env import Environment
|
|
|
68
68
|
from vortex.tools.net import AssistedSsh, AutoRetriesFtp, DEFAULT_FTP_PORT
|
|
69
69
|
from vortex.tools.net import FtpConnectionPool, LinuxNetstats, StdFtp
|
|
70
70
|
import vortex.tools.storage
|
|
71
|
+
from vortex import config
|
|
71
72
|
|
|
72
73
|
#: No automatic export
|
|
73
74
|
__all__ = []
|
|
@@ -800,7 +801,7 @@ class OSExtended(System):
|
|
|
800
801
|
|
|
801
802
|
* **rmtreemin** - as the minimal depth needed for a :meth:`rmsafe`.
|
|
802
803
|
* **cmpaftercp** - as a boolean for activating full comparison after plain cp (default: *True*).
|
|
803
|
-
* **
|
|
804
|
+
* **ftserv** - allows ``smartft*`` methods to use the raw FTP commands
|
|
804
805
|
(e.g. ftget, ftput) instead of the internal Vortex's FTP client
|
|
805
806
|
(default: *False*).
|
|
806
807
|
* **ftputcmd** - The name of the raw FTP command for the "put" action
|
|
@@ -815,7 +816,7 @@ class OSExtended(System):
|
|
|
815
816
|
self._rmtreemin = kw.pop("rmtreemin", 3)
|
|
816
817
|
self._cmpaftercp = kw.pop("cmpaftercp", True)
|
|
817
818
|
# Switches for rawft* methods
|
|
818
|
-
self.
|
|
819
|
+
self._ftserv = kw.pop("ftserv", None)
|
|
819
820
|
self.ftputcmd = kw.pop("ftputcmd", None)
|
|
820
821
|
self.ftgetcmd = kw.pop("ftgetcmd", None)
|
|
821
822
|
# FTP stuff again
|
|
@@ -838,23 +839,33 @@ class OSExtended(System):
|
|
|
838
839
|
self._signal_intercept_init()
|
|
839
840
|
|
|
840
841
|
@property
|
|
841
|
-
def
|
|
842
|
+
def ftserv(self):
|
|
842
843
|
"""Use the system's FTP service (e.g. ftserv)."""
|
|
843
|
-
if self.
|
|
844
|
-
return self.
|
|
844
|
+
if self._ftserv is None:
|
|
845
|
+
return self._use_ftserv()
|
|
845
846
|
else:
|
|
846
|
-
return self.
|
|
847
|
+
return self._ftserv
|
|
847
848
|
|
|
848
|
-
@
|
|
849
|
-
def
|
|
849
|
+
@ftserv.setter
|
|
850
|
+
def ftserv(self, value):
|
|
850
851
|
"""Use the system's FTP service (e.g. ftserv)."""
|
|
851
852
|
self._ftraw = bool(value)
|
|
852
853
|
|
|
853
|
-
@
|
|
854
|
-
def
|
|
854
|
+
@ftserv.deleter
|
|
855
|
+
def ftserv(self):
|
|
855
856
|
"""Use the system's FTP service (e.g. ftserv)."""
|
|
856
857
|
self._ftraw = None
|
|
857
858
|
|
|
859
|
+
def _use_ftserv(self):
|
|
860
|
+
if not config.is_defined(section="ftserv"):
|
|
861
|
+
return False
|
|
862
|
+
for rgxp in config.from_config(
|
|
863
|
+
section="ftserv", key="hostname_patterns"
|
|
864
|
+
):
|
|
865
|
+
if re.match(rgxp, self.hostname):
|
|
866
|
+
return True
|
|
867
|
+
return False
|
|
868
|
+
|
|
858
869
|
def target(self, **kw):
|
|
859
870
|
"""
|
|
860
871
|
Provide a default :class:`~vortex.tools.targets.Target` according
|
|
@@ -2064,7 +2075,7 @@ class OSExtended(System):
|
|
|
2064
2075
|
|
|
2065
2076
|
def rawftput_worthy(self, source, destination):
|
|
2066
2077
|
"""Is it allowed to use FtServ given **source** and **destination**."""
|
|
2067
|
-
return self.
|
|
2078
|
+
return self.ftserv and self.ftserv_allowed(source, destination)
|
|
2068
2079
|
|
|
2069
2080
|
@fmtshcmd
|
|
2070
2081
|
def rawftput(
|
|
@@ -2145,7 +2156,7 @@ class OSExtended(System):
|
|
|
2145
2156
|
|
|
2146
2157
|
``rawftput`` will be used if all of the following conditions are met:
|
|
2147
2158
|
|
|
2148
|
-
* ``self.
|
|
2159
|
+
* ``self.ftserv`` is *True*
|
|
2149
2160
|
* **source** is a string (as opposed to a File like object)
|
|
2150
2161
|
* **destination** is a string (as opposed to a File like object)
|
|
2151
2162
|
"""
|
|
@@ -2177,7 +2188,7 @@ class OSExtended(System):
|
|
|
2177
2188
|
def rawftget_worthy(self, source, destination, cpipeline=None):
|
|
2178
2189
|
"""Is it allowed to use FtServ given **source** and **destination**."""
|
|
2179
2190
|
return (
|
|
2180
|
-
self.
|
|
2191
|
+
self.ftserv
|
|
2181
2192
|
and cpipeline is None
|
|
2182
2193
|
and self.ftserv_allowed(source, destination)
|
|
2183
2194
|
)
|
|
@@ -2259,7 +2270,7 @@ class OSExtended(System):
|
|
|
2259
2270
|
|
|
2260
2271
|
``rawftget`` will be used if all of the following conditions are met:
|
|
2261
2272
|
|
|
2262
|
-
* ``self.
|
|
2273
|
+
* ``self.ftserv`` is *True*
|
|
2263
2274
|
* **cpipeline** is None
|
|
2264
2275
|
* **source** is a string (as opposed to a File like object)
|
|
2265
2276
|
* **destination** is a string (as opposed to a File like object)
|
vortex/util/config.py
CHANGED
|
@@ -164,7 +164,8 @@ def load_template(tplpath, encoding=None, default_templating="legacy"):
|
|
|
164
164
|
"""
|
|
165
165
|
tplpath = Path(tplpath).absolute()
|
|
166
166
|
if not tplpath.exists():
|
|
167
|
-
|
|
167
|
+
msg = f"Template file {tplpath} not found"
|
|
168
|
+
raise FileNotFoundError(msg)
|
|
168
169
|
ignored_lines = set()
|
|
169
170
|
actual_encoding = None if encoding == "script" else encoding
|
|
170
171
|
actual_templating = default_templating
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: vortex-nwp
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.1.1
|
|
4
4
|
Summary: A Python library to write Numerical Weather Prediction pipelines components
|
|
5
5
|
Author-email: The Vortex Team <vortex.support@meteo.fr>
|
|
6
6
|
License: CECILL-C
|
|
@@ -21,6 +21,7 @@ Requires-Dist: sphinx-copybutton; extra == "docs"
|
|
|
21
21
|
Provides-Extra: dev
|
|
22
22
|
Requires-Dist: ruff==0.9.1; extra == "dev"
|
|
23
23
|
Requires-Dist: pytest; extra == "dev"
|
|
24
|
+
Dynamic: license-file
|
|
24
25
|
|
|
25
26
|
## vortex
|
|
26
27
|
|