fsspec-lamindb 0.1.0__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.
@@ -0,0 +1,13 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ .venv/
4
+ .pytest_cache/
5
+ .ruff_cache/
6
+ dist/
7
+ build/
8
+ *.egg-info/
9
+ .env
10
+ .coverage
11
+ htmlcov/
12
+ .mypy_cache/
13
+ .DS_Store
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Andreas Poehlmann
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,371 @@
1
+ Metadata-Version: 2.5
2
+ Name: fsspec-lamindb
3
+ Version: 0.1.0
4
+ Summary: Read-only fsspec filesystem for LaminDB instances: lamin://<owner>/<instance>/... URIs resolved through the LaminHub REST API.
5
+ Project-URL: Homepage, https://github.com/ap--/fsspec-lamindb
6
+ Project-URL: Issues, https://github.com/ap--/fsspec-lamindb/issues
7
+ Project-URL: Source, https://github.com/ap--/fsspec-lamindb
8
+ Project-URL: Changelog, https://github.com/ap--/fsspec-lamindb/blob/main/CHANGELOG.md
9
+ Author-email: Andreas Poehlmann <andreas@poehlmann.io>
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: filesystem,fsspec,lamindb,laminhub
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Framework :: AsyncIO
15
+ Classifier: Intended Audience :: Science/Research
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3 :: Only
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Programming Language :: Python :: 3.14
23
+ Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
24
+ Classifier: Topic :: System :: Filesystems
25
+ Classifier: Typing :: Typed
26
+ Requires-Python: >=3.11
27
+ Requires-Dist: aiohttp>=3.9
28
+ Requires-Dist: fsspec>=2024.6.1
29
+ Requires-Dist: s3fs>=2024.6.1
30
+ Provides-Extra: all
31
+ Requires-Dist: gcsfs>=2024.6.1; extra == 'all'
32
+ Requires-Dist: huggingface-hub>=0.20; extra == 'all'
33
+ Provides-Extra: gcs
34
+ Requires-Dist: gcsfs>=2024.6.1; extra == 'gcs'
35
+ Provides-Extra: hf
36
+ Requires-Dist: huggingface-hub>=0.20; extra == 'hf'
37
+ Description-Content-Type: text/markdown
38
+
39
+ # fsspec-lamindb
40
+
41
+ [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/fsspec-lamindb)](https://pypi.org/project/fsspec-lamindb/)
42
+ [![PyPI - License](https://img.shields.io/pypi/l/fsspec-lamindb)](https://github.com/ap--/fsspec-lamindb/blob/main/LICENSE)
43
+ [![PyPI](https://img.shields.io/pypi/v/fsspec-lamindb.svg)](https://pypi.org/project/fsspec-lamindb/)
44
+
45
+ [![Tests](https://github.com/ap--/fsspec-lamindb/actions/workflows/tests.yml/badge.svg)](https://github.com/ap--/fsspec-lamindb/actions/workflows/tests.yml)
46
+ [![Lint](https://github.com/ap--/fsspec-lamindb/actions/workflows/lint.yml/badge.svg)](https://github.com/ap--/fsspec-lamindb/actions/workflows/lint.yml)
47
+ [![GitHub issues](https://img.shields.io/github/issues/ap--/fsspec-lamindb)](https://github.com/ap--/fsspec-lamindb/issues)
48
+ [![Code style: ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
49
+
50
+ fsspec-lamindb is a Python library that provides a read-only
51
+ [`filesystem_spec`][fsspec] filesystem for [LaminDB][lamindb] instances. Its
52
+ `lamin://` URIs are compatible with [nf-lamin][nf-lamin].
53
+
54
+ [fsspec]: https://filesystem-spec.readthedocs.io/en/latest/intro.html
55
+ [lamindb]: https://docs.lamin.ai
56
+ [nf-lamin]: https://github.com/laminlabs/nf-lamin
57
+
58
+ The package doesn't depend on `lamindb`, `lamindb-core` or `lamindb-setup`:
59
+
60
+ - **Metadata** comes from the [LaminHub REST API][lamin-rest].
61
+ - **Data** is read with the fsspec filesystem of each storage location, such as
62
+ `s3fs`, `gcsfs`, Hugging Face or HTTP.
63
+ - **Credentials** for S3 storage that Lamin manages are temporary credentials
64
+ from LaminHub.
65
+
66
+ [lamin-rest]: https://docs.lamin.ai/rest
67
+
68
+
69
+ ## Installation
70
+
71
+ Install the latest version of `fsspec-lamindb` with pip. S3 and HTTP(S) storage
72
+ locations work out of the box. For other storage locations, you have to install
73
+ additional packages, which are available as extras:
74
+
75
+ ### PyPI
76
+
77
+ ```bash
78
+ python -m pip install fsspec-lamindb
79
+ ```
80
+
81
+ ### Extras
82
+
83
+ ```bash
84
+ python -m pip install "fsspec-lamindb[gcs]" # + Google Cloud Storage (gcsfs)
85
+ python -m pip install "fsspec-lamindb[hf]" # + Hugging Face (huggingface_hub)
86
+ python -m pip install "fsspec-lamindb[all]" # all of the above
87
+ ```
88
+
89
+ ### Adding fsspec-lamindb to your project
90
+
91
+ Below is a `pyproject.toml` based example for adding `fsspec-lamindb` to your
92
+ project as a dependency if you want to read from S3, HTTP(S) and Google Cloud
93
+ Storage locations:
94
+
95
+ ```toml
96
+ [project]
97
+ name = "myproject"
98
+ requires-python = ">=3.11"
99
+ dependencies = [
100
+ "fsspec-lamindb[gcs]>=0.1.0",
101
+ ]
102
+ ```
103
+
104
+ Installing the package registers the `lamin://` protocol with fsspec, so
105
+ `fsspec.filesystem("lamin")` and `fsspec.open("lamin://...")` work without
106
+ importing `fsspec_lamindb` first.
107
+
108
+
109
+ ## Basic Usage
110
+
111
+ ```pycon
112
+ # pip install fsspec-lamindb
113
+ >>> import fsspec
114
+ >>>
115
+ >>> fs = fsspec.filesystem("lamin") # uses $LAMIN_API_KEY if set
116
+ >>> fs.ls("lamin://laminlabs/lamindata", detail=False)
117
+ ['laminlabs/lamindata/artifact']
118
+ >>> fs.cat_file("lamin://laminlabs/lamindata/artifact/K415DrXcuPiJmXlX/meta.csv")[:40]
119
+ b',species,file_name,scientist,instrument\n'
120
+ >>> info = fs.info("lamin://laminlabs/lamindata/artifact/K415DrXcuPiJmXlX")
121
+ >>> info["type"], info["uid"], info["n_files"], info["uri"]
122
+ ('directory', 'K415DrXcuPiJmXlX5iXI', 52, 's3://lamindata/.lamindb/K415DrXcuPiJmXlX')
123
+ >>> fs.artifact_uri("lamin://laminlabs/lamindata/artifact/K415DrXcuPiJmXlX/meta.csv")
124
+ 'lamin://laminlabs/lamindata/artifact/K415DrXcuPiJmXlX5iXI/meta.csv'
125
+ >>> fs.find("lamin://laminlabs/lamindata/artifact/K415DrXcuPiJmXlX")
126
+ ['laminlabs/lamindata/artifact/K415DrXcuPiJmXlX/image_collection_1/iris-0337d20a....jpg',
127
+ ...]
128
+
129
+ ```
130
+
131
+ Pass the API key as a storage option to any fsspec-aware library:
132
+
133
+ ```python
134
+ import pandas as pd
135
+
136
+ df = pd.read_parquet(
137
+ "lamin://laminlabs/lamindata/artifact/k2Mo0c0rRWaaHEOB",
138
+ storage_options={"api_key": "..."},
139
+ )
140
+ ```
141
+
142
+ Public instances can be read without an API key. Pass `api_key=""` to read
143
+ anonymously even if `LAMIN_API_KEY` is set.
144
+
145
+ Prefer the `LAMIN_API_KEY` environment variable to the `api_key` option. Like
146
+ all storage options, an `api_key` becomes part of pickles of the filesystem and
147
+ of `fs.to_json()`, e.g. in kerchunk references or intake catalogs.
148
+ `fs.to_json(include_password=False)` leaves out the API key and the
149
+ credentials in `remote_options`.
150
+
151
+ ### Async usage
152
+
153
+ In async mode, use the filesystem as an async context manager, or call
154
+ `await fs.close()`, to close its sessions when you're done:
155
+
156
+ ```python
157
+ import asyncio
158
+
159
+ from fsspec_lamindb import LaminDBFileSystem
160
+
161
+
162
+ async def main():
163
+ path = "lamin://laminlabs/lamindata/artifact/K415DrXcuPiJmXlX/meta.csv"
164
+ async with LaminDBFileSystem(asynchronous=True) as fs:
165
+ return await fs._cat_file(path)
166
+
167
+
168
+ asyncio.run(main())
169
+ ```
170
+
171
+ ### Python API
172
+
173
+ The package exports three names; everything else is private:
174
+
175
+ - `LaminDBFileSystem`, the filesystem of the `lamin` protocol.
176
+ - `LaminFile`, the files that `fs.open()` returns.
177
+ - `LaminAPIError`, an `OSError` raised for unexpected responses of LaminHub.
178
+ Missing paths raise `FileNotFoundError`, and missing permissions
179
+ `PermissionError`.
180
+
181
+ ### URIs
182
+
183
+ ```text
184
+ lamin://
185
+ └── <owner>/ owners of the instances you can access
186
+ └── <instance>/ instances of an owner
187
+ └── artifact/<uid>[/<subpath>] any artifact by uid, as in nf-lamin
188
+ ```
189
+
190
+ - `artifact/<uid>` addresses any artifact the way nf-lamin does. A 20-character
191
+ uid is that exact version. A 16-character uid addresses the version family and
192
+ resolves to the latest version. Listing `artifact/` shows every artifact on the
193
+ visible branches by its 20-character uid. That includes all versions, artifacts
194
+ without a key, and the artifacts lamindb creates to track runs
195
+ (`kind="__lamindb_run__"` or `"__lamindb_config__"`).
196
+ - Folder artifacts, such as `.zarr` stores, are directories. Their content is
197
+ listed from their storage, and `<subpath>` addresses the files and
198
+ directories inside them.
199
+ - `fs.artifact_uri(path)` resolves a path to the `artifact/<uid>` URI of the
200
+ exact artifact version, e.g. to pin the version that a 16-character uid
201
+ resolves to now. Paths inside a folder artifact keep their path inside it, as
202
+ in `lamin://<owner>/<instance>/artifact/<uid>/<subpath>`. Pass
203
+ `version_family=True` for the 16-character uid, which resolves to the latest
204
+ version. Paths that are neither an artifact nor inside one, such as
205
+ `artifact/` itself, raise `ValueError`.
206
+ - `ls("lamin://")` lists the owners of the instances your account can access,
207
+ and `ls("lamin://<owner>")` lists those instances. Other owners don't exist
208
+ in the filesystem, and anonymous callers can't list any. Public instances can
209
+ still be opened directly as `lamin://<owner>/<instance>`.
210
+
211
+ Artifact entries carry record metadata: `uid`, `key`, `suffix`, `hash`, `kind`,
212
+ `otype`, `description`, `version_tag`, `is_latest`, `n_files`, `n_observations`,
213
+ `created_at`, `updated_at`. They also carry `storage` (the storage root) and
214
+ `uri` (the location of the data). `fs.modified()` and `fs.created()` return the
215
+ record's timestamps. `fs.ukey()` and `fs.checksum()` are derived from the
216
+ artifact's content hash, so caches like `simplecache::` only fetch the data
217
+ again when it changes. Files inside folder artifacts use those of their
218
+ storage filesystem.
219
+
220
+ ### Storage options
221
+
222
+ | Option | Default | Description |
223
+ | --- | --- | --- |
224
+ | `api_key` | `$LAMIN_API_KEY` | Lamin API key. Pass `""` for anonymous access. |
225
+ | `api_url` | `$LAMIN_API_URL`, else `https://aws.us-east-1.lamin.ai/api` | LaminHub API used to look up instances. Any LaminHub API finds any instance. Requests then go to the API in the instance's settings. |
226
+ | `hub_url` | `$LAMIN_HUB_URL`, else `https://hub.lamin.ai` | LaminHub, which grants storage credentials. |
227
+ | `hub_anon_key` | `$LAMIN_HUB_ANON_KEY`, else the public key of `hub.lamin.ai` | LaminHub's public key. |
228
+ | `branch` | `None` | Name or uid of a branch to show on top of `main`, like `lamin switch`. Its artifacts are listed under `artifact/`, and its versions win when a 16-character uid resolves to the latest version. |
229
+ | `storage_protocols` | `["s3", "gs", "hf", "http", "https"]` | Protocols of the storage locations that may be read. The list replaces the default. Add `"file"` to read local storage locations. |
230
+ | `remote_options` | `{}` | Options of the storage filesystems by protocol or storage root, e.g. `{"s3": {"profile": "x"}, "s3://bucket": {"profile": "y"}}`. Options for a root are merged over those for its protocol, and `gcs` is the same as `gs`. S3 credentials given here replace those from LaminHub. |
231
+ | `timeout` | `60` | Timeout of each request to LaminHub in seconds. |
232
+ | `max_concurrency` | `8` | Maximum number of result pages of a query fetched in parallel. |
233
+ | `max_requests` | `16` | Maximum number of concurrent requests to LaminHub. |
234
+ | `max_cached_paths` | `None` | Maximum number of paths whose lookups are cached besides the listings, e.g. the chunks of a Zarr store. The least recently used are dropped first. `None` for no limit. |
235
+ | `trusted_hosts` | `None` | Hosts your access token may be sent to besides `*.lamin.ai` and the host of `api_url`, e.g. `["lamin.example.com", "*.example.org"]` for on-premises deployments. |
236
+
237
+ The standard fsspec options are also supported: `asynchronous`, `loop`,
238
+ `use_listings_cache`, `listings_expiry_time`, and `skip_instance_cache`.
239
+ Listings expire after an hour (`listings_expiry_time=3600`), as artifacts are
240
+ added and 16-character uids resolve to their latest version. Pass
241
+ `listings_expiry_time=None` to keep them until `fs.invalidate_cache()` is
242
+ called. Use `fs.invalidate_cache()` or `fs.ls(path, refresh=True)` to pick up
243
+ changes sooner.
244
+
245
+ `use_listings_cache` and `listings_expiry_time` apply to everything the
246
+ filesystem caches from LaminHub: listings, storage locations, and folder
247
+ artifacts. Only the instance settings and the selected branch are kept
248
+ for the lifetime of the filesystem. With `use_listings_cache=False`, each call
249
+ still fetches what it needs only once.
250
+
251
+ ### Storage access
252
+
253
+ Each storage location gets its own fsspec filesystem, created on first use:
254
+
255
+ | Storage | Credentials |
256
+ | --- | --- |
257
+ | S3 that Lamin manages | Temporary credentials from LaminHub, renewed before they expire, or once when S3 rejects them. They're also issued to anonymous callers for public instances. |
258
+ | Other S3 buckets | Your AWS credentials (environment, `~/.aws`, …). Without any, the bucket is read anonymously. |
259
+ | GCS | Your Google credentials: `GOOGLE_APPLICATION_CREDENTIALS`, `gcloud auth application-default login`, or the service account on Google Cloud. Without any, the bucket is read anonymously. |
260
+ | Hugging Face, HTTP(S) | Your default credentials for the filesystem, e.g. `HF_TOKEN`. |
261
+
262
+ The resulting S3 options include the storage's region. LaminHub only issues
263
+ credentials for S3.
264
+
265
+ Storage locations come from the records of an instance, so whoever owns an
266
+ instance chooses what reading it accesses. That's why only the protocols in
267
+ `storage_protocols` are read; others raise `PermissionError`. Local storage
268
+ locations only exist on the machine that created them. To read them, pass the
269
+ full list, e.g. `storage_protocols=["s3", "gs", "hf", "http", "https", "file"]`.
270
+
271
+ Without Google credentials, gcsfs waits 15 to 30 seconds for the metadata
272
+ server of Google Compute Engine before it reads anonymously. So the lamin
273
+ filesystem checks for credentials without network requests: the variable
274
+ `GOOGLE_APPLICATION_CREDENTIALS`, gcloud's application default credentials,
275
+ gcsfs's token cache, and whether it runs on Google Cloud. If there are none, it
276
+ passes `token="anon"`. Set a `token` in `remote_options`, e.g.
277
+ `{"gs": {"token": "cloud"}}`, if the check misses your credentials. Set
278
+ `NO_GCE_CHECK=true` to never use the credentials of the metadata server, which
279
+ also speeds up gcsfs elsewhere.
280
+
281
+ The storage filesystems are created with `use_listings_cache=False`, as the
282
+ lamin filesystem caches and refreshes the listings itself. Set it in
283
+ `remote_options` to change that.
284
+
285
+
286
+ ## How it works
287
+
288
+ - The filesystem is an fsspec `AsyncFileSystem` built on aiohttp. Async
289
+ consumers such as zarr v3, kerchunk, or `fs.cat` of many files get concurrency
290
+ without threads. The storage filesystems run on the same event loop.
291
+ - An API key is exchanged for an access token, which is refreshed when it
292
+ expires. Transient errors like `502` and `429` are retried.
293
+ - The access token gives access to your whole account. The API URLs of
294
+ instances and storages come from their settings, so the token is only sent
295
+ over HTTPS (or HTTP to localhost) to `*.lamin.ai`, the host of `api_url`, and
296
+ `trusted_hosts`. Other requests fail with `PermissionError`.
297
+ - The API returns at most 200 records per request, so listing `artifact/`
298
+ fetches all artifacts of an instance in parallel pages. Looking up the listed
299
+ artifacts afterwards needs no further requests.
300
+ - `info()`, `open()`, and `cat()` of a single path only look up that artifact.
301
+ - `find()`, and so `glob("**")`, `du()`, and `get(recursive=True)`, search a
302
+ folder artifact with a single recursive search of its storage. A search of an
303
+ instance searches `artifact/`, i.e. every version of every artifact.
304
+
305
+
306
+ ## Known limitations
307
+
308
+ - Read-only. Writing, copying, moving, deleting, and creating directories raise
309
+ `NotImplementedError` before any request is sent.
310
+ - Listings are fetched in pages of 200 records. If records are deleted while a
311
+ listing is being fetched, another record can be missing from that listing.
312
+
313
+
314
+ ## Future development
315
+
316
+ The `lamin://` URIs follow nf-lamin, which only addresses artifacts by uid.
317
+ Once Lamin defines its URI schema further, fsspec-lamindb can support more of
318
+ it, for example:
319
+
320
+ - URIs that reference artifacts by their `key` instead of their uid.
321
+ - Writes, e.g. saving files as new artifacts or as new versions of existing
322
+ ones.
323
+
324
+
325
+ ## Contributing
326
+
327
+ Contributions are very welcome. To set up a development environment, install
328
+ [uv][uv] and [just][just] and run:
329
+
330
+ ```bash
331
+ uv sync
332
+ just test # unit tests against a fake LaminHub
333
+ just test-live # tests and doc examples against laminlabs/lamindata
334
+ just test-lamindb-setup # LaminHub defaults against the latest lamindb-setup
335
+ just lint # lockfile, ruff, and ty
336
+ just fmt # format and fix the code
337
+ just coverage # unit tests with a branch coverage report
338
+ ```
339
+
340
+ Run `just` to list all recipes. Run `pre-commit install` to run the same checks
341
+ as `just lint` before each commit. Set `FSSPEC_LAMINDB_TEST_INSTANCE`,
342
+ `LAMIN_API_KEY`, and `LAMIN_API_URL` to run the live tests against another
343
+ instance. Docstrings follow the [numpydoc][numpydoc] style.
344
+
345
+ [uv]: https://docs.astral.sh/uv/
346
+ [just]: https://just.systems/
347
+ [numpydoc]: https://numpydoc.readthedocs.io/en/latest/format.html
348
+
349
+
350
+ ## Changelog
351
+
352
+ See [CHANGELOG.md](https://github.com/ap--/fsspec-lamindb/blob/main/CHANGELOG.md).
353
+ To report a vulnerability, see
354
+ [SECURITY.md](https://github.com/ap--/fsspec-lamindb/blob/main/SECURITY.md).
355
+
356
+
357
+ ## License
358
+
359
+ Distributed under the terms of the [MIT license][license],
360
+ *fsspec-lamindb* is free and open source software.
361
+
362
+ [license]: https://github.com/ap--/fsspec-lamindb/blob/main/LICENSE
363
+
364
+
365
+ ## Issues
366
+
367
+ If you encounter any problems, please [file an issue][issues] along with a
368
+ detailed description. We are always happy to help with any problems you might
369
+ encounter.
370
+
371
+ [issues]: https://github.com/ap--/fsspec-lamindb/issues