litdata 0.2.61__tar.gz → 0.2.64__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.61/src/litdata.egg-info → litdata-0.2.64}/PKG-INFO +3 -3
- {litdata-0.2.61 → litdata-0.2.64}/src/litdata/__about__.py +1 -1
- {litdata-0.2.61 → litdata-0.2.64}/src/litdata/cli/commands.py +1 -1
- litdata-0.2.64/src/litdata/cli/handler/__init__.py +21 -0
- litdata-0.2.64/src/litdata/cli/handler/cache.py +32 -0
- litdata-0.2.64/src/litdata/cli/handler/optimize.py +19 -0
- {litdata-0.2.61 → litdata-0.2.64}/src/litdata/constants.py +1 -0
- {litdata-0.2.61 → litdata-0.2.64}/src/litdata/processing/data_processor.py +1 -1
- {litdata-0.2.61 → litdata-0.2.64}/src/litdata/streaming/client.py +90 -49
- {litdata-0.2.61 → litdata-0.2.64}/src/litdata/streaming/config.py +5 -0
- {litdata-0.2.61 → litdata-0.2.64}/src/litdata/streaming/dataloader.py +45 -0
- {litdata-0.2.61 → litdata-0.2.64}/src/litdata/streaming/downloader.py +1 -1
- {litdata-0.2.61 → litdata-0.2.64}/src/litdata/streaming/fs_provider.py +9 -7
- {litdata-0.2.61 → litdata-0.2.64}/src/litdata/streaming/item_loader.py +24 -8
- {litdata-0.2.61 → litdata-0.2.64}/src/litdata/streaming/reader.py +29 -15
- {litdata-0.2.61 → litdata-0.2.64}/src/litdata/streaming/resolver.py +13 -1
- {litdata-0.2.61 → litdata-0.2.64}/src/litdata/streaming/serializers.py +32 -0
- {litdata-0.2.61 → litdata-0.2.64}/src/litdata/streaming/writer.py +5 -1
- {litdata-0.2.61 → litdata-0.2.64}/src/litdata/utilities/parquet.py +11 -3
- {litdata-0.2.61 → litdata-0.2.64/src/litdata.egg-info}/PKG-INFO +3 -3
- {litdata-0.2.61 → litdata-0.2.64}/src/litdata.egg-info/SOURCES.txt +3 -0
- {litdata-0.2.61 → litdata-0.2.64}/src/litdata.egg-info/requires.txt +2 -2
- {litdata-0.2.61 → litdata-0.2.64}/CONTRIBUTING.md +0 -0
- {litdata-0.2.61 → litdata-0.2.64}/LICENSE +0 -0
- {litdata-0.2.61 → litdata-0.2.64}/MANIFEST.in +0 -0
- {litdata-0.2.61 → litdata-0.2.64}/README.md +0 -0
- {litdata-0.2.61 → litdata-0.2.64}/requirements.txt +0 -0
- {litdata-0.2.61 → litdata-0.2.64}/setup.cfg +0 -0
- {litdata-0.2.61 → litdata-0.2.64}/setup.py +0 -0
- {litdata-0.2.61 → litdata-0.2.64}/src/litdata/__init__.py +0 -0
- {litdata-0.2.61 → litdata-0.2.64}/src/litdata/__main__.py +0 -0
- {litdata-0.2.61 → litdata-0.2.64}/src/litdata/cli/__init__.py +0 -0
- {litdata-0.2.61 → litdata-0.2.64}/src/litdata/cli/parser.py +0 -0
- {litdata-0.2.61 → litdata-0.2.64}/src/litdata/debugger.py +0 -0
- {litdata-0.2.61 → litdata-0.2.64}/src/litdata/helpers.py +0 -0
- {litdata-0.2.61 → litdata-0.2.64}/src/litdata/imports.py +0 -0
- {litdata-0.2.61 → litdata-0.2.64}/src/litdata/processing/__init__.py +0 -0
- {litdata-0.2.61 → litdata-0.2.64}/src/litdata/processing/functions.py +0 -0
- {litdata-0.2.61 → litdata-0.2.64}/src/litdata/processing/readers.py +0 -0
- {litdata-0.2.61 → litdata-0.2.64}/src/litdata/processing/utilities.py +0 -0
- {litdata-0.2.61 → litdata-0.2.64}/src/litdata/raw/__init__.py +0 -0
- {litdata-0.2.61 → litdata-0.2.64}/src/litdata/raw/dataset.py +0 -0
- {litdata-0.2.61 → litdata-0.2.64}/src/litdata/raw/indexer.py +0 -0
- {litdata-0.2.61 → litdata-0.2.64}/src/litdata/raw/types.py +0 -0
- {litdata-0.2.61 → litdata-0.2.64}/src/litdata/requirements.py +0 -0
- {litdata-0.2.61 → litdata-0.2.64}/src/litdata/streaming/__init__.py +0 -0
- {litdata-0.2.61 → litdata-0.2.64}/src/litdata/streaming/cache.py +0 -0
- {litdata-0.2.61 → litdata-0.2.64}/src/litdata/streaming/combined.py +0 -0
- {litdata-0.2.61 → litdata-0.2.64}/src/litdata/streaming/compression.py +0 -0
- {litdata-0.2.61 → litdata-0.2.64}/src/litdata/streaming/dataset.py +0 -0
- {litdata-0.2.61 → litdata-0.2.64}/src/litdata/streaming/parallel.py +0 -0
- {litdata-0.2.61 → litdata-0.2.64}/src/litdata/streaming/sampler.py +0 -0
- {litdata-0.2.61 → litdata-0.2.64}/src/litdata/streaming/shuffle.py +0 -0
- {litdata-0.2.61 → litdata-0.2.64}/src/litdata/utilities/__init__.py +0 -0
- {litdata-0.2.61 → litdata-0.2.64}/src/litdata/utilities/_pytree.py +0 -0
- {litdata-0.2.61 → litdata-0.2.64}/src/litdata/utilities/base.py +0 -0
- {litdata-0.2.61 → litdata-0.2.64}/src/litdata/utilities/breakpoint.py +0 -0
- {litdata-0.2.61 → litdata-0.2.64}/src/litdata/utilities/broadcast.py +0 -0
- {litdata-0.2.61 → litdata-0.2.64}/src/litdata/utilities/dataset_utilities.py +0 -0
- {litdata-0.2.61 → litdata-0.2.64}/src/litdata/utilities/encryption.py +0 -0
- {litdata-0.2.61 → litdata-0.2.64}/src/litdata/utilities/env.py +0 -0
- {litdata-0.2.61 → litdata-0.2.64}/src/litdata/utilities/format.py +0 -0
- {litdata-0.2.61 → litdata-0.2.64}/src/litdata/utilities/hf_dataset.py +0 -0
- {litdata-0.2.61 → litdata-0.2.64}/src/litdata/utilities/packing.py +0 -0
- {litdata-0.2.61 → litdata-0.2.64}/src/litdata/utilities/shuffle.py +0 -0
- {litdata-0.2.61 → litdata-0.2.64}/src/litdata/utilities/subsample.py +0 -0
- {litdata-0.2.61 → litdata-0.2.64}/src/litdata/utilities/torch_utils.py +0 -0
- {litdata-0.2.61 → litdata-0.2.64}/src/litdata/utilities/train_test_split.py +0 -0
- {litdata-0.2.61 → litdata-0.2.64}/src/litdata.egg-info/dependency_links.txt +0 -0
- {litdata-0.2.61 → litdata-0.2.64}/src/litdata.egg-info/entry_points.txt +0 -0
- {litdata-0.2.61 → litdata-0.2.64}/src/litdata.egg-info/not-zip-safe +0 -0
- {litdata-0.2.61 → litdata-0.2.64}/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.64
|
|
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
|
|
@@ -39,10 +39,10 @@ Requires-Dist: obstore
|
|
|
39
39
|
Provides-Extra: extras
|
|
40
40
|
Requires-Dist: fsspec; extra == "extras"
|
|
41
41
|
Requires-Dist: google-cloud-storage; extra == "extras"
|
|
42
|
-
Requires-Dist: lightning-sdk==
|
|
42
|
+
Requires-Dist: lightning-sdk==2026.2.6; extra == "extras"
|
|
43
43
|
Requires-Dist: pillow; extra == "extras"
|
|
44
44
|
Requires-Dist: polars; extra == "extras"
|
|
45
|
-
Requires-Dist: pyarrow; extra == "extras"
|
|
45
|
+
Requires-Dist: pyarrow<25.0.0; extra == "extras"
|
|
46
46
|
Requires-Dist: tqdm; extra == "extras"
|
|
47
47
|
Requires-Dist: viztracer; extra == "extras"
|
|
48
48
|
Dynamic: author
|
|
@@ -13,9 +13,9 @@
|
|
|
13
13
|
|
|
14
14
|
from argparse import _SubParsersAction
|
|
15
15
|
|
|
16
|
+
from litdata.cli import LitFormatter
|
|
16
17
|
from litdata.cli.handler.cache import clear_cache, show_cache_path
|
|
17
18
|
from litdata.cli.handler.optimize import optimize_dataset
|
|
18
|
-
from litdata.cli.parser import LitFormatter
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
def register_cache_subcommand(subparser: _SubParsersAction) -> None:
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Copyright The Lightning AI team.
|
|
2
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
3
|
+
# you may not use this file except in compliance with the License.
|
|
4
|
+
# You may obtain a copy of the License at
|
|
5
|
+
#
|
|
6
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
7
|
+
#
|
|
8
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
9
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
10
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
11
|
+
# See the License for the specific language governing permissions and
|
|
12
|
+
# limitations under the License.
|
|
13
|
+
|
|
14
|
+
from litdata.cli.handler.cache import clear_cache, show_cache_path
|
|
15
|
+
from litdata.cli.handler.optimize import optimize_dataset
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"clear_cache",
|
|
19
|
+
"show_cache_path",
|
|
20
|
+
"optimize_dataset",
|
|
21
|
+
]
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Copyright The Lightning AI team.
|
|
2
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
3
|
+
# you may not use this file except in compliance with the License.
|
|
4
|
+
# You may obtain a copy of the License at
|
|
5
|
+
#
|
|
6
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
7
|
+
#
|
|
8
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
9
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
10
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
11
|
+
# See the License for the specific language governing permissions and
|
|
12
|
+
# limitations under the License.
|
|
13
|
+
|
|
14
|
+
import shutil
|
|
15
|
+
from argparse import Namespace
|
|
16
|
+
|
|
17
|
+
from litdata.utilities.dataset_utilities import get_default_cache_dir
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def clear_cache(args: Namespace) -> None:
|
|
21
|
+
"""Clear default cache used for StreamingDataset and other utilities."""
|
|
22
|
+
streaming_default_cache_dir = get_default_cache_dir()
|
|
23
|
+
|
|
24
|
+
shutil.rmtree(streaming_default_cache_dir, ignore_errors=True)
|
|
25
|
+
|
|
26
|
+
print(f"Cache directory '{streaming_default_cache_dir}' cleared.")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def show_cache_path(args: Namespace) -> None:
|
|
30
|
+
"""Show the path to the cache directory."""
|
|
31
|
+
streaming_default_cache_dir = get_default_cache_dir()
|
|
32
|
+
print(f"Default cache directory: {streaming_default_cache_dir}")
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Copyright The Lightning AI team.
|
|
2
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
3
|
+
# you may not use this file except in compliance with the License.
|
|
4
|
+
# You may obtain a copy of the License at
|
|
5
|
+
#
|
|
6
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
7
|
+
#
|
|
8
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
9
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
10
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
11
|
+
# See the License for the specific language governing permissions and
|
|
12
|
+
# limitations under the License.
|
|
13
|
+
|
|
14
|
+
from argparse import Namespace
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def optimize_dataset(args: Namespace) -> None:
|
|
18
|
+
"""Handle the optimize command."""
|
|
19
|
+
print(f"Optimizing dataset at {args.dataset}...")
|
|
@@ -49,6 +49,7 @@ _PYARROW_AVAILABLE = RequirementCache("pyarrow")
|
|
|
49
49
|
_POLARS_AVAILABLE = RequirementCache("polars>1.0.0")
|
|
50
50
|
_PIL_AVAILABLE = RequirementCache("PIL")
|
|
51
51
|
_TORCH_VISION_AVAILABLE = RequirementCache("torchvision")
|
|
52
|
+
_TORCH_VISION_LESS_THAN_0_26 = RequirementCache("torchvision<0.26.0")
|
|
52
53
|
_AV_AVAILABLE = RequirementCache("av")
|
|
53
54
|
_OBSTORE_AVAILABLE = RequirementCache("obstore")
|
|
54
55
|
|
|
@@ -160,7 +160,7 @@ def _download_data_target(
|
|
|
160
160
|
if input_dir.path:
|
|
161
161
|
local_path = path.replace(input_dir.path, cache_dir)
|
|
162
162
|
|
|
163
|
-
if input_dir.url and input_dir.path:
|
|
163
|
+
if input_dir.url and input_dir.path and not os.path.isfile(path):
|
|
164
164
|
path = path.replace(input_dir.path, input_dir.url)
|
|
165
165
|
|
|
166
166
|
obj = parse.urlparse(path)
|
|
@@ -45,6 +45,63 @@ class _CustomRetryAdapter(HTTPAdapter):
|
|
|
45
45
|
return super().send(request, **kwargs)
|
|
46
46
|
|
|
47
47
|
|
|
48
|
+
def _login_and_get_temp_bucket_credentials(data_connection_id: str) -> dict[str, Any]:
|
|
49
|
+
"""Mint temporary bucket credentials for a data connection via the Lightning Cloud API.
|
|
50
|
+
|
|
51
|
+
Shared by R2 (lightning storage) connections and by S3 connections marked
|
|
52
|
+
``available_in_non_aws_providers``: both bypass the FUSE mount and need short-lived creds
|
|
53
|
+
from the same control-plane ``temp-bucket-credentials`` endpoint. Returns the raw response
|
|
54
|
+
(accessKeyId/secretAccessKey/sessionToken, plus accountId for R2).
|
|
55
|
+
"""
|
|
56
|
+
retry_strategy = Retry(
|
|
57
|
+
total=_CONNECTION_RETRY_TOTAL,
|
|
58
|
+
backoff_factor=_CONNECTION_RETRY_BACKOFF_FACTOR,
|
|
59
|
+
status_forcelist=[
|
|
60
|
+
408, # Request Timeout
|
|
61
|
+
429, # Too Many Requests
|
|
62
|
+
500, # Internal Server Error
|
|
63
|
+
502, # Bad Gateway
|
|
64
|
+
503, # Service Unavailable
|
|
65
|
+
504, # Gateway Timeout
|
|
66
|
+
],
|
|
67
|
+
)
|
|
68
|
+
adapter = _CustomRetryAdapter(max_retries=retry_strategy, timeout=_DEFAULT_REQUEST_TIMEOUT)
|
|
69
|
+
session = requests.Session()
|
|
70
|
+
session.mount("http://", adapter)
|
|
71
|
+
session.mount("https://", adapter)
|
|
72
|
+
|
|
73
|
+
cloud_url = os.getenv("LIGHTNING_CLOUD_URL", "https://lightning.ai")
|
|
74
|
+
api_key = os.getenv("LIGHTNING_API_KEY")
|
|
75
|
+
username = os.getenv("LIGHTNING_USERNAME")
|
|
76
|
+
project_id = os.getenv("LIGHTNING_CLOUD_PROJECT_ID")
|
|
77
|
+
|
|
78
|
+
if not all([api_key, username, project_id]):
|
|
79
|
+
raise RuntimeError("Missing required environment variables")
|
|
80
|
+
|
|
81
|
+
# Login to get token
|
|
82
|
+
payload = {"apiKey": api_key, "username": username}
|
|
83
|
+
login_url = f"{cloud_url}/v1/auth/login"
|
|
84
|
+
response = session.post(login_url, data=json.dumps(payload))
|
|
85
|
+
|
|
86
|
+
if "token" not in response.json():
|
|
87
|
+
raise RuntimeError("Failed to get authentication token")
|
|
88
|
+
|
|
89
|
+
token = response.json()["token"]
|
|
90
|
+
|
|
91
|
+
# Get temporary bucket credentials
|
|
92
|
+
headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
|
|
93
|
+
credentials_url = (
|
|
94
|
+
f"{cloud_url}/v1/projects/{project_id}/data-connections/{data_connection_id}/temp-bucket-credentials"
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
credentials_response = session.get(credentials_url, headers=headers, timeout=10)
|
|
98
|
+
|
|
99
|
+
if credentials_response.status_code != 200:
|
|
100
|
+
raise RuntimeError(f"Failed to get credentials: {credentials_response.status_code}")
|
|
101
|
+
|
|
102
|
+
return credentials_response.json()
|
|
103
|
+
|
|
104
|
+
|
|
48
105
|
class S3Client:
|
|
49
106
|
# TODO: Generalize to support more cloud providers.
|
|
50
107
|
|
|
@@ -61,6 +118,15 @@ class S3Client:
|
|
|
61
118
|
self._session_options: dict = session_options or {}
|
|
62
119
|
|
|
63
120
|
def _create_client(self) -> None:
|
|
121
|
+
# S3 data connections marked available on non-AWS providers can't reach the bucket via the
|
|
122
|
+
# FUSE mount or an instance profile off AWS, so mint temporary project-role credentials from
|
|
123
|
+
# the control plane instead (mirrors R2Client). Gated on data_connection_id being threaded
|
|
124
|
+
# through storage_options, so plain S3 access is unaffected.
|
|
125
|
+
data_connection_id = self._storage_options.get("data_connection_id")
|
|
126
|
+
if data_connection_id:
|
|
127
|
+
self._create_client_from_temp_credentials(data_connection_id)
|
|
128
|
+
return
|
|
129
|
+
|
|
64
130
|
has_shared_credentials_file = (
|
|
65
131
|
os.getenv("AWS_SHARED_CREDENTIALS_FILE") == os.getenv("AWS_CONFIG_FILE") == "/.credentials/.aws_credentials"
|
|
66
132
|
)
|
|
@@ -86,6 +152,29 @@ class S3Client:
|
|
|
86
152
|
config=botocore.config.Config(retries={"max_attempts": 1000, "mode": "adaptive"}),
|
|
87
153
|
)
|
|
88
154
|
|
|
155
|
+
def _create_client_from_temp_credentials(self, data_connection_id: str) -> None:
|
|
156
|
+
"""Create an S3 client backed by temporary project-role credentials for a data connection.
|
|
157
|
+
|
|
158
|
+
Used for S3 connections available on non-AWS providers. Unlike R2 there is no custom
|
|
159
|
+
endpoint — this is real AWS S3, so botocore resolves the bucket region on first use.
|
|
160
|
+
"""
|
|
161
|
+
temp_credentials = _login_and_get_temp_bucket_credentials(data_connection_id)
|
|
162
|
+
|
|
163
|
+
# data_connection_id is our own metadata; drop it before handing options to boto3.
|
|
164
|
+
storage_options = {k: v for k, v in self._storage_options.items() if k != "data_connection_id"}
|
|
165
|
+
|
|
166
|
+
session = boto3.Session(**self._session_options)
|
|
167
|
+
self._client = session.client(
|
|
168
|
+
"s3",
|
|
169
|
+
**{
|
|
170
|
+
"aws_access_key_id": temp_credentials["accessKeyId"],
|
|
171
|
+
"aws_secret_access_key": temp_credentials["secretAccessKey"],
|
|
172
|
+
"aws_session_token": temp_credentials["sessionToken"],
|
|
173
|
+
"config": botocore.config.Config(retries={"max_attempts": 1000, "mode": "adaptive"}),
|
|
174
|
+
**storage_options,
|
|
175
|
+
},
|
|
176
|
+
)
|
|
177
|
+
|
|
89
178
|
@property
|
|
90
179
|
def client(self) -> Any:
|
|
91
180
|
if self._client is None:
|
|
@@ -121,56 +210,8 @@ class R2Client(S3Client):
|
|
|
121
210
|
|
|
122
211
|
def get_r2_bucket_credentials(self, data_connection_id: str) -> dict[str, str]:
|
|
123
212
|
"""Fetch temporary R2 credentials for the current lightning storage connection."""
|
|
124
|
-
# Create session with retry logic
|
|
125
|
-
retry_strategy = Retry(
|
|
126
|
-
total=_CONNECTION_RETRY_TOTAL,
|
|
127
|
-
backoff_factor=_CONNECTION_RETRY_BACKOFF_FACTOR,
|
|
128
|
-
status_forcelist=[
|
|
129
|
-
408, # Request Timeout
|
|
130
|
-
429, # Too Many Requests
|
|
131
|
-
500, # Internal Server Error
|
|
132
|
-
502, # Bad Gateway
|
|
133
|
-
503, # Service Unavailable
|
|
134
|
-
504, # Gateway Timeout
|
|
135
|
-
],
|
|
136
|
-
)
|
|
137
|
-
adapter = _CustomRetryAdapter(max_retries=retry_strategy, timeout=_DEFAULT_REQUEST_TIMEOUT)
|
|
138
|
-
session = requests.Session()
|
|
139
|
-
session.mount("http://", adapter)
|
|
140
|
-
session.mount("https://", adapter)
|
|
141
|
-
|
|
142
213
|
try:
|
|
143
|
-
|
|
144
|
-
cloud_url = os.getenv("LIGHTNING_CLOUD_URL", "https://lightning.ai")
|
|
145
|
-
api_key = os.getenv("LIGHTNING_API_KEY")
|
|
146
|
-
username = os.getenv("LIGHTNING_USERNAME")
|
|
147
|
-
project_id = os.getenv("LIGHTNING_CLOUD_PROJECT_ID")
|
|
148
|
-
|
|
149
|
-
if not all([api_key, username, project_id]):
|
|
150
|
-
raise RuntimeError("Missing required environment variables")
|
|
151
|
-
|
|
152
|
-
# Login to get token
|
|
153
|
-
payload = {"apiKey": api_key, "username": username}
|
|
154
|
-
login_url = f"{cloud_url}/v1/auth/login"
|
|
155
|
-
response = session.post(login_url, data=json.dumps(payload))
|
|
156
|
-
|
|
157
|
-
if "token" not in response.json():
|
|
158
|
-
raise RuntimeError("Failed to get authentication token")
|
|
159
|
-
|
|
160
|
-
token = response.json()["token"]
|
|
161
|
-
|
|
162
|
-
# Get temporary bucket credentials
|
|
163
|
-
headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
|
|
164
|
-
credentials_url = (
|
|
165
|
-
f"{cloud_url}/v1/projects/{project_id}/data-connections/{data_connection_id}/temp-bucket-credentials"
|
|
166
|
-
)
|
|
167
|
-
|
|
168
|
-
credentials_response = session.get(credentials_url, headers=headers, timeout=10)
|
|
169
|
-
|
|
170
|
-
if credentials_response.status_code != 200:
|
|
171
|
-
raise RuntimeError(f"Failed to get credentials: {credentials_response.status_code}")
|
|
172
|
-
|
|
173
|
-
temp_credentials = credentials_response.json()
|
|
214
|
+
temp_credentials = _login_and_get_temp_bucket_credentials(data_connection_id)
|
|
174
215
|
|
|
175
216
|
endpoint_url = f"https://{temp_credentials['accountId']}.r2.cloudflarestorage.com"
|
|
176
217
|
|
|
@@ -302,6 +302,11 @@ class ChunksConfig:
|
|
|
302
302
|
|
|
303
303
|
return local_chunkpath, begin, filesize_bytes
|
|
304
304
|
|
|
305
|
+
def download_filepath(self, chunk_index: int) -> str:
|
|
306
|
+
"""The raw on-disk path that the chunk is downloaded to before any decompression."""
|
|
307
|
+
assert self._chunks is not None
|
|
308
|
+
return os.path.join(self._cache_dir, self._chunks[chunk_index]["filename"])
|
|
309
|
+
|
|
305
310
|
def _get_chunk_index_from_filename(self, chunk_filename: str) -> int:
|
|
306
311
|
"""Retrieves the associated chunk_index for a given chunk filename."""
|
|
307
312
|
assert self._chunks is not None
|
|
@@ -519,6 +519,43 @@ class StreamingDataLoaderCollateFn:
|
|
|
519
519
|
return self.collate_fn(items)
|
|
520
520
|
|
|
521
521
|
|
|
522
|
+
def _is_fork_context(multiprocessing_context: Any) -> bool:
|
|
523
|
+
import multiprocessing as mp
|
|
524
|
+
|
|
525
|
+
if multiprocessing_context is None:
|
|
526
|
+
try:
|
|
527
|
+
return mp.get_start_method(allow_none=False) == "fork"
|
|
528
|
+
except Exception:
|
|
529
|
+
return False
|
|
530
|
+
|
|
531
|
+
if isinstance(multiprocessing_context, str):
|
|
532
|
+
return multiprocessing_context == "fork"
|
|
533
|
+
|
|
534
|
+
if hasattr(multiprocessing_context, "get_start_method"):
|
|
535
|
+
try:
|
|
536
|
+
return multiprocessing_context.get_start_method() == "fork"
|
|
537
|
+
except Exception:
|
|
538
|
+
return False
|
|
539
|
+
|
|
540
|
+
if hasattr(multiprocessing_context, "start_method"):
|
|
541
|
+
return multiprocessing_context.start_method == "fork"
|
|
542
|
+
|
|
543
|
+
return False
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
def _has_parquet_loader(dataset: Any) -> bool:
|
|
547
|
+
from litdata.streaming.dataset import StreamingDataset
|
|
548
|
+
from litdata.streaming.item_loader import ParquetLoader
|
|
549
|
+
|
|
550
|
+
if isinstance(dataset, StreamingDataset):
|
|
551
|
+
return isinstance(dataset.item_loader, ParquetLoader)
|
|
552
|
+
|
|
553
|
+
if hasattr(dataset, "_datasets") and isinstance(dataset._datasets, (list, tuple)):
|
|
554
|
+
return any(_has_parquet_loader(d) for d in dataset._datasets)
|
|
555
|
+
|
|
556
|
+
return False
|
|
557
|
+
|
|
558
|
+
|
|
522
559
|
class StreamingDataLoader(DataLoader):
|
|
523
560
|
r"""The StreamingDataLoader combines a dataset and a sampler, and provides an iterable over the given dataset.
|
|
524
561
|
|
|
@@ -589,6 +626,14 @@ class StreamingDataLoader(DataLoader):
|
|
|
589
626
|
collate_fn: Callable | None = None,
|
|
590
627
|
**kwargs: Any,
|
|
591
628
|
) -> None: # pyright: ignore
|
|
629
|
+
if num_workers > 0 and _is_fork_context(kwargs.get("multiprocessing_context")) and _has_parquet_loader(dataset):
|
|
630
|
+
raise RuntimeError(
|
|
631
|
+
"The `ParquetLoader` uses Polars, which is not compatible with the `fork` multiprocessing context "
|
|
632
|
+
"used by PyTorch's DataLoader on Linux. Using `fork` will cause deadlocks due to Polars' "
|
|
633
|
+
"internal thread pool. Please pass `multiprocessing_context='spawn'` (or 'forkserver') to "
|
|
634
|
+
"`StreamingDataLoader`. Check thread: https://github.com/Lightning-AI/litData/issues/823"
|
|
635
|
+
)
|
|
636
|
+
|
|
592
637
|
if not isinstance(dataset, (StreamingDataset, _BaseStreamingDatasetWrapper)):
|
|
593
638
|
raise RuntimeError(
|
|
594
639
|
"The provided dataset should be either an instance of StreamingDataset, CombinedStreamingDataset or "
|
|
@@ -268,7 +268,7 @@ class R2Downloader(Downloader):
|
|
|
268
268
|
if obj.scheme != "r2":
|
|
269
269
|
raise ValueError(f"Expected obj.scheme to be `r2`, instead, got {obj.scheme} for remote={remote_filepath}")
|
|
270
270
|
|
|
271
|
-
if not hasattr(self, "
|
|
271
|
+
if not hasattr(self, "_client"):
|
|
272
272
|
self._client = R2Client(storage_options=self._storage_options, session_options=self.session_options)
|
|
273
273
|
|
|
274
274
|
bucket = obj.netloc
|
|
@@ -20,7 +20,9 @@ from litdata.streaming.client import R2Client, S3Client
|
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
class FsProvider(ABC):
|
|
23
|
-
def __init__(self, storage_options: dict[str, Any] | None =
|
|
23
|
+
def __init__(self, storage_options: dict[str, Any] | None = None):
|
|
24
|
+
if storage_options is None:
|
|
25
|
+
storage_options = {}
|
|
24
26
|
self.storage_options = storage_options
|
|
25
27
|
|
|
26
28
|
@abstractmethod
|
|
@@ -50,7 +52,7 @@ class FsProvider(ABC):
|
|
|
50
52
|
|
|
51
53
|
|
|
52
54
|
class GCPFsProvider(FsProvider):
|
|
53
|
-
def __init__(self, storage_options: dict[str, Any] | None =
|
|
55
|
+
def __init__(self, storage_options: dict[str, Any] | None = None):
|
|
54
56
|
if not _GOOGLE_STORAGE_AVAILABLE:
|
|
55
57
|
raise ModuleNotFoundError(str(_GOOGLE_STORAGE_AVAILABLE))
|
|
56
58
|
from google.cloud import storage
|
|
@@ -133,9 +135,9 @@ class GCPFsProvider(FsProvider):
|
|
|
133
135
|
|
|
134
136
|
|
|
135
137
|
class S3FsProvider(FsProvider):
|
|
136
|
-
def __init__(self, storage_options: dict[str, Any] | None =
|
|
138
|
+
def __init__(self, storage_options: dict[str, Any] | None = None):
|
|
137
139
|
super().__init__(storage_options=storage_options)
|
|
138
|
-
self.client = S3Client(storage_options=storage_options)
|
|
140
|
+
self.client = S3Client(storage_options=self.storage_options)
|
|
139
141
|
|
|
140
142
|
def upload_file(self, local_path: str, remote_path: str) -> None:
|
|
141
143
|
bucket_name, blob_path = get_bucket_and_path(remote_path, "s3")
|
|
@@ -225,11 +227,11 @@ class S3FsProvider(FsProvider):
|
|
|
225
227
|
|
|
226
228
|
|
|
227
229
|
class R2FsProvider(S3FsProvider):
|
|
228
|
-
def __init__(self, storage_options: dict[str, Any] | None =
|
|
230
|
+
def __init__(self, storage_options: dict[str, Any] | None = None):
|
|
229
231
|
super().__init__(storage_options=storage_options)
|
|
230
232
|
|
|
231
233
|
# Create R2Client with refreshable credentials
|
|
232
|
-
self.client = R2Client(storage_options=storage_options)
|
|
234
|
+
self.client = R2Client(storage_options=self.storage_options)
|
|
233
235
|
|
|
234
236
|
def upload_file(self, local_path: str, remote_path: str) -> None:
|
|
235
237
|
bucket_name, blob_path = get_bucket_and_path(remote_path, "r2")
|
|
@@ -324,7 +326,7 @@ def get_bucket_and_path(remote_filepath: str, expected_scheme: str = "s3") -> tu
|
|
|
324
326
|
return bucket_name, blob_path
|
|
325
327
|
|
|
326
328
|
|
|
327
|
-
def _get_fs_provider(remote_filepath: str, storage_options: dict[str, Any] | None =
|
|
329
|
+
def _get_fs_provider(remote_filepath: str, storage_options: dict[str, Any] | None = None) -> FsProvider:
|
|
328
330
|
obj = parse.urlparse(remote_filepath)
|
|
329
331
|
if obj.scheme == "gs":
|
|
330
332
|
return GCPFsProvider(storage_options=storage_options)
|
|
@@ -642,6 +642,7 @@ class ParquetLoader(BaseItemLoader):
|
|
|
642
642
|
self._df: dict[int, Any] = {}
|
|
643
643
|
self._chunk_row_groups: dict[int, Any] = {}
|
|
644
644
|
self._chunk_row_group_item_read_count: dict[int, Any] = {}
|
|
645
|
+
self._chunk_row_group_offsets: dict[int, list[int]] = {}
|
|
645
646
|
|
|
646
647
|
def generate_intervals(self) -> list[Interval]:
|
|
647
648
|
intervals = []
|
|
@@ -712,18 +713,28 @@ class ParquetLoader(BaseItemLoader):
|
|
|
712
713
|
Returns:
|
|
713
714
|
Any: The dataframe row corresponding to the specified index.
|
|
714
715
|
"""
|
|
716
|
+
import bisect
|
|
717
|
+
|
|
715
718
|
import polars as pl
|
|
716
719
|
import pyarrow.parquet as pq
|
|
717
720
|
|
|
718
721
|
# Load the Parquet file metadata if not already loaded
|
|
719
722
|
if chunk_index not in self._df:
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
723
|
+
parquet_file = pq.ParquetFile(chunk_filepath)
|
|
724
|
+
self._df[chunk_index] = parquet_file
|
|
725
|
+
# Precompute cumulative row offsets as a prefix-sum so lookup works for row groups of any size.
|
|
726
|
+
offsets = [0]
|
|
727
|
+
num_row_groups = parquet_file.metadata.num_row_groups
|
|
728
|
+
for i in range(num_row_groups):
|
|
729
|
+
num_rows = parquet_file.metadata.row_group(i).num_rows
|
|
730
|
+
offsets.append(offsets[-1] + num_rows)
|
|
731
|
+
self._chunk_row_group_offsets[chunk_index] = offsets
|
|
732
|
+
|
|
733
|
+
# Locate the row group containing row_index and the offset inside it.
|
|
734
|
+
offsets = self._chunk_row_group_offsets[chunk_index]
|
|
735
|
+
row_group_index = bisect.bisect_right(offsets, row_index) - 1
|
|
736
|
+
row_index_within_group = row_index - offsets[row_group_index]
|
|
737
|
+
row_group_size = offsets[row_group_index + 1] - offsets[row_group_index]
|
|
727
738
|
|
|
728
739
|
# Check if the row group is already loaded
|
|
729
740
|
if chunk_index in self._chunk_row_groups and row_group_index in self._chunk_row_groups[chunk_index]:
|
|
@@ -746,7 +757,7 @@ class ParquetLoader(BaseItemLoader):
|
|
|
746
757
|
|
|
747
758
|
# Check if the row group has been fully read and release memory if necessary
|
|
748
759
|
read_count = self._chunk_row_group_item_read_count[chunk_index][row_group_index]
|
|
749
|
-
if read_count >=
|
|
760
|
+
if read_count >= row_group_size:
|
|
750
761
|
# Release memory for the fully read row group
|
|
751
762
|
del self._chunk_row_groups[chunk_index][row_group_index]
|
|
752
763
|
del self._chunk_row_group_item_read_count[chunk_index][row_group_index]
|
|
@@ -797,6 +808,8 @@ class ParquetLoader(BaseItemLoader):
|
|
|
797
808
|
|
|
798
809
|
if chunk_index in self._chunk_row_group_item_read_count:
|
|
799
810
|
del self._chunk_row_group_item_read_count[chunk_index]
|
|
811
|
+
if chunk_index in self._chunk_row_group_offsets:
|
|
812
|
+
del self._chunk_row_group_offsets[chunk_index]
|
|
800
813
|
if os.path.exists(chunk_filepath):
|
|
801
814
|
os.remove(chunk_filepath)
|
|
802
815
|
logger.debug(
|
|
@@ -820,5 +833,8 @@ class ParquetLoader(BaseItemLoader):
|
|
|
820
833
|
if chunk_index in self._chunk_row_group_item_read_count:
|
|
821
834
|
del self._chunk_row_group_item_read_count[chunk_index]
|
|
822
835
|
|
|
836
|
+
if chunk_index in self._chunk_row_group_offsets:
|
|
837
|
+
del self._chunk_row_group_offsets[chunk_index]
|
|
838
|
+
|
|
823
839
|
def encode_data(self, data: list[bytes], sizes: list[int], flattened: list[Any]) -> Any:
|
|
824
840
|
pass
|
|
@@ -239,25 +239,39 @@ class PrepareChunksThread(Thread):
|
|
|
239
239
|
|
|
240
240
|
def _force_download(self) -> None:
|
|
241
241
|
chunk_index = _get_from_queue(self._force_download_queue)
|
|
242
|
-
if chunk_index is
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
242
|
+
if chunk_index is None:
|
|
243
|
+
return
|
|
244
|
+
|
|
245
|
+
chunk_filepath, _, filesize_bytes = self._config[ChunkedIndex(index=-1, chunk_index=chunk_index)]
|
|
246
|
+
download_lock_path = self._config.download_filepath(chunk_index) + ".lock"
|
|
247
|
+
try:
|
|
248
|
+
with FileLock(download_lock_path, timeout=0):
|
|
249
|
+
# The chunk may have been fully downloaded by the time this
|
|
250
|
+
# request was processed, so double check that it still needs
|
|
251
|
+
# downloading before we delete it.
|
|
252
|
+
if os.path.exists(chunk_filepath) and os.stat(chunk_filepath).st_size >= filesize_bytes:
|
|
253
|
+
return
|
|
254
|
+
|
|
255
|
+
# force apply deletion before redownload
|
|
256
|
+
self._apply_delete(chunk_index, skip_lock=True)
|
|
257
|
+
if _DEBUG:
|
|
258
|
+
print(
|
|
259
|
+
f"[Reader] Requested force download for {chunk_filepath} "
|
|
260
|
+
f"by {self._rank} at {datetime.now().isoformat()}"
|
|
261
|
+
)
|
|
251
262
|
|
|
252
263
|
self._config.download_chunk_from_index(chunk_index, skip_lock=True)
|
|
264
|
+
except Timeout:
|
|
265
|
+
# Another worker is actively downloading this chunk. Defer to them.
|
|
266
|
+
return
|
|
253
267
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
268
|
+
# Preload item if possible to gain some time but only
|
|
269
|
+
# if this is one of the pre-downloaded chunk
|
|
270
|
+
if self._pre_download_counter > 0:
|
|
271
|
+
self._pre_load_chunk(chunk_index)
|
|
258
272
|
|
|
259
|
-
|
|
260
|
-
|
|
273
|
+
# Avoid downloading too many chunks in advance at the risk of over using the disk space
|
|
274
|
+
self._pre_download_counter += 1
|
|
261
275
|
|
|
262
276
|
def run(self) -> None:
|
|
263
277
|
while True:
|
|
@@ -217,7 +217,19 @@ def _resolve_data_connection(dir_path: str) -> "V1DataConnection":
|
|
|
217
217
|
def _resolve_s3_connections(dir_path: str) -> Dir:
|
|
218
218
|
data_connection = _resolve_data_connection(dir_path)
|
|
219
219
|
|
|
220
|
-
|
|
220
|
+
# S3 connections flagged available on non-AWS providers can't rely on the FUSE mount or an
|
|
221
|
+
# instance profile off AWS, so thread the connection id through. That makes the S3 client mint
|
|
222
|
+
# temporary project-role credentials (same mechanism as R2 / lightning_storage below). Plain
|
|
223
|
+
# AWS-only S3 connections leave this unset and keep using the ambient credentials.
|
|
224
|
+
data_connection_id = (
|
|
225
|
+
data_connection.id if getattr(data_connection.aws, "available_in_non_aws_providers", False) else None
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
return Dir(
|
|
229
|
+
path=dir_path,
|
|
230
|
+
url=os.path.join(data_connection.aws.source, *dir_path.split("/")[4:]),
|
|
231
|
+
data_connection_id=data_connection_id,
|
|
232
|
+
)
|
|
221
233
|
|
|
222
234
|
|
|
223
235
|
def _resolve_gcs_connections(dir_path: str) -> Dir:
|
|
@@ -20,6 +20,7 @@ from abc import ABC, abstractmethod
|
|
|
20
20
|
from collections import OrderedDict
|
|
21
21
|
from contextlib import suppress
|
|
22
22
|
from copy import deepcopy
|
|
23
|
+
from dataclasses import asdict
|
|
23
24
|
from itertools import chain
|
|
24
25
|
from typing import Any
|
|
25
26
|
|
|
@@ -32,6 +33,7 @@ from litdata.constants import (
|
|
|
32
33
|
_NUMPY_DTYPES_MAPPING,
|
|
33
34
|
_PIL_AVAILABLE,
|
|
34
35
|
_TORCH_DTYPES_MAPPING,
|
|
36
|
+
_TORCH_VISION_LESS_THAN_0_26,
|
|
35
37
|
)
|
|
36
38
|
|
|
37
39
|
|
|
@@ -403,6 +405,13 @@ class VideoSerializer(Serializer):
|
|
|
403
405
|
return f.read(), f"video:{file_extension}"
|
|
404
406
|
|
|
405
407
|
def deserialize(self, data: bytes) -> Any:
|
|
408
|
+
# if using torchvision <=0.25, we will use torchvision.io to decode the video
|
|
409
|
+
# otherwise, we will use torchcodec to decode the video, which is faster and more robust
|
|
410
|
+
if _TORCH_VISION_LESS_THAN_0_26:
|
|
411
|
+
return self._deserialize_with_torchvision_io(data)
|
|
412
|
+
return self._deserialize_with_torchcodec(data)
|
|
413
|
+
|
|
414
|
+
def _deserialize_with_torchvision_io(self, data: bytes) -> Any:
|
|
406
415
|
if not _AV_AVAILABLE:
|
|
407
416
|
raise ModuleNotFoundError("av is required. Run `pip install av`")
|
|
408
417
|
|
|
@@ -416,6 +425,29 @@ class VideoSerializer(Serializer):
|
|
|
416
425
|
stream.write(data)
|
|
417
426
|
return torchvision.io.read_video(fname, pts_unit="sec")
|
|
418
427
|
|
|
428
|
+
def _deserialize_with_torchcodec(self, data: bytes) -> Any:
|
|
429
|
+
try:
|
|
430
|
+
import torch
|
|
431
|
+
from torchcodec.decoders import AudioDecoder, VideoDecoder
|
|
432
|
+
except ImportError:
|
|
433
|
+
raise ModuleNotFoundError("torchcodec is required. Run `pip install torchcodec>0.11`")
|
|
434
|
+
|
|
435
|
+
dec = VideoDecoder(data, dimension_order="NHWC") # NHWC → T,H,W,C after stacking
|
|
436
|
+
metadata = asdict(dec.metadata) if dec.metadata is not None else {}
|
|
437
|
+
|
|
438
|
+
# get_all_frames() returns a FrameBatch; .data is (N, C, H, W) or (N, H, W, C)
|
|
439
|
+
# depending on dimension_order above
|
|
440
|
+
frame_batch = dec.get_all_frames()
|
|
441
|
+
video = frame_batch.data # shape: (T, H, W, C) with NHWC
|
|
442
|
+
|
|
443
|
+
try:
|
|
444
|
+
audio_dec = AudioDecoder(data)
|
|
445
|
+
audio = audio_dec.get_all_samples().data # (num_channels, num_samples)
|
|
446
|
+
except ValueError:
|
|
447
|
+
audio = torch.zeros(1, 0) # old torchvision path returns aframes with shape (1, 0) for no-audio videos.
|
|
448
|
+
|
|
449
|
+
return video, audio, metadata
|
|
450
|
+
|
|
419
451
|
def can_serialize(self, data: Any) -> bool:
|
|
420
452
|
return isinstance(data, str) and os.path.isfile(data) and any(data.endswith(ext) for ext in self._EXTENSIONS)
|
|
421
453
|
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
import json
|
|
15
15
|
import os
|
|
16
|
+
import re
|
|
16
17
|
import uuid
|
|
17
18
|
import warnings
|
|
18
19
|
from dataclasses import dataclass
|
|
@@ -499,7 +500,10 @@ class BinaryWriter:
|
|
|
499
500
|
chunks_info.extend(existing_index["chunks"])
|
|
500
501
|
config = existing_index["config"]
|
|
501
502
|
|
|
502
|
-
|
|
503
|
+
def _natural_key(s: str) -> list:
|
|
504
|
+
return [int(t) if t.isdigit() else t for t in re.split(r"(\d+)", s)]
|
|
505
|
+
|
|
506
|
+
for index_filename in sorted(index_files, key=_natural_key):
|
|
503
507
|
chunk_path = os.path.join(self._cache_dir, index_filename)
|
|
504
508
|
with open(chunk_path) as f:
|
|
505
509
|
data = json.load(f)
|
|
@@ -21,9 +21,12 @@ class ParquetDir(ABC):
|
|
|
21
21
|
self,
|
|
22
22
|
dir_path: str | Dir | None,
|
|
23
23
|
cache_path: str | None = None,
|
|
24
|
-
storage_options: dict | None =
|
|
24
|
+
storage_options: dict | None = None,
|
|
25
25
|
num_workers: int = 4,
|
|
26
26
|
):
|
|
27
|
+
if storage_options is None:
|
|
28
|
+
storage_options = {}
|
|
29
|
+
|
|
27
30
|
self.dir = _resolve_dir(dir_path)
|
|
28
31
|
self.cache_path = cache_path
|
|
29
32
|
self.storage_options = storage_options
|
|
@@ -147,7 +150,7 @@ class CloudParquetDir(ParquetDir):
|
|
|
147
150
|
for provider in _CLOUD_PROVIDER:
|
|
148
151
|
if self.dir.url.startswith(provider):
|
|
149
152
|
# Initialize the cloud filesystem
|
|
150
|
-
self.fs = fsspec.filesystem(provider,
|
|
153
|
+
self.fs = fsspec.filesystem(provider, **self.storage_options)
|
|
151
154
|
print(f"using provider: {provider}")
|
|
152
155
|
break
|
|
153
156
|
|
|
@@ -323,7 +326,12 @@ def get_parquet_indexer_cls(
|
|
|
323
326
|
|
|
324
327
|
obj = parse.urlparse(dir_path)
|
|
325
328
|
|
|
326
|
-
|
|
329
|
+
# On Windows, paths like "C:\\Users\\..." parse with scheme='c'. A single-letter
|
|
330
|
+
# scheme is never a valid URI scheme (RFC 3986 requires >=2 chars) so treat it
|
|
331
|
+
# as a Windows drive letter and dispatch to LocalParquetDir.
|
|
332
|
+
is_windows_drive = len(obj.scheme) == 1 and obj.scheme.isalpha()
|
|
333
|
+
|
|
334
|
+
if obj.scheme in ("local", "") or is_windows_drive:
|
|
327
335
|
return LocalParquetDir(*args)
|
|
328
336
|
if obj.scheme in ("gs", "s3"):
|
|
329
337
|
return CloudParquetDir(*args)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: litdata
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.64
|
|
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
|
|
@@ -39,10 +39,10 @@ Requires-Dist: obstore
|
|
|
39
39
|
Provides-Extra: extras
|
|
40
40
|
Requires-Dist: fsspec; extra == "extras"
|
|
41
41
|
Requires-Dist: google-cloud-storage; extra == "extras"
|
|
42
|
-
Requires-Dist: lightning-sdk==
|
|
42
|
+
Requires-Dist: lightning-sdk==2026.2.6; extra == "extras"
|
|
43
43
|
Requires-Dist: pillow; extra == "extras"
|
|
44
44
|
Requires-Dist: polars; extra == "extras"
|
|
45
|
-
Requires-Dist: pyarrow; extra == "extras"
|
|
45
|
+
Requires-Dist: pyarrow<25.0.0; extra == "extras"
|
|
46
46
|
Requires-Dist: tqdm; extra == "extras"
|
|
47
47
|
Requires-Dist: viztracer; extra == "extras"
|
|
48
48
|
Dynamic: author
|
|
@@ -22,6 +22,9 @@ src/litdata.egg-info/top_level.txt
|
|
|
22
22
|
src/litdata/cli/__init__.py
|
|
23
23
|
src/litdata/cli/commands.py
|
|
24
24
|
src/litdata/cli/parser.py
|
|
25
|
+
src/litdata/cli/handler/__init__.py
|
|
26
|
+
src/litdata/cli/handler/cache.py
|
|
27
|
+
src/litdata/cli/handler/optimize.py
|
|
25
28
|
src/litdata/processing/__init__.py
|
|
26
29
|
src/litdata/processing/data_processor.py
|
|
27
30
|
src/litdata/processing/functions.py
|
|
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
|