anemoi-utils 0.3.14__tar.gz → 0.3.15__tar.gz

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 (57) hide show
  1. {anemoi_utils-0.3.14 → anemoi_utils-0.3.15}/PKG-INFO +1 -1
  2. {anemoi_utils-0.3.14 → anemoi_utils-0.3.15}/src/anemoi/utils/_version.py +2 -2
  3. {anemoi_utils-0.3.14 → anemoi_utils-0.3.15}/src/anemoi/utils/dates.py +92 -85
  4. {anemoi_utils-0.3.14 → anemoi_utils-0.3.15}/src/anemoi/utils/humanize.py +59 -0
  5. {anemoi_utils-0.3.14 → anemoi_utils-0.3.15}/src/anemoi_utils.egg-info/PKG-INFO +1 -1
  6. {anemoi_utils-0.3.14 → anemoi_utils-0.3.15}/src/anemoi_utils.egg-info/SOURCES.txt +1 -0
  7. anemoi_utils-0.3.15/tests/test_frequency.py +37 -0
  8. {anemoi_utils-0.3.14 → anemoi_utils-0.3.15}/.github/workflows/changelog-pr-update.yml +0 -0
  9. {anemoi_utils-0.3.14 → anemoi_utils-0.3.15}/.github/workflows/ci.yml +0 -0
  10. {anemoi_utils-0.3.14 → anemoi_utils-0.3.15}/.github/workflows/label-public-pr.yml +0 -0
  11. {anemoi_utils-0.3.14 → anemoi_utils-0.3.15}/.github/workflows/python-publish.yml +0 -0
  12. {anemoi_utils-0.3.14 → anemoi_utils-0.3.15}/.github/workflows/readthedocs-pr-update.yml +0 -0
  13. {anemoi_utils-0.3.14 → anemoi_utils-0.3.15}/.gitignore +0 -0
  14. {anemoi_utils-0.3.14 → anemoi_utils-0.3.15}/.pre-commit-config.yaml +0 -0
  15. {anemoi_utils-0.3.14 → anemoi_utils-0.3.15}/.readthedocs.yaml +0 -0
  16. {anemoi_utils-0.3.14 → anemoi_utils-0.3.15}/CHANGELOG.md +0 -0
  17. {anemoi_utils-0.3.14 → anemoi_utils-0.3.15}/LICENSE +0 -0
  18. {anemoi_utils-0.3.14 → anemoi_utils-0.3.15}/README.md +0 -0
  19. {anemoi_utils-0.3.14 → anemoi_utils-0.3.15}/docs/Makefile +0 -0
  20. {anemoi_utils-0.3.14 → anemoi_utils-0.3.15}/docs/_static/logo.png +0 -0
  21. {anemoi_utils-0.3.14 → anemoi_utils-0.3.15}/docs/_static/style.css +0 -0
  22. {anemoi_utils-0.3.14 → anemoi_utils-0.3.15}/docs/_templates/.gitkeep +0 -0
  23. {anemoi_utils-0.3.14 → anemoi_utils-0.3.15}/docs/conf.py +0 -0
  24. {anemoi_utils-0.3.14 → anemoi_utils-0.3.15}/docs/index.rst +0 -0
  25. {anemoi_utils-0.3.14 → anemoi_utils-0.3.15}/docs/installing.rst +0 -0
  26. {anemoi_utils-0.3.14 → anemoi_utils-0.3.15}/docs/modules/checkpoints.rst +0 -0
  27. {anemoi_utils-0.3.14 → anemoi_utils-0.3.15}/docs/modules/config.rst +0 -0
  28. {anemoi_utils-0.3.14 → anemoi_utils-0.3.15}/docs/modules/dates.rst +0 -0
  29. {anemoi_utils-0.3.14 → anemoi_utils-0.3.15}/docs/modules/grib.rst +0 -0
  30. {anemoi_utils-0.3.14 → anemoi_utils-0.3.15}/docs/modules/humanize.rst +0 -0
  31. {anemoi_utils-0.3.14 → anemoi_utils-0.3.15}/docs/modules/provenance.rst +0 -0
  32. {anemoi_utils-0.3.14 → anemoi_utils-0.3.15}/docs/modules/s3.rst +0 -0
  33. {anemoi_utils-0.3.14 → anemoi_utils-0.3.15}/docs/modules/text.rst +0 -0
  34. {anemoi_utils-0.3.14 → anemoi_utils-0.3.15}/pyproject.toml +0 -0
  35. {anemoi_utils-0.3.14 → anemoi_utils-0.3.15}/setup.cfg +0 -0
  36. {anemoi_utils-0.3.14 → anemoi_utils-0.3.15}/src/anemoi/utils/__init__.py +0 -0
  37. {anemoi_utils-0.3.14 → anemoi_utils-0.3.15}/src/anemoi/utils/__main__.py +0 -0
  38. {anemoi_utils-0.3.14 → anemoi_utils-0.3.15}/src/anemoi/utils/caching.py +0 -0
  39. {anemoi_utils-0.3.14 → anemoi_utils-0.3.15}/src/anemoi/utils/checkpoints.py +0 -0
  40. {anemoi_utils-0.3.14 → anemoi_utils-0.3.15}/src/anemoi/utils/cli.py +0 -0
  41. {anemoi_utils-0.3.14 → anemoi_utils-0.3.15}/src/anemoi/utils/commands/__init__.py +0 -0
  42. {anemoi_utils-0.3.14 → anemoi_utils-0.3.15}/src/anemoi/utils/commands/config.py +0 -0
  43. {anemoi_utils-0.3.14 → anemoi_utils-0.3.15}/src/anemoi/utils/config.py +0 -0
  44. {anemoi_utils-0.3.14 → anemoi_utils-0.3.15}/src/anemoi/utils/grib.py +0 -0
  45. {anemoi_utils-0.3.14 → anemoi_utils-0.3.15}/src/anemoi/utils/hindcasts.py +0 -0
  46. {anemoi_utils-0.3.14 → anemoi_utils-0.3.15}/src/anemoi/utils/mars/__init__.py +0 -0
  47. {anemoi_utils-0.3.14 → anemoi_utils-0.3.15}/src/anemoi/utils/mars/mars.yaml +0 -0
  48. {anemoi_utils-0.3.14 → anemoi_utils-0.3.15}/src/anemoi/utils/provenance.py +0 -0
  49. {anemoi_utils-0.3.14 → anemoi_utils-0.3.15}/src/anemoi/utils/s3.py +0 -0
  50. {anemoi_utils-0.3.14 → anemoi_utils-0.3.15}/src/anemoi/utils/text.py +0 -0
  51. {anemoi_utils-0.3.14 → anemoi_utils-0.3.15}/src/anemoi/utils/timer.py +0 -0
  52. {anemoi_utils-0.3.14 → anemoi_utils-0.3.15}/src/anemoi_utils.egg-info/dependency_links.txt +0 -0
  53. {anemoi_utils-0.3.14 → anemoi_utils-0.3.15}/src/anemoi_utils.egg-info/entry_points.txt +0 -0
  54. {anemoi_utils-0.3.14 → anemoi_utils-0.3.15}/src/anemoi_utils.egg-info/requires.txt +0 -0
  55. {anemoi_utils-0.3.14 → anemoi_utils-0.3.15}/src/anemoi_utils.egg-info/top_level.txt +0 -0
  56. {anemoi_utils-0.3.14 → anemoi_utils-0.3.15}/tests/test_dates.py +0 -0
  57. {anemoi_utils-0.3.14 → anemoi_utils-0.3.15}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: anemoi-utils
3
- Version: 0.3.14
3
+ Version: 0.3.15
4
4
  Summary: A package to hold various functions to support training of ML models on ECMWF data.
5
5
  Author-email: "European Centre for Medium-Range Weather Forecasts (ECMWF)" <software.support@ecmwf.int>
6
6
  License: Apache License
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '0.3.14'
16
- __version_tuple__ = version_tuple = (0, 3, 14)
15
+ __version__ = version = '0.3.15'
16
+ __version_tuple__ = version_tuple = (0, 3, 15)
@@ -15,17 +15,7 @@ import isodate
15
15
  from .hindcasts import HindcastDatesTimes
16
16
 
17
17
 
18
- def normalise_frequency(frequency):
19
- if isinstance(frequency, int):
20
- return frequency
21
- assert isinstance(frequency, str), (type(frequency), frequency)
22
-
23
- unit = frequency[-1].lower()
24
- v = int(frequency[:-1])
25
- return {"h": v, "d": v * 24}[unit]
26
-
27
-
28
- def no_time_zone(date):
18
+ def _no_time_zone(date):
29
19
  """Remove time zone information from a date.
30
20
 
31
21
  Parameters
@@ -43,13 +33,15 @@ def no_time_zone(date):
43
33
 
44
34
 
45
35
  # this function is use in anemoi-datasets
46
- def as_datetime(date):
36
+ def as_datetime(date, keep_time_zone=False):
47
37
  """Convert a date to a datetime object, removing any time zone information.
48
38
 
49
39
  Parameters
50
40
  ----------
51
41
  date : datetime.date or datetime.datetime or str
52
42
  The date to convert.
43
+ keep_time_zone : bool, optional
44
+ If True, the time zone information is kept, by default False.
53
45
 
54
46
  Returns
55
47
  -------
@@ -57,57 +49,98 @@ def as_datetime(date):
57
49
  The datetime object.
58
50
  """
59
51
 
52
+ tidy = _no_time_zone if not keep_time_zone else lambda x: x
53
+
60
54
  if isinstance(date, datetime.datetime):
61
- return no_time_zone(date)
55
+ return tidy(date)
62
56
 
63
57
  if isinstance(date, datetime.date):
64
- return no_time_zone(datetime.datetime(date.year, date.month, date.day))
58
+ return tidy(datetime.datetime(date.year, date.month, date.day))
65
59
 
66
60
  if isinstance(date, str):
67
- return no_time_zone(datetime.datetime.fromisoformat(date))
61
+ return tidy(datetime.datetime.fromisoformat(date))
68
62
 
69
63
  raise ValueError(f"Invalid date type: {type(date)}")
70
64
 
71
65
 
72
- def _compress_dates(dates):
73
- dates = sorted(dates)
74
- if len(dates) < 3:
75
- yield dates
76
- return
66
+ def frequency_to_timedelta(frequency):
67
+ """Convert a frequency to a timedelta object.
68
+
69
+ Parameters
70
+ ----------
71
+ frequency : int or str or datetime.timedelta
72
+ The frequency to convert. If an integer, it is assumed to be in hours. If a string, it can be in the format:
73
+
74
+ - "1h" for 1 hour
75
+ - "1d" for 1 day
76
+ - "1m" for 1 minute
77
+ - "1s" for 1 second
78
+ - "1:30" for 1 hour and 30 minutes
79
+ - "1:30:10" for 1 hour, 30 minutes and 10 seconds
80
+ - "PT10M" for 10 minutes (ISO8601)
77
81
 
78
- prev = first = dates.pop(0)
79
- curr = dates.pop(0)
80
- delta = curr - prev
81
- while curr - prev == delta:
82
- prev = curr
83
- if not dates:
84
- break
85
- curr = dates.pop(0)
82
+ If a timedelta object is provided, it is returned as is.
83
+
84
+ Returns
85
+ -------
86
+ datetime.timedelta
87
+ The timedelta object.
88
+
89
+ Raises
90
+ ------
91
+ ValueError
92
+ Exception raised if the frequency cannot be converted to a timedelta.
93
+ """
94
+
95
+ if isinstance(frequency, datetime.timedelta):
96
+ return frequency
97
+
98
+ if isinstance(frequency, int):
99
+ return datetime.timedelta(hours=frequency)
86
100
 
87
- yield (first, prev, delta)
88
- if dates:
89
- yield from _compress_dates([curr] + dates)
101
+ assert isinstance(frequency, str), (type(frequency), frequency)
90
102
 
103
+ try:
104
+ return frequency_to_timedelta(int(frequency))
105
+ except ValueError:
106
+ pass
91
107
 
92
- def compress_dates(dates):
93
- dates = [as_datetime(_) for _ in dates]
94
- result = []
108
+ if re.match(r"^\d+[hdms]$", frequency, re.IGNORECASE):
109
+ unit = frequency[-1].lower()
110
+ v = int(frequency[:-1])
111
+ unit = {"h": "hours", "d": "days", "s": "seconds", "m": "minutes"}[unit]
112
+ return datetime.timedelta(**{unit: v})
95
113
 
96
- for n in _compress_dates(dates):
97
- if isinstance(n, list):
98
- result.extend([str(_) for _ in n])
99
- else:
100
- result.append(" ".join([str(n[0]), "to", str(n[1]), "by", str(n[2])]))
114
+ m = frequency.split(":")
115
+ if len(m) == 2:
116
+ return datetime.timedelta(hours=int(m[0]), minutes=int(m[1]))
101
117
 
102
- return result
118
+ if len(m) == 3:
119
+ return datetime.timedelta(hours=int(m[0]), minutes=int(m[1]), seconds=int(m[2]))
103
120
 
121
+ # ISO8601
122
+ try:
123
+ return isodate.parse_duration(frequency)
124
+ except isodate.isoerror.ISO8601Error:
125
+ pass
104
126
 
105
- def print_dates(dates):
106
- print(compress_dates(dates))
127
+ raise ValueError(f"Cannot convert frequency {frequency} to timedelta")
107
128
 
108
129
 
109
130
  def frequency_to_string(frequency):
110
- # TODO: use iso8601
131
+ """Convert a frequency (i.e. a datetime.timedelta) to a string.
132
+
133
+ Parameters
134
+ ----------
135
+ frequency : datetime.timedelta
136
+ The frequency to convert.
137
+
138
+ Returns
139
+ -------
140
+ str
141
+ A string representation of the frequency.
142
+ """
143
+
111
144
  frequency = frequency_to_timedelta(frequency)
112
145
 
113
146
  total_seconds = frequency.total_seconds()
@@ -141,48 +174,23 @@ def frequency_to_string(frequency):
141
174
  return str(frequency)
142
175
 
143
176
 
144
- def frequency_to_timedelta(frequency):
145
- # TODO: use iso8601 or check pytimeparse
146
-
147
- if isinstance(frequency, datetime.timedelta):
148
- return frequency
177
+ def frequency_to_seconds(frequency):
178
+ """Convert a frequency to seconds.
149
179
 
150
- if isinstance(frequency, int):
151
- return datetime.timedelta(hours=frequency)
152
-
153
- assert isinstance(frequency, str), (type(frequency), frequency)
154
-
155
- try:
156
- return frequency_to_timedelta(int(frequency))
157
- except ValueError:
158
- pass
159
-
160
- if re.match(r"^\d+[hdms]$", frequency, re.IGNORECASE):
161
- unit = frequency[-1].lower()
162
- v = int(frequency[:-1])
163
- unit = {"h": "hours", "d": "days", "s": "seconds", "m": "minutes"}[unit]
164
- return datetime.timedelta(**{unit: v})
165
-
166
- m = frequency.split(":")
167
- if len(m) == 2:
168
- return datetime.timedelta(hours=int(m[0]), minutes=int(m[1]))
169
-
170
- if len(m) == 3:
171
- return datetime.timedelta(hours=int(m[0]), minutes=int(m[1]), seconds=int(m[2]))
172
-
173
- # ISO8601
174
- try:
175
- return isodate.parse_duration(frequency)
176
- except isodate.isoerror.ISO8601Error:
177
- pass
178
-
179
- raise ValueError(f"Cannot convert frequency {frequency} to timedelta")
180
+ Parameters
181
+ ----------
182
+ frequency : _type_
183
+ _description_
180
184
 
185
+ Returns
186
+ -------
187
+ int
188
+ Number of seconds.
189
+ """
181
190
 
182
- def normalize_date(x):
183
- if isinstance(x, str):
184
- return no_time_zone(datetime.datetime.fromisoformat(x))
185
- return x
191
+ result = frequency_to_timedelta(frequency).total_seconds()
192
+ assert int(result) == result, result
193
+ return int(result)
186
194
 
187
195
 
188
196
  DOW = {
@@ -261,7 +269,7 @@ class DateTimes:
261
269
  """
262
270
  self.start = as_datetime(start)
263
271
  self.end = as_datetime(end)
264
- self.increment = datetime.timedelta(hours=increment)
272
+ self.increment = frequency_to_timedelta(increment)
265
273
  self.day_of_month = _make_day(day_of_month)
266
274
  self.day_of_week = _make_week(day_of_week)
267
275
  self.calendar_months = _make_months(calendar_months)
@@ -392,8 +400,7 @@ def datetimes_factory(*args, **kwargs):
392
400
 
393
401
  kwargs = kwargs.copy()
394
402
  if "frequency" in kwargs:
395
- freq = kwargs.pop("frequency")
396
- kwargs["increment"] = normalise_frequency(freq)
403
+ kwargs["increment"] = kwargs.pop("frequency")
397
404
  return DateTimes(**kwargs)
398
405
 
399
406
  if not any((isinstance(x, dict) or isinstance(x, list)) for x in args):
@@ -15,6 +15,8 @@ import re
15
15
  import warnings
16
16
  from collections import defaultdict
17
17
 
18
+ from anemoi.utils.dates import as_datetime
19
+
18
20
 
19
21
  def bytes_to_human(n: float) -> str:
20
22
  """Convert a number of bytes to a human readable string
@@ -625,3 +627,60 @@ def shorten_list(lst, max_length=5):
625
627
  if isinstance(lst, tuple):
626
628
  return tuple(result)
627
629
  return result
630
+
631
+
632
+ def _compress_dates(dates):
633
+ dates = sorted(dates)
634
+ if len(dates) < 3:
635
+ yield dates
636
+ return
637
+
638
+ prev = first = dates.pop(0)
639
+ curr = dates.pop(0)
640
+ delta = curr - prev
641
+ while curr - prev == delta:
642
+ prev = curr
643
+ if not dates:
644
+ break
645
+ curr = dates.pop(0)
646
+
647
+ yield (first, prev, delta)
648
+ if dates:
649
+ yield from _compress_dates([curr] + dates)
650
+
651
+
652
+ def compress_dates(dates):
653
+ """Compress a list of dates into a human-readable format.
654
+
655
+ Parameters
656
+ ----------
657
+ dates : list
658
+ A list of dates, as datetime objects or strings.
659
+
660
+ Returns
661
+ -------
662
+ str
663
+ A human-readable string representing the compressed dates.
664
+ """
665
+
666
+ dates = [as_datetime(_) for _ in dates]
667
+ result = []
668
+
669
+ for n in _compress_dates(dates):
670
+ if isinstance(n, list):
671
+ result.extend([str(_) for _ in n])
672
+ else:
673
+ result.append(" ".join([str(n[0]), "to", str(n[1]), "by", str(n[2])]))
674
+
675
+ return result
676
+
677
+
678
+ def print_dates(dates):
679
+ """Print a list of dates in a human-readable format.
680
+
681
+ Parameters
682
+ ----------
683
+ dates : list
684
+ A list of dates, as datetime objects or strings.
685
+ """
686
+ print(compress_dates(dates))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: anemoi-utils
3
- Version: 0.3.14
3
+ Version: 0.3.15
4
4
  Summary: A package to hold various functions to support training of ML models on ECMWF data.
5
5
  Author-email: "European Centre for Medium-Range Weather Forecasts (ECMWF)" <software.support@ecmwf.int>
6
6
  License: Apache License
@@ -51,4 +51,5 @@ src/anemoi_utils.egg-info/entry_points.txt
51
51
  src/anemoi_utils.egg-info/requires.txt
52
52
  src/anemoi_utils.egg-info/top_level.txt
53
53
  tests/test_dates.py
54
+ tests/test_frequency.py
54
55
  tests/test_utils.py
@@ -0,0 +1,37 @@
1
+ # (C) Copyright 2023 European Centre for Medium-Range Weather Forecasts.
2
+ # This software is licensed under the terms of the Apache Licence Version 2.0
3
+ # which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
4
+ # In applying this licence, ECMWF does not waive the privileges and immunities
5
+ # granted to it by virtue of its status as an intergovernmental organisation
6
+ # nor does it submit to any jurisdiction.
7
+
8
+ import datetime
9
+
10
+ from anemoi.utils.dates import frequency_to_string
11
+ from anemoi.utils.dates import frequency_to_timedelta
12
+
13
+
14
+ def test_frequency_to_string():
15
+ assert frequency_to_string(datetime.timedelta(hours=1)) == "1h"
16
+ assert frequency_to_string(datetime.timedelta(hours=1, minutes=30)) == "1:30:00"
17
+ assert frequency_to_string(datetime.timedelta(days=10)) == "10d"
18
+ assert frequency_to_string(datetime.timedelta(minutes=10)) == "10m"
19
+ assert frequency_to_string(datetime.timedelta(minutes=90)) == "1:30:00"
20
+
21
+
22
+ def test_frequency_to_timedelta():
23
+ assert frequency_to_timedelta("1s") == datetime.timedelta(seconds=1)
24
+ assert frequency_to_timedelta("3m") == datetime.timedelta(minutes=3)
25
+ assert frequency_to_timedelta("1h") == datetime.timedelta(hours=1)
26
+ assert frequency_to_timedelta("3d") == datetime.timedelta(days=3)
27
+ assert frequency_to_timedelta("90m") == datetime.timedelta(hours=1, minutes=30)
28
+ assert frequency_to_timedelta("0:30") == datetime.timedelta(minutes=30)
29
+ assert frequency_to_timedelta("0:30:10") == datetime.timedelta(minutes=30, seconds=10)
30
+ assert frequency_to_timedelta("1:30:10") == datetime.timedelta(hours=1, minutes=30, seconds=10)
31
+
32
+ assert frequency_to_timedelta("PT10M") == datetime.timedelta(minutes=10)
33
+
34
+
35
+ if __name__ == "__main__":
36
+ test_frequency_to_string()
37
+ test_frequency_to_timedelta()
File without changes
File without changes
File without changes
File without changes