atlas-python 0.14.0__tar.gz → 0.16.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.
- atlas_python-0.16.0/CONTRIBUTING.md +275 -0
- {atlas_python-0.14.0 → atlas_python-0.16.0}/Cargo.lock +4 -21
- {atlas_python-0.14.0 → atlas_python-0.16.0}/Cargo.toml +9 -8
- atlas_python-0.16.0/PKG-INFO +169 -0
- atlas_python-0.16.0/README.md +272 -0
- {atlas_python-0.14.0 → atlas_python-0.16.0}/atlas-python/Cargo.toml +2 -2
- atlas_python-0.16.0/atlas-python/README.md +140 -0
- atlas_python-0.16.0/atlas-python/docs/cli.md +235 -0
- atlas_python-0.16.0/atlas-python/docs/examples.md +44 -0
- atlas_python-0.16.0/atlas-python/docs/guides/cloud-storage.md +119 -0
- atlas_python-0.16.0/atlas-python/docs/guides/creating.md +174 -0
- atlas_python-0.16.0/atlas-python/docs/guides/dtypes.md +113 -0
- atlas_python-0.16.0/atlas-python/docs/guides/inspecting.md +171 -0
- atlas_python-0.16.0/atlas-python/docs/guides/reading-data.md +109 -0
- atlas_python-0.16.0/atlas-python/docs/guides/removing.md +118 -0
- atlas_python-0.16.0/atlas-python/docs/index.md +120 -0
- atlas_python-0.16.0/atlas-python/docs/installation.md +88 -0
- atlas_python-0.16.0/atlas-python/docs/quickstart.md +154 -0
- atlas_python-0.16.0/atlas-python/docs/reference/api.md +50 -0
- atlas_python-0.16.0/atlas-python/docs/vs-zarr-netcdf.md +115 -0
- atlas_python-0.16.0/atlas-python/examples/01_library.py +106 -0
- atlas_python-0.16.0/atlas-python/examples/02_object_store.py +90 -0
- {atlas_python-0.14.0 → atlas_python-0.16.0}/atlas-python/mkdocs.yml +9 -17
- {atlas_python-0.14.0 → atlas_python-0.16.0}/atlas-python/src/attr.rs +84 -4
- {atlas_python-0.14.0 → atlas_python-0.16.0}/atlas-python/src/dtype.rs +13 -8
- {atlas_python-0.14.0 → atlas_python-0.16.0}/atlas-python/src/error.rs +12 -16
- {atlas_python-0.14.0 → atlas_python-0.16.0}/atlas-python/src/lib.rs +9 -6
- {atlas_python-0.14.0 → atlas_python-0.16.0}/atlas-python/src/logging.rs +13 -14
- atlas_python-0.16.0/atlas-python/src/reader.rs +349 -0
- atlas_python-0.16.0/atlas-python/src/source.rs +35 -0
- atlas_python-0.16.0/atlas-python/src/writer.rs +582 -0
- atlas_python-0.16.0/atlas-python/tests/conftest.py +50 -0
- atlas_python-0.16.0/atlas-python/tests/make_fixture.py +92 -0
- atlas_python-0.16.0/atlas-python/tests/test_cli.py +285 -0
- atlas_python-0.16.0/atlas-python/tests/test_ops.py +537 -0
- atlas_python-0.16.0/atlas-python/tests/test_source.py +83 -0
- atlas_python-0.16.0/docs/README.md +64 -0
- atlas_python-0.16.0/docs/architecture.md +100 -0
- atlas_python-0.16.0/docs/data-model.md +112 -0
- atlas_python-0.16.0/docs/format.md +217 -0
- atlas_python-0.16.0/docs/python.md +168 -0
- atlas_python-0.16.0/docs/read-path.md +157 -0
- atlas_python-0.16.0/docs/write-path.md +126 -0
- atlas_python-0.16.0/examples/lifecycle.rs +110 -0
- atlas_python-0.16.0/examples/sensor_fleet.rs +122 -0
- atlas_python-0.16.0/examples/weather_store.rs +167 -0
- {atlas_python-0.14.0 → atlas_python-0.16.0}/pyproject.toml +5 -7
- atlas_python-0.16.0/python/atlas/__init__.py +44 -0
- atlas_python-0.16.0/python/atlas/__init__.pyi +234 -0
- atlas_python-0.16.0/python/atlas/_cli.py +423 -0
- atlas_python-0.16.0/python/atlas/_ops.py +337 -0
- atlas_python-0.16.0/python/atlas/_source.py +93 -0
- atlas_python-0.16.0/python/atlas/xarray.py +500 -0
- atlas_python-0.16.0/src/config.rs +47 -0
- atlas_python-0.16.0/src/error.rs +76 -0
- atlas_python-0.16.0/src/format/footer.rs +688 -0
- atlas_python-0.16.0/src/format/mask.rs +152 -0
- atlas_python-0.16.0/src/format/mod.rs +209 -0
- atlas_python-0.16.0/src/format/segment_store.rs +338 -0
- atlas_python-0.16.0/src/lib.rs +180 -0
- atlas_python-0.16.0/src/reader/mod.rs +602 -0
- atlas_python-0.16.0/src/schema/array.rs +128 -0
- atlas_python-0.16.0/src/schema/attr.rs +127 -0
- atlas_python-0.16.0/src/schema/dtype.rs +144 -0
- atlas_python-0.16.0/src/schema/mod.rs +26 -0
- atlas_python-0.16.0/src/writer/mod.rs +500 -0
- atlas_python-0.16.0/tests/cross_fixture.rs +204 -0
- atlas_python-0.16.0/tests/fixtures/from_python/data.atlas +0 -0
- atlas_python-0.16.0/tests/fixtures/golden_v1/data.atlas +0 -0
- atlas_python-0.16.0/tests/golden.rs +218 -0
- atlas_python-0.16.0/tests/integration.rs +1340 -0
- atlas_python-0.14.0/CONTRIBUTING.md +0 -218
- atlas_python-0.14.0/PKG-INFO +0 -331
- atlas_python-0.14.0/README.md +0 -439
- atlas_python-0.14.0/atlas-python/README.md +0 -298
- atlas_python-0.14.0/atlas-python/benchmarks/README.md +0 -249
- atlas_python-0.14.0/atlas-python/benchmarks/_common.py +0 -258
- atlas_python-0.14.0/atlas-python/benchmarks/bench_collection.py +0 -757
- atlas_python-0.14.0/atlas-python/benchmarks/generate_bench_charts.py +0 -141
- atlas_python-0.14.0/atlas-python/docs/assets/bench_gridded.svg +0 -1622
- atlas_python-0.14.0/atlas-python/docs/assets/bench_profile.svg +0 -1570
- atlas_python-0.14.0/atlas-python/docs/benchmarks.md +0 -187
- atlas_python-0.14.0/atlas-python/docs/examples.md +0 -37
- atlas_python-0.14.0/atlas-python/docs/guides/attributes.md +0 -115
- atlas_python-0.14.0/atlas-python/docs/guides/bulk-reads.md +0 -107
- atlas_python-0.14.0/atlas-python/docs/guides/cloud-storage.md +0 -151
- atlas_python-0.14.0/atlas-python/docs/guides/codecs-and-meta.md +0 -95
- atlas_python-0.14.0/atlas-python/docs/guides/dask.md +0 -100
- atlas_python-0.14.0/atlas-python/docs/guides/datasets-and-arrays.md +0 -208
- atlas_python-0.14.0/atlas-python/docs/guides/dtypes.md +0 -96
- atlas_python-0.14.0/atlas-python/docs/guides/durability.md +0 -98
- atlas_python-0.14.0/atlas-python/docs/guides/shared-arrays.md +0 -87
- atlas_python-0.14.0/atlas-python/docs/guides/stats.md +0 -177
- atlas_python-0.14.0/atlas-python/docs/guides/xarray.md +0 -168
- atlas_python-0.14.0/atlas-python/docs/index.md +0 -87
- atlas_python-0.14.0/atlas-python/docs/installation.md +0 -94
- atlas_python-0.14.0/atlas-python/docs/quickstart.md +0 -64
- atlas_python-0.14.0/atlas-python/docs/reference/atlas.md +0 -10
- atlas_python-0.14.0/atlas-python/docs/reference/dataset-view.md +0 -10
- atlas_python-0.14.0/atlas-python/docs/reference/xarray-accessor.md +0 -42
- atlas_python-0.14.0/atlas-python/docs/vs-zarr-netcdf.md +0 -139
- atlas_python-0.14.0/atlas-python/examples/01_basics.py +0 -67
- atlas_python-0.14.0/atlas-python/examples/02_xarray.py +0 -75
- atlas_python-0.14.0/atlas-python/examples/03_dask_streaming.py +0 -57
- atlas_python-0.14.0/atlas-python/examples/04_meta_formats.py +0 -83
- atlas_python-0.14.0/atlas-python/examples/05_codecs.py +0 -91
- atlas_python-0.14.0/atlas-python/examples/06_stats_scan.py +0 -77
- atlas_python-0.14.0/atlas-python/examples/07_shared_arrays.py +0 -82
- atlas_python-0.14.0/atlas-python/examples/08_object_store.py +0 -142
- atlas_python-0.14.0/atlas-python/examples/09_missing_data.py +0 -101
- atlas_python-0.14.0/atlas-python/src/dataset.rs +0 -654
- atlas_python-0.14.0/atlas-python/src/store.rs +0 -637
- atlas_python-0.14.0/atlas-python/tests/test_smoke.py +0 -599
- atlas_python-0.14.0/atlas-python/tests/test_xarray.py +0 -686
- atlas_python-0.14.0/docs/README.md +0 -67
- atlas_python-0.14.0/docs/architecture.md +0 -104
- atlas_python-0.14.0/docs/data-model.md +0 -108
- atlas_python-0.14.0/docs/metadata.md +0 -90
- atlas_python-0.14.0/docs/pruning-index.md +0 -159
- atlas_python-0.14.0/docs/python-xarray.md +0 -89
- atlas_python-0.14.0/docs/storage-layout.md +0 -98
- atlas_python-0.14.0/docs/write-path.md +0 -101
- atlas_python-0.14.0/examples/bench_pruning.rs +0 -75
- atlas_python-0.14.0/examples/lifecycle.rs +0 -213
- atlas_python-0.14.0/examples/sensor_fleet.rs +0 -154
- atlas_python-0.14.0/examples/weather_store.rs +0 -340
- atlas_python-0.14.0/python/atlas/__init__.py +0 -5
- atlas_python-0.14.0/python/atlas/__init__.pyi +0 -458
- atlas_python-0.14.0/python/atlas/store.py +0 -106
- atlas_python-0.14.0/python/atlas/xarray.py +0 -775
- atlas_python-0.14.0/src/array.rs +0 -144
- atlas_python-0.14.0/src/config.rs +0 -164
- atlas_python-0.14.0/src/dataset/cache.rs +0 -54
- atlas_python-0.14.0/src/dataset/mod.rs +0 -11
- atlas_python-0.14.0/src/dataset/pending_attrs.rs +0 -244
- atlas_python-0.14.0/src/dataset/view.rs +0 -1027
- atlas_python-0.14.0/src/error.rs +0 -97
- atlas_python-0.14.0/src/lib.rs +0 -201
- atlas_python-0.14.0/src/meta/mod.rs +0 -21
- atlas_python-0.14.0/src/meta/persist.rs +0 -449
- atlas_python-0.14.0/src/meta/schema.rs +0 -230
- atlas_python-0.14.0/src/meta/store_meta.rs +0 -668
- atlas_python-0.14.0/src/meta/type_index.rs +0 -175
- atlas_python-0.14.0/src/pruning/bitmap.rs +0 -84
- atlas_python-0.14.0/src/pruning/column.rs +0 -392
- atlas_python-0.14.0/src/pruning/mod.rs +0 -264
- atlas_python-0.14.0/src/pruning/value.rs +0 -157
- atlas_python-0.14.0/src/schema/array.rs +0 -43
- atlas_python-0.14.0/src/schema/attr.rs +0 -252
- atlas_python-0.14.0/src/schema/dtype.rs +0 -277
- atlas_python-0.14.0/src/schema/mod.rs +0 -17
- atlas_python-0.14.0/src/store/bulk_read.rs +0 -228
- atlas_python-0.14.0/src/store/durability.rs +0 -178
- atlas_python-0.14.0/src/store/mod.rs +0 -877
- atlas_python-0.14.0/tests/integration.rs +0 -1526
- {atlas_python-0.14.0 → atlas_python-0.16.0}/.github/workflows/atlas-python-docs.yaml +0 -0
- {atlas_python-0.14.0 → atlas_python-0.16.0}/.github/workflows/atlas-python-release.yaml +0 -0
- {atlas_python-0.14.0 → atlas_python-0.16.0}/.github/workflows/atlas-rust-release.yaml +0 -0
- {atlas_python-0.14.0 → atlas_python-0.16.0}/.github/workflows/ci.yaml +0 -0
- {atlas_python-0.14.0 → atlas_python-0.16.0}/.gitignore +0 -0
- {atlas_python-0.14.0 → atlas_python-0.16.0}/LICENSE +0 -0
- {atlas_python-0.14.0 → atlas_python-0.16.0}/atlas-python/.python-version +0 -0
- {atlas_python-0.14.0 → atlas_python-0.16.0}/atlas-python/LICENSE +0 -0
- {atlas_python-0.14.0 → atlas_python-0.16.0}/atlas-python/src/runtime.rs +0 -0
- {atlas_python-0.14.0 → atlas_python-0.16.0}/atlas-python/tests/GL_PR_BO_JLKU.nc +0 -0
- {atlas_python-0.14.0 → atlas_python-0.16.0}/python/atlas/py.typed +0 -0
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
# Contributing to ATLAS
|
|
2
|
+
|
|
3
|
+
What the project is, how the pieces fit, and how to get a development
|
|
4
|
+
environment working.
|
|
5
|
+
|
|
6
|
+
For end-user docs see [README.md](README.md) (Rust) and
|
|
7
|
+
[atlas-python/README.md](atlas-python/README.md) (Python).
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## What this is
|
|
12
|
+
|
|
13
|
+
**ATLAS** (Aggregated Tensor Large Array Store) keeps thousands of named
|
|
14
|
+
datasets in one immutable file. A dataset is a set of named N-dimensional
|
|
15
|
+
arrays with attributes — the shape a NetCDF file or an `xarray.Dataset` has.
|
|
16
|
+
|
|
17
|
+
The design goal is that *knowing what a collection holds* costs one request,
|
|
18
|
+
however many datasets it holds.
|
|
19
|
+
|
|
20
|
+
The repository is a Cargo workspace with two crates:
|
|
21
|
+
|
|
22
|
+
| Crate | Purpose |
|
|
23
|
+
|---|---|
|
|
24
|
+
| `atlas-rust` (workspace root, `[lib] name = "atlas"`) | The format, the writer, the reader |
|
|
25
|
+
| `atlas-python` ([atlas-python/](atlas-python/)) | PyO3 bindings, the five operations, the `atlas` command |
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Architecture
|
|
30
|
+
|
|
31
|
+
### On-disk layout
|
|
32
|
+
|
|
33
|
+
```text
|
|
34
|
+
my_collection/
|
|
35
|
+
├── data.atlas ATLS │ segment │ segment │ … │ footer │ trailer
|
|
36
|
+
└── deleted.mask optional: ordinals of deleted datasets
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
One write-once file. Each dataset is a contiguous segment — itself a complete
|
|
40
|
+
[`array-format`](https://github.com/robinskil/array-format) file — and the
|
|
41
|
+
footer records every dataset's name, segment byte range, schema, and attributes.
|
|
42
|
+
|
|
43
|
+
Byte-level detail is in [docs/format.md](docs/format.md). The rest of
|
|
44
|
+
[docs/](docs/) covers the layers, the data model, and the two paths.
|
|
45
|
+
|
|
46
|
+
**The format is Rust only.** `atlas-python` holds no format knowledge — grep it
|
|
47
|
+
for `ATLS` and you get nothing. One implementation of the bytes, one place for a
|
|
48
|
+
bug to live. Keep it that way.
|
|
49
|
+
|
|
50
|
+
### Rust crate
|
|
51
|
+
|
|
52
|
+
| Path | Role |
|
|
53
|
+
|---|---|
|
|
54
|
+
| [src/lib.rs](src/lib.rs) | Public re-exports, `validate_name`, thread-safety asserts |
|
|
55
|
+
| [src/format/mod.rs](src/format/mod.rs) | Container framing: magic, header, trailer |
|
|
56
|
+
| [src/format/footer.rs](src/format/footer.rs) | `CollectionFooter`, `DatasetEntry`, `AttrS`, the interner |
|
|
57
|
+
| [src/format/mask.rs](src/format/mask.rs) | The deletion mask codec |
|
|
58
|
+
| [src/format/segment_store.rs](src/format/segment_store.rs) | `ObjectStore` adapter over one byte range |
|
|
59
|
+
| [src/writer/mod.rs](src/writer/mod.rs) | `AtlasWriter`, `DatasetWriter` |
|
|
60
|
+
| [src/reader/mod.rs](src/reader/mod.rs) | `Atlas`, `DatasetView` |
|
|
61
|
+
| [src/schema/](src/schema/) | `ArraySchema`, `DatasetSchema`, `Attr`, dtype serde |
|
|
62
|
+
| [src/config.rs](src/config.rs) | `Codec`, `WriterConfig` |
|
|
63
|
+
| [src/error.rs](src/error.rs) | `Error` / `Result` |
|
|
64
|
+
|
|
65
|
+
The API is async (tokio). Reads take no locks — the data is immutable. Writes
|
|
66
|
+
share one `tokio::sync::Mutex` over the output stream, held only for a dataset's
|
|
67
|
+
append.
|
|
68
|
+
|
|
69
|
+
### Python bindings
|
|
70
|
+
|
|
71
|
+
Mixed Python/Rust maturin layout:
|
|
72
|
+
|
|
73
|
+
```text
|
|
74
|
+
atlas-python/
|
|
75
|
+
├── Cargo.toml cdylib named `_atlas`
|
|
76
|
+
├── pyproject.toml maturin build backend; declares the `atlas` script
|
|
77
|
+
├── python/atlas/
|
|
78
|
+
│ ├── __init__.py the five operations. `__all__` is the surface
|
|
79
|
+
│ ├── __init__.pyi type stubs (PEP 561) — the authoritative contract
|
|
80
|
+
│ ├── py.typed marker
|
|
81
|
+
│ ├── _ops.py the operations themselves
|
|
82
|
+
│ ├── _cli.py argument parsing and output formatting
|
|
83
|
+
│ ├── _source.py path or URL → something the bindings accept
|
|
84
|
+
│ └── xarray.py the xarray → atlas mapping. Internal
|
|
85
|
+
├── src/
|
|
86
|
+
│ ├── lib.rs #[pymodule] wiring
|
|
87
|
+
│ ├── runtime.rs shared OnceLock<tokio::Runtime>
|
|
88
|
+
│ ├── error.rs atlas::Error → PyErr
|
|
89
|
+
│ ├── source.rs AtlasSource (path | obstore handle), codec parsing
|
|
90
|
+
│ ├── dtype.rs dtype string ⇄ DType
|
|
91
|
+
│ ├── attr.rs Python value ⇄ Attr
|
|
92
|
+
│ ├── writer.rs PyAtlasWriter, PyDatasetWriter — internal
|
|
93
|
+
│ └── reader.rs PyAtlas, PyDatasetView — internal
|
|
94
|
+
├── tests/ pytest, plus make_fixture.py
|
|
95
|
+
└── examples/ runnable scripts
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Key points:
|
|
99
|
+
|
|
100
|
+
- **Five operations, and that is the whole surface.** `create`, `remove`,
|
|
101
|
+
`list_datasets`, `describe`, `info`, as a library and as `atlas create|rm|ls|
|
|
102
|
+
show|info`. `__all__` in `__init__.py` is the contract; a test asserts it.
|
|
103
|
+
- **`_atlas` is private.** It still exposes writer and reader classes because
|
|
104
|
+
the operations need them, but nothing outside `_ops.py` touches them, and
|
|
105
|
+
they are not exported.
|
|
106
|
+
- **Python writes; Rust reads array data.** There is no `read_array` on the
|
|
107
|
+
Python side. See [docs/read-path.md](docs/read-path.md) for the reasoning.
|
|
108
|
+
- **NetCDF is the only ingest route.** `create` scans a directory, sorts it, and
|
|
109
|
+
writes one dataset per file — sorting is what makes ordinals reproducible.
|
|
110
|
+
- **Sync Python API over a tokio runtime.** Each blocking call uses
|
|
111
|
+
`py.detach(|| runtime().block_on(...))` so other Python threads keep running.
|
|
112
|
+
- **numpy zero-copy on the numeric path**, via the `numpy` crate. Strings are
|
|
113
|
+
the exception — they are extracted element by element.
|
|
114
|
+
- **Type dispatch via macros.** `define_array` / `write_array` are generic over
|
|
115
|
+
`T: ArrayElement`; the bindings dispatch at runtime through
|
|
116
|
+
`numeric_dispatch!` in [atlas-python/src/writer.rs](atlas-python/src/writer.rs).
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Prerequisites
|
|
121
|
+
|
|
122
|
+
| Tool | Why |
|
|
123
|
+
|---|---|
|
|
124
|
+
| **Rust** stable, 1.85+ (edition 2024) | Build the workspace |
|
|
125
|
+
| **Python ≥ 3.10** | The wheel targets `abi3-py310` |
|
|
126
|
+
| **`maturin`** | Build the extension. `pip install maturin` |
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Rust: build and test
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
cargo build --workspace
|
|
134
|
+
cargo test -p atlas-rust
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
`cargo test --workspace` does not work: `atlas-python` links
|
|
138
|
+
`pyo3/extension-module`, so its test binary has no libpython to link against.
|
|
139
|
+
Test the core crate; test the bindings through pytest.
|
|
140
|
+
|
|
141
|
+
On macOS, `cargo build --workspace` also fails to link the cdylib for the same
|
|
142
|
+
reason. Use `cargo check --workspace` for type checking and `maturin develop`
|
|
143
|
+
for a real build.
|
|
144
|
+
|
|
145
|
+
Examples:
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
cargo run --example lifecycle
|
|
149
|
+
cargo run --example sensor_fleet
|
|
150
|
+
cargo run --example weather_store
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## Python: build and test
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
python3 -m venv .venv
|
|
159
|
+
source .venv/bin/activate
|
|
160
|
+
pip install --upgrade pip maturin
|
|
161
|
+
|
|
162
|
+
maturin develop --extras test --manifest-path atlas-python/Cargo.toml
|
|
163
|
+
pytest atlas-python/tests -v
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
The install is editable, so changes under `python/atlas/*.py` take effect at
|
|
167
|
+
once. **Rust changes need `maturin develop` again** — otherwise pytest runs
|
|
168
|
+
against the previously built binary.
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## Test fixtures
|
|
173
|
+
|
|
174
|
+
Two committed fixtures pin behaviour that a round-trip test would miss.
|
|
175
|
+
|
|
176
|
+
**`tests/fixtures/golden_v1/`** — a v1 container, read back by
|
|
177
|
+
[tests/golden.rs](tests/golden.rs) with every value asserted. If a change breaks
|
|
178
|
+
compatibility with an existing container, this catches it. Regenerate only when
|
|
179
|
+
you intend to break the format, which means bumping `FORMAT_VERSION`:
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
cargo test --test golden -- --ignored regenerate
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
The writer's output is deliberately not compared byte for byte — zstd makes no
|
|
186
|
+
promise of stable output across versions. Only the framing this crate produces
|
|
187
|
+
itself is asserted exactly.
|
|
188
|
+
|
|
189
|
+
**`tests/fixtures/from_python/`** — written by
|
|
190
|
+
[atlas-python/tests/make_fixture.py](atlas-python/tests/make_fixture.py) and read
|
|
191
|
+
back by [tests/cross_fixture.rs](tests/cross_fixture.rs). This is the only thing
|
|
192
|
+
verifying that the bytes the Python xarray layer writes are the bytes it meant,
|
|
193
|
+
now that pytest cannot read arrays. Regenerate after changing the write path:
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
python atlas-python/tests/make_fixture.py
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## Common workflows
|
|
202
|
+
|
|
203
|
+
### Adding an array dtype
|
|
204
|
+
|
|
205
|
+
1. Confirm `array-format` supports it as an element type.
|
|
206
|
+
2. Add the dispatch arm in `numeric_dispatch!`
|
|
207
|
+
([atlas-python/src/writer.rs](atlas-python/src/writer.rs)), or an explicit
|
|
208
|
+
branch alongside String / TimestampNs.
|
|
209
|
+
3. Extend [atlas-python/src/dtype.rs](atlas-python/src/dtype.rs) to parse the
|
|
210
|
+
name.
|
|
211
|
+
4. Add it to `_NUMPY_TO_ATLAS` in
|
|
212
|
+
[atlas-python/python/atlas/xarray.py](atlas-python/python/atlas/xarray.py) if
|
|
213
|
+
it has a numpy equivalent.
|
|
214
|
+
5. Test in both suites, and extend the cross fixture if it is worth pinning.
|
|
215
|
+
|
|
216
|
+
### Adding to the Python surface
|
|
217
|
+
|
|
218
|
+
The surface is deliberately five operations. Before adding a sixth, check the
|
|
219
|
+
job cannot be done by composing the ones that exist.
|
|
220
|
+
|
|
221
|
+
If it genuinely needs to be new:
|
|
222
|
+
|
|
223
|
+
1. Implement whatever it needs on `Atlas` / `DatasetView` in `src/`.
|
|
224
|
+
2. Expose it through [atlas-python/src/reader.rs](atlas-python/src/reader.rs)
|
|
225
|
+
or [writer.rs](atlas-python/src/writer.rs), releasing the GIL with
|
|
226
|
+
`py.detach` for anything that blocks.
|
|
227
|
+
3. Add the operation to
|
|
228
|
+
[atlas-python/python/atlas/_ops.py](atlas-python/python/atlas/_ops.py) and
|
|
229
|
+
export it from `__init__.py`.
|
|
230
|
+
4. Add a subcommand in
|
|
231
|
+
[atlas-python/python/atlas/_cli.py](atlas-python/python/atlas/_cli.py), with
|
|
232
|
+
both a text renderer and `--json`.
|
|
233
|
+
5. Add the stub to
|
|
234
|
+
[atlas-python/python/atlas/\_\_init\_\_.pyi](atlas-python/python/atlas/__init__.pyi)
|
|
235
|
+
— that file is the contract.
|
|
236
|
+
6. `maturin develop`, then test it in both `test_ops.py` and `test_cli.py`.
|
|
237
|
+
|
|
238
|
+
### Touching the on-disk format
|
|
239
|
+
|
|
240
|
+
Any change to the framing, the footer struct, or the mask is **breaking** — the
|
|
241
|
+
footer is compact MessagePack, so field order is part of the format.
|
|
242
|
+
|
|
243
|
+
1. Bump `FORMAT_VERSION` in [src/format/mod.rs](src/format/mod.rs).
|
|
244
|
+
2. Regenerate the golden fixture and update `tests/golden.rs` to the new
|
|
245
|
+
version.
|
|
246
|
+
3. Update [docs/format.md](docs/format.md) — it is a specification, not a
|
|
247
|
+
summary.
|
|
248
|
+
|
|
249
|
+
There is no migration path, by design. A collection that predates the change is
|
|
250
|
+
rewritten, not upgraded.
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
## Code style
|
|
255
|
+
|
|
256
|
+
- **Rust**: `cargo fmt` before committing, default rustfmt.
|
|
257
|
+
`cargo clippy --all-targets` should be clean.
|
|
258
|
+
- **Python**: 4-space indentation, imports grouped stdlib / third-party / local.
|
|
259
|
+
No formatter is enforced; match what is there.
|
|
260
|
+
- **Comments explain why, not what.** The code already shows what. A comment
|
|
261
|
+
earns its place by recording a constraint, a trade-off, or a trap — the kind
|
|
262
|
+
of thing the next reader would otherwise have to rediscover.
|
|
263
|
+
- **Tests are named as claims.** `an_end_past_the_segment_is_clamped_not_leaked`
|
|
264
|
+
says what it protects; `test_read_3` does not.
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
## Pull requests
|
|
269
|
+
|
|
270
|
+
1. Branch from `main`.
|
|
271
|
+
2. `cargo test -p atlas-rust`, `cargo clippy --all-targets`, and
|
|
272
|
+
`pytest atlas-python/tests` must pass.
|
|
273
|
+
3. If you change the format, the public API, or user-visible behaviour, update
|
|
274
|
+
the relevant docs in the same PR.
|
|
275
|
+
4. Keep commits focused; squash fixups before review.
|
|
@@ -71,7 +71,7 @@ dependencies = [
|
|
|
71
71
|
|
|
72
72
|
[[package]]
|
|
73
73
|
name = "atlas-python"
|
|
74
|
-
version = "0.
|
|
74
|
+
version = "0.16.0"
|
|
75
75
|
dependencies = [
|
|
76
76
|
"atlas-rust",
|
|
77
77
|
"ndarray",
|
|
@@ -86,20 +86,19 @@ dependencies = [
|
|
|
86
86
|
|
|
87
87
|
[[package]]
|
|
88
88
|
name = "atlas-rust"
|
|
89
|
-
version = "0.
|
|
89
|
+
version = "0.16.0"
|
|
90
90
|
dependencies = [
|
|
91
91
|
"array-format",
|
|
92
|
+
"async-trait",
|
|
93
|
+
"bytes",
|
|
92
94
|
"chrono",
|
|
93
95
|
"futures",
|
|
94
96
|
"indexmap",
|
|
95
|
-
"lz4_flex",
|
|
96
97
|
"ndarray",
|
|
97
|
-
"num_cpus",
|
|
98
98
|
"object_store",
|
|
99
99
|
"parking_lot",
|
|
100
100
|
"rmp-serde",
|
|
101
101
|
"serde",
|
|
102
|
-
"serde_json",
|
|
103
102
|
"tempfile",
|
|
104
103
|
"thiserror 2.0.18",
|
|
105
104
|
"tokio",
|
|
@@ -568,12 +567,6 @@ version = "0.5.0"
|
|
|
568
567
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
569
568
|
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
|
570
569
|
|
|
571
|
-
[[package]]
|
|
572
|
-
name = "hermit-abi"
|
|
573
|
-
version = "0.5.2"
|
|
574
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
575
|
-
checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
|
|
576
|
-
|
|
577
570
|
[[package]]
|
|
578
571
|
name = "http"
|
|
579
572
|
version = "1.4.0"
|
|
@@ -1064,16 +1057,6 @@ dependencies = [
|
|
|
1064
1057
|
"autocfg",
|
|
1065
1058
|
]
|
|
1066
1059
|
|
|
1067
|
-
[[package]]
|
|
1068
|
-
name = "num_cpus"
|
|
1069
|
-
version = "1.17.0"
|
|
1070
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1071
|
-
checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b"
|
|
1072
|
-
dependencies = [
|
|
1073
|
-
"hermit-abi",
|
|
1074
|
-
"libc",
|
|
1075
|
-
]
|
|
1076
|
-
|
|
1077
1060
|
[[package]]
|
|
1078
1061
|
name = "numpy"
|
|
1079
1062
|
version = "0.28.0"
|
|
@@ -5,9 +5,9 @@ members = ["atlas-python"]
|
|
|
5
5
|
# Registry name on crates.io (`atlas` is taken). The library is still
|
|
6
6
|
# imported as `atlas` — see `[lib]` below.
|
|
7
7
|
name = "atlas-rust"
|
|
8
|
-
version = "0.
|
|
8
|
+
version = "0.16.0"
|
|
9
9
|
edition = "2024"
|
|
10
|
-
description = "
|
|
10
|
+
description = "Single-file immutable store for thousands of N-dimensional datasets, local or on object storage."
|
|
11
11
|
license = "Apache-2.0"
|
|
12
12
|
repository = "https://github.com/maris-development/atlas"
|
|
13
13
|
readme = "README.md"
|
|
@@ -22,23 +22,24 @@ all-features = true
|
|
|
22
22
|
rustdoc-args = ["--cfg", "docsrs"]
|
|
23
23
|
|
|
24
24
|
[dependencies]
|
|
25
|
-
|
|
25
|
+
# Exact pin: the container format embeds this crate's files verbatim, so a
|
|
26
|
+
# change to its bytes is a change to the atlas format.
|
|
27
|
+
array-format = "=0.12.0"
|
|
26
28
|
object_store = "0.13"
|
|
29
|
+
async-trait = "0.1"
|
|
30
|
+
bytes = "1"
|
|
27
31
|
serde = { version = "1", features = ["derive"] }
|
|
28
|
-
serde_json = "1"
|
|
29
32
|
rmp-serde = "1"
|
|
30
33
|
zstd = "0.13"
|
|
31
|
-
lz4_flex = "0.11"
|
|
32
34
|
thiserror = "2"
|
|
33
35
|
ndarray = "0.17"
|
|
34
36
|
indexmap = { version = "2", features = ["serde"] }
|
|
35
37
|
chrono = { version = "0.4", default-features = false, features = ["std"] }
|
|
36
38
|
parking_lot = "0.12"
|
|
37
|
-
|
|
39
|
+
tempfile = "3"
|
|
40
|
+
tokio = { version = "1", features = ["fs", "io-util", "sync", "rt"] }
|
|
38
41
|
tracing = "0.1"
|
|
39
42
|
futures = "0.3"
|
|
40
|
-
num_cpus = "1"
|
|
41
43
|
|
|
42
44
|
[dev-dependencies]
|
|
43
|
-
tempfile = "3"
|
|
44
45
|
tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros"] }
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: atlas-python
|
|
3
|
+
Version: 0.16.0
|
|
4
|
+
Classifier: Development Status :: 3 - Alpha
|
|
5
|
+
Classifier: Intended Audience :: Science/Research
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Classifier: Programming Language :: Rust
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Classifier: Topic :: Scientific/Engineering
|
|
10
|
+
Requires-Dist: numpy>=1.23
|
|
11
|
+
Requires-Dist: xarray>=2023.1
|
|
12
|
+
Requires-Dist: dask>=2023.1
|
|
13
|
+
Requires-Dist: obstore>=0.9 ; extra == 'cloud'
|
|
14
|
+
Requires-Dist: pytest ; extra == 'test'
|
|
15
|
+
Requires-Dist: netcdf4 ; extra == 'test'
|
|
16
|
+
Provides-Extra: cloud
|
|
17
|
+
Provides-Extra: test
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
Summary: Python bindings for the ATLAS array store
|
|
20
|
+
Keywords: array,ndarray,xarray,scientific,storage,object-store
|
|
21
|
+
Author-email: Robin Kooyman <robin.kooyman.work@gmail.com>
|
|
22
|
+
License-Expression: Apache-2.0
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
25
|
+
Project-URL: Homepage, https://github.com/maris-development/atlas
|
|
26
|
+
Project-URL: Issues, https://github.com/maris-development/atlas/issues
|
|
27
|
+
Project-URL: Repository, https://github.com/maris-development/atlas
|
|
28
|
+
|
|
29
|
+
# atlas-python
|
|
30
|
+
|
|
31
|
+
Thousands of NetCDF datasets in one immutable file. It sits on local disk or on
|
|
32
|
+
object storage: S3, GCS, Azure, or HTTP. A Rust core, five operations, and one
|
|
33
|
+
command.
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pip install atlas-python
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
atlas create /data/nc /data/collection
|
|
41
|
+
atlas ls /data/collection
|
|
42
|
+
atlas show /data/collection 2024-01
|
|
43
|
+
atlas info /data/collection
|
|
44
|
+
atlas rm /data/collection 2024-02 2024-03
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
| Extra | Install | Adds |
|
|
48
|
+
|---|---|---|
|
|
49
|
+
| cloud | `pip install "atlas-python[cloud]"` | S3 / GCS / Azure / HTTP via [obstore](https://github.com/developmentseed/obstore) |
|
|
50
|
+
|
|
51
|
+
`numpy`, `xarray`, and `dask` install automatically.
|
|
52
|
+
|
|
53
|
+
## Five operations
|
|
54
|
+
|
|
55
|
+
The same five as a library:
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
import atlas
|
|
59
|
+
|
|
60
|
+
atlas.create("/data/nc", "/data/collection") # from a directory of NetCDF files
|
|
61
|
+
atlas.list_datasets("/data/collection") # ['2024-01', '2024-02', '2024-03']
|
|
62
|
+
atlas.describe("/data/collection", "2024-01") # types, shapes, attrs, statistics
|
|
63
|
+
atlas.info("/data/collection") # counts, size, codec, statistics
|
|
64
|
+
atlas.remove("/data/collection", ["2024-02"]) # updates the mask
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Every one takes a local path, a URL, or an obstore handle:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
atlas ls s3://my-bucket/collections/2024 --region eu-west-1
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
atlas.list_datasets("s3://my-bucket/collections/2024", region="eu-west-1")
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Two things to internalise
|
|
78
|
+
|
|
79
|
+
**One write builds a collection.** There is no append, no in-place update, and
|
|
80
|
+
no `flush`. The file has a valid trailer, or it is no collection. To change a
|
|
81
|
+
dataset, rebuild the collection. `remove` is the one exception. It writes a
|
|
82
|
+
small mask file, and never touches the container. It therefore reclaims no
|
|
83
|
+
space, and moves no ordinal.
|
|
84
|
+
|
|
85
|
+
**Python writes. Rust reads array data.** From Python you build a collection
|
|
86
|
+
and read its *metadata*. That is the dataset names, the array types, the
|
|
87
|
+
shapes, the chunk shapes, the fill values, the attributes, and the statistics
|
|
88
|
+
of the write. There is no `read_array`. Array values come from the Rust API.
|
|
89
|
+
|
|
90
|
+
That split makes the read side free. The footer an open already fetched answers
|
|
91
|
+
every metadata call. A catalogue of a thousand datasets is therefore one
|
|
92
|
+
request.
|
|
93
|
+
|
|
94
|
+
## What `show` gives you
|
|
95
|
+
|
|
96
|
+
```text
|
|
97
|
+
$ atlas show /data/collection 2024-01
|
|
98
|
+
dataset 2024-01 {
|
|
99
|
+
dimensions:
|
|
100
|
+
lat = 4 ;
|
|
101
|
+
lon = 6 ;
|
|
102
|
+
variables:
|
|
103
|
+
float32 temperature(lat, lon) ;
|
|
104
|
+
temperature:_FillValue = nan ;
|
|
105
|
+
temperature:units = "celsius" ;
|
|
106
|
+
// stats: count=24 min=1.0 max=24.0
|
|
107
|
+
string station(lat) ;
|
|
108
|
+
// stats: count=4 min="a" max="d"
|
|
109
|
+
|
|
110
|
+
// global attributes:
|
|
111
|
+
:month = 1 ;
|
|
112
|
+
|
|
113
|
+
// ordinal 0, segment bytes 8..1691
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
The shape follows `ncdump -h`. It adds the statistics of the write: the
|
|
118
|
+
minimum, the maximum, and how many elements are missing. `--json` on any read
|
|
119
|
+
command gives the same content as a structure.
|
|
120
|
+
|
|
121
|
+
## Ingest
|
|
122
|
+
|
|
123
|
+
`create` scans a directory for `.nc`, `.nc4`, `.cdf`, and `.netcdf`. It sorts
|
|
124
|
+
them, and writes one dataset per file, named after the stem. Each coordinate
|
|
125
|
+
and data variable becomes an array. Each variable attribute becomes a per-array
|
|
126
|
+
attribute. `_FillValue` becomes the fill of the array.
|
|
127
|
+
|
|
128
|
+
Each file opens with dask chunking. A file far larger than memory therefore
|
|
129
|
+
streams block by block. `--chunk-size` sets the block budget, and defaults to
|
|
130
|
+
128 MiB. It is about the memory ceiling per variable. Those blocks also become
|
|
131
|
+
the stored chunk shape.
|
|
132
|
+
|
|
133
|
+
Nothing at the destination is readable until every file lands. A failure
|
|
134
|
+
part-way leaves no collection, and not a partial one. `on_error="skip"`, or
|
|
135
|
+
`--skip-errors`, trades that for progress.
|
|
136
|
+
|
|
137
|
+
## dtypes
|
|
138
|
+
|
|
139
|
+
| numpy | atlas |
|
|
140
|
+
|---|---|
|
|
141
|
+
| int / uint widths, `float32`, `float64` | the same |
|
|
142
|
+
| `datetime64[ns]` | `timestamp_nanoseconds` |
|
|
143
|
+
| `timedelta64[*]` | `int64` nanoseconds, plus a unit marker |
|
|
144
|
+
| `object` / `S` / `U` | `string` |
|
|
145
|
+
|
|
146
|
+
`bool`, `binary`, and the list types work as an *attribute* value. No one of
|
|
147
|
+
them works yet as an array element type.
|
|
148
|
+
|
|
149
|
+
## Documentation
|
|
150
|
+
|
|
151
|
+
The full docs sit at **<https://maris-development.github.io/atlas/>**. They
|
|
152
|
+
hold the [command reference], and guides for [creating], [inspecting],
|
|
153
|
+
[removing], [dtypes], [reading data], and [cloud storage].
|
|
154
|
+
|
|
155
|
+
The format itself is documented in
|
|
156
|
+
[`docs/`](https://github.com/maris-development/atlas/tree/main/docs).
|
|
157
|
+
|
|
158
|
+
[command reference]: https://maris-development.github.io/atlas/cli/
|
|
159
|
+
[creating]: https://maris-development.github.io/atlas/guides/creating/
|
|
160
|
+
[inspecting]: https://maris-development.github.io/atlas/guides/inspecting/
|
|
161
|
+
[removing]: https://maris-development.github.io/atlas/guides/removing/
|
|
162
|
+
[dtypes]: https://maris-development.github.io/atlas/guides/dtypes/
|
|
163
|
+
[reading data]: https://maris-development.github.io/atlas/guides/reading-data/
|
|
164
|
+
[cloud storage]: https://maris-development.github.io/atlas/guides/cloud-storage/
|
|
165
|
+
|
|
166
|
+
## License
|
|
167
|
+
|
|
168
|
+
Apache-2.0. See [LICENSE](https://github.com/maris-development/atlas/blob/main/LICENSE).
|
|
169
|
+
|