vortex-nwp 2.0.0b1__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.
Files changed (146) hide show
  1. vortex/__init__.py +135 -0
  2. vortex/algo/__init__.py +12 -0
  3. vortex/algo/components.py +2136 -0
  4. vortex/algo/mpitools.py +1648 -0
  5. vortex/algo/mpitools_templates/envelope_wrapper_default.tpl +27 -0
  6. vortex/algo/mpitools_templates/envelope_wrapper_mpiauto.tpl +29 -0
  7. vortex/algo/mpitools_templates/wrapstd_wrapper_default.tpl +18 -0
  8. vortex/algo/serversynctools.py +170 -0
  9. vortex/config.py +115 -0
  10. vortex/data/__init__.py +13 -0
  11. vortex/data/abstractstores.py +1572 -0
  12. vortex/data/containers.py +780 -0
  13. vortex/data/contents.py +596 -0
  14. vortex/data/executables.py +284 -0
  15. vortex/data/flow.py +113 -0
  16. vortex/data/geometries.ini +2689 -0
  17. vortex/data/geometries.py +703 -0
  18. vortex/data/handlers.py +1021 -0
  19. vortex/data/outflow.py +67 -0
  20. vortex/data/providers.py +465 -0
  21. vortex/data/resources.py +201 -0
  22. vortex/data/stores.py +1271 -0
  23. vortex/gloves.py +282 -0
  24. vortex/layout/__init__.py +27 -0
  25. vortex/layout/appconf.py +109 -0
  26. vortex/layout/contexts.py +511 -0
  27. vortex/layout/dataflow.py +1069 -0
  28. vortex/layout/jobs.py +1276 -0
  29. vortex/layout/monitor.py +833 -0
  30. vortex/layout/nodes.py +1424 -0
  31. vortex/layout/subjobs.py +464 -0
  32. vortex/nwp/__init__.py +11 -0
  33. vortex/nwp/algo/__init__.py +12 -0
  34. vortex/nwp/algo/assim.py +483 -0
  35. vortex/nwp/algo/clim.py +920 -0
  36. vortex/nwp/algo/coupling.py +609 -0
  37. vortex/nwp/algo/eda.py +632 -0
  38. vortex/nwp/algo/eps.py +613 -0
  39. vortex/nwp/algo/forecasts.py +745 -0
  40. vortex/nwp/algo/fpserver.py +927 -0
  41. vortex/nwp/algo/ifsnaming.py +403 -0
  42. vortex/nwp/algo/ifsroot.py +311 -0
  43. vortex/nwp/algo/monitoring.py +202 -0
  44. vortex/nwp/algo/mpitools.py +554 -0
  45. vortex/nwp/algo/odbtools.py +974 -0
  46. vortex/nwp/algo/oopsroot.py +735 -0
  47. vortex/nwp/algo/oopstests.py +186 -0
  48. vortex/nwp/algo/request.py +579 -0
  49. vortex/nwp/algo/stdpost.py +1285 -0
  50. vortex/nwp/data/__init__.py +12 -0
  51. vortex/nwp/data/assim.py +392 -0
  52. vortex/nwp/data/boundaries.py +261 -0
  53. vortex/nwp/data/climfiles.py +539 -0
  54. vortex/nwp/data/configfiles.py +149 -0
  55. vortex/nwp/data/consts.py +929 -0
  56. vortex/nwp/data/ctpini.py +133 -0
  57. vortex/nwp/data/diagnostics.py +181 -0
  58. vortex/nwp/data/eda.py +148 -0
  59. vortex/nwp/data/eps.py +383 -0
  60. vortex/nwp/data/executables.py +1039 -0
  61. vortex/nwp/data/fields.py +96 -0
  62. vortex/nwp/data/gridfiles.py +308 -0
  63. vortex/nwp/data/logs.py +551 -0
  64. vortex/nwp/data/modelstates.py +334 -0
  65. vortex/nwp/data/monitoring.py +220 -0
  66. vortex/nwp/data/namelists.py +644 -0
  67. vortex/nwp/data/obs.py +748 -0
  68. vortex/nwp/data/oopsexec.py +72 -0
  69. vortex/nwp/data/providers.py +182 -0
  70. vortex/nwp/data/query.py +217 -0
  71. vortex/nwp/data/stores.py +147 -0
  72. vortex/nwp/data/surfex.py +338 -0
  73. vortex/nwp/syntax/__init__.py +9 -0
  74. vortex/nwp/syntax/stdattrs.py +375 -0
  75. vortex/nwp/tools/__init__.py +10 -0
  76. vortex/nwp/tools/addons.py +35 -0
  77. vortex/nwp/tools/agt.py +55 -0
  78. vortex/nwp/tools/bdap.py +48 -0
  79. vortex/nwp/tools/bdcp.py +38 -0
  80. vortex/nwp/tools/bdm.py +21 -0
  81. vortex/nwp/tools/bdmp.py +49 -0
  82. vortex/nwp/tools/conftools.py +1311 -0
  83. vortex/nwp/tools/drhook.py +62 -0
  84. vortex/nwp/tools/grib.py +268 -0
  85. vortex/nwp/tools/gribdiff.py +99 -0
  86. vortex/nwp/tools/ifstools.py +163 -0
  87. vortex/nwp/tools/igastuff.py +249 -0
  88. vortex/nwp/tools/mars.py +56 -0
  89. vortex/nwp/tools/odb.py +548 -0
  90. vortex/nwp/tools/partitioning.py +234 -0
  91. vortex/nwp/tools/satrad.py +56 -0
  92. vortex/nwp/util/__init__.py +6 -0
  93. vortex/nwp/util/async.py +184 -0
  94. vortex/nwp/util/beacon.py +40 -0
  95. vortex/nwp/util/diffpygram.py +359 -0
  96. vortex/nwp/util/ens.py +198 -0
  97. vortex/nwp/util/hooks.py +128 -0
  98. vortex/nwp/util/taskdeco.py +81 -0
  99. vortex/nwp/util/usepygram.py +591 -0
  100. vortex/nwp/util/usetnt.py +87 -0
  101. vortex/proxy.py +6 -0
  102. vortex/sessions.py +341 -0
  103. vortex/syntax/__init__.py +9 -0
  104. vortex/syntax/stdattrs.py +628 -0
  105. vortex/syntax/stddeco.py +176 -0
  106. vortex/toolbox.py +982 -0
  107. vortex/tools/__init__.py +11 -0
  108. vortex/tools/actions.py +457 -0
  109. vortex/tools/addons.py +297 -0
  110. vortex/tools/arm.py +76 -0
  111. vortex/tools/compression.py +322 -0
  112. vortex/tools/date.py +20 -0
  113. vortex/tools/ddhpack.py +10 -0
  114. vortex/tools/delayedactions.py +672 -0
  115. vortex/tools/env.py +513 -0
  116. vortex/tools/folder.py +663 -0
  117. vortex/tools/grib.py +559 -0
  118. vortex/tools/lfi.py +746 -0
  119. vortex/tools/listings.py +354 -0
  120. vortex/tools/names.py +575 -0
  121. vortex/tools/net.py +1790 -0
  122. vortex/tools/odb.py +10 -0
  123. vortex/tools/parallelism.py +336 -0
  124. vortex/tools/prestaging.py +186 -0
  125. vortex/tools/rawfiles.py +10 -0
  126. vortex/tools/schedulers.py +413 -0
  127. vortex/tools/services.py +871 -0
  128. vortex/tools/storage.py +1061 -0
  129. vortex/tools/surfex.py +61 -0
  130. vortex/tools/systems.py +3396 -0
  131. vortex/tools/targets.py +384 -0
  132. vortex/util/__init__.py +9 -0
  133. vortex/util/config.py +1071 -0
  134. vortex/util/empty.py +24 -0
  135. vortex/util/helpers.py +184 -0
  136. vortex/util/introspection.py +63 -0
  137. vortex/util/iosponge.py +76 -0
  138. vortex/util/roles.py +51 -0
  139. vortex/util/storefunctions.py +103 -0
  140. vortex/util/structs.py +26 -0
  141. vortex/util/worker.py +150 -0
  142. vortex_nwp-2.0.0b1.dist-info/LICENSE +517 -0
  143. vortex_nwp-2.0.0b1.dist-info/METADATA +50 -0
  144. vortex_nwp-2.0.0b1.dist-info/RECORD +146 -0
  145. vortex_nwp-2.0.0b1.dist-info/WHEEL +5 -0
  146. vortex_nwp-2.0.0b1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,72 @@
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 = ['oopsbinary', ],
28
+ ),
29
+ gvar = dict(
30
+ default = 'master_[run]',
31
+ ),
32
+ run = dict(
33
+ outcast = known_oops_testcomponent_runs,
34
+ ),
35
+ )
36
+ )
37
+ ]
38
+
39
+ @property
40
+ def realkind(self):
41
+ return 'oopsbinary'
42
+
43
+ def command_line(self, configfile):
44
+ """
45
+ Build command line for execution as a single string.
46
+ """
47
+ cmdline = '{}'.format(configfile)
48
+ return cmdline
49
+
50
+
51
+ class OOPSTestComponent(OOPSBinary):
52
+ """Binary for OOPS Tests of components."""
53
+
54
+ _footprint = dict(
55
+ info = 'OOPS Component Test: can run a sub-test or a family of sub-tests',
56
+ attr = dict(
57
+ run = dict(
58
+ values = known_oops_testcomponent_runs,
59
+ outcast = [],
60
+ ),
61
+ ),
62
+ )
63
+
64
+ def command_line(self, configfile, test_type=None):
65
+ """
66
+ Build command line for execution as a single string.
67
+ """
68
+ cmdline = ''
69
+ if test_type is not None:
70
+ cmdline += '-t {} '.format(test_type)
71
+ cmdline += super().command_line(configfile)
72
+ return cmdline
@@ -0,0 +1,182 @@
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
+ pass
24
+
25
+
26
+ class BdpeConfigurationError(BdpeError):
27
+ """Missing BDPE product description."""
28
+ pass
29
+
30
+
31
+ class BdpeMismatchError(BdpeError):
32
+ """BDPE product description does not match ressource description."""
33
+ pass
34
+
35
+
36
+ class BdpeProvider(Provider):
37
+ """
38
+ Provider to resources stored in the BDPE database.
39
+
40
+ The BDPE only knows about product ids, base datetime, and terms.
41
+ A dedicated ini file describes the relationship between such ids and
42
+ Vortex resources. This link could be used to deduce the BDPE id from
43
+ the resource (a la footprints). For now, it is only checked that the
44
+ resource attributes are compatible with the product description.
45
+
46
+ Canvas of a complete uri:
47
+ bdpe://bdpe.archive.fr/EXPE/date/BDPE_num+term
48
+
49
+ The EXPE part is built from the footprint attributes that correspond
50
+ to env variables used by the underlying tool we use: preferred_target,
51
+ forbidden_target, aso., so that the BDPE store can retrieve them from
52
+ the uri (See :class:`BdpeStore`).
53
+
54
+ When a resource has no ``date`` attribute, the most recent data is
55
+ extracted from the BDPE (this feature may be used for Alert Models).
56
+ """
57
+
58
+ _footprint = [
59
+ namespacefp,
60
+ dict(
61
+ info = 'BDPE provider',
62
+ attr = dict(
63
+ namespace = dict(
64
+ default = Namespace('bdpe.archive.fr'),
65
+ values = ['bdpe.archive.fr'],
66
+ ),
67
+ bdpeid = dict(
68
+ ),
69
+ preferred_target = dict(
70
+ info = "The database we'd like to get the data from - See the BDPE documentation.",
71
+ optional = True,
72
+ default = DelayedEnvValue('BDPE_CIBLE_PREFEREE', 'SEC'),
73
+ values = ['OPER', 'INT', 'SEC',
74
+ 'oper', 'int', 'sec', ],
75
+ ),
76
+ forbidden_target = dict(
77
+ info = "The database we don't want to access - See the BDPE documentation.",
78
+ optional = True,
79
+ default = DelayedEnvValue('BDPE_CIBLE_INTERDITE', 'OPER'),
80
+ values = ['OPER', 'INT', 'SEC',
81
+ 'oper', 'int', 'sec', ],
82
+ ),
83
+ soprano_domain = dict(
84
+ info = 'Databases priorities profile - See the BDPE documentation.',
85
+ optional = True,
86
+ default = DelayedEnvValue('DOMAINE_SOPRA', 'dev'),
87
+ values = ['oper', 'int', 'dev'],
88
+ ),
89
+ allow_archive = dict(
90
+ info = 'Allow the use of the archive version of the BDPE databases.',
91
+ optional = True,
92
+ type = bool,
93
+ default = False,
94
+ ),
95
+ bdpe_timeout = dict(
96
+ info = 'Seconds before abandoning a request.',
97
+ optional = True,
98
+ type = int,
99
+ default = 10,
100
+ ),
101
+ bdpe_retries = dict(
102
+ info = 'Number of retries when a request fails.',
103
+ optional = True,
104
+ type = int,
105
+ default = 3,
106
+ ),
107
+ config = dict(
108
+ info = 'A ready to use configuration file object for this storage place.',
109
+ type = GenericConfigParser,
110
+ optional = True,
111
+ default = None,
112
+ ),
113
+ inifile = dict(
114
+ info = ('The name of the configuration file that will be used (if ' +
115
+ '**config** is not provided.'),
116
+ optional = True,
117
+ default = '@bdpe-map-resources.ini',
118
+ ),
119
+ ),
120
+ fastkeys = {'bdpeid'},
121
+ )
122
+ ]
123
+
124
+ def __init__(self, *args, **kw):
125
+ logger.debug('BDPE provider init %s', self.__class__)
126
+ super().__init__(*args, **kw)
127
+ self._actual_config = self.config
128
+ if self._actual_config is None:
129
+ self._actual_config = GenericConfigParser(inifile=self.inifile)
130
+
131
+ @property
132
+ def realkind(self):
133
+ return 'bdpe'
134
+
135
+ def scheme(self, resource):
136
+ """A dedicated scheme."""
137
+ return 'bdpe'
138
+
139
+ def netloc(self, resource):
140
+ """The actual netloc is the ``namespace`` attribute of the current provider."""
141
+ return self.namespace.netloc
142
+
143
+ def basename(self, resource):
144
+ """Something like 'BDPE_num+term'."""
145
+ myterm = getattr(resource, 'term', Time(0))
146
+ if int(myterm) < 0:
147
+ myterm = Time(9000) - myterm
148
+ return 'BDPE_{}+{!s}'.format(self.bdpeid, myterm)
149
+
150
+ def pathname(self, resource):
151
+ """Something like 'PREFERRED_FORBIDDEN_DOMAIN_ARCHIVE_TIMEOUT_RETRIES/date/'."""
152
+ try:
153
+ requested_date = resource.date.vortex()
154
+ except AttributeError:
155
+ requested_date = 'most_recent'
156
+ return '{}_{}_{}_{}_{}_{}/{}'.format(
157
+ self.preferred_target, self.forbidden_target, self.soprano_domain,
158
+ self.allow_archive, self.bdpe_timeout, self.bdpe_retries, requested_date)
159
+
160
+ def uri(self, resource):
161
+ """
162
+ Overridden to check the resource attributes against
163
+ the BDPE product description from the .ini file.
164
+ """
165
+ # check that the product is described in the configuration file
166
+ if not self._actual_config.has_section(self.bdpeid):
167
+ fmt = 'Missing product n°{} in BDPE configuration file\n"{}"'
168
+ raise BdpeConfigurationError(fmt.format(self.bdpeid, self.config.file))
169
+
170
+ # resource description: rely on the footprint_export (also used to JSONise resources).
171
+ rsrcdict = {k: str(v)
172
+ for k, v in resource.footprint_export().items()}
173
+
174
+ # check the BDPE pairs against the resource's
175
+ for (k, v) in self._actual_config.items(self.bdpeid):
176
+ if k not in rsrcdict:
177
+ raise BdpeMismatchError('Missing key "{}" in resource'.format(k))
178
+ if rsrcdict[k] != v:
179
+ fmt = 'Bad value for key "{}": rsrc="{}" bdpe="{}"'
180
+ raise BdpeMismatchError(fmt.format(k, rsrcdict[k], v))
181
+
182
+ return super().uri(resource)
@@ -0,0 +1,217 @@
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(
29
+ values = ['extract_stuff'],
30
+ default = 'extract_stuff'
31
+ ),
32
+ source = dict(),
33
+ origin = dict(),
34
+ ),
35
+ )
36
+ ]
37
+
38
+ def gget_urlquery(self):
39
+ """GGET specific query : ``extract``."""
40
+ return 'extract=' + self.source
41
+
42
+
43
+ class BDAPQuery(Query):
44
+ """Class to deal with BDAP queries."""
45
+ _footprint = dict(
46
+ info = 'BDAP query',
47
+ attr = dict(
48
+ kind = dict(
49
+ values = ['bdap_query']
50
+ ),
51
+ origin = dict(
52
+ default = 'bdap',
53
+ values = ['bdap'],
54
+ optional = True
55
+ )
56
+ )
57
+ )
58
+
59
+ @property
60
+ def realkind(self):
61
+ return 'bdap_query'
62
+
63
+
64
+ class BDMPQuery(Query):
65
+ """Class to deal with BDMP queries."""
66
+ _footprint = dict(
67
+ info = 'BDMP query',
68
+ attr = dict(
69
+ kind = dict(
70
+ values = ['bdmp_query']
71
+ ),
72
+ origin = dict(
73
+ default = 'bdmp',
74
+ values = ['bdmp'],
75
+ optional = True
76
+ )
77
+ )
78
+ )
79
+
80
+ @property
81
+ def realkind(self):
82
+ return 'bdmp_query'
83
+
84
+
85
+ class BDCPQuery(Query):
86
+ """Class to deal with BDCP queries."""
87
+ _footprint = dict(
88
+ info = 'BDCP query',
89
+ attr = dict(
90
+ kind = dict(
91
+ values = ['bdcp_query']
92
+ ),
93
+ origin = dict(
94
+ default = 'bdcp',
95
+ values = ['bdcp'],
96
+ optional = True
97
+ ),
98
+ )
99
+ )
100
+
101
+ @property
102
+ def realkind(self):
103
+ return 'bdcp_query'
104
+
105
+
106
+ class StaticCutoffDispenser:
107
+ """
108
+ From a dictionary of cutoff times, for a given *obstype*, find the
109
+ best suited cutoff time.
110
+
111
+ The __call__ method takes a unique *obstype* argument. It will return the
112
+ best suited cutoff time for this particular *obstype*. N.B: If no exact
113
+ match is found, the default cutoff time will be used.
114
+ """
115
+
116
+ def __init__(self, default_cutoff, obstype_cutoffs=None):
117
+ self._default_cutoff = default_cutoff
118
+ if obstype_cutoffs:
119
+ self._cutoffs = {k: {o.lower() for o in v}
120
+ for k, v in obstype_cutoffs.items()}
121
+ else:
122
+ self._cutoffs = {}
123
+
124
+ @property
125
+ def max_cutoff(self):
126
+ return self._default_cutoff
127
+
128
+ def __call__(self, obstype):
129
+ """Find the best suited cutoff time for *obstype*."""
130
+ obstype = obstype.lower()
131
+ found = None
132
+ for k, v in self._cutoffs.items():
133
+ if obstype in v:
134
+ found = k
135
+ if found:
136
+ return found
137
+ else:
138
+ return self._default_cutoff
139
+
140
+
141
+ class BDMQueryContent(AlmostListContent):
142
+ """Read the content of BDM query file."""
143
+
144
+ _RE_OBSTYPE = re.compile(r"^(\s*)(OBS\s+TYPE\s*):(\s+)(\w+)$")
145
+
146
+ def add_cutoff_info(self, cutoffs_dispenser):
147
+ """
148
+ Using a :class:`vortex.tools.listings.ListBasedCutoffDispenser` or
149
+ :class:`StaticCutoffDispenser` object, add the cutoff related
150
+ information in the BDM query.
151
+ """
152
+ if cutoffs_dispenser.max_cutoff is None:
153
+ logger.warning("The cutoffs_dispenser is empty. No cutoff data can be retrieved")
154
+ else:
155
+ xdata = list()
156
+ for line in self:
157
+ xdata.append(line)
158
+ l_match = self._RE_OBSTYPE.match(line)
159
+ if l_match:
160
+ cutoff_fmt = '{0:s}{1:<' + str(len(l_match.group(2))) + 's}:{2:s}{3.ymdhms:s}\n'
161
+ cutoff_date = cutoffs_dispenser(l_match.group(4))
162
+ xdata.append(cutoff_fmt.format(l_match.group(1),
163
+ 'CUTOFF',
164
+ l_match.group(3),
165
+ cutoff_date))
166
+ logger.info('CUTOFF=%s added for obstype < %s >.',
167
+ cutoff_date.ymdhms, l_match.group(4))
168
+ self._data = xdata
169
+
170
+
171
+ class BDMQuery(Query):
172
+ """Class to deal with BDM queries."""
173
+ _footprint = dict(
174
+ info = 'BDM query',
175
+ attr = dict(
176
+ kind = dict(
177
+ values = ['bdm_query']
178
+ ),
179
+ origin = dict(
180
+ default = 'bdm',
181
+ values = ['bdm'],
182
+ optional = True
183
+ ),
184
+ clscontents=dict(
185
+ default = BDMQueryContent,
186
+ ),
187
+ )
188
+ )
189
+
190
+ @property
191
+ def realkind(self):
192
+ return 'bdm_query'
193
+
194
+
195
+ class MarsQuery(Query):
196
+ """Class to deal with Mars queries"""
197
+
198
+ _footprint = dict(
199
+ info = 'Mars query',
200
+ attr = dict(
201
+ kind = dict(
202
+ values = ['mars_query']
203
+ ),
204
+ origin = dict(
205
+ default = "mars",
206
+ values = ["mars", ],
207
+ optional = True
208
+ ),
209
+ clscontents=dict(
210
+ default = DataTemplate
211
+ ),
212
+ )
213
+ )
214
+
215
+ @property
216
+ def realkind(self):
217
+ return "mars_query"
@@ -0,0 +1,147 @@
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(
38
+ level = footprints.priorities.top.DEFAULT
39
+ ),
40
+ ),
41
+ ]
42
+
43
+ @property
44
+ def realkind(self):
45
+ return 'bdpe'
46
+
47
+ def bdpelocate(self, remote, options):
48
+ """Reasonably close to whatever 'remote location' could mean.
49
+
50
+ e.g.: ``bdpe://bdpe.archive.fr/EXPE/date/BDPE_num+term``
51
+ """
52
+ return self.scheme + '://' + self.netloc + remote['path']
53
+
54
+ def bdpecheck(self, remote, options):
55
+ """Cannot check a BDPE call a priori."""
56
+ logger.warning("A BdpeStore is not able to perform CHECKs.")
57
+ return False
58
+
59
+ def bdpeput(self, local, remote, options):
60
+ """Cannot write to the BDPE (See :class:`BdpeService`)."""
61
+ logger.error("A BdpeStore is not able to perform PUTs.")
62
+ return False
63
+
64
+ def bdpedelete(self, remote, options):
65
+ """Cannot delete a BDPE product."""
66
+ logger.error("A BdpeStore is not able to perform DELETEs.")
67
+ return False
68
+
69
+ def bdpeget(self, remote, local, options):
70
+ """Real extraction from the BDPE database."""
71
+
72
+ # Check that local is a file (i.e not a virtual container)
73
+ if not isinstance(local, str):
74
+ raise TypeError('The BDPE provider can not deal with virtual containers')
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 = targetmix.split('_')
79
+ productid, str_term = more[5:].split('+')
80
+
81
+ # the 'oper' domain is allowed only to the operational suite
82
+ if domain == 'oper':
83
+ if not vortex.ticket().glove.profile == 'oper':
84
+ logger.warning("Only profile 'oper' can use 'soprano_domain=oper'. Using 'dev' instead.")
85
+ domain = 'dev'
86
+
87
+ if str_date == 'most_recent':
88
+ bdpe_date = '/'
89
+ else:
90
+ bdpe_date = date.Date(str_date).ymdhms
91
+ bdpe_term = date.Time(str_term).fmtraw
92
+ args = [
93
+ productid, # id
94
+ bdpe_date, # date: yyyymmddhhmmss
95
+ bdpe_term, # term: HHHHmm
96
+ local, # local filename
97
+ ]
98
+ extraenv = dict(
99
+ BDPE_CIBLE_PREFEREE=p_target,
100
+ BDPE_CIBLE_INTERDITE=f_target,
101
+ DOMAINE_SOPRA=domain,
102
+ BDPE_TIMEOUT=timeout,
103
+ BDPE_RETRYS=retries,
104
+ )
105
+ if s_archive == 'True':
106
+ extraenv['BDPE_LECTURE_ARCHIVE_AUTORISEE'] = 'oui'
107
+
108
+ wsinterpreter = self.system.default_target.get('bdpe:wsclient_interpreter', None)
109
+ wscommand = self.system.default_target.get('bdpe:wsclient_path', None)
110
+ if wscommand is None:
111
+ raise RuntimeError('bdpe:wsclient_path has to be set in the target config')
112
+
113
+ args.insert(0, wscommand)
114
+ if wsinterpreter is not None:
115
+ args.insert(0, wsinterpreter)
116
+
117
+ logger.debug('lirepe_cmd: %s', " ".join(args))
118
+
119
+ with self.system.env.delta_context(**extraenv):
120
+ rc = self.system.spawn(args, output=False, fatal=False)
121
+ rc = rc and self.system.path.exists(local)
122
+
123
+ diagfile = local + '.diag'
124
+ if not rc:
125
+ logger.warning('Something went wrong with the following command: %s',
126
+ " ".join(args))
127
+ if not rc or bdpe_date == '/':
128
+ if self.system.path.exists(diagfile):
129
+ logger.warning('The %s file is:', diagfile)
130
+ self.system.cat(diagfile)
131
+ if rc and self._actual_cpipeline:
132
+ # Deal with compressed files in the BDPE using the optional attribute
133
+ # store_compressed of the BDPE store.
134
+ tempfile = local + self._actual_cpipeline.suffix
135
+ rc = rc and self.system.mv(local, tempfile)
136
+ self._actual_cpipeline.file2uncompress(tempfile, local)
137
+ rc = rc and self.system.path.exists(local)
138
+ if not rc:
139
+ logger.warning('Something went wrong while uncompressing the file %s.', tempfile)
140
+
141
+ # Final step : deal with format specific packing
142
+ rc = rc and self.system.forceunpack(local, fmt=options.get('fmt'))
143
+
144
+ if self.system.path.exists(diagfile):
145
+ self.system.remove(diagfile)
146
+
147
+ return rc