anemoi-utils 0.4.21__py3-none-any.whl → 0.4.23__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/_version.py CHANGED
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '0.4.21'
21
- __version_tuple__ = version_tuple = (0, 4, 21)
20
+ __version__ = version = '0.4.23'
21
+ __version_tuple__ = version_tuple = (0, 4, 23)
@@ -47,8 +47,11 @@ def lookup_git_repo(path: str) -> Optional[Any]:
47
47
  Repo, optional
48
48
  The git repository if found, otherwise None.
49
49
  """
50
- from git import InvalidGitRepositoryError
51
- from git import Repo
50
+ try:
51
+ from git import InvalidGitRepositoryError
52
+ from git import Repo
53
+ except ImportError:
54
+ return None
52
55
 
53
56
  while path != "/":
54
57
  try:
anemoi/utils/remote/s3.py CHANGED
@@ -1,10 +1,13 @@
1
- # (C) Copyright 2024 European Centre for Medium-Range Weather Forecasts.
1
+ # (C) Copyright 2024-2025 Anemoi contributors.
2
+ #
2
3
  # This software is licensed under the terms of the Apache Licence Version 2.0
3
4
  # which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
5
+ #
4
6
  # In applying this licence, ECMWF does not waive the privileges and immunities
5
7
  # granted to it by virtue of its status as an intergovernmental organisation
6
8
  # nor does it submit to any jurisdiction.
7
9
 
10
+
8
11
  """This module provides functions to upload, download, list and delete files and folders on S3.
9
12
  The functions of this package expect that the AWS credentials are set up in the environment
10
13
  typicaly by setting the `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` environment variables or
@@ -67,12 +70,14 @@ def s3_client(bucket: str, region: str = None) -> Any:
67
70
 
68
71
  key = f"{bucket}-{region}"
69
72
 
70
- boto3_config = dict(max_pool_connections=25)
71
-
72
73
  if key in thread_local.s3_clients:
73
74
  return thread_local.s3_clients[key]
74
75
 
75
- boto3_config = dict(max_pool_connections=25)
76
+ boto3_config = dict(
77
+ max_pool_connections=25,
78
+ request_checksum_calculation="when_required",
79
+ response_checksum_validation="when_required",
80
+ )
76
81
 
77
82
  if region:
78
83
  # This is using AWS
@@ -162,7 +167,14 @@ class S3Upload(BaseUpload):
162
167
  # delete(target)
163
168
 
164
169
  def _transfer_file(
165
- self, source: str, target: str, overwrite: bool, resume: bool, verbosity: int, threads: int, config: dict = None
170
+ self,
171
+ source: str,
172
+ target: str,
173
+ overwrite: bool,
174
+ resume: bool,
175
+ verbosity: int,
176
+ threads: int,
177
+ config: dict = None,
166
178
  ) -> int:
167
179
  """Transfer a file to S3.
168
180
 
@@ -227,7 +239,13 @@ class S3Upload(BaseUpload):
227
239
 
228
240
  if verbosity > 0:
229
241
  with tqdm.tqdm(total=size, unit="B", unit_scale=True, unit_divisor=1024, leave=False) as pbar:
230
- s3.upload_file(source, bucket, key, Callback=lambda x: pbar.update(x), Config=config)
242
+ s3.upload_file(
243
+ source,
244
+ bucket,
245
+ key,
246
+ Callback=lambda x: pbar.update(x),
247
+ Config=config,
248
+ )
231
249
  else:
232
250
  s3.upload_file(source, bucket, key, Config=config)
233
251
 
@@ -326,7 +344,14 @@ class S3Download(BaseDownload):
326
344
  return s3_object["Size"]
327
345
 
328
346
  def _transfer_file(
329
- self, source: str, target: str, overwrite: bool, resume: bool, verbosity: int, threads: int, config: dict = None
347
+ self,
348
+ source: str,
349
+ target: str,
350
+ overwrite: bool,
351
+ resume: bool,
352
+ verbosity: int,
353
+ threads: int,
354
+ config: dict = None,
330
355
  ) -> int:
331
356
  """Transfer a file from S3 to the local filesystem.
332
357
 
@@ -397,7 +422,13 @@ class S3Download(BaseDownload):
397
422
 
398
423
  if verbosity > 0:
399
424
  with tqdm.tqdm(total=size, unit="B", unit_scale=True, unit_divisor=1024, leave=False) as pbar:
400
- s3.download_file(bucket, key, target, Callback=lambda x: pbar.update(x), Config=config)
425
+ s3.download_file(
426
+ bucket,
427
+ key,
428
+ target,
429
+ Callback=lambda x: pbar.update(x),
430
+ Config=config,
431
+ )
401
432
  else:
402
433
  s3.download_file(bucket, key, target, Config=config)
403
434
 
@@ -524,7 +555,9 @@ def list_folder(folder: str) -> Iterable:
524
555
 
525
556
  for page in paginator.paginate(Bucket=bucket, Prefix=prefix, Delimiter="/"):
526
557
  if "CommonPrefixes" in page:
527
- yield from [folder + _["Prefix"] for _ in page.get("CommonPrefixes")]
558
+ yield from [folder + _["Prefix"] for _ in page.get("CommonPrefixes") if _["Prefix"] != "/"]
559
+ if "Contents" in page:
560
+ yield from [folder + _["Key"] for _ in page.get("Contents")]
528
561
 
529
562
 
530
563
  def object_info(target: str) -> dict:
anemoi/utils/testing.py CHANGED
@@ -68,6 +68,23 @@ def _check_path(path: str) -> None:
68
68
  assert not path.startswith("."), f"Path '{path}' should not start with '.'"
69
69
 
70
70
 
71
+ def _temporary_directory_for_test_data(path: str) -> str:
72
+ """Get the temporary directory for a test dataset.
73
+
74
+ Parameters
75
+ ----------
76
+ path : str
77
+ The relative path to the test data in the object store.
78
+
79
+ Returns
80
+ -------
81
+ str
82
+ The path to the temporary directory.
83
+ """
84
+ _check_path(path)
85
+ return os.path.normpath(os.path.join(_temporary_directory(), path))
86
+
87
+
71
88
  def url_for_test_data(path: str) -> str:
72
89
  """Generate the URL for the test data based on the given path.
73
90
 
@@ -101,12 +118,11 @@ def get_test_data(path: str, gzipped=False) -> str:
101
118
  str
102
119
  The local path to the downloaded test data.
103
120
  """
104
- _check_path(path)
105
121
 
106
122
  if _offline():
107
123
  raise RuntimeError("Offline mode: cannot download test data, add @pytest.mark.skipif(not offline(),...)")
108
124
 
109
- target = os.path.normpath(os.path.join(_temporary_directory(), path))
125
+ target = _temporary_directory_for_test_data(path)
110
126
  with lock:
111
127
  if os.path.exists(target):
112
128
  return target
@@ -153,12 +169,18 @@ def get_test_archive(path: str, extension=".extracted") -> str:
153
169
 
154
170
  with lock:
155
171
 
172
+ target = _temporary_directory_for_test_data(path) + extension
173
+
174
+ if os.path.exists(target):
175
+ return target
176
+
156
177
  archive = get_test_data(path)
157
- target = archive + extension
158
178
 
159
179
  shutil.unpack_archive(archive, os.path.dirname(target) + ".tmp")
160
180
  os.rename(os.path.dirname(target) + ".tmp", target)
161
181
 
182
+ os.remove(archive)
183
+
162
184
  return target
163
185
 
164
186
 
@@ -239,12 +261,12 @@ def _run_slow_tests() -> bool:
239
261
  @lru_cache(maxsize=None)
240
262
  def _offline() -> bool:
241
263
  """Check if we are offline."""
242
-
243
- import socket
264
+ from urllib import request
244
265
 
245
266
  try:
246
- socket.create_connection(("anemoi.ecmwf.int", 443), timeout=5)
247
- except OSError:
267
+ request.urlopen("https://anemoi.ecmwf.int", timeout=1)
268
+ return False
269
+ except request.URLError:
248
270
  return True
249
271
 
250
272
  return False
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: anemoi-utils
3
- Version: 0.4.21
3
+ Version: 0.4.23
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
@@ -252,7 +252,7 @@ Provides-Extra: provenance
252
252
  Requires-Dist: gitpython; extra == "provenance"
253
253
  Requires-Dist: nvsmi; extra == "provenance"
254
254
  Provides-Extra: s3
255
- Requires-Dist: boto3<1.36; extra == "s3"
255
+ Requires-Dist: boto3>1.36; extra == "s3"
256
256
  Provides-Extra: tests
257
257
  Requires-Dist: pytest; extra == "tests"
258
258
  Provides-Extra: text
@@ -1,6 +1,6 @@
1
1
  anemoi/utils/__init__.py,sha256=uVhpF-VjIl_4mMywOVtgTutgsdIsqz-xdkwxeMhzuag,730
2
2
  anemoi/utils/__main__.py,sha256=6LlE4MYrPvqqrykxXh7XMi50UZteUY59NeM8P9Zs2dU,910
3
- anemoi/utils/_version.py,sha256=jhdlV71JQ5bBRT2wFnZ7TRbXO71DWPBUoBiZyZnIPYQ,513
3
+ anemoi/utils/_version.py,sha256=AnB7uXIf9bJtYv1LZYemC9XOrzt9AeZnOcUDxAsfacA,513
4
4
  anemoi/utils/caching.py,sha256=rXbeAmpBcMbbfN4EVblaHWKicsrtx1otER84FEBtz98,6183
5
5
  anemoi/utils/checkpoints.py,sha256=N4WpAZXa4etrpSEKhHqUUtG2-x9w3FJMHcLO-dDAXPY,9600
6
6
  anemoi/utils/cli.py,sha256=IyZfnSw0u0yYnrjOrzvm2RuuKvDk4cVb8pf8BkaChgA,6209
@@ -13,13 +13,13 @@ anemoi/utils/grids.py,sha256=uYgkU_KIg8FTUiuKV0Pho2swMMeXcSQ9CQe0MFlRr_I,5262
13
13
  anemoi/utils/hindcasts.py,sha256=iYVIxSNFL2HJcc_k1abCFLkpJFGHT8WKRIR4wcAwA3s,2144
14
14
  anemoi/utils/humanize.py,sha256=pjnFJAKHbEAOfcvn8c48kt-8eFy6FGW_U2ruJvfamrA,25189
15
15
  anemoi/utils/logs.py,sha256=naTgrmPwWHD4eekFttXftS4gtcAGYHpCqG4iwYprNDA,1804
16
- anemoi/utils/provenance.py,sha256=xC6mTstF7f_asqtPSrulC7c34xjOSuAxWhkwc3yKhHg,14629
16
+ anemoi/utils/provenance.py,sha256=iTsn4r-VPq2D8tSHPSuAIqG077_opkqMT42G03DRWJg,14690
17
17
  anemoi/utils/registry.py,sha256=e3nOIRyMYQ-mpEvaHAv5tuvMYNbkJ5yz94ns7BnvkjM,9717
18
18
  anemoi/utils/rules.py,sha256=VspUoPmw7tijrs6l_wl4vDjr_zVQsFjx9ITiBSvxgc8,6972
19
19
  anemoi/utils/s3.py,sha256=xMT48kbcelcjjqsaU567WI3oZ5eqo88Rlgyx5ECszAU,4074
20
20
  anemoi/utils/sanitise.py,sha256=ZYGdSX6qihQANr3pHZjbKnoapnzP1KcrWdW1Ul1mOGk,3668
21
21
  anemoi/utils/sanitize.py,sha256=43ZKDcfVpeXSsJ9TFEc9aZnD6oe2cUh151XnDspM98M,462
22
- anemoi/utils/testing.py,sha256=RJJGlIriQode3eWQ3k1I30ZQe0yXjsO8fZGvMuRAjYM,7263
22
+ anemoi/utils/testing.py,sha256=kwgAgLh3exYOTZSaX4xcPFjiMOyQDz-vcAlPcJqMiZk,7784
23
23
  anemoi/utils/text.py,sha256=HkzIvi24obDceFLpJEwBJ9PmPrJUkQN2TrElJ-A87gU,14441
24
24
  anemoi/utils/timer.py,sha256=_leKMYza2faM7JKlGE7LCNy13rbdPnwaCF7PSrI_NmI,3895
25
25
  anemoi/utils/commands/__init__.py,sha256=5u_6EwdqYczIAgJfCwRSyQAYFEqh2ZuHHT57g9g7sdI,808
@@ -29,13 +29,13 @@ anemoi/utils/mars/__init__.py,sha256=b-Lc3L1TAQd9ODs0Z1YSJzgZCO1K_M3DSgx_yd2qXvM
29
29
  anemoi/utils/mars/mars.yaml,sha256=R0dujp75lLA4wCWhPeOQnzJ45WZAYLT8gpx509cBFlc,66
30
30
  anemoi/utils/mars/requests.py,sha256=VFMHBVAAl0_2lOcMBa1lvaKHctN0lDJsI6_U4BucGew,1142
31
31
  anemoi/utils/remote/__init__.py,sha256=swPWHQoh-B6Xq9R489tPw0FykMue7f-bJ8enneFYSYE,20776
32
- anemoi/utils/remote/s3.py,sha256=spQ8l0rwQjLZh9dZu5cOsYIvNwKihQfCJ6YsFYegeqI,17339
32
+ anemoi/utils/remote/s3.py,sha256=dcXcgNddlgxwJ_OpgqOff8EWk-LT2mz20m7FcHHqz7w,17869
33
33
  anemoi/utils/remote/ssh.py,sha256=xNtsawh8okytCKRehkRCVExbHZj-CRUQNormEHglfuw,8088
34
34
  anemoi/utils/schemas/__init__.py,sha256=nkinKlsPLPXEjfTYQT1mpKC4cvs-14w_zBkDRxakwxw,698
35
35
  anemoi/utils/schemas/errors.py,sha256=lgOXzVTYzAE0qWQf3OZ42vCWixv8lilSqLLhzARBmvI,1831
36
- anemoi_utils-0.4.21.dist-info/licenses/LICENSE,sha256=8HznKF1Vi2IvfLsKNE5A2iVyiri3pRjRPvPC9kxs6qk,11354
37
- anemoi_utils-0.4.21.dist-info/METADATA,sha256=AWNLa8J5pQTvIgxWOPOOo8skiBLCbbCR_DPuCxAGeAI,15439
38
- anemoi_utils-0.4.21.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
39
- anemoi_utils-0.4.21.dist-info/entry_points.txt,sha256=LENOkn88xzFQo-V59AKoA_F_cfYQTJYtrNTtf37YgHY,60
40
- anemoi_utils-0.4.21.dist-info/top_level.txt,sha256=DYn8VPs-fNwr7fNH9XIBqeXIwiYYd2E2k5-dUFFqUz0,7
41
- anemoi_utils-0.4.21.dist-info/RECORD,,
36
+ anemoi_utils-0.4.23.dist-info/licenses/LICENSE,sha256=8HznKF1Vi2IvfLsKNE5A2iVyiri3pRjRPvPC9kxs6qk,11354
37
+ anemoi_utils-0.4.23.dist-info/METADATA,sha256=jxTYBIvx6wzO0g8ftu2MoSPfeViRSTUrE38Zy3HUvlw,15439
38
+ anemoi_utils-0.4.23.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
39
+ anemoi_utils-0.4.23.dist-info/entry_points.txt,sha256=LENOkn88xzFQo-V59AKoA_F_cfYQTJYtrNTtf37YgHY,60
40
+ anemoi_utils-0.4.23.dist-info/top_level.txt,sha256=DYn8VPs-fNwr7fNH9XIBqeXIwiYYd2E2k5-dUFFqUz0,7
41
+ anemoi_utils-0.4.23.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (78.1.0)
2
+ Generator: setuptools (80.7.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5