vortex-nwp 2.0.0b1__py3-none-any.whl → 2.1.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.
Files changed (141) hide show
  1. vortex/__init__.py +75 -47
  2. vortex/algo/__init__.py +3 -2
  3. vortex/algo/components.py +944 -618
  4. vortex/algo/mpitools.py +802 -497
  5. vortex/algo/mpitools_templates/__init__.py +1 -0
  6. vortex/algo/serversynctools.py +34 -33
  7. vortex/config.py +19 -22
  8. vortex/data/__init__.py +9 -3
  9. vortex/data/abstractstores.py +593 -655
  10. vortex/data/containers.py +217 -162
  11. vortex/data/contents.py +65 -39
  12. vortex/data/executables.py +93 -102
  13. vortex/data/flow.py +40 -34
  14. vortex/data/geometries.py +228 -132
  15. vortex/data/handlers.py +436 -227
  16. vortex/data/outflow.py +15 -15
  17. vortex/data/providers.py +185 -163
  18. vortex/data/resources.py +48 -42
  19. vortex/data/stores.py +540 -417
  20. vortex/data/sync_templates/__init__.py +0 -0
  21. vortex/gloves.py +114 -87
  22. vortex/layout/__init__.py +1 -8
  23. vortex/layout/contexts.py +150 -84
  24. vortex/layout/dataflow.py +353 -202
  25. vortex/layout/monitor.py +264 -128
  26. vortex/nwp/__init__.py +5 -2
  27. vortex/nwp/algo/__init__.py +14 -5
  28. vortex/nwp/algo/assim.py +205 -151
  29. vortex/nwp/algo/clim.py +683 -517
  30. vortex/nwp/algo/coupling.py +447 -225
  31. vortex/nwp/algo/eda.py +437 -229
  32. vortex/nwp/algo/eps.py +403 -231
  33. vortex/nwp/algo/forecasts.py +416 -275
  34. vortex/nwp/algo/fpserver.py +683 -307
  35. vortex/nwp/algo/ifsnaming.py +205 -145
  36. vortex/nwp/algo/ifsroot.py +215 -122
  37. vortex/nwp/algo/monitoring.py +137 -76
  38. vortex/nwp/algo/mpitools.py +330 -190
  39. vortex/nwp/algo/odbtools.py +637 -353
  40. vortex/nwp/algo/oopsroot.py +454 -273
  41. vortex/nwp/algo/oopstests.py +90 -56
  42. vortex/nwp/algo/request.py +287 -206
  43. vortex/nwp/algo/stdpost.py +878 -522
  44. vortex/nwp/data/__init__.py +22 -4
  45. vortex/nwp/data/assim.py +125 -137
  46. vortex/nwp/data/boundaries.py +121 -68
  47. vortex/nwp/data/climfiles.py +193 -211
  48. vortex/nwp/data/configfiles.py +73 -69
  49. vortex/nwp/data/consts.py +426 -401
  50. vortex/nwp/data/ctpini.py +59 -43
  51. vortex/nwp/data/diagnostics.py +94 -66
  52. vortex/nwp/data/eda.py +50 -51
  53. vortex/nwp/data/eps.py +195 -146
  54. vortex/nwp/data/executables.py +440 -434
  55. vortex/nwp/data/fields.py +63 -48
  56. vortex/nwp/data/gridfiles.py +183 -111
  57. vortex/nwp/data/logs.py +250 -217
  58. vortex/nwp/data/modelstates.py +180 -151
  59. vortex/nwp/data/monitoring.py +72 -99
  60. vortex/nwp/data/namelists.py +254 -202
  61. vortex/nwp/data/obs.py +400 -308
  62. vortex/nwp/data/oopsexec.py +22 -20
  63. vortex/nwp/data/providers.py +90 -65
  64. vortex/nwp/data/query.py +71 -82
  65. vortex/nwp/data/stores.py +49 -36
  66. vortex/nwp/data/surfex.py +136 -137
  67. vortex/nwp/syntax/__init__.py +1 -1
  68. vortex/nwp/syntax/stdattrs.py +173 -111
  69. vortex/nwp/tools/__init__.py +2 -2
  70. vortex/nwp/tools/addons.py +22 -17
  71. vortex/nwp/tools/agt.py +24 -12
  72. vortex/nwp/tools/bdap.py +16 -5
  73. vortex/nwp/tools/bdcp.py +4 -1
  74. vortex/nwp/tools/bdm.py +3 -0
  75. vortex/nwp/tools/bdmp.py +14 -9
  76. vortex/nwp/tools/conftools.py +728 -378
  77. vortex/nwp/tools/drhook.py +12 -8
  78. vortex/nwp/tools/grib.py +65 -39
  79. vortex/nwp/tools/gribdiff.py +22 -17
  80. vortex/nwp/tools/ifstools.py +82 -42
  81. vortex/nwp/tools/igastuff.py +167 -143
  82. vortex/nwp/tools/mars.py +14 -2
  83. vortex/nwp/tools/odb.py +234 -125
  84. vortex/nwp/tools/partitioning.py +61 -37
  85. vortex/nwp/tools/satrad.py +27 -12
  86. vortex/nwp/util/async.py +83 -55
  87. vortex/nwp/util/beacon.py +10 -10
  88. vortex/nwp/util/diffpygram.py +174 -86
  89. vortex/nwp/util/ens.py +144 -63
  90. vortex/nwp/util/hooks.py +30 -19
  91. vortex/nwp/util/taskdeco.py +28 -24
  92. vortex/nwp/util/usepygram.py +278 -172
  93. vortex/nwp/util/usetnt.py +31 -17
  94. vortex/sessions.py +72 -39
  95. vortex/syntax/__init__.py +1 -1
  96. vortex/syntax/stdattrs.py +410 -171
  97. vortex/syntax/stddeco.py +31 -22
  98. vortex/toolbox.py +327 -192
  99. vortex/tools/__init__.py +11 -2
  100. vortex/tools/actions.py +110 -121
  101. vortex/tools/addons.py +111 -92
  102. vortex/tools/arm.py +42 -22
  103. vortex/tools/compression.py +72 -69
  104. vortex/tools/date.py +11 -4
  105. vortex/tools/delayedactions.py +242 -132
  106. vortex/tools/env.py +75 -47
  107. vortex/tools/folder.py +342 -171
  108. vortex/tools/grib.py +341 -162
  109. vortex/tools/lfi.py +423 -216
  110. vortex/tools/listings.py +109 -40
  111. vortex/tools/names.py +218 -156
  112. vortex/tools/net.py +655 -299
  113. vortex/tools/parallelism.py +93 -61
  114. vortex/tools/prestaging.py +55 -31
  115. vortex/tools/schedulers.py +172 -105
  116. vortex/tools/services.py +403 -334
  117. vortex/tools/storage.py +293 -358
  118. vortex/tools/surfex.py +24 -24
  119. vortex/tools/systems.py +1234 -643
  120. vortex/tools/targets.py +156 -100
  121. vortex/util/__init__.py +1 -1
  122. vortex/util/config.py +378 -327
  123. vortex/util/empty.py +2 -2
  124. vortex/util/helpers.py +56 -24
  125. vortex/util/introspection.py +18 -12
  126. vortex/util/iosponge.py +8 -4
  127. vortex/util/roles.py +4 -6
  128. vortex/util/storefunctions.py +39 -13
  129. vortex/util/structs.py +3 -3
  130. vortex/util/worker.py +29 -17
  131. vortex_nwp-2.1.0.dist-info/METADATA +67 -0
  132. vortex_nwp-2.1.0.dist-info/RECORD +144 -0
  133. {vortex_nwp-2.0.0b1.dist-info → vortex_nwp-2.1.0.dist-info}/WHEEL +1 -1
  134. vortex/layout/appconf.py +0 -109
  135. vortex/layout/jobs.py +0 -1276
  136. vortex/layout/nodes.py +0 -1424
  137. vortex/layout/subjobs.py +0 -464
  138. vortex_nwp-2.0.0b1.dist-info/METADATA +0 -50
  139. vortex_nwp-2.0.0b1.dist-info/RECORD +0 -146
  140. {vortex_nwp-2.0.0b1.dist-info → vortex_nwp-2.1.0.dist-info/licenses}/LICENSE +0 -0
  141. {vortex_nwp-2.0.0b1.dist-info → vortex_nwp-2.1.0.dist-info}/top_level.txt +0 -0
vortex/tools/storage.py CHANGED
@@ -27,13 +27,12 @@ aspects. Using the :mod:`footprints` package, for a given execution target, it
27
27
  allows to customise the way data are accessed leaving the :class:`Store` objects
28
28
  unchanged.
29
29
  """
30
+
30
31
  import contextlib
31
32
  import ftplib
32
33
  import re
33
34
  import time
34
- from collections import defaultdict
35
35
  from datetime import datetime
36
- import os
37
36
 
38
37
  import footprints
39
38
  from bronx.fancies import loggers
@@ -42,9 +41,7 @@ from bronx.syntax.decorators import nicedeco
42
41
  from vortex import sessions
43
42
  from vortex.tools.actions import actiond as ad
44
43
  from vortex.tools.delayedactions import d_action_status
45
- from vortex.tools.systems import istruedef
46
- # TODO clean instances of GenericConfigParser
47
- from vortex.util.config import GenericConfigParser
44
+
48
45
  from vortex import config
49
46
 
50
47
  #: No automatic export
@@ -60,6 +57,7 @@ HARDLINK_THRESHOLD = 1048576
60
57
  # Decorators: for internal use in the Storage class
61
58
  # -------------------------------------------------
62
59
 
60
+
63
61
  def do_recording(flag):
64
62
  """Add a record line in the History object (if sensible)."""
65
63
 
@@ -92,6 +90,7 @@ def enforce_readonly(f):
92
90
  # Main Storage abstract class
93
91
  # ---------------------------
94
92
 
93
+
95
94
  class Storage(footprints.FootprintBase):
96
95
  """Root class for any Storage class, ex: Cache, Archive, ...
97
96
 
@@ -111,14 +110,10 @@ class Storage(footprints.FootprintBase):
111
110
  dictionary whose items will be written in the object's record.
112
111
  """
113
112
 
114
- _abstract = True,
113
+ _abstract = (True,)
115
114
  _footprint = dict(
116
- info = 'Default/Abstract storage place description.',
117
- attr = dict(
118
- kind=dict(
119
- info="The storage place's kind.",
120
- values=['std'],
121
- ),
115
+ info="Default/Abstract storage place description.",
116
+ attr=dict(
122
117
  storage=dict(
123
118
  info="The storage target.",
124
119
  ),
@@ -127,7 +122,7 @@ class Storage(footprints.FootprintBase):
127
122
  type=bool,
128
123
  optional=True,
129
124
  default=False,
130
- access='rwx',
125
+ access="rwx",
131
126
  ),
132
127
  readonly=dict(
133
128
  info="Disallow insert and delete action for this storage place.",
@@ -135,11 +130,11 @@ class Storage(footprints.FootprintBase):
135
130
  optional=True,
136
131
  default=False,
137
132
  ),
138
- )
133
+ ),
139
134
  )
140
135
 
141
136
  def __init__(self, *args, **kw):
142
- logger.debug('Abstract storage init %s', self.__class__)
137
+ logger.debug("Abstract storage init %s", self.__class__)
143
138
  super().__init__(*args, **kw)
144
139
  self._history = History(tag=self.tag)
145
140
 
@@ -150,10 +145,10 @@ class Storage(footprints.FootprintBase):
150
145
 
151
146
  @property
152
147
  def realkind(self):
153
- return 'storage'
148
+ return "storage"
154
149
 
155
150
  def _str_more(self):
156
- return 'tag={:s}'.format(self.tag)
151
+ return "tag={:s}".format(self.tag)
157
152
 
158
153
  @property
159
154
  def context(self):
@@ -239,7 +234,7 @@ class Storage(footprints.FootprintBase):
239
234
  return rc
240
235
 
241
236
  @enforce_readonly
242
- @do_recording('INSERT')
237
+ @do_recording("INSERT")
243
238
  def insert(self, item, local, **kwargs):
244
239
  """Insert an **item** in the current storage place.
245
240
 
@@ -247,7 +242,7 @@ class Storage(footprints.FootprintBase):
247
242
  """
248
243
  return self._actual_insert(item, local, **kwargs)
249
244
 
250
- @do_recording('RETRIEVE')
245
+ @do_recording("RETRIEVE")
251
246
  def retrieve(self, item, local, **kwargs):
252
247
  """Retrieve an **item** from the current storage place.
253
248
 
@@ -271,19 +266,23 @@ class Storage(footprints.FootprintBase):
271
266
 
272
267
  :note: **local** may be a path to a file or any kind of file like objects.
273
268
  """
274
- rc, idict = self._actual_finaliseretrieve(retrieve_id, item, local, **kwargs)
269
+ rc, idict = self._actual_finaliseretrieve(
270
+ retrieve_id, item, local, **kwargs
271
+ )
275
272
  if rc is not None:
276
273
  infos = self._findout_record_infos(kwargs)
277
274
  infos.update(idict)
278
- self.addrecord('RETRIEVE', item, status=rc, **infos)
275
+ self.addrecord("RETRIEVE", item, status=rc, **infos)
279
276
  return rc
280
277
 
281
- def _actual_finaliseretrieve(self, retrieve_id, item, local, **kwargs): # @UnusedVariable
278
+ def _actual_finaliseretrieve(
279
+ self, retrieve_id, item, local, **kwargs
280
+ ): # @UnusedVariable
282
281
  """No delayedretrieve implemented by default."""
283
282
  return None, dict()
284
283
 
285
284
  @enforce_readonly
286
- @do_recording('DELETE')
285
+ @do_recording("DELETE")
287
286
  def delete(self, item, **kwargs):
288
287
  """Delete an **item** from the current storage place."""
289
288
  return self._actual_delete(item, **kwargs)
@@ -292,44 +291,46 @@ class Storage(footprints.FootprintBase):
292
291
  # Defining the two main flavours of storage places
293
292
  # -----------------------------------------------
294
293
 
294
+
295
295
  class Cache(Storage):
296
296
  """Root class for any :class:Cache subclasses."""
297
297
 
298
- _abstract = True
299
- _collector = ('cache',)
298
+ _collector = ("cache",)
300
299
  _footprint = dict(
301
- info = 'Default cache description',
302
- attr = dict(
303
- headdir = dict(
304
- info = "The cache's subdirectory (within **rootdir**).",
305
- optional = True,
306
- default = 'cache',
300
+ info="Default cache description",
301
+ attr=dict(
302
+ entry=dict(
303
+ optional=False,
304
+ type=str,
305
+ info="The absolute path to the cache space",
307
306
  ),
308
307
  # TODO is 'storage' used in any way?
309
- storage = dict(
310
- optional = True,
311
- default = 'localhost',
308
+ storage=dict(
309
+ optional=True,
310
+ default="localhost",
312
311
  ),
313
- rtouch = dict(
314
- info = "Perform the recursive touch command on the directory structure.",
315
- type = bool,
316
- optional = True,
317
- default = False,
312
+ rtouch=dict(
313
+ info="Perform the recursive touch command on the directory structure.",
314
+ type=bool,
315
+ optional=True,
316
+ default=False,
318
317
  ),
319
- rtouchskip = dict(
320
- info = "Do not 'touch' the first **rtouchskip** directories.",
321
- type = int,
322
- optional = True,
323
- default = 0,
318
+ rtouchskip=dict(
319
+ info="Do not 'touch' the first **rtouchskip** directories.",
320
+ type=int,
321
+ optional=True,
322
+ default=0,
324
323
  ),
325
- rtouchdelay = dict(
326
- info = ("Do not perfom a touch if it has already been done in " +
327
- "the last X seconds."),
328
- type = float,
329
- optional = True,
330
- default = 600., # 10 minutes
324
+ rtouchdelay=dict(
325
+ info=(
326
+ "Do not perfom a touch if it has already been done in "
327
+ + "the last X seconds."
328
+ ),
329
+ type=float,
330
+ optional=True,
331
+ default=600.0, # 10 minutes
331
332
  ),
332
- )
333
+ ),
333
334
  )
334
335
 
335
336
  def __init__(self, *kargs, **kwargs):
@@ -338,22 +339,24 @@ class Cache(Storage):
338
339
 
339
340
  @property
340
341
  def realkind(self):
341
- return 'cache'
342
+ return "cache"
342
343
 
343
344
  @property
344
345
  def tag(self):
345
346
  """The identifier of this cache place."""
346
- return '{:s}_{:s}_{:s}'.format(self.realkind, self.kind, self.headdir)
347
+ return "{:s}_{:s}".format(self.realkind, self.entry)
347
348
 
348
349
  def _formatted_path(self, subpath, **kwargs): # @UnusedVariable
349
- raise NotImplementedError()
350
+ return self.sh.path.join(self.entry, subpath.lstrip("/"))
350
351
 
351
352
  def catalog(self):
352
353
  """List all files present in this cache.
353
354
 
354
355
  :note: It might be quite slow...
355
356
  """
356
- raise NotImplementedError()
357
+ entry = self.sh.path.expanduser(self.entry)
358
+ files = self.sh.ffind(entry)
359
+ return [f[len(entry) :] for f in files]
357
360
 
358
361
  def _xtouch(self, path):
359
362
  """
@@ -363,11 +366,11 @@ class Cache(Storage):
363
366
  ts = time.time()
364
367
  ts_delay = ts - self._touch_tracker.get(path, 0)
365
368
  if ts_delay > self.rtouchdelay:
366
- logger.debug('Touching: %s (delay was %.2f)', path, ts_delay)
369
+ logger.debug("Touching: %s (delay was %.2f)", path, ts_delay)
367
370
  self.sh.touch(path)
368
371
  self._touch_tracker[path] = ts
369
372
  else:
370
- logger.debug('Skipping touch: %s (delay was %.2f)', path, ts_delay)
373
+ logger.debug("Skipping touch: %s (delay was %.2f)", path, ts_delay)
371
374
 
372
375
  def _recursive_touch(self, rc, item, writing=False):
373
376
  """Make recursive touches on parent directories.
@@ -375,13 +378,15 @@ class Cache(Storage):
375
378
  It might be useful for cleaning scripts.
376
379
  """
377
380
  if self.rtouch and (not self.readonly) and rc:
378
- items = item.lstrip('/').split('/')
381
+ items = item.lstrip("/").split("/")
379
382
  items = items[:-1]
380
383
  if writing:
381
384
  # It's useless to touch the rightmost directory
382
385
  items = items[:-1] if len(items) > 1 else []
383
386
  for index in range(len(items), self.rtouchskip, -1):
384
- self._xtouch(self._formatted_path(self.sh.path.join(*items[:index])))
387
+ self._xtouch(
388
+ self._formatted_path(self.sh.path.join(*items[:index]))
389
+ )
385
390
 
386
391
  def _actual_fullpath(self, item, **kwargs):
387
392
  """Return the path/URI to the **item**'s storage location."""
@@ -389,8 +394,9 @@ class Cache(Storage):
389
394
 
390
395
  def _actual_prestageinfo(self, item, **kwargs):
391
396
  """Returns pre-staging informations."""
392
- return dict(strategy=self.kind,
393
- location=self.fullpath(item, **kwargs)), dict()
397
+ return dict(
398
+ strategy="std", location=self.fullpath(item, **kwargs)
399
+ ), dict()
394
400
 
395
401
  def _actual_check(self, item, **kwargs):
396
402
  """Check/Stat an **item** from the current storage place."""
@@ -421,11 +427,18 @@ class Cache(Storage):
421
427
  fmt = kwargs.get("fmt", "foo")
422
428
  # Insert the element
423
429
  tpath = self._formatted_path(item)
430
+ if not self.sh.path.exists(self.entry):
431
+ self.sh.mkdir(self.entry)
424
432
  if tpath is not None:
425
- rc = self.sh.cp(local, tpath, intent=intent, fmt=fmt,
426
- smartcp_threshold=HARDLINK_THRESHOLD)
433
+ rc = self.sh.cp(
434
+ local,
435
+ tpath,
436
+ intent=intent,
437
+ fmt=fmt,
438
+ smartcp_threshold=HARDLINK_THRESHOLD,
439
+ )
427
440
  else:
428
- logger.warning('No target location for < %s >', item)
441
+ logger.warning("No target location for < %s >", item)
429
442
  rc = False
430
443
  self._recursive_touch(rc, item, writing=True)
431
444
  return rc, dict(intent=intent, fmt=fmt)
@@ -442,30 +455,55 @@ class Cache(Storage):
442
455
  source = self._formatted_path(item)
443
456
  if source is not None:
444
457
  # If auto_dirextract, copy recursively each file contained in source
445
- if dirextract and self.sh.path.isdir(source) and self.sh.is_tarname(local):
458
+ if (
459
+ dirextract
460
+ and self.sh.path.isdir(source)
461
+ and self.sh.is_tarname(local)
462
+ ):
446
463
  rc = True
447
464
  destdir = self.sh.path.dirname(self.sh.path.realpath(local))
448
- logger.info('Automatic directory extract to: %s', destdir)
449
- for subpath in self.sh.glob(source + '/*'):
450
- rc = rc and self.sh.cp(subpath,
451
- self.sh.path.join(destdir, self.sh.path.basename(subpath)),
452
- intent=intent, fmt=fmt,
453
- smartcp_threshold=HARDLINK_THRESHOLD)
465
+ logger.info("Automatic directory extract to: %s", destdir)
466
+ for subpath in self.sh.glob(source + "/*"):
467
+ rc = rc and self.sh.cp(
468
+ subpath,
469
+ self.sh.path.join(
470
+ destdir, self.sh.path.basename(subpath)
471
+ ),
472
+ intent=intent,
473
+ fmt=fmt,
474
+ smartcp_threshold=HARDLINK_THRESHOLD,
475
+ )
454
476
  # For the insitu feature to work...
455
477
  rc = rc and self.sh.touch(local)
456
478
  # The usual case: just copy source
457
479
  else:
458
- rc = self.sh.cp(source, local, intent=intent, fmt=fmt, silent=silent,
459
- smartcp_threshold=HARDLINK_THRESHOLD)
480
+ rc = self.sh.cp(
481
+ source,
482
+ local,
483
+ intent=intent,
484
+ fmt=fmt,
485
+ silent=silent,
486
+ smartcp_threshold=HARDLINK_THRESHOLD,
487
+ )
460
488
  # If auto_tarextract, a potential tar file is extracted
461
- if (rc and tarextract and not self.sh.path.isdir(local) and
462
- self.sh.is_tarname(local) and self.sh.is_tarfile(local)):
463
- destdir = self.sh.path.dirname(self.sh.path.realpath(local))
464
- logger.info('Automatic Tar extract to: %s', destdir)
465
- rc = rc and self.sh.smartuntar(local, destdir,
466
- uniquelevel_ignore=uniquelevel_ignore)
489
+ if (
490
+ rc
491
+ and tarextract
492
+ and not self.sh.path.isdir(local)
493
+ and self.sh.is_tarname(local)
494
+ and self.sh.is_tarfile(local)
495
+ ):
496
+ destdir = self.sh.path.dirname(
497
+ self.sh.path.realpath(local)
498
+ )
499
+ logger.info("Automatic Tar extract to: %s", destdir)
500
+ rc = rc and self.sh.smartuntar(
501
+ local, destdir, uniquelevel_ignore=uniquelevel_ignore
502
+ )
467
503
  else:
468
- getattr(logger, 'info' if silent else 'warning')('No readable source for < %s >', item)
504
+ getattr(logger, "info" if silent else "warning")(
505
+ "No readable source for < %s >", item
506
+ )
469
507
  rc = False
470
508
  self._recursive_touch(rc, item)
471
509
  return rc, dict(intent=intent, fmt=fmt)
@@ -479,43 +517,60 @@ class Cache(Storage):
479
517
  if tpath is not None:
480
518
  rc = self.sh.remove(tpath, fmt=fmt)
481
519
  else:
482
- logger.warning('No target location for < %s >', item)
520
+ logger.warning("No target location for < %s >", item)
483
521
  rc = False
484
522
  return rc, dict(fmt=fmt)
485
523
 
524
+ def flush(self, dumpfile=None):
525
+ """Flush actual history to the specified ``dumpfile`` if record is on."""
526
+ if dumpfile is None:
527
+ logfile = ".".join(
528
+ (
529
+ "HISTORY",
530
+ datetime.now().strftime("%Y%m%d%H%M%S.%f"),
531
+ "P{:06d}".format(self.sh.getpid()),
532
+ self.sh.getlogname(),
533
+ )
534
+ )
535
+ dumpfile = self.sh.path.join(self.entry, ".history", logfile)
536
+ if self.record:
537
+ self.sh.pickle_dump(self.history, dumpfile)
538
+
486
539
 
487
540
  class AbstractArchive(Storage):
488
541
  """The default class to handle storage to some kind if Archive."""
489
542
 
490
543
  _abstract = True
491
- _collector = ('archive',)
544
+ _collector = ("archive",)
492
545
  _footprint = dict(
493
- info = 'Default archive description',
494
- attr = dict(
495
- tube = dict(
496
- info = "How to communicate with the archive ?",
546
+ info="Default archive description",
547
+ attr=dict(
548
+ tube=dict(
549
+ info="How to communicate with the archive ?",
497
550
  ),
498
- )
551
+ ),
499
552
  )
500
553
 
501
554
  @property
502
555
  def tag(self):
503
556
  """The identifier of this cache place."""
504
- return '{:s}_{:s}_{:s}'.format(self.realkind, self.storage, self.kind)
557
+ return "{:s}_{:s}".format(self.realkind, self.storage)
505
558
 
506
559
  @property
507
560
  def realkind(self):
508
- return 'archive'
561
+ return "archive"
509
562
 
510
563
  def _formatted_path(self, rawpath, **kwargs):
511
- root = kwargs.get('root', None)
564
+ root = kwargs.get("root", None)
512
565
  if root is not None:
513
- rawpath = self.sh.path.join(root, rawpath.lstrip('/'))
566
+ rawpath = self.sh.path.join(root, rawpath.lstrip("/"))
514
567
  # Deal with compression
515
- compressionpipeline = kwargs.get('compressionpipeline', None)
568
+ compressionpipeline = kwargs.get("compressionpipeline", None)
516
569
  if compressionpipeline is not None:
517
570
  rawpath += compressionpipeline.suffix
518
- return self.sh.anyft_remote_rewrite(rawpath, fmt=kwargs.get('fmt', 'foo'))
571
+ return self.sh.anyft_remote_rewrite(
572
+ rawpath, fmt=kwargs.get("fmt", "foo")
573
+ )
519
574
 
520
575
  def _actual_proxy_method(self, pmethod):
521
576
  """Create a proxy method based on the **pmethod** actual method."""
@@ -532,18 +587,23 @@ class AbstractArchive(Storage):
532
587
 
533
588
  def __getattr__(self, attr):
534
589
  """Provides proxy methods for _actual_* methods."""
535
- methods = r'fullpath|prestageinfo|check|list|insert|retrieve|delete'
536
- mattr = re.match(r'_actual_(?P<action>' + methods + r')', attr)
590
+ methods = r"fullpath|prestageinfo|check|list|insert|retrieve|delete"
591
+ mattr = re.match(r"_actual_(?P<action>" + methods + r")", attr)
537
592
  if mattr:
538
- pmethod = getattr(self, '_{:s}{:s}'.format(self.tube, mattr.group('action')))
593
+ pmethod = getattr(
594
+ self, "_{:s}{:s}".format(self.tube, mattr.group("action"))
595
+ )
539
596
  return self._actual_proxy_method(pmethod)
540
597
  else:
541
- raise AttributeError("The {:s} attribute was not found in this object"
542
- .format(attr))
598
+ raise AttributeError(
599
+ "The {:s} attribute was not found in this object".format(attr)
600
+ )
543
601
 
544
602
  def _actual_earlyretrieve(self, item, local, **kwargs):
545
603
  """Proxy to the appropriate tube dependent earlyretrieve method (if available)."""
546
- pmethod = getattr(self, '_{:s}{:s}'.format(self.tube, 'earlyretrieve'), None)
604
+ pmethod = getattr(
605
+ self, "_{:s}{:s}".format(self.tube, "earlyretrieve"), None
606
+ )
547
607
  if pmethod:
548
608
  return self._actual_proxy_method(pmethod)(item, local, **kwargs)
549
609
  else:
@@ -551,9 +611,13 @@ class AbstractArchive(Storage):
551
611
 
552
612
  def _actual_finaliseretrieve(self, retrieve_id, item, local, **kwargs):
553
613
  """Proxy to the appropriate tube dependent finaliseretrieve method (if available)."""
554
- pmethod = getattr(self, '_{:s}{:s}'.format(self.tube, 'finaliseretrieve'), None)
614
+ pmethod = getattr(
615
+ self, "_{:s}{:s}".format(self.tube, "finaliseretrieve"), None
616
+ )
555
617
  if pmethod:
556
- return self._actual_proxy_method(pmethod)(item, local, retrieve_id, **kwargs)
618
+ return self._actual_proxy_method(pmethod)(
619
+ item, local, retrieve_id, **kwargs
620
+ )
557
621
  else:
558
622
  return None, dict()
559
623
 
@@ -562,41 +626,46 @@ class Archive(AbstractArchive):
562
626
  """The default class to handle storage to a remote location."""
563
627
 
564
628
  _footprint = dict(
565
- info = 'Default archive description',
566
- attr = dict(
567
- tube = dict(
568
- values = ['ftp'],
629
+ info="Default archive description",
630
+ attr=dict(
631
+ tube=dict(
632
+ values=["ftp"],
569
633
  ),
570
- )
634
+ ),
571
635
  )
572
636
 
573
637
  def __init__(self, *kargs, **kwargs):
574
638
  super().__init__(*kargs, **kwargs)
575
639
  self.default_usejeeves = config.from_config(
576
- section="storage", key="usejeeves",
640
+ section="storage",
641
+ key="usejeeves",
577
642
  )
578
643
 
579
644
  @property
580
645
  def _ftp_hostinfos(self):
581
646
  """Return the FTP hostname end port number."""
582
- s_storage = self.storage.split(':', 1)
647
+ s_storage = self.storage.split(":", 1)
583
648
  hostname = s_storage[0]
584
649
  port = None
585
650
  if len(s_storage) > 1:
586
651
  try:
587
652
  port = int(s_storage[1])
588
653
  except ValueError:
589
- logger.error('Invalid port number < %s >. Ignoring it', s_storage[1])
654
+ logger.error(
655
+ "Invalid port number < %s >. Ignoring it", s_storage[1]
656
+ )
590
657
  return hostname, port
591
658
 
592
659
  def _ftp_client(self, logname=None, delayed=False):
593
660
  """Return a FTP client object."""
594
661
  hostname, port = self._ftp_hostinfos
595
- return self.sh.ftp(hostname, logname=logname, delayed=delayed, port=port)
662
+ return self.sh.ftp(
663
+ hostname, logname=logname, delayed=delayed, port=port
664
+ )
596
665
 
597
666
  def _ftpfullpath(self, item, **kwargs):
598
667
  """Actual _fullpath using ftp."""
599
- username = kwargs.get('username', None)
668
+ username = kwargs.get("username", None)
600
669
  rc = None
601
670
  ftp = self._ftp_client(logname=username, delayed=True)
602
671
  if ftp:
@@ -608,7 +677,7 @@ class Archive(AbstractArchive):
608
677
 
609
678
  def _ftpprestageinfo(self, item, **kwargs):
610
679
  """Actual _prestageinfo using ftp."""
611
- username = kwargs.get('username', None)
680
+ username = kwargs.get("username", None)
612
681
  if username is None:
613
682
  ftp = self._ftp_client(logname=username, delayed=True)
614
683
  if ftp:
@@ -616,15 +685,17 @@ class Archive(AbstractArchive):
616
685
  username = ftp.logname
617
686
  finally:
618
687
  ftp.close()
619
- baseinfo = dict(storage=self.storage,
620
- logname=username,
621
- location=item, )
688
+ baseinfo = dict(
689
+ storage=self.storage,
690
+ logname=username,
691
+ location=item,
692
+ )
622
693
  return baseinfo, dict()
623
694
 
624
695
  def _ftpcheck(self, item, **kwargs):
625
696
  """Actual _check using ftp."""
626
697
  rc = None
627
- ftp = self._ftp_client(logname=kwargs.get('username', None))
698
+ ftp = self._ftp_client(logname=kwargs.get("username", None))
628
699
  if ftp:
629
700
  try:
630
701
  rc = ftp.size(item)
@@ -638,7 +709,7 @@ class Archive(AbstractArchive):
638
709
 
639
710
  def _ftplist(self, item, **kwargs):
640
711
  """Actual _list using ftp."""
641
- ftp = self._ftp_client(logname=kwargs.get('username', None))
712
+ ftp = self._ftp_client(logname=kwargs.get("username", None))
642
713
  rc = None
643
714
  if ftp:
644
715
  try:
@@ -657,26 +728,30 @@ class Archive(AbstractArchive):
657
728
  else:
658
729
  # Content of the directory...
659
730
  if rc:
660
- rc = ftp.nlst('.')
731
+ rc = ftp.nlst(".")
661
732
  finally:
662
733
  ftp.close()
663
734
  return rc, dict()
664
735
 
665
736
  def _ftpretrieve(self, item, local, **kwargs):
666
737
  """Actual _retrieve using ftp."""
667
- logger.info('ftpget on ftp://%s/%s (to: %s)', self.storage, item, local)
668
- extras = dict(fmt=kwargs.get('fmt', 'foo'),
669
- cpipeline=kwargs.get('compressionpipeline', None))
738
+ logger.info(
739
+ "ftpget on ftp://%s/%s (to: %s)", self.storage, item, local
740
+ )
741
+ extras = dict(
742
+ fmt=kwargs.get("fmt", "foo"),
743
+ cpipeline=kwargs.get("compressionpipeline", None),
744
+ )
670
745
  hostname, port = self._ftp_hostinfos
671
746
  if port is not None:
672
- extras['port'] = port
747
+ extras["port"] = port
673
748
  rc = self.sh.smartftget(
674
749
  item,
675
750
  local,
676
751
  # Ftp control
677
752
  hostname=hostname,
678
- logname=kwargs.get('username', None),
679
- **extras
753
+ logname=kwargs.get("username", None),
754
+ **extras,
680
755
  )
681
756
  return rc, extras
682
757
 
@@ -685,32 +760,42 @@ class Archive(AbstractArchive):
685
760
  If FtServ/ftraw is used, trigger a delayed action in order to fetch
686
761
  several files at once.
687
762
  """
688
- cpipeline = kwargs.get('compressionpipeline', None)
763
+ cpipeline = kwargs.get("compressionpipeline", None)
689
764
  if self.sh.rawftget_worthy(item, local, cpipeline):
690
- return self.context.delayedactions_hub.register((item, kwargs.get('fmt', 'foo')),
691
- kind='archive',
692
- storage=self.storage,
693
- goal='get',
694
- tube='ftp',
695
- raw=True,
696
- logname=kwargs.get('username', None))
765
+ return self.context.delayedactions_hub.register(
766
+ (item, kwargs.get("fmt", "foo")),
767
+ kind="archive",
768
+ storage=self.storage,
769
+ goal="get",
770
+ tube="ftp",
771
+ raw=True,
772
+ logname=kwargs.get("username", None),
773
+ )
697
774
  else:
698
775
  return None
699
776
 
700
- def _ftpfinaliseretrieve(self, item, local, retrieve_id, **kwargs): # @UnusedVariable
777
+ def _ftpfinaliseretrieve(
778
+ self, item, local, retrieve_id, **kwargs
779
+ ): # @UnusedVariable
701
780
  """
702
781
  Get the resource given the **retrieve_id** identifier returned by the
703
782
  :meth:`_ftpearlyretrieve` method.
704
783
  """
705
- extras = dict(fmt=kwargs.get('fmt', 'foo'), )
706
- d_action = self.context.delayedactions_hub.retrieve(retrieve_id, bareobject=True)
784
+ extras = dict(
785
+ fmt=kwargs.get("fmt", "foo"),
786
+ )
787
+ d_action = self.context.delayedactions_hub.retrieve(
788
+ retrieve_id, bareobject=True
789
+ )
707
790
  if d_action.status == d_action_status.done:
708
791
  if self.sh.filecocoon(local):
709
792
  rc = self.sh.mv(d_action.result, local, **extras)
710
793
  else:
711
- raise OSError('Could not cocoon: {!s}'.format(local))
794
+ raise OSError("Could not cocoon: {!s}".format(local))
712
795
  elif d_action.status == d_action_status.failed:
713
- logger.info('The earlyretrieve failed (retrieve_id=%s)', retrieve_id)
796
+ logger.info(
797
+ "The earlyretrieve failed (retrieve_id=%s)", retrieve_id
798
+ )
714
799
  rc = False
715
800
  else:
716
801
  rc = None
@@ -718,64 +803,78 @@ class Archive(AbstractArchive):
718
803
 
719
804
  def _ftpinsert(self, item, local, **kwargs):
720
805
  """Actual _insert using ftp."""
721
- usejeeves = kwargs.get('usejeeves', None)
806
+ usejeeves = kwargs.get("usejeeves", None)
722
807
  if usejeeves is None:
723
808
  usejeeves = self.default_usejeeves
724
809
  hostname, port = self._ftp_hostinfos
725
810
  if not usejeeves:
726
- logger.info('ftpput to ftp://%s/%s (from: %s)', self.storage, item, local)
727
- extras = dict(fmt=kwargs.get('fmt', 'foo'),
728
- cpipeline=kwargs.get('compressionpipeline', None))
811
+ logger.info(
812
+ "ftpput to ftp://%s/%s (from: %s)", self.storage, item, local
813
+ )
814
+ extras = dict(
815
+ fmt=kwargs.get("fmt", "foo"),
816
+ cpipeline=kwargs.get("compressionpipeline", None),
817
+ )
729
818
  if port is not None:
730
- extras['port'] = port
819
+ extras["port"] = port
731
820
  rc = self.sh.smartftput(
732
821
  local,
733
822
  item,
734
823
  # Ftp control
735
824
  hostname=hostname,
736
- logname=kwargs.get('username', None),
737
- sync=kwargs.get('enforcesync', False),
738
- **extras
825
+ logname=kwargs.get("username", None),
826
+ sync=kwargs.get("enforcesync", False),
827
+ **extras,
739
828
  )
740
829
  else:
741
- logger.info('delayed ftpput to ftp://%s/%s (from: %s)', self.storage, item, local)
742
- tempo = footprints.proxy.service(kind='hiddencache',
743
- asfmt=kwargs.get('fmt'))
744
- compressionpipeline = kwargs.get('compressionpipeline', '')
830
+ logger.info(
831
+ "delayed ftpput to ftp://%s/%s (from: %s)",
832
+ self.storage,
833
+ item,
834
+ local,
835
+ )
836
+ tempo = footprints.proxy.service(
837
+ kind="hiddencache", asfmt=kwargs.get("fmt")
838
+ )
839
+ compressionpipeline = kwargs.get("compressionpipeline", "")
745
840
  if compressionpipeline:
746
841
  compressionpipeline = compressionpipeline.description_string
747
- extras = dict(fmt=kwargs.get('fmt', 'foo'),
748
- cpipeline=compressionpipeline)
842
+ extras = dict(
843
+ fmt=kwargs.get("fmt", "foo"), cpipeline=compressionpipeline
844
+ )
749
845
  if port is not None:
750
- extras['port'] = port
846
+ extras["port"] = port
751
847
 
752
848
  rc = ad.jeeves(
753
849
  hostname=hostname,
754
850
  # Explicitly resolve the logname (because jeeves FTP client is not
755
851
  # running with the same glove (i.e. Jeeves ftuser configuration may
756
852
  # be different).
757
- logname=self.sh.fix_ftuser(hostname,
758
- kwargs.get('username', None)),
759
- todo='ftput',
760
- rhandler=kwargs.get('info', None),
853
+ logname=self.sh.fix_ftuser(
854
+ hostname, kwargs.get("username", None)
855
+ ),
856
+ todo="ftput",
857
+ rhandler=kwargs.get("info", None),
761
858
  source=tempo(local),
762
859
  destination=item,
763
860
  original=self.sh.path.abspath(local),
764
- **extras
861
+ **extras,
765
862
  )
766
863
  return rc, extras
767
864
 
768
865
  def _ftpdelete(self, item, **kwargs):
769
866
  """Actual _delete using ftp."""
770
867
  rc = None
771
- ftp = self._ftp_client(logname=kwargs.get('username', None))
868
+ ftp = self._ftp_client(logname=kwargs.get("username", None))
772
869
  if ftp:
773
870
  if self._ftpcheck(item, **kwargs)[0]:
774
- logger.info('ftpdelete on ftp://%s/%s', self.storage, item)
871
+ logger.info("ftpdelete on ftp://%s/%s", self.storage, item)
775
872
  rc = ftp.delete(item)
776
873
  ftp.close()
777
874
  else:
778
- logger.error('Try to remove a non-existing resource <%s>', item)
875
+ logger.error(
876
+ "Try to remove a non-existing resource <%s>", item
877
+ )
779
878
  return rc, dict()
780
879
 
781
880
 
@@ -784,12 +883,14 @@ class AbstractLocalArchive(AbstractArchive):
784
883
 
785
884
  _abstract = True
786
885
  _footprint = dict(
787
- info = 'Generic local archive description',
788
- attr = dict(
789
- tube = dict(
790
- values = ['inplace', ],
886
+ info="Generic local archive description",
887
+ attr=dict(
888
+ tube=dict(
889
+ values=[
890
+ "inplace",
891
+ ],
791
892
  ),
792
- )
893
+ ),
793
894
  )
794
895
 
795
896
  def _inplacefullpath(self, item, **kwargs):
@@ -818,14 +919,14 @@ class AbstractLocalArchive(AbstractArchive):
818
919
 
819
920
  def _inplaceretrieve(self, item, local, **kwargs):
820
921
  """Actual _retrieve using ftp."""
821
- logger.info('inplaceget on file:///%s (to: %s)', item, local)
822
- fmt = kwargs.get('fmt', 'foo')
823
- cpipeline = kwargs.get('compressionpipeline', None)
922
+ logger.info("inplaceget on file:///%s (to: %s)", item, local)
923
+ fmt = kwargs.get("fmt", "foo")
924
+ cpipeline = kwargs.get("compressionpipeline", None)
824
925
  if cpipeline:
825
926
  rc = cpipeline.file2uncompress(item, local)
826
927
  else:
827
928
  # Do not use fmt=... on purpose (otherwise "forceunpack" may be called twice)
828
- rc = self.sh.cp(item, local, intent='in')
929
+ rc = self.sh.cp(item, local, intent="in")
829
930
  rc = rc and self.sh.forceunpack(local, fmt=fmt)
830
931
  return rc, dict(fmt=fmt, cpipeline=cpipeline)
831
932
 
@@ -842,20 +943,20 @@ class AbstractLocalArchive(AbstractArchive):
842
943
 
843
944
  def _inplaceinsert(self, item, local, **kwargs):
844
945
  """Actual _insert using ftp."""
845
- logger.info('inplaceput to file:///%s (from: %s)', item, local)
846
- cpipeline = kwargs.get('compressionpipeline', None)
847
- fmt = kwargs.get('fmt', 'foo')
946
+ logger.info("inplaceput to file:///%s (from: %s)", item, local)
947
+ cpipeline = kwargs.get("compressionpipeline", None)
948
+ fmt = kwargs.get("fmt", "foo")
848
949
  with self._inplaceinsert_pack(local, fmt) as local_packed:
849
950
  if cpipeline:
850
951
  rc = cpipeline.compress2file(local_packed, item)
851
952
  else:
852
953
  # Do not use fmt=... on purpose (otherwise "forcepack" may be called twice)
853
- rc = self.sh.cp(local_packed, item, intent='in')
954
+ rc = self.sh.cp(local_packed, item, intent="in")
854
955
  return rc, dict(fmt=fmt, cpipeline=cpipeline)
855
956
 
856
957
  def _inplacedelete(self, item, **kwargs):
857
958
  """Actual _delete using ftp."""
858
- fmt = kwargs.get('fmt', 'foo')
959
+ fmt = kwargs.get("fmt", "foo")
859
960
  rc = None
860
961
  if self._inplacecheck(item, **kwargs)[0]:
861
962
  rc = self.sh.rm(item, fmt=fmt)
@@ -866,196 +967,30 @@ class LocalArchive(AbstractLocalArchive):
866
967
  """The default class to handle storage to the same host."""
867
968
 
868
969
  _footprint = dict(
869
- info = 'Default local archive description',
870
- attr = dict(
871
- storage = dict(
872
- values = ['localhost', ],
970
+ info="Default local archive description",
971
+ attr=dict(
972
+ storage=dict(
973
+ values=[
974
+ "localhost",
975
+ ],
873
976
  ),
874
- auto_self_expand = dict(
875
- info = ('Automatically expand the current user home if ' +
876
- 'a relative path is given (should always be True ' +
877
- 'except during unit-testing)'),
878
- type = bool,
879
- default = True,
880
- optional = True,
977
+ auto_self_expand=dict(
978
+ info=(
979
+ "Automatically expand the current user home if "
980
+ + "a relative path is given (should always be True "
981
+ + "except during unit-testing)"
982
+ ),
983
+ type=bool,
984
+ default=True,
985
+ optional=True,
881
986
  ),
882
- )
987
+ ),
883
988
  )
884
989
 
885
990
  def _formatted_path(self, rawpath, **kwargs):
886
991
  rawpath = self.sh.path.expanduser(rawpath)
887
- if '~' in rawpath:
992
+ if "~" in rawpath:
888
993
  raise OSError('User expansion failed for "{:s}"'.format(rawpath))
889
994
  if self.auto_self_expand and not self.sh.path.isabs(rawpath):
890
- rawpath = self.sh.path.expanduser(self.sh.path.join('~', rawpath))
995
+ rawpath = self.sh.path.expanduser(self.sh.path.join("~", rawpath))
891
996
  return super()._formatted_path(rawpath, **kwargs)
892
-
893
-
894
- # Concrete cache implementations
895
- # ------------------------------
896
-
897
-
898
- class FixedEntryCache(Cache):
899
- _abstract = True
900
- _footprint = dict(
901
- info = 'Default cache description (with a fixed entry point)',
902
- attr = dict(
903
- rootdir = dict(
904
- info = "The cache's location (usually on a filesystem).",
905
- optional = True,
906
- default = None,
907
- ),
908
- )
909
- )
910
-
911
- @property
912
- def entry(self):
913
- """Tries to figure out what could be the actual entry point for storage space."""
914
- if not self.rootdir:
915
- self.rootdir = "/tmp"
916
- return self.sh.path.join(self.rootdir, self.kind, self.headdir)
917
-
918
- @property
919
- def tag(self):
920
- """The identifier of this cache place."""
921
- return '{:s}_{:s}'.format(self.realkind, self.entry)
922
-
923
- def _formatted_path(self, subpath, **kwargs): # @UnusedVariable
924
- return self.sh.path.join(self.entry, subpath.lstrip('/'))
925
-
926
- def catalog(self):
927
- """List all files present in this cache.
928
-
929
- :note: It might be quite slow...
930
- """
931
- entry = self.sh.path.expanduser(self.entry)
932
- files = self.sh.ffind(entry)
933
- return [f[len(entry):] for f in files]
934
-
935
- def flush(self, dumpfile=None):
936
- """Flush actual history to the specified ``dumpfile`` if record is on."""
937
- if dumpfile is None:
938
- logfile = '.'.join((
939
- 'HISTORY',
940
- datetime.now().strftime('%Y%m%d%H%M%S.%f'),
941
- 'P{:06d}'.format(self.sh.getpid()),
942
- self.sh.getlogname()
943
- ))
944
- dumpfile = self.sh.path.join(self.entry, '.history', logfile)
945
- if self.record:
946
- self.sh.pickle_dump(self.history, dumpfile)
947
-
948
-
949
- class MtoolCache(FixedEntryCache):
950
- """Cache items for the MTOOL jobs (or any job that acts like it)."""
951
-
952
- _footprint = dict(
953
- info = 'MTOOL like Cache',
954
- attr = dict(
955
- kind = dict(
956
- values = ['mtool', 'swapp'],
957
- remap = dict(swapp = 'mtool'),
958
- ),
959
- headdir = dict(
960
- optional = True,
961
- default = "",
962
- ),
963
- )
964
- )
965
-
966
- @property
967
- def entry(self):
968
- """Tries to figure out what could be the actual entry point
969
- for cache space.
970
-
971
- """
972
- if self.rootdir:
973
- return os.path.join(self.rootdir, self.headdir)
974
-
975
- if config.is_defined(section="data-tree", key="rootdir"):
976
- rootdir = config.from_config(
977
- section="data-tree", key="rootdir",
978
- )
979
- else:
980
- rootdir = self.sh.path.join(os.environ["HOME"], ".vortex.d")
981
-
982
- return self.sh.path.join(rootdir, self.headdir)
983
-
984
-
985
- class FtStashCache(MtoolCache):
986
- """A place to store file to be sent with ftserv."""
987
-
988
- _footprint = dict(
989
- info = 'A place to store file to be sent with ftserv',
990
- attr = dict(
991
- kind = dict(
992
- values = ['ftstash', ],
993
- ),
994
- headdir = dict(
995
- optional = True,
996
- default = 'ftspool',
997
- ),
998
- )
999
- )
1000
-
1001
-
1002
- class Op2ResearchCache(FixedEntryCache):
1003
- """Cache of the operational suite (read-only)."""
1004
-
1005
- _footprint = dict(
1006
- info = 'MTOOL like Operations Cache (read-only)',
1007
- attr = dict(
1008
- kind = dict(
1009
- values = ['op2r'],
1010
- ),
1011
- headdir = dict(
1012
- optional = True,
1013
- default = 'vortex',
1014
- ),
1015
- readonly = dict(
1016
- values = [True, ],
1017
- default = True,
1018
- )
1019
- )
1020
- )
1021
-
1022
- @property
1023
- def entry(self):
1024
- cache = (
1025
- self.rootdir or
1026
- config.from_config(section="data-tree", key="op_rootdir")
1027
- )
1028
- return self.sh.path.join(cache, self.headdir)
1029
-
1030
-
1031
- class HackerCache(FixedEntryCache):
1032
- """A dirty cache where users can hack things."""
1033
-
1034
- _footprint = dict(
1035
- info = 'A place to hack things...',
1036
- attr = dict(
1037
- kind = dict(
1038
- values = ['hack'],
1039
- ),
1040
- rootdir = dict(
1041
- optional = True,
1042
- default = 'auto'
1043
- ),
1044
- readonly = dict(
1045
- default = True,
1046
- ),
1047
- )
1048
- )
1049
-
1050
- @property
1051
- def entry(self):
1052
- """Tries to figure out what could be the actual entry point for cache space."""
1053
- sh = self.sh
1054
- if self.rootdir == 'auto':
1055
- gl = sessions.current().glove
1056
- sweethome = sh.path.join(gl.configrc, 'hack')
1057
- sh.mkdir(sweethome)
1058
- logger.debug('Using %s hack cache: %s', self.__class__, sweethome)
1059
- else:
1060
- sweethome = self.rootdir
1061
- return sh.path.join(sweethome, self.headdir)