flyteplugins-lance 2.5.15__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.
- flyteplugins/lance/__init__.py +3 -0
- flyteplugins/lance/df_transformer.py +239 -0
- flyteplugins_lance-2.5.15.dist-info/METADATA +78 -0
- flyteplugins_lance-2.5.15.dist-info/RECORD +7 -0
- flyteplugins_lance-2.5.15.dist-info/WHEEL +5 -0
- flyteplugins_lance-2.5.15.dist-info/entry_points.txt +2 -0
- flyteplugins_lance-2.5.15.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
import typing
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import flyte.storage as storage
|
|
6
|
+
from flyte._utils import lazy_module
|
|
7
|
+
from flyte.io._dataframe.dataframe import DataFrame
|
|
8
|
+
from flyte.io.extend import (
|
|
9
|
+
DataFrameDecoder,
|
|
10
|
+
DataFrameEncoder,
|
|
11
|
+
DataFrameTransformerEngine,
|
|
12
|
+
)
|
|
13
|
+
from flyteidl2.core import literals_pb2, types_pb2
|
|
14
|
+
|
|
15
|
+
if typing.TYPE_CHECKING:
|
|
16
|
+
import pyarrow as pa
|
|
17
|
+
|
|
18
|
+
import lance
|
|
19
|
+
else:
|
|
20
|
+
lance = lazy_module("lance")
|
|
21
|
+
pa = lazy_module("pyarrow")
|
|
22
|
+
|
|
23
|
+
# Format string registered with the DataFrame type system. A DataFrame with this
|
|
24
|
+
# format is backed by a Lance dataset directory on (possibly remote) storage.
|
|
25
|
+
LANCE = "lance"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def get_lance_storage_options(protocol: typing.Optional[str]) -> typing.Dict[str, str]:
|
|
29
|
+
"""
|
|
30
|
+
Build a flat string storage_options dict for Lance from Flyte's storage config.
|
|
31
|
+
|
|
32
|
+
Lance is backed by the same object_store crate as Polars, so the keys mirror the
|
|
33
|
+
Polars ones closely. The one difference for S3 is the endpoint key: Lance uses
|
|
34
|
+
"aws_endpoint" (Polars uses "aws_endpoint_url").
|
|
35
|
+
"""
|
|
36
|
+
from flyte._initialize import get_storage
|
|
37
|
+
from flyte.errors import InitializationError
|
|
38
|
+
|
|
39
|
+
if not protocol:
|
|
40
|
+
return {}
|
|
41
|
+
|
|
42
|
+
try:
|
|
43
|
+
storage_config = get_storage()
|
|
44
|
+
except InitializationError:
|
|
45
|
+
storage_config = None
|
|
46
|
+
|
|
47
|
+
match protocol:
|
|
48
|
+
case "s3":
|
|
49
|
+
from flyte.storage import S3
|
|
50
|
+
|
|
51
|
+
if storage_config and isinstance(storage_config, S3):
|
|
52
|
+
s3_config = storage_config
|
|
53
|
+
else:
|
|
54
|
+
s3_config = S3.auto()
|
|
55
|
+
|
|
56
|
+
opts: typing.Dict[str, str] = {}
|
|
57
|
+
if s3_config.access_key_id:
|
|
58
|
+
opts["aws_access_key_id"] = s3_config.access_key_id
|
|
59
|
+
if s3_config.secret_access_key:
|
|
60
|
+
opts["aws_secret_access_key"] = s3_config.secret_access_key
|
|
61
|
+
if s3_config.region:
|
|
62
|
+
opts["aws_region"] = s3_config.region
|
|
63
|
+
if s3_config.endpoint:
|
|
64
|
+
opts["aws_endpoint"] = s3_config.endpoint
|
|
65
|
+
# Custom endpoints (e.g. a local MinIO) are usually plain HTTP; the
|
|
66
|
+
# object_store client refuses HTTP unless explicitly allowed.
|
|
67
|
+
if s3_config.endpoint.startswith("http://"):
|
|
68
|
+
opts["aws_allow_http"] = "true"
|
|
69
|
+
return opts
|
|
70
|
+
|
|
71
|
+
case "gs" | "gcs":
|
|
72
|
+
# GCS typically uses application default credentials, which the
|
|
73
|
+
# object_store client picks up automatically.
|
|
74
|
+
return {}
|
|
75
|
+
|
|
76
|
+
case "abfs" | "abfss":
|
|
77
|
+
from flyte.storage import ABFS
|
|
78
|
+
|
|
79
|
+
if storage_config and isinstance(storage_config, ABFS):
|
|
80
|
+
abfs_config = storage_config
|
|
81
|
+
else:
|
|
82
|
+
abfs_config = ABFS.auto()
|
|
83
|
+
|
|
84
|
+
opts = {}
|
|
85
|
+
if abfs_config.account_name:
|
|
86
|
+
opts["azure_storage_account_name"] = abfs_config.account_name
|
|
87
|
+
if abfs_config.account_key:
|
|
88
|
+
opts["azure_storage_account_key"] = abfs_config.account_key
|
|
89
|
+
if abfs_config.tenant_id:
|
|
90
|
+
opts["azure_storage_tenant_id"] = abfs_config.tenant_id
|
|
91
|
+
if abfs_config.client_id:
|
|
92
|
+
opts["azure_storage_client_id"] = abfs_config.client_id
|
|
93
|
+
if abfs_config.client_secret:
|
|
94
|
+
opts["azure_storage_client_secret"] = abfs_config.client_secret
|
|
95
|
+
return opts
|
|
96
|
+
|
|
97
|
+
case _:
|
|
98
|
+
return {}
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _resolve_write_uri(dataframe: DataFrame) -> str:
|
|
102
|
+
"""Pick the URI to write to: an explicit one on the DataFrame, or a fresh
|
|
103
|
+
Flyte-managed path from the task's raw-data prefix (local temp or blob store)."""
|
|
104
|
+
if dataframe.uri:
|
|
105
|
+
return typing.cast(str, dataframe.uri)
|
|
106
|
+
|
|
107
|
+
from flyte._context import internal_ctx
|
|
108
|
+
|
|
109
|
+
return str(internal_ctx().raw_data.get_random_remote_path())
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _storage_options_for(uri: str) -> typing.Optional[typing.Dict[str, str]]:
|
|
113
|
+
"""Storage options for a URI, or None for local paths (Lance wants None locally)."""
|
|
114
|
+
if not storage.is_remote(uri):
|
|
115
|
+
return None
|
|
116
|
+
filesystem = storage.get_underlying_filesystem(path=uri)
|
|
117
|
+
protocol = filesystem.protocol
|
|
118
|
+
if isinstance(protocol, (list, tuple)):
|
|
119
|
+
protocol = protocol[0]
|
|
120
|
+
return get_lance_storage_options(protocol=protocol) or None
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class LanceToLanceDatasetDecodingHandler(DataFrameDecoder):
|
|
124
|
+
"""Decode a "lance" DataFrame into a live lance.LanceDataset handle.
|
|
125
|
+
|
|
126
|
+
This is the streaming path: the dataset is opened lazily (manifest only) and
|
|
127
|
+
the caller streams from it via scanner()/take()/to_batches(). Column subsetting
|
|
128
|
+
is intentionally left to the caller's scanner(columns=...), which is more
|
|
129
|
+
flexible for streaming than eagerly subsetting here.
|
|
130
|
+
"""
|
|
131
|
+
|
|
132
|
+
def __init__(self):
|
|
133
|
+
super().__init__(lance.LanceDataset, None, LANCE)
|
|
134
|
+
|
|
135
|
+
async def decode(
|
|
136
|
+
self,
|
|
137
|
+
flyte_value: literals_pb2.StructuredDataset,
|
|
138
|
+
current_task_metadata: literals_pb2.StructuredDatasetMetadata,
|
|
139
|
+
) -> "lance.LanceDataset":
|
|
140
|
+
uri = flyte_value.uri
|
|
141
|
+
return lance.dataset(uri, storage_options=_storage_options_for(uri))
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class LanceDatasetToLanceEncodingHandler(DataFrameEncoder):
|
|
145
|
+
"""Encode a lance.LanceDataset by copying it into Flyte-managed storage.
|
|
146
|
+
|
|
147
|
+
The source dataset is streamed fragment-by-fragment via its RecordBatchReader,
|
|
148
|
+
so this does not materialize the whole dataset in memory.
|
|
149
|
+
"""
|
|
150
|
+
|
|
151
|
+
def __init__(self):
|
|
152
|
+
super().__init__(lance.LanceDataset, None, LANCE)
|
|
153
|
+
|
|
154
|
+
async def encode(
|
|
155
|
+
self,
|
|
156
|
+
dataframe: DataFrame,
|
|
157
|
+
structured_dataset_type: types_pb2.StructuredDatasetType,
|
|
158
|
+
) -> literals_pb2.StructuredDataset:
|
|
159
|
+
uri = _resolve_write_uri(dataframe)
|
|
160
|
+
if not storage.is_remote(uri):
|
|
161
|
+
Path(uri).parent.mkdir(parents=True, exist_ok=True)
|
|
162
|
+
|
|
163
|
+
source = typing.cast("lance.LanceDataset", dataframe.val)
|
|
164
|
+
reader = source.scanner().to_reader()
|
|
165
|
+
lance.write_dataset(reader, uri, storage_options=_storage_options_for(uri))
|
|
166
|
+
|
|
167
|
+
structured_dataset_type.format = LANCE
|
|
168
|
+
return literals_pb2.StructuredDataset(
|
|
169
|
+
uri=uri,
|
|
170
|
+
metadata=literals_pb2.StructuredDatasetMetadata(structured_dataset_type=structured_dataset_type),
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
class ArrowToLanceEncodingHandler(DataFrameEncoder):
|
|
175
|
+
"""Encode an in-memory pyarrow.Table as a Lance dataset.
|
|
176
|
+
|
|
177
|
+
Not registered as the default for pyarrow.Table (that stays Parquet); opt in
|
|
178
|
+
with an explicit "lance" format, e.g. Annotated[DataFrame, "lance"].
|
|
179
|
+
"""
|
|
180
|
+
|
|
181
|
+
def __init__(self):
|
|
182
|
+
super().__init__(pa.Table, None, LANCE)
|
|
183
|
+
|
|
184
|
+
async def encode(
|
|
185
|
+
self,
|
|
186
|
+
dataframe: DataFrame,
|
|
187
|
+
structured_dataset_type: types_pb2.StructuredDatasetType,
|
|
188
|
+
) -> literals_pb2.StructuredDataset:
|
|
189
|
+
uri = _resolve_write_uri(dataframe)
|
|
190
|
+
if not storage.is_remote(uri):
|
|
191
|
+
Path(uri).parent.mkdir(parents=True, exist_ok=True)
|
|
192
|
+
|
|
193
|
+
table = typing.cast("pa.Table", dataframe.val)
|
|
194
|
+
lance.write_dataset(table, uri, storage_options=_storage_options_for(uri))
|
|
195
|
+
|
|
196
|
+
structured_dataset_type.format = LANCE
|
|
197
|
+
return literals_pb2.StructuredDataset(
|
|
198
|
+
uri=uri,
|
|
199
|
+
metadata=literals_pb2.StructuredDatasetMetadata(structured_dataset_type=structured_dataset_type),
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
class LanceToArrowDecodingHandler(DataFrameDecoder):
|
|
204
|
+
"""Decode a "lance" DataFrame eagerly into a pyarrow.Table.
|
|
205
|
+
|
|
206
|
+
This materializes the whole dataset in memory. For large or multimodal datasets
|
|
207
|
+
(e.g. image bytes) decode to lance.LanceDataset and stream instead.
|
|
208
|
+
"""
|
|
209
|
+
|
|
210
|
+
def __init__(self):
|
|
211
|
+
super().__init__(pa.Table, None, LANCE)
|
|
212
|
+
|
|
213
|
+
async def decode(
|
|
214
|
+
self,
|
|
215
|
+
flyte_value: literals_pb2.StructuredDataset,
|
|
216
|
+
current_task_metadata: literals_pb2.StructuredDatasetMetadata,
|
|
217
|
+
) -> "pa.Table":
|
|
218
|
+
uri = flyte_value.uri
|
|
219
|
+
columns = None
|
|
220
|
+
if current_task_metadata.structured_dataset_type and current_task_metadata.structured_dataset_type.columns:
|
|
221
|
+
columns = [c.name for c in current_task_metadata.structured_dataset_type.columns]
|
|
222
|
+
return lance.dataset(uri, storage_options=_storage_options_for(uri)).to_table(columns=columns)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
@functools.lru_cache(maxsize=None)
|
|
226
|
+
def register_lance_df_transformers():
|
|
227
|
+
"""Register Lance DataFrame encoders and decoders with the DataFrameTransformerEngine.
|
|
228
|
+
|
|
229
|
+
This function is called automatically via the flyte.plugins.types entry point
|
|
230
|
+
when flyte.init() is called with load_plugin_type_transformers=True (the default).
|
|
231
|
+
"""
|
|
232
|
+
DataFrameTransformerEngine.register(LanceDatasetToLanceEncodingHandler(), default_format_for_type=True)
|
|
233
|
+
DataFrameTransformerEngine.register(LanceToLanceDatasetDecodingHandler(), default_format_for_type=True)
|
|
234
|
+
DataFrameTransformerEngine.register(ArrowToLanceEncodingHandler())
|
|
235
|
+
DataFrameTransformerEngine.register(LanceToArrowDecodingHandler())
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
# Also register at module import time for backwards compatibility
|
|
239
|
+
register_lance_df_transformers()
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: flyteplugins-lance
|
|
3
|
+
Version: 2.5.15
|
|
4
|
+
Summary: Lance Plugin for Flyte
|
|
5
|
+
Author-email: Samhita Alla <samhita@union.ai>
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: pylance
|
|
9
|
+
Requires-Dist: pyarrow
|
|
10
|
+
Requires-Dist: flyte
|
|
11
|
+
|
|
12
|
+
# Lance Plugin
|
|
13
|
+
|
|
14
|
+
This plugin adds a "lance" format to the Flyte DataFrame, so a Lance dataset can
|
|
15
|
+
be passed between tasks as a typed `flyte.io.DataFrame`.
|
|
16
|
+
|
|
17
|
+
Lance is a columnar, multimodal, streaming-optimized format. Its central property
|
|
18
|
+
is that a dataset is opened lazily and streamed on demand — sequentially for a
|
|
19
|
+
scan or by random access for shuffled training — without materializing the whole
|
|
20
|
+
thing in memory. This plugin preserves that: the primary decoder hands back a live
|
|
21
|
+
`lance.LanceDataset` handle you can stream from, not a materialized table.
|
|
22
|
+
|
|
23
|
+
The plugin registers:
|
|
24
|
+
|
|
25
|
+
- `lance.LanceDataset` as the default in-memory type for the "lance" format —
|
|
26
|
+
encoded by copying the dataset to Flyte-managed storage, decoded lazily via
|
|
27
|
+
`lance.dataset(uri)`. This is the streaming path.
|
|
28
|
+
- `pyarrow.Table` for the "lance" format, for handing off an in-memory table.
|
|
29
|
+
Because `pyarrow.Table` already defaults to Parquet, this is the one case where
|
|
30
|
+
you opt into Lance explicitly, with `Annotated[DataFrame, "lance"]`. Encoded with
|
|
31
|
+
`lance.write_dataset` and decoded eagerly with `dataset.to_table()`, which
|
|
32
|
+
materializes the whole dataset — prefer `lance.LanceDataset` for large or
|
|
33
|
+
multimodal data.
|
|
34
|
+
|
|
35
|
+
Object-store credentials are threaded through Lance's `storage_options` from
|
|
36
|
+
Flyte's storage configuration, so remote reads and writes go through the same
|
|
37
|
+
credentials as the rest of Flyte.
|
|
38
|
+
|
|
39
|
+
To install the plugin, run the following command:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install flyteplugins-lance
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Usage:
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
import tempfile
|
|
49
|
+
|
|
50
|
+
import flyte
|
|
51
|
+
import lance
|
|
52
|
+
import pyarrow as pa
|
|
53
|
+
|
|
54
|
+
# Installing the plugin in the task image is all that is needed. Flyte discovers
|
|
55
|
+
# it through the flyte.plugins.types entry point and registers the "lance" format
|
|
56
|
+
# automatically, so there is nothing to import in your task code.
|
|
57
|
+
env = flyte.TaskEnvironment(
|
|
58
|
+
name="lance-example",
|
|
59
|
+
image=flyte.Image.from_debian_base().with_pip_packages("flyteplugins-lance"),
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@env.task
|
|
64
|
+
async def make() -> lance.LanceDataset:
|
|
65
|
+
uri = f"{tempfile.mkdtemp()}/example.lance"
|
|
66
|
+
lance.write_dataset(pa.table({"id": [1, 2, 3]}), uri)
|
|
67
|
+
return lance.dataset(uri) # encoded as "lance" — the default format for a LanceDataset
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@env.task
|
|
71
|
+
async def consume(ds: lance.LanceDataset) -> int:
|
|
72
|
+
return ds.count_rows() # a live, streaming handle — no wrapper, no .open()
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@env.task
|
|
76
|
+
async def main() -> int:
|
|
77
|
+
return await consume(await make())
|
|
78
|
+
```
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
flyteplugins/lance/__init__.py,sha256=2ZUkBBLhiz82qMldYo3cQlrgk2Mr801RXo1ck4tVq2w,123
|
|
2
|
+
flyteplugins/lance/df_transformer.py,sha256=GdH3xmWT2QLSOSjVAOMwasZqMz8PLCZ1CsrfCSm_Tvc,8977
|
|
3
|
+
flyteplugins_lance-2.5.15.dist-info/METADATA,sha256=gv52lMBAXVrBou7dniJTtGLmR3XM1jXvWxZ9mDGCeuk,2697
|
|
4
|
+
flyteplugins_lance-2.5.15.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
5
|
+
flyteplugins_lance-2.5.15.dist-info/entry_points.txt,sha256=jhQy7HRvLrTyKds7iW0Ax_hHqamPyZTgN72nj4FMUNg,95
|
|
6
|
+
flyteplugins_lance-2.5.15.dist-info/top_level.txt,sha256=cgd779rPu9EsvdtuYgUxNHHgElaQvPn74KhB5XSeMBE,13
|
|
7
|
+
flyteplugins_lance-2.5.15.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
flyteplugins
|