vortex-nwp 2.0.0b1__py3-none-any.whl → 2.0.0b2__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 (139) hide show
  1. vortex/__init__.py +59 -45
  2. vortex/algo/__init__.py +3 -2
  3. vortex/algo/components.py +940 -614
  4. vortex/algo/mpitools.py +802 -497
  5. vortex/algo/serversynctools.py +34 -33
  6. vortex/config.py +19 -22
  7. vortex/data/__init__.py +9 -3
  8. vortex/data/abstractstores.py +593 -655
  9. vortex/data/containers.py +217 -162
  10. vortex/data/contents.py +65 -39
  11. vortex/data/executables.py +93 -102
  12. vortex/data/flow.py +40 -34
  13. vortex/data/geometries.py +228 -132
  14. vortex/data/handlers.py +428 -225
  15. vortex/data/outflow.py +15 -15
  16. vortex/data/providers.py +185 -163
  17. vortex/data/resources.py +48 -42
  18. vortex/data/stores.py +544 -413
  19. vortex/gloves.py +114 -87
  20. vortex/layout/__init__.py +1 -8
  21. vortex/layout/contexts.py +150 -84
  22. vortex/layout/dataflow.py +353 -202
  23. vortex/layout/monitor.py +264 -128
  24. vortex/nwp/__init__.py +5 -2
  25. vortex/nwp/algo/__init__.py +14 -5
  26. vortex/nwp/algo/assim.py +205 -151
  27. vortex/nwp/algo/clim.py +683 -517
  28. vortex/nwp/algo/coupling.py +447 -225
  29. vortex/nwp/algo/eda.py +437 -229
  30. vortex/nwp/algo/eps.py +403 -231
  31. vortex/nwp/algo/forecasts.py +420 -271
  32. vortex/nwp/algo/fpserver.py +683 -307
  33. vortex/nwp/algo/ifsnaming.py +205 -145
  34. vortex/nwp/algo/ifsroot.py +210 -122
  35. vortex/nwp/algo/monitoring.py +132 -76
  36. vortex/nwp/algo/mpitools.py +321 -191
  37. vortex/nwp/algo/odbtools.py +617 -353
  38. vortex/nwp/algo/oopsroot.py +449 -273
  39. vortex/nwp/algo/oopstests.py +90 -56
  40. vortex/nwp/algo/request.py +287 -206
  41. vortex/nwp/algo/stdpost.py +878 -522
  42. vortex/nwp/data/__init__.py +22 -4
  43. vortex/nwp/data/assim.py +125 -137
  44. vortex/nwp/data/boundaries.py +121 -68
  45. vortex/nwp/data/climfiles.py +193 -211
  46. vortex/nwp/data/configfiles.py +73 -69
  47. vortex/nwp/data/consts.py +426 -401
  48. vortex/nwp/data/ctpini.py +59 -43
  49. vortex/nwp/data/diagnostics.py +94 -66
  50. vortex/nwp/data/eda.py +50 -51
  51. vortex/nwp/data/eps.py +195 -146
  52. vortex/nwp/data/executables.py +440 -434
  53. vortex/nwp/data/fields.py +63 -48
  54. vortex/nwp/data/gridfiles.py +183 -111
  55. vortex/nwp/data/logs.py +250 -217
  56. vortex/nwp/data/modelstates.py +180 -151
  57. vortex/nwp/data/monitoring.py +72 -99
  58. vortex/nwp/data/namelists.py +254 -202
  59. vortex/nwp/data/obs.py +400 -308
  60. vortex/nwp/data/oopsexec.py +22 -20
  61. vortex/nwp/data/providers.py +90 -65
  62. vortex/nwp/data/query.py +71 -82
  63. vortex/nwp/data/stores.py +49 -36
  64. vortex/nwp/data/surfex.py +136 -137
  65. vortex/nwp/syntax/__init__.py +1 -1
  66. vortex/nwp/syntax/stdattrs.py +173 -111
  67. vortex/nwp/tools/__init__.py +2 -2
  68. vortex/nwp/tools/addons.py +22 -17
  69. vortex/nwp/tools/agt.py +24 -12
  70. vortex/nwp/tools/bdap.py +16 -5
  71. vortex/nwp/tools/bdcp.py +4 -1
  72. vortex/nwp/tools/bdm.py +3 -0
  73. vortex/nwp/tools/bdmp.py +14 -9
  74. vortex/nwp/tools/conftools.py +728 -378
  75. vortex/nwp/tools/drhook.py +12 -8
  76. vortex/nwp/tools/grib.py +65 -39
  77. vortex/nwp/tools/gribdiff.py +22 -17
  78. vortex/nwp/tools/ifstools.py +82 -42
  79. vortex/nwp/tools/igastuff.py +167 -143
  80. vortex/nwp/tools/mars.py +14 -2
  81. vortex/nwp/tools/odb.py +234 -125
  82. vortex/nwp/tools/partitioning.py +61 -37
  83. vortex/nwp/tools/satrad.py +27 -12
  84. vortex/nwp/util/async.py +83 -55
  85. vortex/nwp/util/beacon.py +10 -10
  86. vortex/nwp/util/diffpygram.py +174 -86
  87. vortex/nwp/util/ens.py +144 -63
  88. vortex/nwp/util/hooks.py +30 -19
  89. vortex/nwp/util/taskdeco.py +28 -24
  90. vortex/nwp/util/usepygram.py +278 -172
  91. vortex/nwp/util/usetnt.py +31 -17
  92. vortex/sessions.py +72 -39
  93. vortex/syntax/__init__.py +1 -1
  94. vortex/syntax/stdattrs.py +410 -171
  95. vortex/syntax/stddeco.py +31 -22
  96. vortex/toolbox.py +327 -192
  97. vortex/tools/__init__.py +11 -2
  98. vortex/tools/actions.py +125 -59
  99. vortex/tools/addons.py +111 -92
  100. vortex/tools/arm.py +42 -22
  101. vortex/tools/compression.py +72 -69
  102. vortex/tools/date.py +11 -4
  103. vortex/tools/delayedactions.py +242 -132
  104. vortex/tools/env.py +75 -47
  105. vortex/tools/folder.py +342 -171
  106. vortex/tools/grib.py +311 -149
  107. vortex/tools/lfi.py +423 -216
  108. vortex/tools/listings.py +109 -40
  109. vortex/tools/names.py +218 -156
  110. vortex/tools/net.py +632 -298
  111. vortex/tools/parallelism.py +93 -61
  112. vortex/tools/prestaging.py +55 -31
  113. vortex/tools/schedulers.py +172 -105
  114. vortex/tools/services.py +402 -333
  115. vortex/tools/storage.py +293 -358
  116. vortex/tools/surfex.py +24 -24
  117. vortex/tools/systems.py +1211 -631
  118. vortex/tools/targets.py +156 -100
  119. vortex/util/__init__.py +1 -1
  120. vortex/util/config.py +377 -327
  121. vortex/util/empty.py +2 -2
  122. vortex/util/helpers.py +56 -24
  123. vortex/util/introspection.py +18 -12
  124. vortex/util/iosponge.py +8 -4
  125. vortex/util/roles.py +4 -6
  126. vortex/util/storefunctions.py +39 -13
  127. vortex/util/structs.py +3 -3
  128. vortex/util/worker.py +29 -17
  129. vortex_nwp-2.0.0b2.dist-info/METADATA +66 -0
  130. vortex_nwp-2.0.0b2.dist-info/RECORD +142 -0
  131. {vortex_nwp-2.0.0b1.dist-info → vortex_nwp-2.0.0b2.dist-info}/WHEEL +1 -1
  132. vortex/layout/appconf.py +0 -109
  133. vortex/layout/jobs.py +0 -1276
  134. vortex/layout/nodes.py +0 -1424
  135. vortex/layout/subjobs.py +0 -464
  136. vortex_nwp-2.0.0b1.dist-info/METADATA +0 -50
  137. vortex_nwp-2.0.0b1.dist-info/RECORD +0 -146
  138. {vortex_nwp-2.0.0b1.dist-info → vortex_nwp-2.0.0b2.dist-info}/LICENSE +0 -0
  139. {vortex_nwp-2.0.0b1.dist-info → vortex_nwp-2.0.0b2.dist-info}/top_level.txt +0 -0
vortex/tools/net.py CHANGED
@@ -44,28 +44,28 @@ def uriparse(uristring):
44
44
  * username
45
45
  * password
46
46
  """
47
- (realscheme, other) = uristring.split(':', 1)
48
- rp = urlparse.urlparse('http:' + other)
47
+ (realscheme, other) = uristring.split(":", 1)
48
+ rp = urlparse.urlparse("http:" + other)
49
49
  uridict = rp._asdict()
50
- netloc = uridict['netloc'].split('@', 1)
51
- hostport = netloc.pop().split(':')
52
- uridict['netloc'] = hostport.pop(0)
50
+ netloc = uridict["netloc"].split("@", 1)
51
+ hostport = netloc.pop().split(":")
52
+ uridict["netloc"] = hostport.pop(0)
53
53
  if hostport:
54
- uridict['port'] = hostport.pop()
54
+ uridict["port"] = hostport.pop()
55
55
  else:
56
- uridict['port'] = None
56
+ uridict["port"] = None
57
57
  if netloc:
58
- userpass = netloc.pop().split(':')
59
- uridict['username'] = userpass.pop(0)
58
+ userpass = netloc.pop().split(":")
59
+ uridict["username"] = userpass.pop(0)
60
60
  if userpass:
61
- uridict['password'] = userpass.pop()
61
+ uridict["password"] = userpass.pop()
62
62
  else:
63
- uridict['password'] = None
63
+ uridict["password"] = None
64
64
  else:
65
- uridict['username'] = None
66
- uridict['password'] = None
67
- uridict['scheme'] = realscheme
68
- uridict['query'] = urlparse.parse_qs(uridict['query'])
65
+ uridict["username"] = None
66
+ uridict["password"] = None
67
+ uridict["scheme"] = realscheme
68
+ uridict["query"] = urlparse.parse_qs(uridict["query"])
69
69
  return uridict
70
70
 
71
71
 
@@ -74,20 +74,24 @@ def uriunparse(uridesc):
74
74
  return urlparse.urlunparse(uridesc)
75
75
 
76
76
 
77
- def http_post_data(url, data, ok_statuses=(), proxies=None, headers=None, verify=None):
77
+ def http_post_data(
78
+ url, data, ok_statuses=(), proxies=None, headers=None, verify=None
79
+ ):
78
80
  """Make a http/https POST request, encoding **data**."""
79
81
  if isinstance(proxies, (list, tuple)):
80
- proxies = {scheme: proxies for scheme in ('http', 'https')}
82
+ proxies = {scheme: proxies for scheme in ("http", "https")}
81
83
  # Try to use the requests package
82
84
  try:
83
85
  import requests
86
+
84
87
  use_requests = True
85
88
  except ImportError:
86
89
  use_requests = False
87
90
  # The modern way
88
91
  if use_requests:
89
- resp = requests.post(url=url, data=data, headers=headers,
90
- proxies=proxies, verify=verify)
92
+ resp = requests.post(
93
+ url=url, data=data, headers=headers, proxies=proxies, verify=verify
94
+ )
91
95
  if ok_statuses:
92
96
  is_ok = resp.status_code in ok_statuses
93
97
  else:
@@ -95,16 +99,20 @@ def http_post_data(url, data, ok_statuses=(), proxies=None, headers=None, verify
95
99
  return is_ok, resp.status_code, resp.headers, resp.text
96
100
  else:
97
101
  if not isinstance(data, bytes):
98
- data = urlparse.urlencode(data).encode('utf-8')
99
- if uriparse(url)['scheme'] == 'https':
100
- raise RuntimeError('HTTPS is not properly supported by urllib.request ({}).'
101
- .format(url))
102
+ data = urlparse.urlencode(data).encode("utf-8")
103
+ if uriparse(url)["scheme"] == "https":
104
+ raise RuntimeError(
105
+ "HTTPS is not properly supported by urllib.request ({}).".format(
106
+ url
107
+ )
108
+ )
102
109
  handlers = []
103
110
  if isinstance(proxies, dict):
104
111
  handlers.append(urlrequest.ProxyHandler(proxies))
105
- opener = urlrequest.build_opener(* handlers)
106
- req = urlrequest.Request(url=url, data=data,
107
- headers={} if headers is None else headers)
112
+ opener = urlrequest.build_opener(*handlers)
113
+ req = urlrequest.Request(
114
+ url=url, data=data, headers={} if headers is None else headers
115
+ )
108
116
  try:
109
117
  req_f = opener.open(req)
110
118
  except Exception as e:
@@ -116,7 +124,7 @@ def http_post_data(url, data, ok_statuses=(), proxies=None, headers=None, verify
116
124
  try:
117
125
  req_rc = req_f.getcode()
118
126
  req_info = req_f.info()
119
- req_data = req_f.read().decode('utf-8')
127
+ req_data = req_f.read().decode("utf-8")
120
128
  if ok_statuses:
121
129
  return req_rc in ok_statuses, req_rc, req_info, req_data
122
130
  else:
@@ -146,21 +154,21 @@ def netrc_lookup(logname, hostname, nrcfile=None):
146
154
  auth = nrc.authenticators(hostname, login=logname)
147
155
  if not auth:
148
156
  # self.host may be a FQDN, try to guess only the hostname
149
- auth = nrc.authenticators(hostname.split('.')[0], login=logname)
157
+ auth = nrc.authenticators(hostname.split(".")[0], login=logname)
150
158
  # for backward compatibility: This might be removed one day
151
159
  if not auth:
152
160
  auth = nrc.authenticators(hostname)
153
161
  if not auth:
154
162
  # self.host may be a FQDN, try to guess only the hostname
155
- auth = nrc.authenticators(hostname.split('.')[0])
163
+ auth = nrc.authenticators(hostname.split(".")[0])
156
164
  # End of backward compatibility section
157
165
  if auth:
158
166
  actual_logname = auth[0]
159
167
  actual_pwd = auth[2]
160
168
  else:
161
- logger.warning('netrc lookup failed (%s)', str(auth))
169
+ logger.warning("netrc lookup failed (%s)", str(auth))
162
170
  else:
163
- logger.warning('unable to fetch .netrc file')
171
+ logger.warning("unable to fetch .netrc file")
164
172
  return actual_logname, actual_pwd
165
173
 
166
174
 
@@ -170,7 +178,7 @@ class ExtendedFtplib:
170
178
  It wraps the standard ftplib object to add or overwrite methods.
171
179
  """
172
180
 
173
- def __init__(self, system, ftpobj, hostname='', port=DEFAULT_FTP_PORT):
181
+ def __init__(self, system, ftpobj, hostname="", port=DEFAULT_FTP_PORT):
174
182
  """
175
183
  :param ~vortex.tools.systems.OSExtended system: The system object to work with
176
184
  :param ftplib.FTP ftpobj: The FTP object to work with / to extend
@@ -178,7 +186,7 @@ class ExtendedFtplib:
178
186
  self._system = system
179
187
  self._ftplib = ftpobj
180
188
  self._closed = True
181
- self._logname = 'not_logged_in'
189
+ self._logname = "not_logged_in"
182
190
  self._created = datetime.now()
183
191
  self._opened = None
184
192
  self._deleted = None
@@ -200,8 +208,8 @@ class ExtendedFtplib:
200
208
  Nicely formatted print, built as the concatenation
201
209
  of the class full name and `logname` and `length` attributes.
202
210
  """
203
- return '{:s} | host={:s} logname={:s} since={!s}>'.format(
204
- repr(self).rstrip('>'),
211
+ return "{:s} | host={:s} logname={:s} since={!s}>".format(
212
+ repr(self).rstrip(">"),
205
213
  self.host,
206
214
  self.logname,
207
215
  self.length,
@@ -212,10 +220,11 @@ class ExtendedFtplib:
212
220
  """Gateway to undefined method or attributes if present in ``_ftplib``."""
213
221
  actualattr = getattr(self._ftplib, key)
214
222
  if callable(actualattr):
223
+
215
224
  def osproxy(*args, **kw):
216
225
  cmd = [key]
217
226
  cmd.extend(args)
218
- cmd.extend(['{:s}={!s}'.format(x, kw[x]) for x in kw.keys()])
227
+ cmd.extend(["{:s}={!s}".format(x, kw[x]) for x in kw.keys()])
219
228
  self.stderr(*cmd)
220
229
  return actualattr(*args, **kw)
221
230
 
@@ -234,7 +243,7 @@ class ExtendedFtplib:
234
243
 
235
244
  def stderr(self, cmd, *args):
236
245
  """Proxy to local system's standard error."""
237
- self.system.stderr('ftp:' + cmd, *args)
246
+ self.system.stderr("ftp:" + cmd, *args)
238
247
 
239
248
  @property
240
249
  def closed(self):
@@ -254,12 +263,14 @@ class ExtendedFtplib:
254
263
  topnow = datetime.now() if self._deleted is None else self._deleted
255
264
  timelength = (topnow - self._opened).total_seconds()
256
265
  except TypeError:
257
- logger.warning('Could not evaluate connexion length %s', repr(self))
266
+ logger.warning(
267
+ "Could not evaluate connexion length %s", repr(self)
268
+ )
258
269
  return timelength
259
270
 
260
271
  def close(self):
261
272
  """Proxy to ftplib :meth:`ftplib.FTP.close`."""
262
- self.stderr('close')
273
+ self.stderr("close")
263
274
  rc = True
264
275
  if not self.closed:
265
276
  rc = self._ftplib.close() or True
@@ -269,7 +280,7 @@ class ExtendedFtplib:
269
280
 
270
281
  def login(self, *args):
271
282
  """Proxy to ftplib :meth:`ftplib.FTP.login`."""
272
- self.stderr('login', args[0])
283
+ self.stderr("login", args[0])
273
284
  self._logname = args[0]
274
285
  # kept for debugging, but this exposes the user's password!
275
286
  # logger.debug('FTP login <args:%s>', str(args))
@@ -279,48 +290,48 @@ class ExtendedFtplib:
279
290
  self._deleted = None
280
291
  self._opened = datetime.now()
281
292
  else:
282
- logger.warning('FTP could not login <args:%s>', str(args))
293
+ logger.warning("FTP could not login <args:%s>", str(args))
283
294
  return rc
284
295
 
285
296
  def list(self, *args):
286
297
  """Returns standard directory listing from ftp protocol."""
287
- self.stderr('list', *args)
298
+ self.stderr("list", *args)
288
299
  contents = []
289
- self.retrlines('LIST', callback=contents.append)
300
+ self.retrlines("LIST", callback=contents.append)
290
301
  return contents
291
302
 
292
303
  def dir(self, *args):
293
304
  """Proxy to ftplib :meth:`ftplib.FTP.dir`."""
294
- self.stderr('dir', *args)
305
+ self.stderr("dir", *args)
295
306
  return self._ftplib.dir(*args)
296
307
 
297
308
  def ls(self, *args):
298
309
  """Returns directory listing."""
299
- self.stderr('ls', *args)
310
+ self.stderr("ls", *args)
300
311
  return self.dir(*args)
301
312
 
302
313
  ll = ls
303
314
 
304
315
  def get(self, source, destination):
305
316
  """Retrieve a remote `destination` file to a local `source` file object."""
306
- self.stderr('get', source, destination)
317
+ self.stderr("get", source, destination)
307
318
  if isinstance(destination, str):
308
319
  self.system.filecocoon(destination)
309
- target = open(destination, 'wb')
320
+ target = open(destination, "wb")
310
321
  xdestination = True
311
322
  else:
312
323
  target = destination
313
324
  xdestination = False
314
- logger.info('FTP <get:{:s}>'.format(source))
325
+ logger.info("FTP <get:{:s}>".format(source))
315
326
  rc = False
316
327
  try:
317
- self.retrbinary('RETR ' + source, target.write)
328
+ self.retrbinary("RETR " + source, target.write)
318
329
  if xdestination:
319
330
  target.seek(0, io.SEEK_END)
320
331
  if self.size(source) == target.tell():
321
332
  rc = True
322
333
  else:
323
- logger.error('FTP incomplete get %s', repr(source))
334
+ logger.error("FTP incomplete get %s", repr(source))
324
335
  else:
325
336
  rc = True
326
337
  finally:
@@ -341,9 +352,9 @@ class ExtendedFtplib:
341
352
  When `exact` is True, the size is checked against the size of the
342
353
  destination, and a mismatch is considered a failure.
343
354
  """
344
- self.stderr('put', source, destination)
355
+ self.stderr("put", source, destination)
345
356
  if isinstance(source, str):
346
- inputsrc = open(source, 'rb')
357
+ inputsrc = open(source, "rb")
347
358
  xsource = True
348
359
  else:
349
360
  inputsrc = source
@@ -354,43 +365,60 @@ class ExtendedFtplib:
354
365
  exact = True
355
366
  inputsrc.seek(0)
356
367
  except AttributeError:
357
- logger.warning('Could not rewind <source:%s>', str(source))
368
+ logger.warning("Could not rewind <source:%s>", str(source))
358
369
  except OSError:
359
- logger.debug('Seek trouble <source:%s>', str(source))
370
+ logger.debug("Seek trouble <source:%s>", str(source))
360
371
 
361
372
  self.rmkdir(destination)
362
373
  try:
363
374
  self.delete(destination)
364
- logger.info('Replacing <file:%s>', str(destination))
375
+ logger.info("Replacing <file:%s>", str(destination))
365
376
  except ftplib.error_perm:
366
- logger.info('Creating <file:%s>', str(destination))
367
- except (ValueError, TypeError, OSError,
368
- ftplib.error_proto, ftplib.error_reply, ftplib.error_temp) as e:
369
- logger.error('Serious delete trouble <file:%s> <error:%s>',
370
- str(destination), str(e))
371
-
372
- logger.info('FTP <put:%s>', str(destination))
377
+ logger.info("Creating <file:%s>", str(destination))
378
+ except (
379
+ ValueError,
380
+ TypeError,
381
+ OSError,
382
+ ftplib.error_proto,
383
+ ftplib.error_reply,
384
+ ftplib.error_temp,
385
+ ) as e:
386
+ logger.error(
387
+ "Serious delete trouble <file:%s> <error:%s>",
388
+ str(destination),
389
+ str(e),
390
+ )
391
+
392
+ logger.info("FTP <put:%s>", str(destination))
373
393
  rc = False
374
394
 
375
395
  if size is not None:
376
396
  try:
377
- self.voidcmd('ALLO {:d}'.format(size))
397
+ self.voidcmd("ALLO {:d}".format(size))
378
398
  except ftplib.error_perm:
379
399
  pass
380
400
 
381
401
  try:
382
- self.storbinary('STOR ' + destination, inputsrc)
402
+ self.storbinary("STOR " + destination, inputsrc)
383
403
  if exact:
384
404
  if self.size(destination) == size:
385
405
  rc = True
386
406
  else:
387
- logger.error('FTP incomplete put %s (%d / %d bytes)', repr(source),
388
- self.size(destination), size)
407
+ logger.error(
408
+ "FTP incomplete put %s (%d / %d bytes)",
409
+ repr(source),
410
+ self.size(destination),
411
+ size,
412
+ )
389
413
  else:
390
414
  rc = True
391
415
  if self.size(destination) != size:
392
- logger.info('FTP put %s: estimated %s bytes, real %s bytes',
393
- repr(source), str(size), self.size(destination))
416
+ logger.info(
417
+ "FTP put %s: estimated %s bytes, real %s bytes",
418
+ repr(source),
419
+ str(size),
420
+ self.size(destination),
421
+ )
394
422
  finally:
395
423
  if xsource:
396
424
  inputsrc.close()
@@ -398,29 +426,29 @@ class ExtendedFtplib:
398
426
 
399
427
  def rmkdir(self, destination):
400
428
  """Recursive directory creation (mimics `mkdir -p`)."""
401
- self.stderr('rmkdir', destination)
429
+ self.stderr("rmkdir", destination)
402
430
  origin = self.pwd()
403
- if destination.startswith('/'):
404
- path_pre = '/'
405
- elif destination.startswith('~'):
406
- path_pre = ''
431
+ if destination.startswith("/"):
432
+ path_pre = "/"
433
+ elif destination.startswith("~"):
434
+ path_pre = ""
407
435
  else:
408
- path_pre = origin + '/'
436
+ path_pre = origin + "/"
409
437
 
410
- for subdir in self.system.path.dirname(destination).split('/'):
438
+ for subdir in self.system.path.dirname(destination).split("/"):
411
439
  current = path_pre + subdir
412
440
  try:
413
441
  self.cwd(current)
414
- path_pre = current + '/'
442
+ path_pre = current + "/"
415
443
  except ftplib.error_perm:
416
- self.stderr('mkdir', current)
444
+ self.stderr("mkdir", current)
417
445
  try:
418
446
  self.mkd(current)
419
447
  except ftplib.error_perm as errmkd:
420
- if 'File exists' not in str(errmkd):
448
+ if "File exists" not in str(errmkd):
421
449
  raise
422
450
  self.cwd(current)
423
- path_pre = current + '/'
451
+ path_pre = current + "/"
424
452
  self.cwd(origin)
425
453
 
426
454
  def cd(self, destination):
@@ -433,16 +461,16 @@ class ExtendedFtplib:
433
461
 
434
462
  def mtime(self, filename):
435
463
  """Retrieve the modification time of a file."""
436
- resp = self.sendcmd('MDTM ' + filename)
437
- if resp[:3] == '213':
464
+ resp = self.sendcmd("MDTM " + filename)
465
+ if resp[:3] == "213":
438
466
  s = resp[3:].strip().split()[-1]
439
467
  return int(s)
440
468
 
441
469
  def size(self, filename):
442
470
  """Retrieve the size of a file."""
443
471
  # The SIZE command is defined in RFC-3659
444
- resp = self.sendcmd('SIZE ' + filename)
445
- if resp[:3] == '213':
472
+ resp = self.sendcmd("SIZE " + filename)
473
+ if resp[:3] == "213":
446
474
  s = resp[3:].strip().split()[-1]
447
475
  return int(s)
448
476
 
@@ -465,11 +493,23 @@ class StdFtp:
465
493
  :class:`ExtendedFtplib` and :class:`ftplib.FTP` class).
466
494
  """
467
495
 
468
- _PROXY_TYPES = ('no-auth-logname-based', )
469
-
470
- _NO_AUTOLOGIN = ('set_debuglevel', 'connect', 'login', 'stderr',)
471
-
472
- def __init__(self, system, hostname, port=DEFAULT_FTP_PORT, nrcfile=None, ignoreproxy=False):
496
+ _PROXY_TYPES = ("no-auth-logname-based",)
497
+
498
+ _NO_AUTOLOGIN = (
499
+ "set_debuglevel",
500
+ "connect",
501
+ "login",
502
+ "stderr",
503
+ )
504
+
505
+ def __init__(
506
+ self,
507
+ system,
508
+ hostname,
509
+ port=DEFAULT_FTP_PORT,
510
+ nrcfile=None,
511
+ ignoreproxy=False,
512
+ ):
473
513
  """
474
514
  :param ~vortex.tools.systems.OSExtended system: The system object to work with
475
515
  :param str hostname: The remote host's network name
@@ -477,12 +517,18 @@ class StdFtp:
477
517
  :param str nrcfile: The path to the .netrc file (if `None` the ~/.netrc default is used)
478
518
  :param bool ignoreproxy: Forcibly ignore any proxy related environment variables
479
519
  """
480
- logger.debug('FTP init <host:%s>', hostname)
520
+ logger.debug("FTP init <host:%s>", hostname)
481
521
  self._system = system
482
522
  if ignoreproxy:
483
- self._proxy_host, self._proxy_port, self._proxy_type = (None, None, None)
523
+ self._proxy_host, self._proxy_port, self._proxy_type = (
524
+ None,
525
+ None,
526
+ None,
527
+ )
484
528
  else:
485
- self._proxy_host, self._proxy_port, self._proxy_type = self._proxy_init()
529
+ self._proxy_host, self._proxy_port, self._proxy_type = (
530
+ self._proxy_init()
531
+ )
486
532
  self._hostname = hostname
487
533
  self._port = port
488
534
  self._nrcfile = nrcfile
@@ -495,18 +541,22 @@ class StdFtp:
495
541
  """Return the proxy type, address and port."""
496
542
  p_netloc = (None, None)
497
543
  p_url = self.system.env.get(
498
- 'VORTEX_FTP_PROXY', self.system.env.get('FTP_PROXY', None)
544
+ "VORTEX_FTP_PROXY", self.system.env.get("FTP_PROXY", None)
499
545
  )
500
546
  if p_url:
501
- p_netloc = p_url.split(':', 1)
547
+ p_netloc = p_url.split(":", 1)
502
548
  if len(p_netloc) == 1:
503
549
  p_netloc.append(DEFAULT_FTP_PORT)
504
550
  else:
505
551
  p_netloc[1] = int(p_netloc[1])
506
- p_type = self.system.env.get('VORTEX_FTP_PROXY_TYPE', self._PROXY_TYPES[0])
552
+ p_type = self.system.env.get(
553
+ "VORTEX_FTP_PROXY_TYPE", self._PROXY_TYPES[0]
554
+ )
507
555
  if p_type not in self._PROXY_TYPES:
508
- raise ValueError('Incorrect value for the VORTEX_FTP_PROXY_TYPE ' +
509
- 'environment variable (got: {:s})'.format(p_type))
556
+ raise ValueError(
557
+ "Incorrect value for the VORTEX_FTP_PROXY_TYPE "
558
+ + "environment variable (got: {:s})".format(p_type)
559
+ )
510
560
  return p_netloc[0], p_netloc[1], p_type
511
561
 
512
562
  def _extended_ftp_host_and_port(self):
@@ -523,9 +573,9 @@ class StdFtp:
523
573
  It is created on-demand.
524
574
  """
525
575
  if self._internal_ftp is None:
526
- self._internal_ftp = ExtendedFtplib(self._system,
527
- ftplib.FTP(),
528
- * self._extended_ftp_host_and_port())
576
+ self._internal_ftp = ExtendedFtplib(
577
+ self._system, ftplib.FTP(), *self._extended_ftp_host_and_port()
578
+ )
529
579
  return self._internal_ftp
530
580
 
531
581
  _loginlike_extended_ftp = _extended_ftp
@@ -559,7 +609,7 @@ class StdFtp:
559
609
  @property
560
610
  def proxy(self):
561
611
  if self._proxy_host:
562
- return '{0._proxy_host}:{0._proxy_port}'.format(self)
612
+ return "{0._proxy_host}:{0._proxy_port}".format(self)
563
613
  else:
564
614
  return None
565
615
 
@@ -570,15 +620,20 @@ class StdFtp:
570
620
 
571
621
  def netpath(self, remote):
572
622
  """The complete qualified net path of the remote resource."""
573
- return '{:s}@{:s}:{:s}'.format(self.logname if self.logname is not None else 'unknown',
574
- self.host, remote)
623
+ return "{:s}@{:s}:{:s}".format(
624
+ self.logname if self.logname is not None else "unknown",
625
+ self.host,
626
+ remote,
627
+ )
575
628
 
576
629
  def delayedlogin(self):
577
630
  """Login to the FTP server (if it was not already done)."""
578
631
  if self._loginlike_extended_ftp.closed:
579
632
  if self._logname is None or self.cached_pwd is None:
580
- logger.warning('FTP logname/password must be set first. Use the fastlogin method.')
581
- raise RuntimeError('logname/password were not provided')
633
+ logger.warning(
634
+ "FTP logname/password must be set first. Use the fastlogin method."
635
+ )
636
+ raise RuntimeError("logname/password were not provided")
582
637
  return self.login(self._logname, self.cached_pwd)
583
638
  else:
584
639
  return True
@@ -588,11 +643,15 @@ class StdFtp:
588
643
  if logname and password:
589
644
  bare_logname = logname
590
645
  else:
591
- bare_logname, password = netrc_lookup(logname, self.host, nrcfile=self._nrcfile)
646
+ bare_logname, password = netrc_lookup(
647
+ logname, self.host, nrcfile=self._nrcfile
648
+ )
592
649
  logname = bare_logname
593
650
  if logname and self._proxy_host:
594
651
  if self._proxy_type == self._PROXY_TYPES[0]:
595
- logname = '{0:s}@{1.host:s}:{1.port:d}'.format(bare_logname, self)
652
+ logname = "{0:s}@{1.host:s}:{1.port:d}".format(
653
+ bare_logname, self
654
+ )
596
655
  if logname:
597
656
  return logname, password, bare_logname
598
657
  else:
@@ -614,7 +673,9 @@ class StdFtp:
614
673
  necessary).
615
674
  """
616
675
  rc = False
617
- p_logname, p_password, p_barelogname = self._process_logname_password(logname, password)
676
+ p_logname, p_password, p_barelogname = self._process_logname_password(
677
+ logname, password
678
+ )
618
679
  if p_logname and p_password:
619
680
  self._logname = p_logname
620
681
  self._cached_pwd = p_password
@@ -627,7 +688,7 @@ class StdFtp:
627
688
 
628
689
  def _extended_ftp_lookup_check(self, key):
629
690
  """Are we allowed to look for *key* in the `self._extended_ftp` object ?"""
630
- return not key.startswith('_')
691
+ return not key.startswith("_")
631
692
 
632
693
  def _extended_ftp_lookup(self, key):
633
694
  """Look if the `self._extended_ftp` object can provide a given method.
@@ -637,6 +698,7 @@ class StdFtp:
637
698
  """
638
699
  actualattr = getattr(self._extended_ftp, key)
639
700
  if callable(actualattr):
701
+
640
702
  def osproxy(*args, **kw):
641
703
  # For most of the native commands, we want autologin to be performed
642
704
  if key not in self._NO_AUTOLOGIN:
@@ -679,9 +741,20 @@ class AutoRetriesFtp(StdFtp):
679
741
  the retry-on-failure capability.
680
742
  """
681
743
 
682
- def __init__(self, system, hostname, port=DEFAULT_FTP_PORT, nrcfile=None, ignoreproxy=False,
683
- retrycount_default=6, retrycount_connect=8, retrycount_login=3,
684
- retrydelay_default=15, retrydelay_connect=15, retrydelay_login=10):
744
+ def __init__(
745
+ self,
746
+ system,
747
+ hostname,
748
+ port=DEFAULT_FTP_PORT,
749
+ nrcfile=None,
750
+ ignoreproxy=False,
751
+ retrycount_default=6,
752
+ retrycount_connect=8,
753
+ retrycount_login=3,
754
+ retrydelay_default=15,
755
+ retrydelay_connect=15,
756
+ retrydelay_login=10,
757
+ ):
685
758
  """
686
759
  :param ~vortex.tools.systems.OSExtended system: The system object to work with.
687
760
  :param str hostname: The remote host's network name.
@@ -695,7 +768,7 @@ class AutoRetriesFtp(StdFtp):
695
768
  :param int retrycount_login: The maximum number of retries when login in to the FTP server.
696
769
  :param int retrydelay_login: The delay (in seconds) between two retries when login in to the FTP server.
697
770
  """
698
- logger.debug('AutoRetries FTP init <host:%s>', hostname)
771
+ logger.debug("AutoRetries FTP init <host:%s>", hostname)
699
772
  # Retry stuff
700
773
  self.retrycount_default = retrycount_default
701
774
  self.retrycount_connect = retrycount_connect
@@ -706,11 +779,17 @@ class AutoRetriesFtp(StdFtp):
706
779
  # Reset everything
707
780
  self._initialise()
708
781
  # Finalise
709
- super().__init__(system, hostname, port=port, nrcfile=nrcfile, ignoreproxy=ignoreproxy)
782
+ super().__init__(
783
+ system,
784
+ hostname,
785
+ port=port,
786
+ nrcfile=nrcfile,
787
+ ignoreproxy=ignoreproxy,
788
+ )
710
789
 
711
790
  def _initialise(self):
712
791
  self._internal_retries_max = None
713
- self._cwd = ''
792
+ self._cwd = ""
714
793
  self._autodestroy()
715
794
 
716
795
  def _autodestroy(self):
@@ -720,25 +799,39 @@ class AutoRetriesFtp(StdFtp):
720
799
  def _get_extended_ftp(self, retrycount, retrydelay, exceptions_extras):
721
800
  """Delay the call to 'connect' as much as possible."""
722
801
  if self._internal_ftp is None:
723
- eftplib = self._retry_wrapped_callable(ExtendedFtplib,
724
- retrycount=retrycount,
725
- retrydelay=retrydelay,
726
- exceptions_extras=exceptions_extras)
727
- self._internal_ftp = eftplib(self._system, ftplib.FTP(),
728
- * self._extended_ftp_host_and_port())
802
+ eftplib = self._retry_wrapped_callable(
803
+ ExtendedFtplib,
804
+ retrycount=retrycount,
805
+ retrydelay=retrydelay,
806
+ exceptions_extras=exceptions_extras,
807
+ )
808
+ self._internal_ftp = eftplib(
809
+ self._system, ftplib.FTP(), *self._extended_ftp_host_and_port()
810
+ )
729
811
  return self._internal_ftp
730
812
 
731
813
  @property
732
814
  def _extended_ftp(self):
733
815
  """Delay the call to 'connect' as much as possible."""
734
- return self._get_extended_ftp(self.retrycount_connect, self.retrydelay_connect,
735
- [socket.timeout, ])
816
+ return self._get_extended_ftp(
817
+ self.retrycount_connect,
818
+ self.retrydelay_connect,
819
+ [
820
+ socket.timeout,
821
+ ],
822
+ )
736
823
 
737
824
  @property
738
825
  def _loginlike_extended_ftp(self):
739
826
  """Delay the call to 'connect' as much as possible."""
740
- return self._get_extended_ftp(self.retrycount_login, self.retrydelay_login,
741
- [ftplib.error_perm, socket.error, ])
827
+ return self._get_extended_ftp(
828
+ self.retrycount_login,
829
+ self.retrydelay_login,
830
+ [
831
+ ftplib.error_perm,
832
+ socket.error,
833
+ ],
834
+ )
742
835
 
743
836
  def _actual_login(self, *args):
744
837
  """Actually log in + save logname/password + correct the cwd if needed."""
@@ -750,22 +843,23 @@ class AutoRetriesFtp(StdFtp):
750
843
  self._cached_pwd = args[1]
751
844
  if rc and self._cwd:
752
845
  cocoondir = self._cwd
753
- self._cwd = ''
846
+ self._cwd = ""
754
847
  rc = rc and self.cwd(cocoondir)
755
848
  return rc
756
849
 
757
850
  def login(self, *args):
758
851
  """Proxy to ftplib :meth:`ftplib.FTP.login`."""
759
- wftplogin = self._retry_wrapped_callable(self._actual_login,
760
- retrycount=self.retrycount_login,
761
- retrydelay=self.retrydelay_login,
762
- exceptions_extras=[ftplib.error_perm,
763
- socket.error,
764
- EOFError])
852
+ wftplogin = self._retry_wrapped_callable(
853
+ self._actual_login,
854
+ retrycount=self.retrycount_login,
855
+ retrydelay=self.retrydelay_login,
856
+ exceptions_extras=[ftplib.error_perm, socket.error, EOFError],
857
+ )
765
858
  return wftplogin(*args)
766
859
 
767
- def _retry_wrapped_callable(self, func, retrycount=None, retrydelay=None,
768
- exceptions_extras=None):
860
+ def _retry_wrapped_callable(
861
+ self, func, retrycount=None, retrydelay=None, exceptions_extras=None
862
+ ):
769
863
  """
770
864
  Wraps the *func* function in order to implement a retry on failure
771
865
  mechanism.
@@ -782,7 +876,11 @@ class AutoRetriesFtp(StdFtp):
782
876
  """
783
877
  actual_rcount = retrycount or self.retrycount_default
784
878
  actual_rdelay = retrydelay or self.retrydelay_default
785
- actual_exc = [ftplib.error_temp, ftplib.error_proto, ftplib.error_reply, ]
879
+ actual_exc = [
880
+ ftplib.error_temp,
881
+ ftplib.error_proto,
882
+ ftplib.error_reply,
883
+ ]
786
884
  if exceptions_extras:
787
885
  actual_exc.extend(exceptions_extras)
788
886
  actual_exc = tuple(actual_exc)
@@ -791,22 +889,29 @@ class AutoRetriesFtp(StdFtp):
791
889
  globalcounter_driver = self._internal_retries_max is None
792
890
  if globalcounter_driver:
793
891
  self._internal_retries_max = actual_rcount
794
- retriesleft = max(min(self._internal_retries_max, actual_rcount), 1)
892
+ retriesleft = max(
893
+ min(self._internal_retries_max, actual_rcount), 1
894
+ )
795
895
  try:
796
896
  while retriesleft:
797
897
  try:
798
898
  return func(*args, **kw)
799
899
  except actual_exc as e:
800
- logger.warning('An error occurred (in "%s"): %s',
801
- func.__name__, e)
900
+ logger.warning(
901
+ 'An error occurred (in "%s"): %s', func.__name__, e
902
+ )
802
903
  retriesleft -= 1
803
904
  self._internal_retries_max -= 1
804
905
  if not retriesleft:
805
- logger.warning('The maximum number of retries (%d) was reached.',
806
- actual_rcount)
906
+ logger.warning(
907
+ "The maximum number of retries (%d) was reached.",
908
+ actual_rcount,
909
+ )
807
910
  raise
808
- logger.warning('Sleeping %d sec. before the next attempt.',
809
- actual_rdelay)
911
+ logger.warning(
912
+ "Sleeping %d sec. before the next attempt.",
913
+ actual_rdelay,
914
+ )
810
915
  self._autodestroy()
811
916
  self.system.sleep(actual_rdelay)
812
917
  finally:
@@ -825,15 +930,19 @@ class AutoRetriesFtp(StdFtp):
825
930
  attr = self._extended_ftp_lookup(key)
826
931
  if callable(attr):
827
932
  if key not in self._NO_AUTOLOGIN:
828
- attr = self._retry_wrapped_callable(attr,
829
- exceptions_extras=[socket.error, ])
933
+ attr = self._retry_wrapped_callable(
934
+ attr,
935
+ exceptions_extras=[
936
+ socket.error,
937
+ ],
938
+ )
830
939
  setattr(self, key, attr)
831
940
  return attr
832
941
  raise AttributeError(key)
833
942
 
834
943
  def cwd(self, pathname):
835
944
  """Change the current directory to the *pathname* directory."""
836
- todo = self._retry_wrapped_callable(self._extended_ftp_lookup('cwd'))
945
+ todo = self._retry_wrapped_callable(self._extended_ftp_lookup("cwd"))
837
946
  rc = todo(pathname)
838
947
  if rc:
839
948
  if self.system.path.isabs(pathname):
@@ -850,7 +959,9 @@ class AutoRetriesFtp(StdFtp):
850
959
  def quit(self):
851
960
  """Quit the current ftp session politely."""
852
961
  try:
853
- rc = self._retry_wrapped_callable(self._extended_ftp_lookup('quit'))()
962
+ rc = self._retry_wrapped_callable(
963
+ self._extended_ftp_lookup("quit")
964
+ )()
854
965
  finally:
855
966
  self._initialise()
856
967
  return rc
@@ -885,7 +996,7 @@ class ResetableAutoRetriesFtp(AutoRetriesFtp):
885
996
  def reset(self):
886
997
  """Reset the current working directory to its initial value."""
887
998
  if self._initialpath is not None and self._cwd:
888
- self._cwd = ''
999
+ self._cwd = ""
889
1000
  return self.cwd(self._initialpath)
890
1001
 
891
1002
 
@@ -904,7 +1015,9 @@ class PooledResetableAutoRetriesFtp(ResetableAutoRetriesFtp):
904
1015
  """
905
1016
  self._pool = pool
906
1017
  super().__init__(*kargs, **kwargs)
907
- logger.debug('Pooled FTP init <host:%s> <pool:%s>', self.host, repr(pool))
1018
+ logger.debug(
1019
+ "Pooled FTP init <host:%s> <pool:%s>", self.host, repr(pool)
1020
+ )
908
1021
 
909
1022
  def forceclose(self):
910
1023
  """Really quit the ftp session."""
@@ -965,40 +1078,60 @@ class FtpConnectionPool:
965
1078
 
966
1079
  def __str__(self):
967
1080
  """Print a summary of the connection pool activity."""
968
- out = 'Current connection pool size: {:d}\n'.format(self.poolsize)
969
- out += ' # of created objects: {:d}\n'.format(self._created)
970
- out += ' # of re-used objects: {:d}\n'.format(self._reused)
971
- out += ' # of given back objects: {:d}\n'.format(self._givenback)
1081
+ out = "Current connection pool size: {:d}\n".format(self.poolsize)
1082
+ out += " # of created objects: {:d}\n".format(self._created)
1083
+ out += " # of re-used objects: {:d}\n".format(self._reused)
1084
+ out += " # of given back objects: {:d}\n".format(self._givenback)
972
1085
  if self.poolsize:
973
- out += '\nDetailed list of current spare clients:\n'
1086
+ out += "\nDetailed list of current spare clients:\n"
974
1087
  for ident, hpool in self._reusable.items():
975
1088
  for client in hpool:
976
- out += ' - {id[1]:s}@{id[0]:s}: {cl!r}\n'.format(id=ident, cl=client)
1089
+ out += " - {id[1]:s}@{id[0]:s}: {cl!r}\n".format(
1090
+ id=ident, cl=client
1091
+ )
977
1092
  return out
978
1093
 
979
- def deal(self, hostname, logname, port=DEFAULT_FTP_PORT, delayed=True, ignoreproxy=False):
1094
+ def deal(
1095
+ self,
1096
+ hostname,
1097
+ logname,
1098
+ port=DEFAULT_FTP_PORT,
1099
+ delayed=True,
1100
+ ignoreproxy=False,
1101
+ ):
980
1102
  """Retrieve an FTP client for the *hostname*/*logname* pair."""
981
1103
  p_logname, _ = netrc_lookup(logname, hostname, nrcfile=self._nrcfile)
982
1104
  if self._reusable[(hostname, port, p_logname)]:
983
1105
  ftpc = self._reusable[(hostname, port, p_logname)].pop()
984
1106
  ftpc.reset()
985
- logger.debug('Re-using a client: %s', repr(ftpc))
1107
+ logger.debug("Re-using a client: %s", repr(ftpc))
986
1108
  if not delayed:
987
1109
  # If requested, ensure that we are logged in
988
1110
  ftpc.delayedlogin()
989
1111
  self._reused += 1
990
1112
  return ftpc
991
1113
  else:
992
- ftpc = self._FTPCLIENT_CLASS(self, self._system, hostname,
993
- port=port, nrcfile=self._nrcfile,
994
- ignoreproxy=self._ignoreproxy)
1114
+ ftpc = self._FTPCLIENT_CLASS(
1115
+ self,
1116
+ self._system,
1117
+ hostname,
1118
+ port=port,
1119
+ nrcfile=self._nrcfile,
1120
+ ignoreproxy=self._ignoreproxy,
1121
+ )
995
1122
  rc = ftpc.fastlogin(p_logname, delayed=delayed)
996
1123
  if rc:
997
- logger.debug('Creating a new client: %s', repr(ftpc))
1124
+ logger.debug("Creating a new client: %s", repr(ftpc))
998
1125
  self._created += 1
999
1126
  return ftpc
1000
1127
  else:
1001
- logger.warning('Could not login on %s:%d as %s [%s]', hostname, port, p_logname, str(rc))
1128
+ logger.warning(
1129
+ "Could not login on %s:%d as %s [%s]",
1130
+ hostname,
1131
+ port,
1132
+ p_logname,
1133
+ str(rc),
1134
+ )
1002
1135
  return None
1003
1136
 
1004
1137
  def relinquishing(self, client):
@@ -1010,19 +1143,32 @@ class FtpConnectionPool:
1010
1143
  its `close` method is called.
1011
1144
  """
1012
1145
  assert isinstance(client, self._FTPCLIENT_CLASS)
1013
- self._reusable[(client.host, client.port, client.logname)].append(client)
1146
+ self._reusable[(client.host, client.port, client.logname)].append(
1147
+ client
1148
+ )
1014
1149
  self._givenback += 1
1015
- logger.debug("Spare client for %s@%s:%d has been stored (poolsize=%d).",
1016
- client.logname, client.host, client.port, self.poolsize)
1150
+ logger.debug(
1151
+ "Spare client for %s@%s:%d has been stored (poolsize=%d).",
1152
+ client.logname,
1153
+ client.host,
1154
+ client.port,
1155
+ self.poolsize,
1156
+ )
1017
1157
  if self.poolsize >= self._REUSABLE_THRESHOLD:
1018
- logger.warning('The FTP pool is too big ! (%d >= %d). Here are the details:\n%s',
1019
- self.poolsize, self._REUSABLE_THRESHOLD, str(self))
1158
+ logger.warning(
1159
+ "The FTP pool is too big ! (%d >= %d). Here are the details:\n%s",
1160
+ self.poolsize,
1161
+ self._REUSABLE_THRESHOLD,
1162
+ str(self),
1163
+ )
1020
1164
 
1021
1165
  def clear(self):
1022
1166
  """Destroy all the spare FTP clients."""
1023
1167
  for hpool in self._reusable.values():
1024
1168
  for client in hpool:
1025
- logger.debug("Destroying client for %s@%s", client.logname, client.host)
1169
+ logger.debug(
1170
+ "Destroying client for %s@%s", client.logname, client.host
1171
+ )
1026
1172
  client.forceclose()
1027
1173
  hpool.clear()
1028
1174
 
@@ -1047,14 +1193,16 @@ class Ssh:
1047
1193
  self._remote = hostname
1048
1194
 
1049
1195
  target = sh.default_target
1050
- self._sshcmd = target.get(key='services:sshcmd', default='ssh')
1051
- self._scpcmd = target.get(key='services:scpcmd', default='scp')
1196
+ self._sshcmd = target.get(key="services:sshcmd", default="ssh")
1197
+ self._scpcmd = target.get(key="services:scpcmd", default="scp")
1052
1198
  self._sshopts = (
1053
- target.get(key='services:sshopts', default='-x').split() +
1054
- (sshopts or '').split())
1199
+ target.get(key="services:sshopts", default="-x").split()
1200
+ + (sshopts or "").split()
1201
+ )
1055
1202
  self._scpopts = (
1056
- target.get(key='services:scpopts', default='-Bp').split() +
1057
- (scpopts or '').split())
1203
+ target.get(key="services:scpopts", default="-Bp").split()
1204
+ + (scpopts or "").split()
1205
+ )
1058
1206
 
1059
1207
  @property
1060
1208
  def sh(self):
@@ -1062,13 +1210,15 @@ class Ssh:
1062
1210
 
1063
1211
  @property
1064
1212
  def remote(self):
1065
- return ('' if self._logname is None else self._logname + '@') + self._remote
1213
+ return (
1214
+ "" if self._logname is None else self._logname + "@"
1215
+ ) + self._remote
1066
1216
 
1067
1217
  def check_ok(self):
1068
1218
  """Is the connexion ok ?"""
1069
- return self.execute('true') is not False
1219
+ return self.execute("true") is not False
1070
1220
 
1071
- def execute(self, remote_command, sshopts=''):
1221
+ def execute(self, remote_command, sshopts=""):
1072
1222
  """Execute the command remotely.
1073
1223
 
1074
1224
  Return the output of the command (list of lines), or False on error.
@@ -1083,12 +1233,24 @@ class Ssh:
1083
1233
  myremote = self.remote
1084
1234
  if myremote is None:
1085
1235
  return False
1086
- cmd = ([self._sshcmd, ] +
1087
- self._sshopts + sshopts.split() +
1088
- [myremote, ] + [remote_command, ])
1236
+ cmd = (
1237
+ [
1238
+ self._sshcmd,
1239
+ ]
1240
+ + self._sshopts
1241
+ + sshopts.split()
1242
+ + [
1243
+ myremote,
1244
+ ]
1245
+ + [
1246
+ remote_command,
1247
+ ]
1248
+ )
1089
1249
  return self.sh.spawn(cmd, output=True, fatal=False)
1090
1250
 
1091
- def background_execute(self, remote_command, sshopts='', stdout=None, stderr=None):
1251
+ def background_execute(
1252
+ self, remote_command, sshopts="", stdout=None, stderr=None
1253
+ ):
1092
1254
  """Execute the command remotely and return the object representing the ssh process.
1093
1255
 
1094
1256
  Return a Popen object representing the ssh process. The user is reponsible
@@ -1097,9 +1259,19 @@ class Ssh:
1097
1259
  myremote = self.remote
1098
1260
  if myremote is None:
1099
1261
  return False
1100
- cmd = ([self._sshcmd, ] +
1101
- self._sshopts + sshopts.split() +
1102
- [myremote, ] + [remote_command, ])
1262
+ cmd = (
1263
+ [
1264
+ self._sshcmd,
1265
+ ]
1266
+ + self._sshopts
1267
+ + sshopts.split()
1268
+ + [
1269
+ myremote,
1270
+ ]
1271
+ + [
1272
+ remote_command,
1273
+ ]
1274
+ )
1103
1275
  return self.sh.popen(cmd, stdout=stdout, stderr=stderr)
1104
1276
 
1105
1277
  def cocoon(self, destination):
@@ -1108,14 +1280,18 @@ class Ssh:
1108
1280
  Return ``False`` on failure.
1109
1281
  """
1110
1282
  remote_dir = self.sh.path.dirname(destination)
1111
- if remote_dir == '':
1283
+ if remote_dir == "":
1112
1284
  return True
1113
1285
  logger.debug('Cocooning remote directory "%s"', remote_dir)
1114
1286
  cmd = 'mkdir -p "{}"'.format(remote_dir)
1115
1287
  rc = self.execute(cmd)
1116
1288
  if not rc:
1117
- logger.error('Cannot cocoon on %s (user: %s) for %s',
1118
- str(self._remote), str(self._logname), destination)
1289
+ logger.error(
1290
+ "Cannot cocoon on %s (user: %s) for %s",
1291
+ str(self._remote),
1292
+ str(self._logname),
1293
+ destination,
1294
+ )
1119
1295
  return rc
1120
1296
 
1121
1297
  def remove(self, target):
@@ -1128,33 +1304,43 @@ class Ssh:
1128
1304
  cmd = 'rm -fr "{}"'.format(target)
1129
1305
  rc = self.execute(cmd)
1130
1306
  if not rc:
1131
- logger.error('Cannot remove from %s (user: %s) item "%s"',
1132
- str(self._remote), str(self._logname), target)
1307
+ logger.error(
1308
+ 'Cannot remove from %s (user: %s) item "%s"',
1309
+ str(self._remote),
1310
+ str(self._logname),
1311
+ target,
1312
+ )
1133
1313
  return rc
1134
1314
 
1135
1315
  def _scp_putget_commons(self, source, destination):
1136
1316
  """Common checks on source and destination."""
1137
1317
  if not isinstance(source, str):
1138
- msg = 'Source is not a plain file path: {!r}'.format(source)
1318
+ msg = "Source is not a plain file path: {!r}".format(source)
1139
1319
  raise TypeError(msg)
1140
1320
  if not isinstance(destination, str):
1141
- msg = 'Destination is not a plain file path: {!r}'.format(destination)
1321
+ msg = "Destination is not a plain file path: {!r}".format(
1322
+ destination
1323
+ )
1142
1324
  raise TypeError(msg)
1143
1325
 
1144
1326
  # avoid special cases
1145
- if destination == '' or destination == '.':
1146
- destination = './'
1327
+ if destination == "" or destination == ".":
1328
+ destination = "./"
1147
1329
  else:
1148
- if destination.endswith('..'):
1149
- destination += '/'
1150
- if '../' in destination:
1151
- raise ValueError('"../" is not allowed in the destination path')
1152
- if destination.endswith('/'):
1153
- destination = self.sh.path.join(destination, self.sh.path.basename(source))
1330
+ if destination.endswith(".."):
1331
+ destination += "/"
1332
+ if "../" in destination:
1333
+ raise ValueError(
1334
+ '"../" is not allowed in the destination path'
1335
+ )
1336
+ if destination.endswith("/"):
1337
+ destination = self.sh.path.join(
1338
+ destination, self.sh.path.basename(source)
1339
+ )
1154
1340
 
1155
1341
  return source, destination
1156
1342
 
1157
- def scpput(self, source, destination, scpopts=''):
1343
+ def scpput(self, source, destination, scpopts=""):
1158
1344
  r"""Send ``source`` to ``destination``.
1159
1345
 
1160
1346
  - ``source`` is a single file or a directory, not a pattern (no '\*.grib').
@@ -1170,7 +1356,7 @@ class Ssh:
1170
1356
  source, destination = self._scp_putget_commons(source, destination)
1171
1357
 
1172
1358
  if not self.sh.path.exists(source):
1173
- logger.error('No such file or directory: %s', source)
1359
+ logger.error("No such file or directory: %s", source)
1174
1360
  return False
1175
1361
 
1176
1362
  source = self.sh.path.realpath(source)
@@ -1186,25 +1372,29 @@ class Ssh:
1186
1372
  return False
1187
1373
 
1188
1374
  if self.sh.path.isdir(source):
1189
- scpopts += ' -r'
1375
+ scpopts += " -r"
1190
1376
 
1191
- if not self.remove(destination + '.tmp'):
1377
+ if not self.remove(destination + ".tmp"):
1192
1378
  return False
1193
1379
 
1194
1380
  # transfer to a temporary place.
1195
1381
  # when ``destination`` contains spaces, 1 round of quoting
1196
1382
  # is necessary, to avoid an 'scp: ambiguous target' error.
1197
- cmd = ([self._scpcmd, ] +
1198
- self._scpopts + scpopts.split() +
1199
- [source,
1200
- myremote + ':' + shlex.quote(destination + '.tmp')])
1383
+ cmd = (
1384
+ [
1385
+ self._scpcmd,
1386
+ ]
1387
+ + self._scpopts
1388
+ + scpopts.split()
1389
+ + [source, myremote + ":" + shlex.quote(destination + ".tmp")]
1390
+ )
1201
1391
  rc = self.sh.spawn(cmd, output=False, fatal=False)
1202
1392
  if rc:
1203
1393
  # success, rename the tmp
1204
1394
  rc = self.execute('mv "{0}.tmp" "{0}"'.format(destination))
1205
1395
  return rc
1206
1396
 
1207
- def scpget(self, source, destination, scpopts='', isadir=False):
1397
+ def scpget(self, source, destination, scpopts="", isadir=False):
1208
1398
  r"""Send ``source`` to ``destination``.
1209
1399
 
1210
1400
  - ``source`` is the remote name, not a pattern (no '\*.grib').
@@ -1229,19 +1419,23 @@ class Ssh:
1229
1419
  if isadir:
1230
1420
  if not self.sh.remove(destination):
1231
1421
  return False
1232
- scpopts += ' -r'
1422
+ scpopts += " -r"
1233
1423
 
1234
1424
  # transfer to a temporary place.
1235
1425
  # when ``source`` contains spaces, 1 round of quoting
1236
1426
  # is necessary, to avoid an 'scp: ambiguous target' error.
1237
- cmd = ([self._scpcmd, ] +
1238
- self._scpopts + scpopts.split() +
1239
- [myremote + ':' + shlex.quote(source),
1240
- destination + '.tmp'])
1427
+ cmd = (
1428
+ [
1429
+ self._scpcmd,
1430
+ ]
1431
+ + self._scpopts
1432
+ + scpopts.split()
1433
+ + [myremote + ":" + shlex.quote(source), destination + ".tmp"]
1434
+ )
1241
1435
  rc = self.sh.spawn(cmd, output=False, fatal=False)
1242
1436
  if rc:
1243
1437
  # success, rename the tmp
1244
- rc = self.sh.move(destination + '.tmp', destination)
1438
+ rc = self.sh.move(destination + ".tmp", destination)
1245
1439
  return rc
1246
1440
 
1247
1441
  def get_permissions(self, source):
@@ -1252,7 +1446,7 @@ class Ssh:
1252
1446
  mode = self.sh.stat(source).st_mode
1253
1447
  return stat.S_IMODE(mode)
1254
1448
 
1255
- def scpput_stream(self, stream, destination, permissions=None, sshopts=''):
1449
+ def scpput_stream(self, stream, destination, permissions=None, sshopts=""):
1256
1450
  """Send the ``stream`` to the ``destination``.
1257
1451
 
1258
1452
  - ``stream`` is a ``file`` (typically returned by open(),
@@ -1262,11 +1456,15 @@ class Ssh:
1262
1456
  Return True for ok, False on error.
1263
1457
  """
1264
1458
  if not isinstance(stream, io.IOBase):
1265
- msg = "stream is a {}, should be a <type 'file'>".format(type(stream))
1459
+ msg = "stream is a {}, should be a <type 'file'>".format(
1460
+ type(stream)
1461
+ )
1266
1462
  raise TypeError(msg)
1267
1463
 
1268
1464
  if not isinstance(destination, str):
1269
- msg = 'Destination is not a plain file path: {!r}'.format(destination)
1465
+ msg = "Destination is not a plain file path: {!r}".format(
1466
+ destination
1467
+ )
1270
1468
  raise TypeError(msg)
1271
1469
 
1272
1470
  myremote = self.remote
@@ -1277,16 +1475,25 @@ class Ssh:
1277
1475
  return False
1278
1476
 
1279
1477
  # transfer to a tmp, rename and set permissions in one go
1280
- remote_cmd = 'cat > {0}.tmp && mv {0}.tmp {0}'.format(shlex.quote(destination))
1478
+ remote_cmd = "cat > {0}.tmp && mv {0}.tmp {0}".format(
1479
+ shlex.quote(destination)
1480
+ )
1281
1481
  if permissions:
1282
- remote_cmd += ' && chmod -v {:o} {}'.format(permissions, shlex.quote(destination))
1283
-
1284
- cmd = ([self._sshcmd, ] +
1285
- self._sshopts + sshopts.split() +
1286
- [myremote, remote_cmd])
1482
+ remote_cmd += " && chmod -v {:o} {}".format(
1483
+ permissions, shlex.quote(destination)
1484
+ )
1485
+
1486
+ cmd = (
1487
+ [
1488
+ self._sshcmd,
1489
+ ]
1490
+ + self._sshopts
1491
+ + sshopts.split()
1492
+ + [myremote, remote_cmd]
1493
+ )
1287
1494
  return self.sh.spawn(cmd, stdin=stream, output=False, fatal=False)
1288
1495
 
1289
- def scpget_stream(self, source, stream, sshopts=''):
1496
+ def scpget_stream(self, source, stream, sshopts=""):
1290
1497
  """Send the ``source`` to the ``stream``.
1291
1498
 
1292
1499
  - ``source`` is the remote file name.
@@ -1296,11 +1503,13 @@ class Ssh:
1296
1503
  Return True for ok, False on error.
1297
1504
  """
1298
1505
  if not isinstance(stream, io.IOBase):
1299
- msg = "stream is a {}, should be a <type 'file'>".format(type(stream))
1506
+ msg = "stream is a {}, should be a <type 'file'>".format(
1507
+ type(stream)
1508
+ )
1300
1509
  raise TypeError(msg)
1301
1510
 
1302
1511
  if not isinstance(source, str):
1303
- msg = 'Source is not a plain file path: {!r}'.format(source)
1512
+ msg = "Source is not a plain file path: {!r}".format(source)
1304
1513
  raise TypeError(msg)
1305
1514
 
1306
1515
  myremote = self.remote
@@ -1308,14 +1517,25 @@ class Ssh:
1308
1517
  return False
1309
1518
 
1310
1519
  # transfer to a tmp, rename and set permissions in one go
1311
- remote_cmd = 'cat {}'.format(shlex.quote(source))
1312
- cmd = ([self._sshcmd, ] +
1313
- self._sshopts + sshopts.split() +
1314
- [myremote, remote_cmd])
1520
+ remote_cmd = "cat {}".format(shlex.quote(source))
1521
+ cmd = (
1522
+ [
1523
+ self._sshcmd,
1524
+ ]
1525
+ + self._sshopts
1526
+ + sshopts.split()
1527
+ + [myremote, remote_cmd]
1528
+ )
1315
1529
  return self.sh.spawn(cmd, output=stream, fatal=False)
1316
1530
 
1317
- def tunnel(self, finaldestination, finalport=0, entranceport=None,
1318
- maxwait=3., checkdelay=0.25):
1531
+ def tunnel(
1532
+ self,
1533
+ finaldestination,
1534
+ finalport=0,
1535
+ entranceport=None,
1536
+ maxwait=3.0,
1537
+ checkdelay=0.25,
1538
+ ):
1319
1539
  """Create an SSH tunnel and check that it actually starts.
1320
1540
 
1321
1541
  :param str finaldestination: The destination hostname (i.e the machine
@@ -1343,45 +1563,84 @@ class Ssh:
1343
1563
  entranceport = self.sh.available_localport()
1344
1564
  else:
1345
1565
  if self.sh.check_localport(entranceport):
1346
- logger.error('The SSH tunnel creation failed ' +
1347
- '(entrance: %d, dest: %s:%d, via %s).',
1348
- entranceport, finaldestination, finalport, myremote)
1349
- logger.error('The entrance port is already in use.')
1566
+ logger.error(
1567
+ "The SSH tunnel creation failed "
1568
+ + "(entrance: %d, dest: %s:%d, via %s).",
1569
+ entranceport,
1570
+ finaldestination,
1571
+ finalport,
1572
+ myremote,
1573
+ )
1574
+ logger.error("The entrance port is already in use.")
1350
1575
  return False
1351
- if finaldestination == 'socks':
1352
- p = self.sh.popen([self._sshcmd, ] + self._sshopts +
1353
- ['-N', '-D', '{:d}'.format(entranceport), myremote],
1354
- stdin=False, output=False)
1576
+ if finaldestination == "socks":
1577
+ p = self.sh.popen(
1578
+ [
1579
+ self._sshcmd,
1580
+ ]
1581
+ + self._sshopts
1582
+ + ["-N", "-D", "{:d}".format(entranceport), myremote],
1583
+ stdin=False,
1584
+ output=False,
1585
+ )
1355
1586
  else:
1356
1587
  if finalport <= 0:
1357
- raise ValueError('Erroneous finalport value: {!s}'.format(finalport))
1358
- p = self.sh.popen([self._sshcmd, ] + self._sshopts +
1359
- ['-N', '-L',
1360
- '{:d}:{:s}:{:d}'.format(entranceport,
1361
- finaldestination, finalport),
1362
- myremote],
1363
- stdin=False, output=False)
1364
- tunnel = ActiveSshTunnel(self.sh, p, entranceport, finaldestination, finalport)
1365
- elapsed = 0.
1366
- while (not self.sh.check_localport(entranceport)) and elapsed < maxwait:
1588
+ raise ValueError(
1589
+ "Erroneous finalport value: {!s}".format(finalport)
1590
+ )
1591
+ p = self.sh.popen(
1592
+ [
1593
+ self._sshcmd,
1594
+ ]
1595
+ + self._sshopts
1596
+ + [
1597
+ "-N",
1598
+ "-L",
1599
+ "{:d}:{:s}:{:d}".format(
1600
+ entranceport, finaldestination, finalport
1601
+ ),
1602
+ myremote,
1603
+ ],
1604
+ stdin=False,
1605
+ output=False,
1606
+ )
1607
+ tunnel = ActiveSshTunnel(
1608
+ self.sh, p, entranceport, finaldestination, finalport
1609
+ )
1610
+ elapsed = 0.0
1611
+ while (
1612
+ not self.sh.check_localport(entranceport)
1613
+ ) and elapsed < maxwait:
1367
1614
  self.sh.sleep(checkdelay)
1368
1615
  elapsed += checkdelay
1369
1616
  if not self.sh.check_localport(entranceport):
1370
- logger.error('The SSH tunnel creation failed ' +
1371
- '(entrance: %d, dest: %s:%d, via %s).',
1372
- entranceport, finaldestination, finalport, myremote)
1617
+ logger.error(
1618
+ "The SSH tunnel creation failed "
1619
+ + "(entrance: %d, dest: %s:%d, via %s).",
1620
+ entranceport,
1621
+ finaldestination,
1622
+ finalport,
1623
+ myremote,
1624
+ )
1373
1625
  tunnel.close()
1374
1626
  tunnel = False
1375
- logger.info('SSH tunnel opened, enjoy the ride ! ' +
1376
- '(entrance: %d, dest: %s:%d, via %s).',
1377
- entranceport, finaldestination, finalport, myremote)
1627
+ logger.info(
1628
+ "SSH tunnel opened, enjoy the ride ! "
1629
+ + "(entrance: %d, dest: %s:%d, via %s).",
1630
+ entranceport,
1631
+ finaldestination,
1632
+ finalport,
1633
+ myremote,
1634
+ )
1378
1635
  return tunnel
1379
1636
 
1380
1637
 
1381
1638
  class ActiveSshTunnel:
1382
1639
  """Hold an opened SSH tunnel."""
1383
1640
 
1384
- def __init__(self, sh, activeprocess, entranceport, finaldestination, finalport):
1641
+ def __init__(
1642
+ self, sh, activeprocess, entranceport, finaldestination, finalport
1643
+ ):
1385
1644
  """
1386
1645
  :param Popen activeprocess: The active tunnel process.
1387
1646
  :param int entranceport: Tunnel's entrance port.
@@ -1407,12 +1666,18 @@ class ActiveSshTunnel:
1407
1666
  t0 = time.time()
1408
1667
  while self.opened and time.time() - t0 < 5:
1409
1668
  self._sh.sleep(0.1)
1410
- logger.debug("Tunnel termination took: %f seconds", time.time() - t0)
1669
+ logger.debug(
1670
+ "Tunnel termination took: %f seconds", time.time() - t0
1671
+ )
1411
1672
  if self.opened:
1412
1673
  logger.debug("Tunnel termination failed: issuing SIGKILL")
1413
1674
  self.activeprocess.kill()
1414
- logger.info('SSH tunnel closed (entrance: %d, dest: %s:%d).',
1415
- self.entranceport, self.finaldestination, self.finalport)
1675
+ logger.info(
1676
+ "SSH tunnel closed (entrance: %d, dest: %s:%d).",
1677
+ self.entranceport,
1678
+ self.finaldestination,
1679
+ self.finalport,
1680
+ )
1416
1681
 
1417
1682
  @property
1418
1683
  def opened(self):
@@ -1444,9 +1709,14 @@ def _check_fatal(func):
1444
1709
  try:
1445
1710
  rc = func(self, *args[1:], **kwargs)
1446
1711
  if not rc:
1447
- logger.error("The maximum number of retries (%s) was reached...", self._maxtries)
1712
+ logger.error(
1713
+ "The maximum number of retries (%s) was reached...",
1714
+ self._maxtries,
1715
+ )
1448
1716
  if self._fatal:
1449
- raise RuntimeError("Could not execute the SSH command.")
1717
+ raise RuntimeError(
1718
+ "Could not execute the SSH command."
1719
+ )
1450
1720
  finally:
1451
1721
  self._fatal_in_progress = False
1452
1722
  return rc
@@ -1474,7 +1744,11 @@ def _tryagain(func):
1474
1744
  rc = func(self, *args[1:], **kwargs)
1475
1745
  while not rc and trycount < self._maxtries:
1476
1746
  trycount += 1
1477
- logger.info("Trying again (retries=%d/%d)...", trycount, self._maxtries)
1747
+ logger.info(
1748
+ "Trying again (retries=%d/%d)...",
1749
+ trycount,
1750
+ self._maxtries,
1751
+ )
1478
1752
  self.sh.sleep(self._triesdelay)
1479
1753
  rc = func(self, *args[1:], **kwargs)
1480
1754
  finally:
@@ -1503,13 +1777,17 @@ class _AssistedSshMeta(type):
1503
1777
  """
1504
1778
  bare_methods = list(d.keys())
1505
1779
  # Add the tryagain decorator...
1506
- for tagain in [x for x in d['_auto_retries'] if x not in bare_methods]:
1780
+ for tagain in [x for x in d["_auto_retries"] if x not in bare_methods]:
1507
1781
  inherited = [base for base in b if hasattr(base, tagain)]
1508
1782
  d[tagain] = _tryagain(getattr(inherited[0], tagain))
1509
1783
  # Add the check_fatal decorator...
1510
- for cfatal in [x for x in d['_auto_checkfatal'] if x not in bare_methods]:
1784
+ for cfatal in [
1785
+ x for x in d["_auto_checkfatal"] if x not in bare_methods
1786
+ ]:
1511
1787
  inherited = [base for base in b if hasattr(base, cfatal)]
1512
- d[cfatal] = _check_fatal(d.get(cfatal, getattr(inherited[0], cfatal)))
1788
+ d[cfatal] = _check_fatal(
1789
+ d.get(cfatal, getattr(inherited[0], cfatal))
1790
+ )
1513
1791
  return super().__new__(cls, n, b, d)
1514
1792
 
1515
1793
 
@@ -1577,16 +1855,42 @@ class AssistedSsh(Ssh, metaclass=_AssistedSshMeta):
1577
1855
 
1578
1856
  """
1579
1857
 
1580
- _auto_checkfatal = ['check_ok', 'execute', 'cocoon', 'remove',
1581
- 'scpput', 'scpget', 'scpput_stream', 'scpget_stream',
1582
- 'tunnel']
1858
+ _auto_checkfatal = [
1859
+ "check_ok",
1860
+ "execute",
1861
+ "cocoon",
1862
+ "remove",
1863
+ "scpput",
1864
+ "scpget",
1865
+ "scpput_stream",
1866
+ "scpget_stream",
1867
+ "tunnel",
1868
+ ]
1583
1869
  # No retries on scpput_stream since it's not guaranteed that the stream is seekable.
1584
- _auto_retries = ['check_ok', 'execute', 'cocoon', 'remove',
1585
- 'scpput', 'scpget', 'tunnel']
1586
-
1587
- def __init__(self, sh, hostname, logname=None, sshopts=None, scpopts=None,
1588
- maxtries=1, triesdelay=1, virtualnode=False, permut=True,
1589
- fatal=False, mandatory_hostcheck=True):
1870
+ _auto_retries = [
1871
+ "check_ok",
1872
+ "execute",
1873
+ "cocoon",
1874
+ "remove",
1875
+ "scpput",
1876
+ "scpget",
1877
+ "tunnel",
1878
+ ]
1879
+
1880
+ def __init__(
1881
+ self,
1882
+ sh,
1883
+ hostname,
1884
+ logname=None,
1885
+ sshopts=None,
1886
+ scpopts=None,
1887
+ maxtries=1,
1888
+ triesdelay=1,
1889
+ virtualnode=False,
1890
+ permut=True,
1891
+ fatal=False,
1892
+ mandatory_hostcheck=True,
1893
+ ):
1590
1894
  """
1591
1895
  :param System sh: The :class:`System` object that is to be used.
1592
1896
  :param hostname: The target hostname(s).
@@ -1616,7 +1920,9 @@ class AssistedSsh(Ssh, metaclass=_AssistedSshMeta):
1616
1920
  self._fatal = fatal
1617
1921
  self._mandatory_hostcheck = mandatory_hostcheck
1618
1922
  if self._virtualnode and isinstance(self._remote, (list, tuple)):
1619
- raise ValueError('When virtual nodes are used, the hostname must be a string')
1923
+ raise ValueError(
1924
+ "When virtual nodes are used, the hostname must be a string"
1925
+ )
1620
1926
 
1621
1927
  self._retry_in_progress = False
1622
1928
  self._fatal_in_progress = False
@@ -1640,7 +1946,7 @@ class AssistedSsh(Ssh, metaclass=_AssistedSshMeta):
1640
1946
  else:
1641
1947
  targets = [self._remote]
1642
1948
  if self._logname is not None:
1643
- targets = [self._logname + '@' + x for x in targets]
1949
+ targets = [self._logname + "@" + x for x in targets]
1644
1950
  if self._permut:
1645
1951
  random.shuffle(targets)
1646
1952
  return targets
@@ -1667,7 +1973,16 @@ class AssistedSsh(Ssh, metaclass=_AssistedSshMeta):
1667
1973
  if self._mandatory_hostcheck:
1668
1974
  if self._chosen_target is None:
1669
1975
  for guess in self.targets:
1670
- cmd = [self._sshcmd, ] + self._sshopts + [guess, 'true', ]
1976
+ cmd = (
1977
+ [
1978
+ self._sshcmd,
1979
+ ]
1980
+ + self._sshopts
1981
+ + [
1982
+ guess,
1983
+ "true",
1984
+ ]
1985
+ )
1671
1986
  try:
1672
1987
  self.sh.spawn(cmd, output=False, silent=True)
1673
1988
  except Exception:
@@ -1680,9 +1995,16 @@ class AssistedSsh(Ssh, metaclass=_AssistedSshMeta):
1680
1995
  return next(self._targets_iter)
1681
1996
 
1682
1997
 
1683
- _ConnectionStatusAttrs = ('Family', 'LocalAddr', 'LocalPort', 'DestAddr', 'DestPort', 'Status')
1684
- TcpConnectionStatus = namedtuple('TcpConnectionStatus', _ConnectionStatusAttrs)
1685
- UdpConnectionStatus = namedtuple('UdpConnectionStatus', _ConnectionStatusAttrs)
1998
+ _ConnectionStatusAttrs = (
1999
+ "Family",
2000
+ "LocalAddr",
2001
+ "LocalPort",
2002
+ "DestAddr",
2003
+ "DestPort",
2004
+ "Status",
2005
+ )
2006
+ TcpConnectionStatus = namedtuple("TcpConnectionStatus", _ConnectionStatusAttrs)
2007
+ UdpConnectionStatus = namedtuple("UdpConnectionStatus", _ConnectionStatusAttrs)
1686
2008
 
1687
2009
 
1688
2010
  class AbstractNetstats(metaclass=abc.ABCMeta):
@@ -1730,11 +2052,9 @@ class AbstractNetstats(metaclass=abc.ABCMeta):
1730
2052
  class LinuxNetstats(AbstractNetstats):
1731
2053
  """A Netstats implementation for Linux (based on the /proc/net data)."""
1732
2054
 
1733
- _LINUX_LPORT = '/proc/sys/net/ipv4/ip_local_port_range'
1734
- _LINUX_PORTS_V4 = {'tcp': '/proc/net/tcp',
1735
- 'udp': '/proc/net/udp'}
1736
- _LINUX_PORTS_V6 = {'tcp': '/proc/net/tcp6',
1737
- 'udp': '/proc/net/udp6'}
2055
+ _LINUX_LPORT = "/proc/sys/net/ipv4/ip_local_port_range"
2056
+ _LINUX_PORTS_V4 = {"tcp": "/proc/net/tcp", "udp": "/proc/net/udp"}
2057
+ _LINUX_PORTS_V6 = {"tcp": "/proc/net/tcp6", "udp": "/proc/net/udp6"}
1738
2058
  _LINUX_AF_INET4 = socket.AF_INET
1739
2059
  _LINUX_AF_INET6 = socket.AF_INET6
1740
2060
 
@@ -1747,7 +2067,9 @@ class LinuxNetstats(AbstractNetstats):
1747
2067
  with open(self._LINUX_LPORT) as tmprange:
1748
2068
  tmpports = [int(x) for x in tmprange.readline().split()]
1749
2069
  unports = set(range(5001, 65536))
1750
- self.__unprivileged_ports = sorted(unports - set(range(tmpports[0], tmpports[1] + 1)))
2070
+ self.__unprivileged_ports = sorted(
2071
+ unports - set(range(tmpports[0], tmpports[1] + 1))
2072
+ )
1751
2073
  return self.__unprivileged_ports
1752
2074
 
1753
2075
  @classmethod
@@ -1755,8 +2077,7 @@ class LinuxNetstats(AbstractNetstats):
1755
2077
  if family == cls._LINUX_AF_INET4:
1756
2078
  packed = struct.pack(b"<I", int(hexip, 16))
1757
2079
  elif family == cls._LINUX_AF_INET6:
1758
- packed = struct.unpack(b">IIII",
1759
- binascii.a2b_hex(hexip))
2080
+ packed = struct.unpack(b">IIII", binascii.a2b_hex(hexip))
1760
2081
  packed = struct.pack(b"@IIII", *packed)
1761
2082
  else:
1762
2083
  raise ValueError("Unknown address family.")
@@ -1766,25 +2087,38 @@ class LinuxNetstats(AbstractNetstats):
1766
2087
  tmpports = dict()
1767
2088
  with open(self._LINUX_PORTS_V4[proto]) as netstats:
1768
2089
  netstats.readline() # Skip the header line
1769
- tmpports[self._LINUX_AF_INET4] = [re.split(r':\b|\s+', x.strip())[1:6]
1770
- for x in netstats.readlines()]
2090
+ tmpports[self._LINUX_AF_INET4] = [
2091
+ re.split(r":\b|\s+", x.strip())[1:6]
2092
+ for x in netstats.readlines()
2093
+ ]
1771
2094
  try:
1772
2095
  with open(self._LINUX_PORTS_V6[proto]) as netstats:
1773
2096
  netstats.readline() # Skip the header line
1774
- tmpports[self._LINUX_AF_INET6] = [re.split(r':\b|\s+', x.strip())[1:6]
1775
- for x in netstats.readlines()]
2097
+ tmpports[self._LINUX_AF_INET6] = [
2098
+ re.split(r":\b|\s+", x.strip())[1:6]
2099
+ for x in netstats.readlines()
2100
+ ]
1776
2101
  except OSError:
1777
2102
  # Apparently, no IPv6 support on this machine
1778
2103
  tmpports[self._LINUX_AF_INET6] = []
1779
- tmpports = [[rclass(family,
1780
- self._ip_from_hex(l[0], family), int(l[1], 16),
1781
- self._ip_from_hex(l[2], family), int(l[3], 16),
1782
- int(l[4], 16)) for l in tmpports[family]]
1783
- for family in (self._LINUX_AF_INET4, self._LINUX_AF_INET6)]
2104
+ tmpports = [
2105
+ [
2106
+ rclass(
2107
+ family,
2108
+ self._ip_from_hex(l[0], family),
2109
+ int(l[1], 16),
2110
+ self._ip_from_hex(l[2], family),
2111
+ int(l[3], 16),
2112
+ int(l[4], 16),
2113
+ )
2114
+ for l in tmpports[family]
2115
+ ]
2116
+ for family in (self._LINUX_AF_INET4, self._LINUX_AF_INET6)
2117
+ ]
1784
2118
  return functools.reduce(operator.add, tmpports)
1785
2119
 
1786
2120
  def tcp_netstats(self):
1787
- return self._generic_netstats('tcp', TcpConnectionStatus)
2121
+ return self._generic_netstats("tcp", TcpConnectionStatus)
1788
2122
 
1789
2123
  def udp_netstats(self):
1790
- return self._generic_netstats('udp', UdpConnectionStatus)
2124
+ return self._generic_netstats("udp", UdpConnectionStatus)