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
vortex/util/empty.py ADDED
@@ -0,0 +1,24 @@
1
+ """
2
+ An empty module to be filled with some kind of blackholes objects.
3
+ """
4
+
5
+ from bronx.fancies import loggers
6
+
7
+ #: No automatic export
8
+ __all__ = []
9
+
10
+ logger = loggers.getLogger(__name__)
11
+
12
+
13
+ class DataConst:
14
+ """Constants stored as raw attributes."""
15
+
16
+ def __init__(self, **kw):
17
+ self.__dict__.update(kw)
18
+ logger.debug('DataConst init %s', self)
19
+
20
+ def __str__(self):
21
+ return super().__str__() + ' : ' + str(sorted(self.__dict__.keys()))
22
+
23
+ def __contains__(self, item):
24
+ return item in self.__dict__
vortex/util/helpers.py ADDED
@@ -0,0 +1,184 @@
1
+ """
2
+ Some convenient functions that may simplify scripts
3
+ """
4
+
5
+ from collections import defaultdict
6
+
7
+ from bronx.compat import random
8
+ from bronx.fancies import loggers
9
+ from bronx.stdtypes.date import Date
10
+ import footprints as fp
11
+
12
+ from vortex.data.handlers import Handler
13
+ from vortex.layout.dataflow import Section
14
+ from vortex import sessions
15
+
16
+ logger = loggers.getLogger(__name__)
17
+
18
+
19
+ class InputCheckerError(Exception):
20
+ """Exception raised when the Input checking process fails."""
21
+ pass
22
+
23
+
24
+ def generic_input_checker(grouping_keys, min_items, *rhandlers, **kwargs):
25
+ """
26
+ Check which input resources are present.
27
+
28
+ First, the resource handlers (*rhandlers* attribute) are split
29
+ into groups based on the values of their properties (only the properties
30
+ specified in the *grouping_keys* attribute are considered).
31
+
32
+ Then, for each group, the **check** method is called upon the resource
33
+ handlers. The group description is returned only if the **check** call
34
+ succeed for all the members of the group.
35
+
36
+ If the number of groups successfully checked is lower than *min_items*,
37
+ an :class:`InputCheckerError` exception is raised.
38
+ """
39
+
40
+ if len(rhandlers) == 0:
41
+ raise ValueError('At least one resource handler have to be provided')
42
+ # Just in case min_items is not an int...
43
+ min_items = int(min_items)
44
+
45
+ # Create a flat ResourceHandlers list (rhandlers may consists of lists)
46
+ flat_rhlist = []
47
+ flat_rhmandatory = []
48
+ for inlist, outlist in ((rhandlers, flat_rhlist),
49
+ (kwargs.pop('mandatory', []), flat_rhmandatory),):
50
+ for rh in inlist:
51
+ if isinstance(rh, list) or isinstance(rh, tuple):
52
+ outlist.extend(rh)
53
+ else:
54
+ outlist.append(rh)
55
+
56
+ # Extract the group informations for each of the resource handlers
57
+ rhgroups = defaultdict(list)
58
+ for rh in flat_rhlist:
59
+ keylist = list()
60
+ for key in grouping_keys:
61
+ value = rh.wide_key_lookup(key, exports=True, fatal=False)
62
+ keylist.append(value)
63
+ rhgroups[tuple(keylist)].append(rh)
64
+
65
+ candidateslist = [
66
+ fp.stdtypes.FPDict({k: v
67
+ for k, v in zip(grouping_keys, group)
68
+ if v is not None})
69
+ for group in rhgroups.keys()
70
+ ]
71
+
72
+ # Activate FTP connections pooling (for enhanced performances)
73
+ t = sessions.current()
74
+ with t.sh.ftppool():
75
+
76
+ # Check mandatory stuff
77
+ mychecks = [(rh, rh.check()) for rh in flat_rhmandatory]
78
+ if not all([acheck[1] for acheck in mychecks]):
79
+ for rh in [acheck[0] for acheck in mychecks if not acheck[1]]:
80
+ logger.error(" Missing location: %s", str(rh.locate()))
81
+ raise InputCheckerError("Some of the mandatory resources are missing.")
82
+
83
+ # Check call for non-mandatory stuff
84
+ outputlist = list()
85
+ # Is the check real or a delusion ?
86
+ fakecheck = kwargs.pop('fakecheck', False)
87
+ # The keys are sorted so that results remains reproducible
88
+ for grouping_values in sorted(rhgroups.keys()):
89
+ mychecks = [(rh, fakecheck or rh.check()) for rh in rhgroups[grouping_values]]
90
+ groupid = fp.stdtypes.FPDict({k: v
91
+ for k, v in zip(grouping_keys, grouping_values)
92
+ if v is not None})
93
+ if all([acheck[1] for acheck in mychecks]):
94
+ outputlist.append(groupid)
95
+ logger.info("Group (%s): All the input files are accounted for.", str(groupid))
96
+ else:
97
+ logger.warning("Group (%s): Discarded because some of the input files are missing (see below).",
98
+ str(groupid))
99
+ for rh in [acheck[0] for acheck in mychecks if not acheck[1]]:
100
+ logger.warning(" Missing location: %s", str(rh.locate()))
101
+
102
+ # Enforce min_items
103
+ if len(outputlist) < min_items:
104
+ raise InputCheckerError("The number of input groups is too small " +
105
+ "({:d} < {:d})".format(len(outputlist), min_items))
106
+
107
+ return fp.stdtypes.FPList(outputlist), fp.stdtypes.FPList(candidateslist)
108
+
109
+
110
+ def members_input_checker(min_items, *rhandlers, **kwargs):
111
+ """
112
+ This is a shortcut for the generic_input_checher: only the member number is
113
+ considered and the return values corresponds to a list of members.
114
+ """
115
+ mlist = [desc['member'] for desc in generic_input_checker(('member', ), min_items,
116
+ *rhandlers, **kwargs)[0]]
117
+ return fp.stdtypes.FPList(sorted(mlist))
118
+
119
+
120
+ def colorfull_input_checker(min_items, *rhandlers, **kwargs):
121
+ """
122
+ This is a shortcut for the generic_input_checher: it returns a list of
123
+ dictionaries that described the available data.
124
+ """
125
+ return generic_input_checker(('vapp', 'vconf', 'experiment', 'cutoff', 'date', 'member'),
126
+ min_items, *rhandlers, **kwargs)
127
+
128
+
129
+ def merge_contents(*kargs):
130
+ """Automatically merge several DataContents.
131
+
132
+ Example:
133
+ .. code-block:: python
134
+
135
+ mergedcontent = merge_contents(content1, content2, content3)
136
+ # With a list
137
+ mergedcontent = merge_contents([content1, content2, content3])
138
+ # With a list of ResourceHandlers (e.g. as returned by toolbox.input)
139
+ mergedcontent = merge_contents([rh1, rh2, rh3])
140
+ # With a list of Sections (e.g. as returned by effective_inputs)
141
+ mergedcontent = merge_contents([section1, section2, section3])
142
+
143
+ """
144
+ # Expand list or tuple elements
145
+ ctlist = list()
146
+ for elt in kargs:
147
+ if isinstance(elt, (list, tuple)):
148
+ ctlist.extend(elt)
149
+ else:
150
+ ctlist.append(elt)
151
+ # kargs may be a list of resource handlers (as returned by the toolbox)
152
+ if all([isinstance(obj, Handler) for obj in ctlist]):
153
+ ctlist = [obj.contents for obj in ctlist]
154
+ # kargs may be a list of sections
155
+ elif all([isinstance(obj, Section) for obj in ctlist]):
156
+ ctlist = [obj.rh.contents for obj in ctlist]
157
+ # Take the first content as a model for the new object
158
+ newcontent = ctlist[0].__class__()
159
+ newcontent.merge(*ctlist)
160
+ return newcontent
161
+
162
+
163
+ def mix_list(list_elements, date=None, member=None):
164
+ """Mix a list using a determined seed, if member and/or date are present."""
165
+ dateinfo = date if date is None else Date(date)
166
+ memberinfo = member if member is None else int(member)
167
+ rgen = random.Random()
168
+ if (dateinfo is not None) or (memberinfo is not None):
169
+ if dateinfo is not None:
170
+ seed = dateinfo.epoch * 100
171
+ else:
172
+ seed = 9999999
173
+ if memberinfo:
174
+ seed = seed // memberinfo
175
+ logger.debug("The random seed is %s.", seed)
176
+ rgen.seed(seed)
177
+ else:
178
+ logger.info("The random seed not initialised")
179
+ logger.debug("The list of elements is %s.", " ".join([str(x) for x in list_elements]))
180
+ result_list_elements = list_elements
181
+ result_list_elements.sort()
182
+ rgen.shuffle(result_list_elements)
183
+ logger.debug("The mixed list of elements is %s.", " ".join([str(x) for x in result_list_elements]))
184
+ return result_list_elements
@@ -0,0 +1,63 @@
1
+ """
2
+ Some convenient functions to explore the source code or its documentation.
3
+ """
4
+
5
+ import re
6
+ import inspect
7
+
8
+ from bronx.fancies import loggers
9
+
10
+ from vortex import sessions
11
+
12
+ #: No automatic export
13
+ __all__ = []
14
+
15
+ logger = loggers.getLogger(__name__)
16
+
17
+
18
+ class Sherlock:
19
+ """Centralized interface to introspection functions."""
20
+
21
+ def __init__(self, **kw):
22
+ self.verbose = False
23
+ self.ticket = kw.pop('ticket', sessions.current())
24
+ self.glove = self.ticket.glove
25
+ self.__dict__.update(kw)
26
+ logger.debug('Sherlock init %s', self)
27
+
28
+ def rstfile(self, modpath):
29
+ """Return the sphinx documentation associated to module reference or module path given."""
30
+ if not isinstance(modpath, str):
31
+ modpath = modpath.__file__
32
+ subpath = modpath
33
+ for installpath in self.glove.sitesrc:
34
+ subpath = re.sub(installpath, '', subpath)
35
+ subpath = re.sub(r'\.pyc?', '', subpath)
36
+ subpath = subpath.split('/')
37
+ if subpath[-1] == '__init__':
38
+ subpath[-1] = subpath[-2]
39
+ subpath[-1] += '.rst'
40
+
41
+ subpath[1:1] = ['library', ]
42
+ return self.glove.sitedoc + '/'.join(subpath)
43
+
44
+ def rstshort(self, filename):
45
+ """Return relative path name of ``filename`` according to :meth:`siteroot`."""
46
+ return re.sub(self.glove.siteroot, '', filename)[1:]
47
+
48
+ def getlocalmembers(self, obj, topmodule=None):
49
+ """Return members of the module ``obj`` which are defined in the source file of the module."""
50
+ objs = dict()
51
+ if topmodule is None:
52
+ topmodule = obj
53
+ modfile = topmodule.__file__.rstrip('c')
54
+ for x, y in inspect.getmembers(obj):
55
+ if inspect.isclass(y) or inspect.isfunction(y) or inspect.ismethod(y):
56
+ try:
57
+ if modfile == inspect.getsourcefile(y):
58
+ if self.verbose:
59
+ print(x, y)
60
+ objs[x] = y
61
+ except TypeError:
62
+ pass
63
+ return objs
@@ -0,0 +1,76 @@
1
+ """
2
+ Provide a File-Like object that reads in the first N bytes in order to count
3
+ them precisely.
4
+ """
5
+
6
+ import io
7
+
8
+ #: No automatic export
9
+ __all__ = []
10
+
11
+ IOSPONGE_DEFAULT_SIZECHECK = 33 * 1024 * 1024 # 33Mb
12
+
13
+
14
+ class IoSponge(io.BufferedIOBase):
15
+ """Buffer the first bytes in order to compute an accurate size for the
16
+ underlying stream.
17
+
18
+ This class just acts as a buffer. It looks like a file object and should
19
+ be used as such.
20
+
21
+ If the size of the underlying stream is <= *size_check* bytes : the **size**
22
+ property will return an exact estimate of the file-like object size. Passed
23
+ that limit the maximum of *size_check* and *guessed_size* is returned.
24
+ """
25
+
26
+ def __init__(self, rawio, size_check=IOSPONGE_DEFAULT_SIZECHECK, guessed_size=0):
27
+ """
28
+ :param file rawio: Any kind of file-like object
29
+ :param int size_check: The first size_check bytes will be buffered in
30
+ order to be properly accounted for.
31
+ :param int gressed_size: An estimate of the file-like object size (in
32
+ bytes)
33
+ """
34
+ self._rawio = rawio
35
+ self._size_check = size_check
36
+ self._guessed_size = int(guessed_size)
37
+ self._first_bytes = self._rawio.read(size_check)
38
+ self._seek = 0
39
+
40
+ @property
41
+ def size(self):
42
+ """The (exact or estimated) size of the underlying file-like object."""
43
+ if len(self._first_bytes) < self._size_check:
44
+ return len(self._first_bytes)
45
+ else:
46
+ return max(len(self._first_bytes), self._guessed_size)
47
+
48
+ def tell(self):
49
+ """The amount of bytes read in this strem."""
50
+ return self._seek
51
+
52
+ def _generic_read(self, size, raw_read_cb):
53
+ ret = b''
54
+ if self._seek < len(self._first_bytes):
55
+ if size is None:
56
+ ret = self._first_bytes[self._seek:]
57
+ else:
58
+ ret = self._first_bytes[self._seek:min(self._size_check, self._seek + size)]
59
+ if size is None:
60
+ ret += raw_read_cb(None)
61
+ elif len(ret) < size:
62
+ ret += raw_read_cb(size - len(ret))
63
+ self._seek += len(ret)
64
+ return ret
65
+
66
+ def read(self, size=None):
67
+ """Read *size* bytes from the file."""
68
+ return self._generic_read(size, self._rawio.read)
69
+
70
+ def read1(self, size=None):
71
+ """Read *size* bytes from the file (at once)."""
72
+ return self._generic_read(size, self._rawio.read1)
73
+
74
+ def readable(self):
75
+ """Is this file-like object readable ?"""
76
+ return True
vortex/util/roles.py ADDED
@@ -0,0 +1,51 @@
1
+ """
2
+ Factory for named roles.
3
+ """
4
+
5
+ #: No automatic export
6
+ __all__ = []
7
+
8
+ _activetag = 'default'
9
+
10
+
11
+ def stdfactoryrole(role):
12
+ """Standard processing for role names."""
13
+ return ''.join([s[0].upper() + s[1:] for s in role.split()])
14
+
15
+
16
+ def switchfactory(tag='default'):
17
+ """Switch the current active factory to the existing one identified through its ``tag``."""
18
+ if tag in _rolesgateway:
19
+ global _activetag
20
+ _activetag = tag
21
+
22
+
23
+ def setfactoryrole(factory=None, tag=None):
24
+ """
25
+ Defines the specified ``factory`` function as the current processing role translator
26
+ associated with ``tag``.
27
+ """
28
+ global _activetag
29
+ if not tag:
30
+ tag = _activetag
31
+ if factory and tag:
32
+ _rolesgateway[tag] = factory
33
+
34
+
35
+ def setrole(role, tag=None):
36
+ """
37
+ Entry point for handling strings ``role``.
38
+ Returns the processed string according to the current active factory name
39
+ or using the one associated with ``tag``.
40
+ """
41
+ if not role:
42
+ return None
43
+ global _activetag
44
+ if not tag:
45
+ tag = _activetag
46
+ return _rolesgateway[tag](role)
47
+
48
+
49
+ _rolesgateway = dict(
50
+ default=stdfactoryrole
51
+ )
@@ -0,0 +1,103 @@
1
+ """
2
+ General purpose functions that can be used in conjunction with the
3
+ :class:`~vortex.data.stores.FunctionStore`.
4
+ """
5
+
6
+ import io
7
+
8
+ from footprints import proxy as fpx
9
+
10
+ from vortex.data.stores import FunctionStoreCallbackError
11
+ from vortex.tools.env import vartrue
12
+ from vortex import sessions
13
+ from . import helpers
14
+
15
+ #: No automatic export
16
+ __all__ = []
17
+
18
+
19
+ def mergecontents(options):
20
+ """
21
+ Merge the DataContent's of the Section objects designated by the
22
+ *role* option.
23
+
24
+ An additional *sort* option may be provided if the resulting merged file
25
+ like object needs to be sorted.
26
+
27
+ :param options: The only argument is a dictionary that contains all the options
28
+ passed to the store plus anything from the query part of the URI.
29
+
30
+ :return: Content of the desired local file/container
31
+
32
+ :rtype: A file like object
33
+ """
34
+ todo = options.get('role', None)
35
+ sort = vartrue.match(options.get('sort', ['false', ]).pop())
36
+ if todo is not None:
37
+ ctx = sessions.current().context
38
+ sections = list()
39
+ for a_role in todo:
40
+ sections.extend(ctx.sequence.effective_inputs(role=a_role))
41
+ if len(sections) == 0:
42
+ raise FunctionStoreCallbackError("Nothing to store: the effective inputs sequence is void.")
43
+ newcontent = helpers.merge_contents(sections)
44
+ if sort:
45
+ newcontent.sort()
46
+ else:
47
+ raise FunctionStoreCallbackError('At least one *role* option must be provided')
48
+ # Create a Virtual container and dump the new content inside it
49
+ virtualcont = fpx.container(incore=True)
50
+ newcontent.rewrite(virtualcont)
51
+ virtualcont.rewind()
52
+ # Force the new container to be in bytes mode
53
+ if virtualcont.actualmode and 'b' not in virtualcont.actualmode:
54
+ virtualcont_b = fpx.container(incore=True, mode='w+b')
55
+ virtualcont_b.write(virtualcont.read().encode(encoding='utf-8'))
56
+ virtualcont = virtualcont_b
57
+ return virtualcont
58
+
59
+
60
+ def dumpinputs(options):
61
+ """
62
+ Dump the content of the sequence's effective inputs into a JSON file
63
+
64
+ :note: the effective=False option can be provided. If so, all input sections
65
+ are dumped.
66
+
67
+ :return: a file like object
68
+ """
69
+ t = sessions.current()
70
+ ctx = t.context
71
+ if vartrue.match(options.get('effective', ['true', ]).pop()):
72
+ sequence = ctx.sequence.effective_inputs()
73
+ else:
74
+ sequence = list(ctx.sequence.inputs())
75
+ if len(sequence) == 0:
76
+ raise FunctionStoreCallbackError("Nothing to store: the effective inputs sequence is void.")
77
+ fileout = io.StringIO()
78
+ t.sh.json_dump([s.as_dict() for s in sequence], fileout, indent=4)
79
+ return fileout
80
+
81
+
82
+ def defaultinput(options):
83
+ """
84
+ Dump the content of a fake section into a JSON file
85
+ """
86
+ prefix = "d_input_"
87
+ content = dict()
88
+
89
+ def export_value(v):
90
+ if hasattr(v, 'footprint_export'):
91
+ return v.footprint_export()
92
+ elif hasattr(v, 'export_dict'):
93
+ return v.export_dict()
94
+ else:
95
+ return v
96
+
97
+ for k, v in options.items():
98
+ if isinstance(k, str) and k.startswith(prefix):
99
+ content[k[len(prefix):]] = export_value(v)
100
+ t = sessions.current()
101
+ fileout = io.StringIO()
102
+ t.sh.json_dump([content, ], fileout, indent=4)
103
+ return fileout
vortex/util/structs.py ADDED
@@ -0,0 +1,26 @@
1
+ """
2
+ This module defines common base classes for miscellaneous purposes.
3
+ """
4
+
5
+ import json
6
+
7
+ from bronx.fancies import loggers
8
+
9
+ #: No automatic export
10
+ __all__ = []
11
+
12
+ logger = loggers.getLogger(__name__)
13
+
14
+
15
+ class ShellEncoder(json.JSONEncoder):
16
+ """Encoder for :mod:`json` dumps method."""
17
+
18
+ def default(self, obj):
19
+ """Overwrite the default encoding if the current object has a ``export_dict`` method."""
20
+ if hasattr(obj, 'export_dict'):
21
+ return obj.export_dict()
22
+ elif hasattr(obj, 'footprint_export'):
23
+ return obj.footprint_export()
24
+ elif hasattr(obj, '__dict__'):
25
+ return vars(obj)
26
+ return super().default(obj)
vortex/util/worker.py ADDED
@@ -0,0 +1,150 @@
1
+ """
2
+ This package defines a class for default contexts used
3
+ by a PoolWorker process of the Jeeves daemon.
4
+ """
5
+
6
+ import logging
7
+
8
+ from bronx.fancies import loggers
9
+
10
+ logger = loggers.getLogger(__name__)
11
+
12
+
13
+ class AttrDict(dict):
14
+ """Dict object that can be accessed by attributes.
15
+
16
+ >>> obj = AttrDict()
17
+ >>> obj.test = 'hi'
18
+ >>> print(obj['test'])
19
+ hi
20
+
21
+ >>> obj['test'] = "bye"
22
+ >>> print(obj.test)
23
+ bye
24
+
25
+ >>> print(len(obj))
26
+ 1
27
+
28
+ >>> obj.clear()
29
+ >>> print(len(obj))
30
+ 0
31
+
32
+ >>> obj.a
33
+ Traceback (most recent call last):
34
+ ...
35
+ AttributeError: 'AttrDict' object has no attribute 'a'
36
+ """
37
+
38
+ def __init__(self, *args, **kwargs):
39
+ super().__init__(*args, **kwargs)
40
+ self.__dict__ = self
41
+
42
+
43
+ class VortexWorker:
44
+ """Context for a vortex session handled by an asynchronous process such as Jeeves.
45
+
46
+ An _oper_ profile should be used from Jeeves: the default is to use a _research_ profile.
47
+ See :mod:`vortex.gloves`.
48
+ """
49
+
50
+ _PRIVATESESSION_TAG = 'asyncworker_view'
51
+ _PRIVATEGLOVE_TAG = 'asyncworker_id'
52
+ _PRIVATESESSION = None
53
+ _PRIVATEMODULES = set()
54
+
55
+ def __init__(self, modules=tuple(), verbose=False, logger=None, profile=None):
56
+ self._logger = logger
57
+ self._modules = modules
58
+ self._context_lock = False
59
+ self._context_prev_ticket = None
60
+ self.verbose = verbose
61
+ self.profile = profile
62
+ self.rc = True
63
+
64
+ @property
65
+ def logger(self):
66
+ return self._logger
67
+
68
+ @property
69
+ def modules(self):
70
+ return self._modules
71
+
72
+ @property
73
+ def session(self):
74
+ """The session associated with Async Worker."""
75
+ if self._PRIVATESESSION is None:
76
+ import vortex
77
+ t = vortex.sessions.get(
78
+ tag=self._PRIVATESESSION_TAG,
79
+ glove=vortex.sessions.getglove(
80
+ tag=self._PRIVATEGLOVE_TAG,
81
+ profile=self.profile
82
+ )
83
+ )
84
+ sh = t.system()
85
+ import vortex.tools.lfi # @UnusedImport
86
+ import vortex.tools.grib # @UnusedImport
87
+ import vortex.tools.folder # @UnusedImport
88
+ import footprints as fp
89
+ fp.proxy.addon(kind='lfi', shell=sh)
90
+ fp.proxy.addon(kind='grib', shell=sh)
91
+ fp.proxy.addon(kind='allfolders', shell=sh, verboseload=False)
92
+ self._PRIVATESESSION = t
93
+ return self._PRIVATESESSION
94
+
95
+ def get_dataset(self, ask):
96
+ """Struct friendly access to data request."""
97
+ return AttrDict(ask.data)
98
+
99
+ def reset_loggers(self, logger):
100
+ if not self.verbose:
101
+ # footprints & bronx can be very talkative... we try to limit that !
102
+ global_level = logger.getEffectiveLevel()
103
+ f_logger = loggers.getLogger('footprints')
104
+ b_logger = loggers.getLogger('bronx')
105
+ if global_level <= logging.INFO and not self.verbose:
106
+ f_logger.setLevel(logging.INFO)
107
+ b_logger.setLevel(logging.INFO)
108
+ else:
109
+ f_logger.setLevel(logging.NOTSET)
110
+ b_logger.setLevel(logging.NOTSET)
111
+
112
+ def __enter__(self, *args):
113
+ if self._context_lock:
114
+ raise RuntimeError('Imbricated context manager calls are forbidden.')
115
+ self._context_lock = True
116
+ if self.logger is None:
117
+ self._logger = logger
118
+ else:
119
+ self.reset_loggers(self.logger)
120
+ # Activate our own session
121
+ import vortex
122
+ self._context_prev_ticket = vortex.sessions.current()
123
+ if not self.session.active:
124
+ self.session.activate()
125
+ # Import extra modules
126
+ for modname in self.modules:
127
+ if modname not in self._PRIVATEMODULES:
128
+ self.session.sh.import_module(modname)
129
+ self._PRIVATEMODULES.add(modname)
130
+ # Ok, let's talk...
131
+ self.logger.info('VORTEX enter glove_profile=%s ', self.session.glove.profile)
132
+ self.logger.debug(' modules=%s addons=%s', self.modules, self.session.sh.loaded_addons())
133
+ return self
134
+
135
+ def __exit__(self, exc_type, exc_value, exc_traceback):
136
+ """Well... nothing much to do..."""
137
+ if exc_value is not None:
138
+ self.logger.critical('VORTEX exits on error', exc_info=exc_value)
139
+ self.rc = False
140
+ else:
141
+ self.logger.debug('VORTEX exits nicely.')
142
+ self._context_prev_ticket.activate()
143
+ self._context_lock = False
144
+ return True
145
+
146
+
147
+ if __name__ == '__main__':
148
+ import doctest
149
+
150
+ doctest.testmod(verbose=False)