mlbricks-core 0.2.0__tar.gz → 0.2.1__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mlbricks-core
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: Core ML tooling, including config resolution, registry, and storage.
5
5
  Author: Shivam Verma
6
6
  Author-email: Shivam Verma <shivam.vvm.verma@gmail.com>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mlbricks-core"
3
- version = "0.2.0"
3
+ version = "0.2.1"
4
4
  description = "Core ML tooling, including config resolution, registry, and storage."
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mlbricks-core"
3
- version = "0.2.0"
3
+ version = "0.2.1"
4
4
  description = "Core ML tooling, including config resolution, registry, and storage."
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -6,18 +6,29 @@ Public API: ``StorageDriver`` only. ``Storage``, ``LocalStorage``, and
6
6
 
7
7
  import fnmatch
8
8
  import glob as glob_module
9
+ import logging
9
10
  import shutil
11
+ import time
10
12
  from abc import ABC, abstractmethod
11
13
  from pathlib import Path
12
14
  from typing import Any
13
15
 
14
16
  import boto3
15
17
  from botocore.exceptions import ClientError
18
+ from s3transfer.exceptions import S3DownloadFailedError
16
19
 
17
20
  from mlbricks.utils import path_slug
18
21
 
19
22
  __all__ = ["StorageDriver"]
20
23
 
24
+ logger = logging.getLogger(__name__)
25
+
26
+ # Retry policy for the transient object-changed-mid-download race: another
27
+ # writer overwrites the key between S3Storage.get's head_object and the
28
+ # completion of its ranged GETs. See docs/storage.md for the full writeup.
29
+ _GET_MAX_ATTEMPTS = 4
30
+ _GET_RETRY_BASE_DELAY_SECONDS = 4.0
31
+
21
32
 
22
33
  def _validate_uri(uri: str) -> None:
23
34
  """Raise ValueError if uri is an absolute local path."""
@@ -160,14 +171,29 @@ class S3Storage(Storage):
160
171
 
161
172
  bucket, key = self._parse_uri(uri)
162
173
  client = self._get_client()
163
-
164
- try:
165
- staging.parent.mkdir(parents=True, exist_ok=True)
166
- client.download_file(bucket, key, str(staging))
167
- except ClientError as exc:
168
- if exc.response["Error"]["Code"] in ("404", "NoSuchKey"):
169
- return None
170
- raise
174
+ staging.parent.mkdir(parents=True, exist_ok=True)
175
+
176
+ for attempt in range(1, _GET_MAX_ATTEMPTS + 1):
177
+ try:
178
+ client.download_file(bucket, key, str(staging))
179
+ return staging
180
+ except ClientError as exc:
181
+ if exc.response["Error"]["Code"] in ("404", "NoSuchKey"):
182
+ return None
183
+ raise
184
+ except S3DownloadFailedError:
185
+ if attempt == _GET_MAX_ATTEMPTS:
186
+ raise
187
+ delay = _GET_RETRY_BASE_DELAY_SECONDS * 2 ** (attempt - 1)
188
+ logger.warning(
189
+ "S3 download of %r failed ETag validation (attempt %d/%d);"
190
+ " retrying in %.1fs",
191
+ uri,
192
+ attempt,
193
+ _GET_MAX_ATTEMPTS,
194
+ delay,
195
+ )
196
+ time.sleep(delay)
171
197
 
172
198
  return staging
173
199
 
File without changes