litdata 0.2.71__tar.gz → 0.2.72__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.
- {litdata-0.2.71/src/litdata.egg-info → litdata-0.2.72}/PKG-INFO +2 -1
- {litdata-0.2.71 → litdata-0.2.72}/requirements.txt +1 -0
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata/__about__.py +1 -1
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata/streaming/client.py +190 -35
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata/streaming/serializers.py +2 -2
- {litdata-0.2.71 → litdata-0.2.72/src/litdata.egg-info}/PKG-INFO +2 -1
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata.egg-info/requires.txt +1 -0
- {litdata-0.2.71 → litdata-0.2.72}/CONTRIBUTING.md +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/LICENSE +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/MANIFEST.in +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/README.md +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/setup.cfg +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/setup.py +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata/__init__.py +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata/__main__.py +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata/cli/__init__.py +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata/cli/commands.py +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata/cli/handler/__init__.py +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata/cli/handler/cache.py +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata/cli/handler/optimize.py +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata/cli/parser.py +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata/constants.py +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata/debugger.py +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata/exceptions.py +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata/helpers.py +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata/imports.py +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata/processing/__init__.py +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata/processing/complete.py +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata/processing/data_processor.py +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata/processing/functions.py +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata/processing/media_folder.py +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata/processing/readers.py +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata/processing/utilities.py +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata/raw/__init__.py +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata/raw/dataset.py +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata/raw/indexer.py +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata/raw/types.py +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata/requirements.py +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata/streaming/__init__.py +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata/streaming/async_prefetch.py +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata/streaming/cache.py +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata/streaming/collate.py +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata/streaming/combined.py +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata/streaming/compression.py +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata/streaming/config.py +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata/streaming/dataloader.py +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata/streaming/dataset.py +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata/streaming/dataset_update.py +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata/streaming/downloader.py +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata/streaming/elastic.py +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata/streaming/fs_provider.py +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata/streaming/item_loader.py +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata/streaming/parallel.py +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata/streaming/posix_fast.py +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata/streaming/reader.py +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata/streaming/resolver.py +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata/streaming/sampler.py +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata/streaming/shuffle.py +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata/streaming/timing.py +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata/streaming/writer.py +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata/types.py +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata/utilities/__init__.py +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata/utilities/_pytree.py +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata/utilities/base.py +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata/utilities/breakpoint.py +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata/utilities/broadcast.py +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata/utilities/dataset_utilities.py +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata/utilities/encryption.py +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata/utilities/env.py +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata/utilities/format.py +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata/utilities/hf_dataset.py +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata/utilities/keys_index.py +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata/utilities/packing.py +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata/utilities/parquet.py +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata/utilities/shuffle.py +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata/utilities/subsample.py +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata/utilities/torch_utils.py +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata/utilities/train_test_split.py +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata.egg-info/SOURCES.txt +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata.egg-info/dependency_links.txt +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata.egg-info/entry_points.txt +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata.egg-info/not-zip-safe +0 -0
- {litdata-0.2.71 → litdata-0.2.72}/src/litdata.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: litdata
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.72
|
|
4
4
|
Summary: The Deep Learning framework to train, deploy, and ship AI products Lightning fast.
|
|
5
5
|
Home-page: https://github.com/Lightning-AI/litdata
|
|
6
6
|
Download-URL: https://github.com/Lightning-AI/litdata
|
|
@@ -34,6 +34,7 @@ Requires-Dist: filelock
|
|
|
34
34
|
Requires-Dist: numpy
|
|
35
35
|
Requires-Dist: boto3
|
|
36
36
|
Requires-Dist: requests
|
|
37
|
+
Requires-Dist: urllib3>=1.26
|
|
37
38
|
Requires-Dist: tifffile
|
|
38
39
|
Requires-Dist: obstore
|
|
39
40
|
Provides-Extra: extras
|
|
@@ -12,9 +12,11 @@
|
|
|
12
12
|
# limitations under the License.
|
|
13
13
|
|
|
14
14
|
import json
|
|
15
|
+
import logging
|
|
15
16
|
import os
|
|
17
|
+
import random
|
|
16
18
|
import threading
|
|
17
|
-
from time import time
|
|
19
|
+
from time import sleep, time
|
|
18
20
|
from typing import Any
|
|
19
21
|
|
|
20
22
|
import boto3
|
|
@@ -27,14 +29,60 @@ from urllib3.util.retry import Retry
|
|
|
27
29
|
|
|
28
30
|
from litdata.constants import _IS_IN_STUDIO
|
|
29
31
|
|
|
32
|
+
logger = logging.getLogger("litdata.streaming.client")
|
|
33
|
+
|
|
30
34
|
# Constants for the retry adapter. Docs: https://urllib3.readthedocs.io/en/stable/reference/urllib3.util.html
|
|
31
|
-
#
|
|
32
|
-
|
|
35
|
+
# Retries per request. Deliberately small: a refresh runs inline on whichever DataLoader worker
|
|
36
|
+
# thread crossed the interval and holds the client lock for its whole duration, so it has to
|
|
37
|
+
# return in seconds, not hours. `S3Client._refresh_client` is what rides out a longer outage.
|
|
38
|
+
_CONNECTION_RETRY_TOTAL = 4
|
|
33
39
|
# Backoff factor for connection retries (wait time increases by this factor after each failure)
|
|
34
40
|
_CONNECTION_RETRY_BACKOFF_FACTOR = 0.5
|
|
35
41
|
# Default timeout for each HTTP request in seconds
|
|
36
42
|
_DEFAULT_REQUEST_TIMEOUT = 30 # seconds
|
|
37
43
|
|
|
44
|
+
# The control plane mints credentials with a 1 hour TTL for S3 project-role data connections, and
|
|
45
|
+
# the response carries no expiry for us to read, so refresh well inside it. The remaining time is
|
|
46
|
+
# the window in which a failed refresh can be retried while the credentials in hand still work.
|
|
47
|
+
_DEFAULT_REFETCH_INTERVAL = 2700 # seconds
|
|
48
|
+
# How long past the refetch interval we keep serving existing credentials while refreshes fail.
|
|
49
|
+
# Sized against the TTL, not comfort: 2700 + 600 leaves ~5 minutes before the 1 hour S3 expiry,
|
|
50
|
+
# so we stop before reads start failing as unexplained S3 403s.
|
|
51
|
+
_REFRESH_GRACE_PERIOD = 600 # seconds
|
|
52
|
+
# How long to wait for the control plane when there are no credentials yet. No TTL constrains
|
|
53
|
+
# this one — nothing is being served — so it can be more patient than the refresh grace.
|
|
54
|
+
_INITIAL_RETRY_BUDGET = 900 # seconds
|
|
55
|
+
# Spacing between refresh attempts once one has failed. Without it every subsequent read
|
|
56
|
+
# re-requests credentials, turning one control-plane blip into a request storm per worker.
|
|
57
|
+
_REFRESH_RETRY_INTERVAL = 60 # seconds
|
|
58
|
+
# Fraction of the interval by which each process refreshes early. DataLoader workers are forked
|
|
59
|
+
# together, so without jitter they all reach the interval — and stampede — in the same instant.
|
|
60
|
+
_REFETCH_JITTER_RATIO = 0.1
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class _CredentialsError(RuntimeError):
|
|
64
|
+
"""Raised when credentials could not be obtained from the control plane or IMDS.
|
|
65
|
+
|
|
66
|
+
Only these are retried. Anything else out of ``_create_client`` — a bad ``storage_options``
|
|
67
|
+
key, a malformed endpoint — is a local mistake that no amount of waiting fixes, and must
|
|
68
|
+
reach the caller immediately rather than stalling the job for the retry budget.
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class _CredentialsUnavailableError(_CredentialsError):
|
|
73
|
+
"""A credential failure that may clear on its own: unreachable, timed out, 5xx, 429."""
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class _CredentialsConfigurationError(_CredentialsError):
|
|
77
|
+
"""A credential failure that retrying cannot fix: missing configuration, or rejected auth."""
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _credentials_error(status_code: int, message: str) -> _CredentialsError:
|
|
81
|
+
"""Classify an HTTP failure. A 4xx other than 408/429 will not fix itself on a retry."""
|
|
82
|
+
if 400 <= status_code < 500 and status_code not in (408, 429):
|
|
83
|
+
return _CredentialsConfigurationError(message)
|
|
84
|
+
return _CredentialsUnavailableError(message)
|
|
85
|
+
|
|
38
86
|
|
|
39
87
|
class _CustomRetryAdapter(HTTPAdapter):
|
|
40
88
|
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
@@ -42,7 +90,11 @@ class _CustomRetryAdapter(HTTPAdapter):
|
|
|
42
90
|
super().__init__(*args, **kwargs)
|
|
43
91
|
|
|
44
92
|
def send(self, request: Any, *args: Any, **kwargs: Any) -> Any:
|
|
45
|
-
|
|
93
|
+
# requests always passes `timeout` explicitly, as None when the caller gave none, so a
|
|
94
|
+
# `kwargs.get("timeout", self.timeout)` default never applied and every request without
|
|
95
|
+
# an explicit timeout could hang forever.
|
|
96
|
+
if kwargs.get("timeout") is None:
|
|
97
|
+
kwargs["timeout"] = self.timeout
|
|
46
98
|
return super().send(request, **kwargs)
|
|
47
99
|
|
|
48
100
|
|
|
@@ -57,6 +109,10 @@ def _login_and_get_temp_bucket_credentials(data_connection_id: str) -> dict[str,
|
|
|
57
109
|
retry_strategy = Retry(
|
|
58
110
|
total=_CONNECTION_RETRY_TOTAL,
|
|
59
111
|
backoff_factor=_CONNECTION_RETRY_BACKOFF_FACTOR,
|
|
112
|
+
# urllib3 leaves POST out of its default allowed_methods, which left the login below
|
|
113
|
+
# unretried: a single 429 or 502 there failed the whole refresh even though the
|
|
114
|
+
# credentials GET would have retried it. Minting a token is safe to repeat.
|
|
115
|
+
allowed_methods=Retry.DEFAULT_ALLOWED_METHODS | {"POST"},
|
|
60
116
|
status_forcelist=[
|
|
61
117
|
408, # Request Timeout
|
|
62
118
|
429, # Too Many Requests
|
|
@@ -77,17 +133,27 @@ def _login_and_get_temp_bucket_credentials(data_connection_id: str) -> dict[str,
|
|
|
77
133
|
project_id = os.getenv("LIGHTNING_CLOUD_PROJECT_ID")
|
|
78
134
|
|
|
79
135
|
if not all([api_key, username, project_id]):
|
|
80
|
-
raise
|
|
136
|
+
raise _CredentialsConfigurationError("Missing required environment variables")
|
|
81
137
|
|
|
82
138
|
# Login to get token
|
|
83
139
|
payload = {"apiKey": api_key, "username": username}
|
|
84
140
|
login_url = f"{cloud_url}/v1/auth/login"
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
raise
|
|
141
|
+
try:
|
|
142
|
+
response = session.post(login_url, data=json.dumps(payload))
|
|
143
|
+
except requests.exceptions.RequestException as e:
|
|
144
|
+
raise _CredentialsUnavailableError(f"Could not reach the Lightning Cloud API to log in: {e}") from e
|
|
145
|
+
|
|
146
|
+
# Check the status before the body: a proxy in front of the API answers with HTML, which
|
|
147
|
+
# would otherwise surface as a JSONDecodeError with nothing pointing back at the login call.
|
|
148
|
+
if response.status_code != 200:
|
|
149
|
+
raise _credentials_error(
|
|
150
|
+
response.status_code, f"Failed to log in to the Lightning Cloud API: {response.status_code}"
|
|
151
|
+
)
|
|
89
152
|
|
|
90
|
-
|
|
153
|
+
try:
|
|
154
|
+
token = response.json()["token"]
|
|
155
|
+
except (ValueError, KeyError) as e:
|
|
156
|
+
raise RuntimeError("Failed to get authentication token") from e
|
|
91
157
|
|
|
92
158
|
# Get temporary bucket credentials
|
|
93
159
|
headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
|
|
@@ -95,10 +161,15 @@ def _login_and_get_temp_bucket_credentials(data_connection_id: str) -> dict[str,
|
|
|
95
161
|
f"{cloud_url}/v1/projects/{project_id}/data-connections/{data_connection_id}/temp-bucket-credentials"
|
|
96
162
|
)
|
|
97
163
|
|
|
98
|
-
|
|
164
|
+
try:
|
|
165
|
+
credentials_response = session.get(credentials_url, headers=headers, timeout=10)
|
|
166
|
+
except requests.exceptions.RequestException as e:
|
|
167
|
+
raise _CredentialsUnavailableError(f"Could not reach the Lightning Cloud API for credentials: {e}") from e
|
|
99
168
|
|
|
100
169
|
if credentials_response.status_code != 200:
|
|
101
|
-
raise
|
|
170
|
+
raise _credentials_error(
|
|
171
|
+
credentials_response.status_code, f"Failed to get credentials: {credentials_response.status_code}"
|
|
172
|
+
)
|
|
102
173
|
|
|
103
174
|
return credentials_response.json()
|
|
104
175
|
|
|
@@ -108,17 +179,33 @@ class S3Client:
|
|
|
108
179
|
|
|
109
180
|
def __init__(
|
|
110
181
|
self,
|
|
111
|
-
refetch_interval: int =
|
|
182
|
+
refetch_interval: int = _DEFAULT_REFETCH_INTERVAL,
|
|
112
183
|
storage_options: dict | None = {},
|
|
113
184
|
session_options: dict | None = {},
|
|
114
185
|
) -> None:
|
|
115
186
|
self._refetch_interval = refetch_interval
|
|
116
187
|
self._last_time: float | None = None
|
|
117
|
-
self._client: Any | None = None
|
|
118
188
|
self._storage_options: dict = storage_options or {}
|
|
119
189
|
self._session_options: dict = session_options or {}
|
|
190
|
+
self._reset_process_state()
|
|
191
|
+
|
|
192
|
+
def _reset_process_state(self) -> None:
|
|
193
|
+
"""Reset the state that must not be shared across a fork.
|
|
194
|
+
|
|
195
|
+
Re-rolls the refresh jitter too: DataLoader workers inherit the parent's timings, so
|
|
196
|
+
without a fresh roll per process they would all refresh in the same instant.
|
|
197
|
+
"""
|
|
198
|
+
self._client: Any | None = None
|
|
120
199
|
# Guards lazy create + credential refresh (range GETs hit .client from many threads).
|
|
121
200
|
self._client_lock = threading.Lock()
|
|
201
|
+
self._owner_pid = os.getpid()
|
|
202
|
+
self._refetch_deadline = self._jittered_refetch_interval()
|
|
203
|
+
self._refresh_retry_time: float | None = None
|
|
204
|
+
|
|
205
|
+
def _jittered_refetch_interval(self) -> float:
|
|
206
|
+
# Only ever early, never late: callers set the interval as an upper bound on how long
|
|
207
|
+
# a set of credentials is held, and the grace period below is measured against it.
|
|
208
|
+
return self._refetch_interval * (1.0 - random.uniform(0.0, _REFETCH_JITTER_RATIO)) # noqa: S311
|
|
122
209
|
|
|
123
210
|
def __getstate__(self) -> dict[str, Any]:
|
|
124
211
|
state = self.__dict__.copy()
|
|
@@ -130,9 +217,7 @@ class S3Client:
|
|
|
130
217
|
|
|
131
218
|
def __setstate__(self, state: dict[str, Any]) -> None:
|
|
132
219
|
self.__dict__.update(state)
|
|
133
|
-
self.
|
|
134
|
-
self._client_lock = threading.Lock()
|
|
135
|
-
self._owner_pid = os.getpid()
|
|
220
|
+
self._reset_process_state()
|
|
136
221
|
|
|
137
222
|
def _create_client(self) -> None:
|
|
138
223
|
# S3 data connections marked available on non-AWS providers can't reach the bucket via the
|
|
@@ -159,7 +244,10 @@ class S3Client:
|
|
|
159
244
|
)
|
|
160
245
|
else:
|
|
161
246
|
provider = InstanceMetadataProvider(iam_role_fetcher=InstanceMetadataFetcher(timeout=3600, num_attempts=5))
|
|
162
|
-
|
|
247
|
+
try:
|
|
248
|
+
credentials = provider.load()
|
|
249
|
+
except Exception as e:
|
|
250
|
+
raise _CredentialsUnavailableError(f"Could not load instance metadata credentials: {e}") from e
|
|
163
251
|
session = boto3.Session()
|
|
164
252
|
self._client = session.client(
|
|
165
253
|
"s3",
|
|
@@ -192,24 +280,91 @@ class S3Client:
|
|
|
192
280
|
},
|
|
193
281
|
)
|
|
194
282
|
|
|
283
|
+
def _mark_refreshed(self) -> None:
|
|
284
|
+
self._last_time = time()
|
|
285
|
+
self._refetch_deadline = self._jittered_refetch_interval()
|
|
286
|
+
self._refresh_retry_time = None
|
|
287
|
+
|
|
288
|
+
def _create_initial_client(self) -> None:
|
|
289
|
+
"""Create the first client, waiting out a control plane that is briefly unavailable.
|
|
290
|
+
|
|
291
|
+
Unlike a refresh there are no credentials to fall back on, so this has to keep trying
|
|
292
|
+
rather than hand the caller an error. Bounded by ``_INITIAL_RETRY_BUDGET``: a job that
|
|
293
|
+
cannot reach the control plane for that long should fail with a clear reason.
|
|
294
|
+
"""
|
|
295
|
+
started = time()
|
|
296
|
+
attempt = 0
|
|
297
|
+
while True:
|
|
298
|
+
attempt += 1
|
|
299
|
+
try:
|
|
300
|
+
self._create_client()
|
|
301
|
+
# Only a control plane that might come back is worth waiting for. A configuration
|
|
302
|
+
# error, or any local failure building the client, propagates on the first attempt.
|
|
303
|
+
except _CredentialsUnavailableError as e:
|
|
304
|
+
waited = time() - started
|
|
305
|
+
if waited + _REFRESH_RETRY_INTERVAL >= _INITIAL_RETRY_BUDGET:
|
|
306
|
+
raise RuntimeError(f"Could not get credentials after {waited:.0f}s of retrying: {e}") from e
|
|
307
|
+
logger.warning(
|
|
308
|
+
"Could not get credentials (attempt %d, %.0fs elapsed), so data loading is blocked; "
|
|
309
|
+
"retrying in %ds: %s",
|
|
310
|
+
attempt,
|
|
311
|
+
waited,
|
|
312
|
+
_REFRESH_RETRY_INTERVAL,
|
|
313
|
+
e,
|
|
314
|
+
)
|
|
315
|
+
sleep(_REFRESH_RETRY_INTERVAL)
|
|
316
|
+
else:
|
|
317
|
+
self._mark_refreshed()
|
|
318
|
+
return
|
|
319
|
+
|
|
320
|
+
def _refresh_client(self) -> None:
|
|
321
|
+
"""Re-mint credentials, tolerating a control plane that is briefly unavailable.
|
|
322
|
+
|
|
323
|
+
Credentials are refreshed before they expire, so a failed refresh is not immediately
|
|
324
|
+
fatal — the ones already in hand still work. Keep serving those and retry on a timer
|
|
325
|
+
until the grace period runs out, rather than failing the read on the first error, which
|
|
326
|
+
kills the DataLoader worker and with it the run.
|
|
327
|
+
"""
|
|
328
|
+
now = time()
|
|
329
|
+
if self._refresh_retry_time is not None and now < self._refresh_retry_time:
|
|
330
|
+
return
|
|
331
|
+
|
|
332
|
+
try:
|
|
333
|
+
self._create_client()
|
|
334
|
+
# Both kinds get the grace period here, unlike initial creation. The credentials in hand
|
|
335
|
+
# still work, so there is nothing to gain by failing fast on a 403 that might be a proxy
|
|
336
|
+
# misbehaving mid-deploy — and if it is a real revocation, the deadline still catches it.
|
|
337
|
+
except _CredentialsError as e:
|
|
338
|
+
held_for = 0.0 if self._last_time is None else now - self._last_time
|
|
339
|
+
if held_for > self._refetch_deadline + _REFRESH_GRACE_PERIOD:
|
|
340
|
+
raise RuntimeError(
|
|
341
|
+
f"Failed to refresh credentials for {held_for:.0f}s, so they are assumed expired: {e}"
|
|
342
|
+
) from e
|
|
343
|
+
self._refresh_retry_time = now + _REFRESH_RETRY_INTERVAL
|
|
344
|
+
logger.warning(
|
|
345
|
+
"Could not refresh credentials (%.0fs since the last successful refresh); reusing the current "
|
|
346
|
+
"ones and retrying in %ds: %s",
|
|
347
|
+
held_for,
|
|
348
|
+
_REFRESH_RETRY_INTERVAL,
|
|
349
|
+
e,
|
|
350
|
+
)
|
|
351
|
+
return
|
|
352
|
+
|
|
353
|
+
self._mark_refreshed()
|
|
354
|
+
|
|
195
355
|
@property
|
|
196
356
|
def client(self) -> Any:
|
|
197
357
|
# boto3 clients are thread-safe for requests; construction/refresh is not.
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
self._client_lock = threading.Lock()
|
|
203
|
-
self._owner_pid = pid
|
|
358
|
+
if getattr(self, "_owner_pid", None) != os.getpid():
|
|
359
|
+
# DataLoader fork: drop the inherited client, lock and refresh schedule.
|
|
360
|
+
self._reset_process_state()
|
|
361
|
+
|
|
204
362
|
with self._client_lock:
|
|
205
363
|
if self._client is None:
|
|
206
|
-
self.
|
|
207
|
-
self._last_time = time()
|
|
208
|
-
|
|
364
|
+
self._create_initial_client()
|
|
209
365
|
# Re-generate credentials for EC2 / temporary Studio creds
|
|
210
|
-
|
|
211
|
-
self.
|
|
212
|
-
self._last_time = time()
|
|
366
|
+
elif self._last_time is None or (time() - self._last_time) > self._refetch_deadline:
|
|
367
|
+
self._refresh_client()
|
|
213
368
|
|
|
214
369
|
return self._client
|
|
215
370
|
|
|
@@ -219,7 +374,7 @@ class R2Client(S3Client):
|
|
|
219
374
|
|
|
220
375
|
def __init__(
|
|
221
376
|
self,
|
|
222
|
-
refetch_interval: int =
|
|
377
|
+
refetch_interval: int = _DEFAULT_REFETCH_INTERVAL,
|
|
223
378
|
storage_options: dict | None = {},
|
|
224
379
|
session_options: dict | None = {},
|
|
225
380
|
) -> None:
|
|
@@ -248,17 +403,17 @@ class R2Client(S3Client):
|
|
|
248
403
|
"endpoint_url": endpoint_url,
|
|
249
404
|
}
|
|
250
405
|
|
|
406
|
+
except _CredentialsError:
|
|
407
|
+
raise
|
|
251
408
|
except Exception as e:
|
|
252
|
-
|
|
253
|
-
print(f"Failed to get R2 credentials from API: {e}. Using fallback credentials.")
|
|
254
|
-
raise RuntimeError(f"Failed to get R2 credentials and no fallback available: {e}")
|
|
409
|
+
raise RuntimeError(f"Failed to get R2 credentials: {e}") from e
|
|
255
410
|
|
|
256
411
|
def _create_client(self) -> None:
|
|
257
412
|
"""Create a new R2 client with fresh credentials."""
|
|
258
413
|
# Get data connection ID from storage options
|
|
259
414
|
data_connection_id = self._base_storage_options.get("data_connection_id")
|
|
260
415
|
if not data_connection_id:
|
|
261
|
-
raise
|
|
416
|
+
raise _CredentialsConfigurationError("data_connection_id is required in storage_options for R2 client")
|
|
262
417
|
|
|
263
418
|
# Get fresh R2 credentials
|
|
264
419
|
r2_credentials = self.get_r2_bucket_credentials(data_connection_id)
|
|
@@ -477,7 +477,7 @@ class NumpySerializer(Serializer):
|
|
|
477
477
|
shape.append(np.frombuffer(data[8 + 4 * shape_idx : 8 + 4 * (shape_idx + 1)], np.uint32).item())
|
|
478
478
|
|
|
479
479
|
# deserialize the numpy array bytes
|
|
480
|
-
tensor = np.frombuffer(data[8 + 4 * shape_size : len(data)], dtype=dtype)
|
|
480
|
+
tensor = np.frombuffer(data[8 + 4 * shape_size : len(data)], dtype=dtype).copy()
|
|
481
481
|
if tensor.shape == shape:
|
|
482
482
|
return tensor
|
|
483
483
|
return np.reshape(tensor, shape)
|
|
@@ -503,7 +503,7 @@ class NoHeaderNumpySerializer(Serializer):
|
|
|
503
503
|
|
|
504
504
|
def deserialize(self, data: bytes) -> np.ndarray:
|
|
505
505
|
assert self._dtype
|
|
506
|
-
return np.frombuffer(data, dtype=self._dtype)
|
|
506
|
+
return np.frombuffer(data, dtype=self._dtype).copy()
|
|
507
507
|
|
|
508
508
|
def can_serialize(self, item: np.ndarray) -> bool:
|
|
509
509
|
return isinstance(item, np.ndarray) and len(item.shape) == 1
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: litdata
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.72
|
|
4
4
|
Summary: The Deep Learning framework to train, deploy, and ship AI products Lightning fast.
|
|
5
5
|
Home-page: https://github.com/Lightning-AI/litdata
|
|
6
6
|
Download-URL: https://github.com/Lightning-AI/litdata
|
|
@@ -34,6 +34,7 @@ Requires-Dist: filelock
|
|
|
34
34
|
Requires-Dist: numpy
|
|
35
35
|
Requires-Dist: boto3
|
|
36
36
|
Requires-Dist: requests
|
|
37
|
+
Requires-Dist: urllib3>=1.26
|
|
37
38
|
Requires-Dist: tifffile
|
|
38
39
|
Requires-Dist: obstore
|
|
39
40
|
Provides-Extra: extras
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|