anemoi-utils 0.4.11__py3-none-any.whl → 0.4.13__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.

Potentially problematic release.


This version of anemoi-utils might be problematic. Click here for more details.

anemoi/utils/s3.py CHANGED
@@ -8,6 +8,9 @@
8
8
  # nor does it submit to any jurisdiction.
9
9
 
10
10
  import warnings
11
+ from typing import Any
12
+ from typing import Callable
13
+ from typing import Optional
11
14
 
12
15
  from .remote import transfer
13
16
  from .remote.s3 import delete as delete_
@@ -21,7 +24,21 @@ warnings.warn(
21
24
  )
22
25
 
23
26
 
24
- def s3_client(*args, **kwargs):
27
+ def s3_client(*args: Any, **kwargs: Any) -> Any:
28
+ """Create an S3 client.
29
+
30
+ Parameters
31
+ ----------
32
+ *args : Any
33
+ Positional arguments for the S3 client.
34
+ **kwargs : Any
35
+ Keyword arguments for the S3 client.
36
+
37
+ Returns
38
+ -------
39
+ Any
40
+ The S3 client.
41
+ """
25
42
  warnings.warn(
26
43
  "The 's3_client' function (from anemoi.utils.s3 import s3_client) function is deprecated and will be removed in a future release. "
27
44
  "Please use the 's3_client' function (from anemoi.utils.remote.s3 import s3_client) instead.",
@@ -31,7 +48,35 @@ def s3_client(*args, **kwargs):
31
48
  return s3_client_(*args, **kwargs)
32
49
 
33
50
 
34
- def upload(source, target, *, overwrite=False, resume=False, verbosity=1, progress=None, threads=1) -> None:
51
+ def upload(
52
+ source: str,
53
+ target: str,
54
+ *,
55
+ overwrite: bool = False,
56
+ resume: bool = False,
57
+ verbosity: int = 1,
58
+ progress: Optional[Callable] = None,
59
+ threads: int = 1,
60
+ ) -> None:
61
+ """Upload a file to S3.
62
+
63
+ Parameters
64
+ ----------
65
+ source : str
66
+ The source file path.
67
+ target : str
68
+ The target S3 path.
69
+ overwrite : bool, optional
70
+ Whether to overwrite the target file, by default False.
71
+ resume : bool, optional
72
+ Whether to resume a previous upload, by default False.
73
+ verbosity : int, optional
74
+ The verbosity level, by default 1.
75
+ progress : Callable, optional
76
+ A callback function for progress updates, by default None.
77
+ threads : int, optional
78
+ The number of threads to use, by default 1.
79
+ """
35
80
  warnings.warn(
36
81
  "The 'upload' function (from anemoi.utils.s3 import upload) function is deprecated and will be removed in a future release. "
37
82
  "Please use the 'transfer' function (from anemoi.utils.remote import transfer) instead.",
@@ -43,7 +88,21 @@ def upload(source, target, *, overwrite=False, resume=False, verbosity=1, progre
43
88
  )
44
89
 
45
90
 
46
- def download(*args, **kwargs):
91
+ def download(*args: Any, **kwargs: Any) -> Any:
92
+ """Download a file from S3.
93
+
94
+ Parameters
95
+ ----------
96
+ *args : Any
97
+ Positional arguments for the download.
98
+ **kwargs : Any
99
+ Keyword arguments for the download.
100
+
101
+ Returns
102
+ -------
103
+ Any
104
+ The result of the download.
105
+ """
47
106
  warnings.warn(
48
107
  "The 'download' function (from anemoi.utils.s3 import download) function is deprecated and will be removed in a future release. "
49
108
  "Please use the 'transfer' function (from anemoi.utils.remote import transfer) instead.",
@@ -53,7 +112,21 @@ def download(*args, **kwargs):
53
112
  return transfer(*args, **kwargs)
54
113
 
55
114
 
56
- def delete(*args, **kwargs):
115
+ def delete(*args: Any, **kwargs: Any) -> Any:
116
+ """Delete a file from S3.
117
+
118
+ Parameters
119
+ ----------
120
+ *args : Any
121
+ Positional arguments for the delete.
122
+ **kwargs : Any
123
+ Keyword arguments for the delete.
124
+
125
+ Returns
126
+ -------
127
+ Any
128
+ The result of the delete.
129
+ """
57
130
  warnings.warn(
58
131
  "The 'delete' function (from anemoi.utils.s3 import delete) function is deprecated and will be removed in a future release. "
59
132
  "Please use the 'transfer' function (from anemoi.utils.remote.s3 import delete) instead.",
anemoi/utils/sanitise.py CHANGED
@@ -11,6 +11,7 @@
11
11
  import os
12
12
  import re
13
13
  from pathlib import Path
14
+ from typing import Any
14
15
  from urllib.parse import parse_qs
15
16
  from urllib.parse import urlencode
16
17
  from urllib.parse import urlparse
@@ -22,10 +23,18 @@ RE1 = re.compile(r"{([^}]*)}")
22
23
  RE2 = re.compile(r"\(([^}]*)\)")
23
24
 
24
25
 
25
- def sanitise(obj):
26
- """sanitise an object:
27
- - by replacing all full paths with shortened versions.
28
- - by replacing URL passwords with '***'.
26
+ def sanitise(obj: Any) -> Any:
27
+ """Sanitise an object by replacing all full paths with shortened versions and URL passwords with '***'.
28
+
29
+ Parameters
30
+ ----------
31
+ obj : Any
32
+ The object to sanitise.
33
+
34
+ Returns
35
+ -------
36
+ Any
37
+ The sanitised object.
29
38
  """
30
39
 
31
40
  if isinstance(obj, dict):
@@ -43,7 +52,19 @@ def sanitise(obj):
43
52
  return obj
44
53
 
45
54
 
46
- def _sanitise_string(obj):
55
+ def _sanitise_string(obj: str) -> str:
56
+ """Sanitise a string by replacing full paths and URL passwords.
57
+
58
+ Parameters
59
+ ----------
60
+ obj : str
61
+ The string to sanitise.
62
+
63
+ Returns
64
+ -------
65
+ str
66
+ The sanitised string.
67
+ """
47
68
 
48
69
  parsed = urlparse(obj, allow_fragments=True)
49
70
 
@@ -56,7 +77,19 @@ def _sanitise_string(obj):
56
77
  return obj
57
78
 
58
79
 
59
- def _sanitise_url(parsed):
80
+ def _sanitise_url(parsed: Any) -> str:
81
+ """Sanitise a URL by replacing passwords with '***'.
82
+
83
+ Parameters
84
+ ----------
85
+ parsed : Any
86
+ The parsed URL.
87
+
88
+ Returns
89
+ -------
90
+ str
91
+ The sanitised URL.
92
+ """
60
93
 
61
94
  LIST = [
62
95
  "pass",
@@ -100,7 +133,19 @@ def _sanitise_url(parsed):
100
133
  return urlunparse([scheme, netloc, path, params, query, fragment])
101
134
 
102
135
 
103
- def _sanitise_path(path):
136
+ def _sanitise_path(path: str) -> str:
137
+ """Sanitise a file path by shortening it.
138
+
139
+ Parameters
140
+ ----------
141
+ path : str
142
+ The file path to sanitise.
143
+
144
+ Returns
145
+ -------
146
+ str
147
+ The sanitised file path.
148
+ """
104
149
  bits = list(reversed(Path(path).parts))
105
150
  result = [bits.pop(0)]
106
151
  for bit in bits:
anemoi/utils/text.py CHANGED
@@ -8,16 +8,21 @@
8
8
  # nor does it submit to any jurisdiction.
9
9
 
10
10
 
11
- """Text utilities"""
11
+ """Text utilities."""
12
12
 
13
13
  import re
14
14
  from collections import defaultdict
15
+ from typing import Any
16
+ from typing import List
17
+ from typing import Optional
18
+ from typing import Tuple
19
+ from typing import Union
15
20
 
16
21
  # https://en.wikipedia.org/wiki/Box-drawing_character
17
22
 
18
23
 
19
24
  def dotted_line(width=84) -> str:
20
- """Return a dotted line using '┈'
25
+ """Return a dotted line using '┈'.
21
26
 
22
27
  >>> dotted_line(40)
23
28
  ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈
@@ -40,12 +45,35 @@ def dotted_line(width=84) -> str:
40
45
  _ansi_escape = re.compile(r"\x1b\[([0-9;]*[mGKH])")
41
46
 
42
47
 
43
- def _has_ansi_escape(s):
48
+ def _has_ansi_escape(s: str) -> bool:
49
+ """Check if a string contains ANSI escape codes.
50
+
51
+ Parameters
52
+ ----------
53
+ s : str
54
+ The string to check.
55
+
56
+ Returns
57
+ -------
58
+ bool
59
+ True if the string contains ANSI escape codes, False otherwise.
60
+ """
44
61
  return _ansi_escape.search(s) is not None
45
62
 
46
63
 
47
- def _split_tokens(s):
48
- """Split a string into a list of visual characters with their lenghts."""
64
+ def _split_tokens(s: str) -> List[Tuple[str, int]]:
65
+ """Split a string into a list of visual characters with their lengths.
66
+
67
+ Parameters
68
+ ----------
69
+ s : str
70
+ The string to split.
71
+
72
+ Returns
73
+ -------
74
+ list of tuple
75
+ A list of tuples where each tuple contains a visual character and its length.
76
+ """
49
77
  from wcwidth import wcswidth
50
78
 
51
79
  initial = s
@@ -76,8 +104,19 @@ def _split_tokens(s):
76
104
  return out
77
105
 
78
106
 
79
- def visual_len(s):
80
- """Compute the length of a string as it appears on the terminal."""
107
+ def visual_len(s: Union[str, List[Tuple[str, int]]]) -> int:
108
+ """Compute the length of a string as it appears on the terminal.
109
+
110
+ Parameters
111
+ ----------
112
+ s : str or list of tuple
113
+ The string or list of visual characters with their lengths.
114
+
115
+ Returns
116
+ -------
117
+ int
118
+ The visual length of the string.
119
+ """
81
120
  if isinstance(s, str):
82
121
  s = _split_tokens(s)
83
122
  assert isinstance(s, (tuple, list)), (type(s), s)
@@ -92,8 +131,8 @@ def visual_len(s):
92
131
  return n
93
132
 
94
133
 
95
- def boxed(text, min_width=80, max_width=80) -> str:
96
- """Put a box around a text
134
+ def boxed(text: str, min_width: int = 80, max_width: int = 80) -> str:
135
+ """Put a box around a text.
97
136
 
98
137
  >>> boxed("Hello,\\nWorld!", max_width=40)
99
138
  ┌──────────────────────────────────────────┐
@@ -114,12 +153,11 @@ def boxed(text, min_width=80, max_width=80) -> str:
114
153
  -------
115
154
  str
116
155
  A boxed version of the input text
117
-
118
156
  """
119
157
 
120
158
  lines = []
121
159
  for line in text.split("\n"):
122
- line = line.strip()
160
+ line = line.rstrip()
123
161
  line = _split_tokens(line)
124
162
  lines.append(line)
125
163
 
@@ -158,68 +196,157 @@ def boxed(text, min_width=80, max_width=80) -> str:
158
196
  return "\n".join(box)
159
197
 
160
198
 
161
- def bold(text):
199
+ def bold(text: str) -> str:
200
+ """Make the text bold.
201
+
202
+ Parameters
203
+ ----------
204
+ text : str
205
+ The text to make bold.
206
+
207
+ Returns
208
+ -------
209
+ str
210
+ The bold text.
211
+ """
162
212
  from termcolor import colored
163
213
 
164
214
  return colored(text, attrs=["bold"])
165
215
 
166
216
 
167
- def red(text):
217
+ def red(text: str) -> str:
218
+ """Make the text red.
219
+
220
+ Parameters
221
+ ----------
222
+ text : str
223
+ The text to make red.
224
+
225
+ Returns
226
+ -------
227
+ str
228
+ The red text.
229
+ """
168
230
  from termcolor import colored
169
231
 
170
232
  return colored(text, "red")
171
233
 
172
234
 
173
- def green(text):
235
+ def green(text: str) -> str:
236
+ """Make the text green.
237
+
238
+ Parameters
239
+ ----------
240
+ text : str
241
+ The text to make green.
242
+
243
+ Returns
244
+ -------
245
+ str
246
+ The green text.
247
+ """
174
248
  from termcolor import colored
175
249
 
176
250
  return colored(text, "green")
177
251
 
178
252
 
179
- def blue(text):
253
+ def blue(text: str) -> str:
254
+ """Make the text blue.
255
+
256
+ Parameters
257
+ ----------
258
+ text : str
259
+ The text to make blue.
260
+
261
+ Returns
262
+ -------
263
+ str
264
+ The blue text.
265
+ """
180
266
  from termcolor import colored
181
267
 
182
268
  return colored(text, "blue")
183
269
 
184
270
 
185
271
  class Tree:
186
- """Tree data structure."""
272
+ """Tree data structure.
187
273
 
188
- def __init__(self, actor, parent=None):
274
+ Parameters
275
+ ----------
276
+ actor : Any
277
+ The actor associated with the tree node.
278
+ parent : Tree, optional
279
+ The parent tree node, by default None.
280
+ """
281
+
282
+ def __init__(self, actor: Any, parent: Optional["Tree"] = None):
189
283
  self._actor = actor
190
284
  self._kids = []
191
285
  self._parent = parent
192
286
 
193
- def adopt(self, kid):
287
+ def adopt(self, kid: "Tree") -> None:
288
+ """Adopt a child tree node.
289
+
290
+ Parameters
291
+ ----------
292
+ kid : Tree
293
+ The child tree node to adopt.
294
+ """
194
295
  kid._parent._kids.remove(kid)
195
296
  self._kids.append(kid)
196
297
  kid._parent = self
197
298
  # assert False
198
299
 
199
- def forget(self):
300
+ def forget(self) -> None:
301
+ """Forget the current tree node."""
200
302
  self._parent._kids.remove(self)
201
303
  self._parent = None
202
304
 
203
305
  @property
204
- def is_leaf(self):
306
+ def is_leaf(self) -> bool:
307
+ """Bool: True if the tree node is a leaf, False otherwise."""
205
308
  return len(self._kids) == 0
206
309
 
207
310
  @property
208
- def key(self):
311
+ def key(self) -> Tuple:
312
+ """Tuple: The key of the tree node."""
209
313
  return tuple(sorted(self._actor.as_dict().items()))
210
314
 
211
315
  @property
212
- def _text(self):
316
+ def _text(self) -> str:
317
+ """Str: The text representation of the tree node."""
213
318
  return self._actor.summary
214
319
 
215
320
  @property
216
- def summary(self):
321
+ def summary(self) -> str:
322
+ """Str: The summary of the tree node."""
217
323
  return self._actor.summary
218
324
 
219
- def as_dict(self):
325
+ def as_dict(self) -> dict:
326
+ """Convert the tree node to a dictionary.
327
+
328
+ Returns
329
+ -------
330
+ dict
331
+ The dictionary representation of the tree node.
332
+ """
220
333
  return self._actor.as_dict()
221
334
 
222
- def node(self, actor, insert=False):
335
+ def node(self, actor: Any, insert: bool = False) -> "Tree":
336
+ """Create a new tree node.
337
+
338
+ Parameters
339
+ ----------
340
+ actor : Any
341
+ The actor associated with the new tree node.
342
+ insert : bool, optional
343
+ Whether to insert the new tree node at the beginning, by default False.
344
+
345
+ Returns
346
+ -------
347
+ Tree
348
+ The new tree node.
349
+ """
223
350
  node = Tree(actor, self)
224
351
  if insert:
225
352
  self._kids.insert(0, node)
@@ -227,7 +354,8 @@ class Tree:
227
354
  self._kids.append(node)
228
355
  return node
229
356
 
230
- def print(self):
357
+ def print(self) -> None:
358
+ """Print the tree."""
231
359
  padding = []
232
360
 
233
361
  while self._factorise():
@@ -235,14 +363,28 @@ class Tree:
235
363
 
236
364
  self._print(padding)
237
365
 
238
- def _leaves(self, result):
366
+ def _leaves(self, result: List["Tree"]) -> None:
367
+ """Collect all leaf nodes.
368
+
369
+ Parameters
370
+ ----------
371
+ result : list of Tree
372
+ The list to collect the leaf nodes.
373
+ """
239
374
  if self.is_leaf:
240
375
  result.append(self)
241
376
  else:
242
377
  for kid in self._kids:
243
378
  kid._leaves(result)
244
379
 
245
- def _factorise(self):
380
+ def _factorise(self) -> bool:
381
+ """Factorise the tree.
382
+
383
+ Returns
384
+ -------
385
+ bool
386
+ True if the tree was factorised, False otherwise.
387
+ """
246
388
  if len(self._kids) == 0:
247
389
  return False
248
390
 
@@ -290,7 +432,14 @@ class Tree:
290
432
 
291
433
  return result
292
434
 
293
- def _print(self, padding):
435
+ def _print(self, padding: List[str]) -> None:
436
+ """Print the tree with padding.
437
+
438
+ Parameters
439
+ ----------
440
+ padding : list of str
441
+ The padding for each level of the tree.
442
+ """
294
443
  for i, p in enumerate(padding[:-1]):
295
444
  if p == " └":
296
445
  padding[i] = " "
@@ -308,7 +457,19 @@ class Tree:
308
457
 
309
458
  padding.pop()
310
459
 
311
- def to_json(self, depth=0):
460
+ def to_json(self, depth: int = 0) -> dict:
461
+ """Convert the tree to a JSON serializable dictionary.
462
+
463
+ Parameters
464
+ ----------
465
+ depth : int, optional
466
+ The depth of the tree, by default 0.
467
+
468
+ Returns
469
+ -------
470
+ dict
471
+ The JSON serializable dictionary representation of the tree.
472
+ """
312
473
  while self._factorise():
313
474
  pass
314
475
 
@@ -319,20 +480,20 @@ class Tree:
319
480
  }
320
481
 
321
482
 
322
- def table(rows, header, align, margin=0) -> str:
323
- """Format a table
483
+ def table(rows: List[List[Any]], header: List[str], align: List[str], margin: int = 0) -> str:
484
+ """Format a table.
324
485
 
325
- >>> table([['Aa', 12, 5],
326
- ['B', 120, 1],
327
- ['C', 9, 123]],
328
- ['C1', 'C2', 'C3'],
329
- ['<', '>', '>'])
330
- C1 │ C2 │ C3
331
- ───┼─────┼────
332
- Aa │ 12 │ 5
333
- B │ 120 │ 1
334
- C │ 9 │ 123
335
- ───┴─────┴────
486
+ >>> table([['Aa', 12, 5],
487
+ ['B', 120, 1],
488
+ ['C', 9, 123]],
489
+ ['C1', 'C2', 'C3'],
490
+ ['<', '>', '>'])
491
+ C1 │ C2 │ C3
492
+ ───┼─────┼────
493
+ Aa │ 12 │ 5
494
+ B │ 120 │ 1
495
+ C │ 9 │ 123
496
+ ───┴─────┴────
336
497
 
337
498
  Parameters
338
499
  ----------
@@ -397,26 +558,29 @@ def table(rows, header, align, margin=0) -> str:
397
558
  return "\n".join(result)
398
559
 
399
560
 
400
- def progress(done, todo, width=80) -> str:
401
- """_summary_
402
-
403
- >>> print(progress(10, 100,width=50))
404
- █████▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
561
+ def progress(done: int, todo: int, width: int = 80) -> str:
562
+ """Generates a progress bar string.
405
563
 
406
564
  Parameters
407
565
  ----------
408
- done : function
409
- _description_
410
- todo : _type_
411
- _description_
566
+
567
+ done : int
568
+ The number of tasks completed.
569
+ todo : int
570
+ The total number of tasks.
412
571
  width : int, optional
413
- _description_, by default 80
572
+ The width of the progress bar, by default 80.
414
573
 
415
574
  Returns
416
575
  -------
417
576
  str
418
- _description_
577
+ A string representing the progress bar.
419
578
 
579
+ Example
580
+ -------
581
+
582
+ >>> print(progress(10, 100,width=50))
583
+ █████▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
420
584
  """
421
585
  done = min(int(done / todo * width + 0.5), width)
422
586
  return green("█" * done) + red("█" * (width - done))