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

Files changed (37) hide show
  1. anemoi/utils/__init__.py +1 -0
  2. anemoi/utils/__main__.py +12 -2
  3. anemoi/utils/_version.py +9 -4
  4. anemoi/utils/caching.py +138 -13
  5. anemoi/utils/checkpoints.py +81 -13
  6. anemoi/utils/cli.py +83 -7
  7. anemoi/utils/commands/__init__.py +4 -0
  8. anemoi/utils/commands/config.py +19 -2
  9. anemoi/utils/commands/requests.py +18 -2
  10. anemoi/utils/compatibility.py +6 -5
  11. anemoi/utils/config.py +254 -23
  12. anemoi/utils/dates.py +204 -50
  13. anemoi/utils/devtools.py +68 -7
  14. anemoi/utils/grib.py +30 -9
  15. anemoi/utils/grids.py +85 -8
  16. anemoi/utils/hindcasts.py +25 -8
  17. anemoi/utils/humanize.py +357 -52
  18. anemoi/utils/logs.py +31 -3
  19. anemoi/utils/mars/__init__.py +46 -12
  20. anemoi/utils/mars/requests.py +15 -1
  21. anemoi/utils/provenance.py +189 -32
  22. anemoi/utils/registry.py +234 -44
  23. anemoi/utils/remote/__init__.py +386 -38
  24. anemoi/utils/remote/s3.py +252 -29
  25. anemoi/utils/remote/ssh.py +140 -8
  26. anemoi/utils/s3.py +77 -4
  27. anemoi/utils/sanitise.py +52 -7
  28. anemoi/utils/testing.py +182 -0
  29. anemoi/utils/text.py +218 -54
  30. anemoi/utils/timer.py +91 -15
  31. {anemoi_utils-0.4.12.dist-info → anemoi_utils-0.4.14.dist-info}/METADATA +8 -4
  32. anemoi_utils-0.4.14.dist-info/RECORD +38 -0
  33. {anemoi_utils-0.4.12.dist-info → anemoi_utils-0.4.14.dist-info}/WHEEL +1 -1
  34. anemoi_utils-0.4.12.dist-info/RECORD +0 -37
  35. {anemoi_utils-0.4.12.dist-info → anemoi_utils-0.4.14.dist-info}/entry_points.txt +0 -0
  36. {anemoi_utils-0.4.12.dist-info → anemoi_utils-0.4.14.dist-info/licenses}/LICENSE +0 -0
  37. {anemoi_utils-0.4.12.dist-info → anemoi_utils-0.4.14.dist-info}/top_level.txt +0 -0
anemoi/utils/humanize.py CHANGED
@@ -8,43 +8,44 @@
8
8
  # nor does it submit to any jurisdiction.
9
9
 
10
10
 
11
- """Generate human readable strings"""
11
+ """Generate human readable strings."""
12
12
 
13
13
  import datetime
14
14
  import json
15
15
  import re
16
16
  import warnings
17
17
  from collections import defaultdict
18
+ from typing import Any
19
+ from typing import Callable
20
+ from typing import Dict
21
+ from typing import Generator
22
+ from typing import List
23
+ from typing import Optional
24
+ from typing import Tuple
25
+ from typing import Union
18
26
 
19
27
  from anemoi.utils.dates import as_datetime
20
28
 
21
29
 
22
30
  def bytes_to_human(n: float) -> str:
23
- """Convert a number of bytes to a human readable string
31
+ """Convert a number of bytes to a human readable string.
24
32
 
25
- >>> bytes(4096)
33
+ >>> bytes_to_human(4096)
26
34
  '4 KiB'
27
35
 
28
- >>> bytes(4000)
36
+ >>> bytes_to_human(4000)
29
37
  '3.9 KiB'
30
38
 
31
39
  Parameters
32
40
  ----------
33
41
  n : float
34
- the number of bytes
42
+ The number of bytes
35
43
 
36
44
  Returns
37
45
  -------
38
46
  str
39
- a human readable string
40
- """
41
-
42
- """
43
-
44
-
45
-
47
+ A human readable string
46
48
  """
47
-
48
49
  if n < 0:
49
50
  sign = "-"
50
51
  n -= 0
@@ -60,6 +61,18 @@ def bytes_to_human(n: float) -> str:
60
61
 
61
62
 
62
63
  def bytes(n: float) -> str:
64
+ """Deprecated function to convert bytes to a human readable string.
65
+
66
+ Parameters
67
+ ----------
68
+ n : float
69
+ The number of bytes
70
+
71
+ Returns
72
+ -------
73
+ str
74
+ A human readable string
75
+ """
63
76
  warnings.warn(
64
77
  "Function bytes is deprecated and will be removed in a future version. Use bytes_to_human instead.",
65
78
  category=DeprecationWarning,
@@ -68,8 +81,19 @@ def bytes(n: float) -> str:
68
81
  return bytes_to_human(n)
69
82
 
70
83
 
71
- def base2_to_human(n) -> str:
84
+ def base2_to_human(n: float) -> str:
85
+ """Convert a number to a human readable string using base 2 units.
86
+
87
+ Parameters
88
+ ----------
89
+ n : float
90
+ The number to convert
72
91
 
92
+ Returns
93
+ -------
94
+ str
95
+ A human readable string
96
+ """
73
97
  u = ["", "K", "M", "G", "T", " P", "E", "Z", "Y"]
74
98
  i = 0
75
99
  while n >= 1024:
@@ -78,8 +102,19 @@ def base2_to_human(n) -> str:
78
102
  return "%g%s" % (int(n * 10 + 0.5) / 10.0, u[i])
79
103
 
80
104
 
81
- def base2(n) -> str:
105
+ def base2(n: float) -> str:
106
+ """Deprecated function to convert a number to a human readable string using base 2 units.
82
107
 
108
+ Parameters
109
+ ----------
110
+ n : float
111
+ The number to convert
112
+
113
+ Returns
114
+ -------
115
+ str
116
+ A human readable string
117
+ """
83
118
  warnings.warn(
84
119
  "Function base2 is deprecated and will be removed in a future version. Use base2_to_human instead.",
85
120
  category=DeprecationWarning,
@@ -97,15 +132,27 @@ PERIODS = (
97
132
  )
98
133
 
99
134
 
100
- def _plural(count):
135
+ def _plural(count: int) -> str:
136
+ """Return 's' if count is not 1, otherwise return an empty string.
137
+
138
+ Parameters
139
+ ----------
140
+ count : int
141
+ The count
142
+
143
+ Returns
144
+ -------
145
+ str
146
+ 's' if count is not 1, otherwise an empty string
147
+ """
101
148
  if count != 1:
102
149
  return "s"
103
150
  else:
104
151
  return ""
105
152
 
106
153
 
107
- def seconds_to_human(seconds: float) -> str:
108
- """Convert a number of seconds to a human readable string
154
+ def seconds_to_human(seconds: Union[float, datetime.timedelta]) -> str:
155
+ """Convert a number of seconds to a human readable string.
109
156
 
110
157
  >>> seconds_to_human(4000)
111
158
  '1 hour 6 minutes 40 seconds'
@@ -119,7 +166,6 @@ def seconds_to_human(seconds: float) -> str:
119
166
  -------
120
167
  str
121
168
  A human readable string
122
-
123
169
  """
124
170
  if isinstance(seconds, datetime.timedelta):
125
171
  seconds = seconds.total_seconds()
@@ -164,6 +210,18 @@ def seconds_to_human(seconds: float) -> str:
164
210
 
165
211
 
166
212
  def seconds(seconds: float) -> str:
213
+ """Deprecated function to convert seconds to a human readable string.
214
+
215
+ Parameters
216
+ ----------
217
+ seconds : float
218
+ The number of seconds
219
+
220
+ Returns
221
+ -------
222
+ str
223
+ A human readable string
224
+ """
167
225
  warnings.warn(
168
226
  "Function seconds is deprecated and will be removed in a future version. Use seconds_to_human instead.",
169
227
  category=DeprecationWarning,
@@ -172,7 +230,21 @@ def seconds(seconds: float) -> str:
172
230
  return seconds_to_human(seconds)
173
231
 
174
232
 
175
- def plural(value, what):
233
+ def plural(value: int, what: str) -> str:
234
+ """Return a string with the value and the pluralized form of what.
235
+
236
+ Parameters
237
+ ----------
238
+ value : int
239
+ The value
240
+ what : str
241
+ The string to pluralize
242
+
243
+ Returns
244
+ -------
245
+ str
246
+ The value and the pluralized form of what
247
+ """
176
248
  return f"{value:,} {what}{_plural(value)}"
177
249
 
178
250
 
@@ -202,7 +274,19 @@ MONTH = [
202
274
  ]
203
275
 
204
276
 
205
- def __(n):
277
+ def __(n: int) -> str:
278
+ """Return the ordinal suffix for a number.
279
+
280
+ Parameters
281
+ ----------
282
+ n : int
283
+ The number
284
+
285
+ Returns
286
+ -------
287
+ str
288
+ The ordinal suffix
289
+ """
206
290
  if n in (11, 12, 13):
207
291
  return "th"
208
292
 
@@ -218,8 +302,10 @@ def __(n):
218
302
  return "th"
219
303
 
220
304
 
221
- def when(then, now=None, short=True, use_utc=False) -> str:
222
- """Generate a human readable string for a date, relative to now
305
+ def when(
306
+ then: datetime.datetime, now: Optional[datetime.datetime] = None, short: bool = True, use_utc: bool = False
307
+ ) -> str:
308
+ """Generate a human readable string for a date, relative to now.
223
309
 
224
310
  >>> when(datetime.datetime.now() - datetime.timedelta(hours=2))
225
311
  '2 hours ago'
@@ -243,7 +329,7 @@ def when(then, now=None, short=True, use_utc=False) -> str:
243
329
  now : datetime.datetime, optional
244
330
  The reference date, by default NOW
245
331
  short : bool, optional
246
- Genererate shorter strings, by default True
332
+ Generate shorter strings, by default True
247
333
  use_utc : bool, optional
248
334
  Use UTC time, by default False
249
335
 
@@ -251,7 +337,6 @@ def when(then, now=None, short=True, use_utc=False) -> str:
251
337
  -------
252
338
  str
253
339
  A human readable string
254
-
255
340
  """
256
341
  last = "last"
257
342
 
@@ -344,7 +429,21 @@ def when(then, now=None, short=True, use_utc=False) -> str:
344
429
  )
345
430
 
346
431
 
347
- def string_distance(s, t):
432
+ def string_distance(s: str, t: str) -> int:
433
+ """Calculate the Levenshtein distance between two strings.
434
+
435
+ Parameters
436
+ ----------
437
+ s : str
438
+ The first string
439
+ t : str
440
+ The second string
441
+
442
+ Returns
443
+ -------
444
+ int
445
+ The Levenshtein distance
446
+ """
348
447
  import numpy as np
349
448
 
350
449
  m = len(s)
@@ -369,8 +468,8 @@ def string_distance(s, t):
369
468
  return d[m, n]
370
469
 
371
470
 
372
- def did_you_mean(word, vocabulary) -> str:
373
- """Pick the closest word in a vocabulary
471
+ def did_you_mean(word: str, vocabulary: List[str]) -> str:
472
+ """Pick the closest word in a vocabulary.
374
473
 
375
474
  >>> did_you_mean("aple", ["banana", "lemon", "apple", "orange"])
376
475
  'apple'
@@ -379,7 +478,7 @@ def did_you_mean(word, vocabulary) -> str:
379
478
  ----------
380
479
  word : str
381
480
  The word to look for
382
- vocabulary : list of strings
481
+ vocabulary : list of str
383
482
  The list of known words
384
483
 
385
484
  Returns
@@ -392,14 +491,26 @@ def did_you_mean(word, vocabulary) -> str:
392
491
  return best
393
492
 
394
493
 
395
- def dict_to_human(query):
494
+ def dict_to_human(query: Dict[str, Any]) -> str:
495
+ """Convert a dictionary to a human readable string.
496
+
497
+ Parameters
498
+ ----------
499
+ query : dict
500
+ The dictionary to convert
501
+
502
+ Returns
503
+ -------
504
+ str
505
+ A human readable string
506
+ """
396
507
  lst = [f"{k}={v}" for k, v in sorted(query.items())]
397
508
 
398
509
  return list_to_human(lst)
399
510
 
400
511
 
401
- def list_to_human(lst, conjunction="and") -> str:
402
- """Convert a list of strings to a human readable string
512
+ def list_to_human(lst: List[str], conjunction: str = "and") -> str:
513
+ """Convert a list of strings to a human readable string.
403
514
 
404
515
  >>> list_to_human(["banana", "lemon", "apple", "orange"])
405
516
  'banana, lemon, apple and orange'
@@ -425,7 +536,25 @@ def list_to_human(lst, conjunction="and") -> str:
425
536
  return f" {conjunction} ".join(lst)
426
537
 
427
538
 
428
- def human_to_number(value, name, units, none_ok):
539
+ def human_to_number(value: Union[str, int], name: str, units: Dict[str, int], none_ok: bool) -> Optional[int]:
540
+ """Convert a human readable string to a number.
541
+
542
+ Parameters
543
+ ----------
544
+ value : str or int
545
+ The value to convert
546
+ name : str
547
+ The name of the value
548
+ units : dict
549
+ The units to use for conversion
550
+ none_ok : bool
551
+ Whether None is an acceptable value
552
+
553
+ Returns
554
+ -------
555
+ int or None
556
+ The converted value
557
+ """
429
558
  if value is None and none_ok:
430
559
  return None
431
560
 
@@ -444,7 +573,27 @@ def human_to_number(value, name, units, none_ok):
444
573
  return value * units[unit]
445
574
 
446
575
 
447
- def as_number(value, name=None, units=None, none_ok=False):
576
+ def as_number(
577
+ value: Union[str, int], name: Optional[str] = None, units: Optional[Dict[str, int]] = None, none_ok: bool = False
578
+ ) -> Optional[int]:
579
+ """Deprecated function to convert a human readable string to a number.
580
+
581
+ Parameters
582
+ ----------
583
+ value : str or int
584
+ The value to convert
585
+ name : str, optional
586
+ The name of the value
587
+ units : dict, optional
588
+ The units to use for conversion
589
+ none_ok : bool, optional
590
+ Whether None is an acceptable value
591
+
592
+ Returns
593
+ -------
594
+ int or None
595
+ The converted value
596
+ """
448
597
  warnings.warn(
449
598
  "Function as_number is deprecated and will be removed in a future version. Use human_to_number instead.",
450
599
  category=DeprecationWarning,
@@ -453,12 +602,44 @@ def as_number(value, name=None, units=None, none_ok=False):
453
602
  return human_to_number(value, name, units, none_ok)
454
603
 
455
604
 
456
- def human_seconds(value, name=None, none_ok=False):
605
+ def human_seconds(value: Union[str, int], name: Optional[str] = None, none_ok: bool = False) -> Optional[int]:
606
+ """Convert a human readable string to seconds.
607
+
608
+ Parameters
609
+ ----------
610
+ value : str or int
611
+ The value to convert
612
+ name : str, optional
613
+ The name of the value
614
+ none_ok : bool, optional
615
+ Whether None is an acceptable value
616
+
617
+ Returns
618
+ -------
619
+ int or None
620
+ The converted value in seconds
621
+ """
457
622
  units = dict(s=1, m=60, h=3600, d=86400, w=86400 * 7)
458
623
  return human_to_number(value, name, units, none_ok)
459
624
 
460
625
 
461
- def as_seconds(value, name=None, none_ok=False):
626
+ def as_seconds(value: Union[str, int], name: Optional[str] = None, none_ok: bool = False) -> Optional[int]:
627
+ """Deprecated function to convert a human readable string to seconds.
628
+
629
+ Parameters
630
+ ----------
631
+ value : str or int
632
+ The value to convert
633
+ name : str, optional
634
+ The name of the value
635
+ none_ok : bool, optional
636
+ Whether None is an acceptable value
637
+
638
+ Returns
639
+ -------
640
+ int or None
641
+ The converted value in seconds
642
+ """
462
643
  warnings.warn(
463
644
  "Function as_seconds is deprecated and will be removed in a future version. Use human_seconds instead.",
464
645
  category=DeprecationWarning,
@@ -467,12 +648,44 @@ def as_seconds(value, name=None, none_ok=False):
467
648
  return human_seconds(value, name, none_ok)
468
649
 
469
650
 
470
- def human_to_percent(value, name=None, none_ok=False):
651
+ def human_to_percent(value: Union[str, int], name: Optional[str] = None, none_ok: bool = False) -> Optional[int]:
652
+ """Convert a human readable string to a percentage.
653
+
654
+ Parameters
655
+ ----------
656
+ value : str or int
657
+ The value to convert
658
+ name : str, optional
659
+ The name of the value
660
+ none_ok : bool, optional
661
+ Whether None is an acceptable value
662
+
663
+ Returns
664
+ -------
665
+ int or None
666
+ The converted value in percentage
667
+ """
471
668
  units = {"%": 1}
472
669
  return human_to_number(value, name, units, none_ok)
473
670
 
474
671
 
475
- def as_percent(value, name=None, none_ok=False):
672
+ def as_percent(value: Union[str, int], name: Optional[str] = None, none_ok: bool = False) -> Optional[int]:
673
+ """Deprecated function to convert a human readable string to a percentage.
674
+
675
+ Parameters
676
+ ----------
677
+ value : str or int
678
+ The value to convert
679
+ name : str, optional
680
+ The name of the value
681
+ none_ok : bool, optional
682
+ Whether None is an acceptable value
683
+
684
+ Returns
685
+ -------
686
+ int or None
687
+ The converted value in percentage
688
+ """
476
689
  warnings.warn(
477
690
  "Function as_percent is deprecated and will be removed in a future version. Use human_to_percent instead.",
478
691
  category=DeprecationWarning,
@@ -481,7 +694,23 @@ def as_percent(value, name=None, none_ok=False):
481
694
  return human_to_percent(value, name, none_ok)
482
695
 
483
696
 
484
- def human_to_bytes(value, name=None, none_ok=False):
697
+ def human_to_bytes(value: Union[str, int], name: Optional[str] = None, none_ok: bool = False) -> Optional[int]:
698
+ """Convert a human readable string to bytes.
699
+
700
+ Parameters
701
+ ----------
702
+ value : str or int
703
+ The value to convert
704
+ name : str, optional
705
+ The name of the value
706
+ none_ok : bool, optional
707
+ Whether None is an acceptable value
708
+
709
+ Returns
710
+ -------
711
+ int or None
712
+ The converted value in bytes
713
+ """
485
714
  units = {}
486
715
  n = 1
487
716
  for u in "KMGTP":
@@ -492,7 +721,23 @@ def human_to_bytes(value, name=None, none_ok=False):
492
721
  return human_to_number(value, name, units, none_ok)
493
722
 
494
723
 
495
- def as_bytes(value, name=None, none_ok=False):
724
+ def as_bytes(value: Union[str, int], name: Optional[str] = None, none_ok: bool = False) -> Optional[int]:
725
+ """Deprecated function to convert a human readable string to bytes.
726
+
727
+ Parameters
728
+ ----------
729
+ value : str or int
730
+ The value to convert
731
+ name : str, optional
732
+ The name of the value
733
+ none_ok : bool, optional
734
+ Whether None is an acceptable value
735
+
736
+ Returns
737
+ -------
738
+ int or None
739
+ The converted value in bytes
740
+ """
496
741
  warnings.warn(
497
742
  "Function as_bytes is deprecated and will be removed in a future version. Use human_to_bytes instead.",
498
743
  category=DeprecationWarning,
@@ -501,7 +746,23 @@ def as_bytes(value, name=None, none_ok=False):
501
746
  return human_to_bytes(value, name, none_ok)
502
747
 
503
748
 
504
- def human_to_timedelta(value, name=None, none_ok=False):
749
+ def human_to_timedelta(value: str, name: Optional[str] = None, none_ok: bool = False) -> datetime.timedelta:
750
+ """Convert a human readable string to a timedelta.
751
+
752
+ Parameters
753
+ ----------
754
+ value : str
755
+ The value to convert
756
+ name : str, optional
757
+ The name of the value
758
+ none_ok : bool, optional
759
+ Whether None is an acceptable value
760
+
761
+ Returns
762
+ -------
763
+ datetime.timedelta
764
+ The converted value as a timedelta
765
+ """
505
766
  if value is None and none_ok:
506
767
  return None
507
768
 
@@ -537,7 +798,23 @@ def human_to_timedelta(value, name=None, none_ok=False):
537
798
  )
538
799
 
539
800
 
540
- def as_timedelta(value, name=None, none_ok=False):
801
+ def as_timedelta(value: str, name: Optional[str] = None, none_ok: bool = False) -> datetime.timedelta:
802
+ """Deprecated function to convert a human readable string to a timedelta.
803
+
804
+ Parameters
805
+ ----------
806
+ value : str
807
+ The value to convert
808
+ name : str, optional
809
+ The name of the value
810
+ none_ok : bool, optional
811
+ Whether None is an acceptable value
812
+
813
+ Returns
814
+ -------
815
+ datetime.timedelta
816
+ The converted value as a timedelta
817
+ """
541
818
  warnings.warn(
542
819
  "Function as_timedelta is deprecated and will be removed in a future version. Use human_to_timedelta instead.",
543
820
  category=DeprecationWarning,
@@ -546,14 +823,26 @@ def as_timedelta(value, name=None, none_ok=False):
546
823
  return human_to_timedelta(value, name, none_ok)
547
824
 
548
825
 
549
- def rounded_datetime(d):
826
+ def rounded_datetime(d: datetime.datetime) -> datetime.datetime:
827
+ """Round a datetime to the nearest second.
828
+
829
+ Parameters
830
+ ----------
831
+ d : datetime.datetime
832
+ The datetime to round
833
+
834
+ Returns
835
+ -------
836
+ datetime.datetime
837
+ The rounded datetime
838
+ """
550
839
  if float(d.microsecond) / 1000.0 / 1000.0 >= 0.5:
551
840
  d = d + datetime.timedelta(seconds=1)
552
841
  d = d.replace(microsecond=0)
553
842
  return d
554
843
 
555
844
 
556
- def json_pretty_dump(obj, max_line_length=120, default=str) -> str:
845
+ def json_pretty_dump(obj: Any, max_line_length: int = 120, default: Callable = str) -> str:
557
846
  """Custom JSON dump function that keeps dicts and lists on one line if they are short enough.
558
847
 
559
848
  Parameters
@@ -571,7 +860,7 @@ def json_pretty_dump(obj, max_line_length=120, default=str) -> str:
571
860
  JSON string.
572
861
  """
573
862
 
574
- def _format_json(obj, indent_level=0):
863
+ def _format_json(obj: Any, indent_level: int = 0) -> str:
575
864
  """Helper function to format JSON objects with custom pretty-print rules.
576
865
 
577
866
  Parameters
@@ -609,19 +898,19 @@ def json_pretty_dump(obj, max_line_length=120, default=str) -> str:
609
898
  return _format_json(obj)
610
899
 
611
900
 
612
- def shorten_list(lst, max_length=5) -> list:
901
+ def shorten_list(lst: Union[List[Any], Tuple[Any]], max_length: int = 5) -> Union[List[Any], Tuple[Any]]:
613
902
  """Shorten a list to a maximum length.
614
903
 
615
904
  Parameters
616
905
  ----------
617
- lst : list
906
+ lst : list or tuple
618
907
  The list to be shortened.
619
908
  max_length : int, optional
620
909
  Maximum length of the shortened list. Default is 5.
621
910
 
622
911
  Returns
623
912
  -------
624
- list
913
+ list or tuple
625
914
  Shortened list.
626
915
  """
627
916
  if len(lst) <= max_length:
@@ -634,7 +923,23 @@ def shorten_list(lst, max_length=5) -> list:
634
923
  return result
635
924
 
636
925
 
637
- def _compress_dates(dates):
926
+ def _compress_dates(
927
+ dates: List[datetime.datetime],
928
+ ) -> Generator[
929
+ Union[List[datetime.datetime], Tuple[datetime.datetime, datetime.datetime, datetime.timedelta]], None, None
930
+ ]:
931
+ """Compress a list of dates into a more compact representation.
932
+
933
+ Parameters
934
+ ----------
935
+ dates : list of datetime.datetime
936
+ The list of dates to compress
937
+
938
+ Returns
939
+ -------
940
+ list or tuple
941
+ The compressed dates
942
+ """
638
943
  dates = sorted(dates)
639
944
  if len(dates) < 3:
640
945
  yield dates
@@ -654,7 +959,7 @@ def _compress_dates(dates):
654
959
  yield from _compress_dates([curr] + dates)
655
960
 
656
961
 
657
- def compress_dates(dates) -> str:
962
+ def compress_dates(dates: List[Union[datetime.datetime, str]]) -> str:
658
963
  """Compress a list of dates into a human-readable format.
659
964
 
660
965
  Parameters
@@ -680,7 +985,7 @@ def compress_dates(dates) -> str:
680
985
  return result
681
986
 
682
987
 
683
- def print_dates(dates) -> None:
988
+ def print_dates(dates: List[Union[datetime.datetime, str]]) -> None:
684
989
  """Print a list of dates in a human-readable format.
685
990
 
686
991
  Parameters
@@ -691,7 +996,7 @@ def print_dates(dates) -> None:
691
996
  print(compress_dates(dates))
692
997
 
693
998
 
694
- def make_list_int(value) -> list:
999
+ def make_list_int(value: Union[str, List[int], Tuple[int], int]) -> List[int]:
695
1000
  """Convert a string like "1/2/3" or "1/to/3" or "1/to/10/by/2" to a list of integers.
696
1001
 
697
1002
  Parameters