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.
Files changed (166) hide show
  1. atlas_python-0.16.0/CONTRIBUTING.md +275 -0
  2. {atlas_python-0.14.0 → atlas_python-0.16.0}/Cargo.lock +4 -21
  3. {atlas_python-0.14.0 → atlas_python-0.16.0}/Cargo.toml +9 -8
  4. atlas_python-0.16.0/PKG-INFO +169 -0
  5. atlas_python-0.16.0/README.md +272 -0
  6. {atlas_python-0.14.0 → atlas_python-0.16.0}/atlas-python/Cargo.toml +2 -2
  7. atlas_python-0.16.0/atlas-python/README.md +140 -0
  8. atlas_python-0.16.0/atlas-python/docs/cli.md +235 -0
  9. atlas_python-0.16.0/atlas-python/docs/examples.md +44 -0
  10. atlas_python-0.16.0/atlas-python/docs/guides/cloud-storage.md +119 -0
  11. atlas_python-0.16.0/atlas-python/docs/guides/creating.md +174 -0
  12. atlas_python-0.16.0/atlas-python/docs/guides/dtypes.md +113 -0
  13. atlas_python-0.16.0/atlas-python/docs/guides/inspecting.md +171 -0
  14. atlas_python-0.16.0/atlas-python/docs/guides/reading-data.md +109 -0
  15. atlas_python-0.16.0/atlas-python/docs/guides/removing.md +118 -0
  16. atlas_python-0.16.0/atlas-python/docs/index.md +120 -0
  17. atlas_python-0.16.0/atlas-python/docs/installation.md +88 -0
  18. atlas_python-0.16.0/atlas-python/docs/quickstart.md +154 -0
  19. atlas_python-0.16.0/atlas-python/docs/reference/api.md +50 -0
  20. atlas_python-0.16.0/atlas-python/docs/vs-zarr-netcdf.md +115 -0
  21. atlas_python-0.16.0/atlas-python/examples/01_library.py +106 -0
  22. atlas_python-0.16.0/atlas-python/examples/02_object_store.py +90 -0
  23. {atlas_python-0.14.0 → atlas_python-0.16.0}/atlas-python/mkdocs.yml +9 -17
  24. {atlas_python-0.14.0 → atlas_python-0.16.0}/atlas-python/src/attr.rs +84 -4
  25. {atlas_python-0.14.0 → atlas_python-0.16.0}/atlas-python/src/dtype.rs +13 -8
  26. {atlas_python-0.14.0 → atlas_python-0.16.0}/atlas-python/src/error.rs +12 -16
  27. {atlas_python-0.14.0 → atlas_python-0.16.0}/atlas-python/src/lib.rs +9 -6
  28. {atlas_python-0.14.0 → atlas_python-0.16.0}/atlas-python/src/logging.rs +13 -14
  29. atlas_python-0.16.0/atlas-python/src/reader.rs +349 -0
  30. atlas_python-0.16.0/atlas-python/src/source.rs +35 -0
  31. atlas_python-0.16.0/atlas-python/src/writer.rs +582 -0
  32. atlas_python-0.16.0/atlas-python/tests/conftest.py +50 -0
  33. atlas_python-0.16.0/atlas-python/tests/make_fixture.py +92 -0
  34. atlas_python-0.16.0/atlas-python/tests/test_cli.py +285 -0
  35. atlas_python-0.16.0/atlas-python/tests/test_ops.py +537 -0
  36. atlas_python-0.16.0/atlas-python/tests/test_source.py +83 -0
  37. atlas_python-0.16.0/docs/README.md +64 -0
  38. atlas_python-0.16.0/docs/architecture.md +100 -0
  39. atlas_python-0.16.0/docs/data-model.md +112 -0
  40. atlas_python-0.16.0/docs/format.md +217 -0
  41. atlas_python-0.16.0/docs/python.md +168 -0
  42. atlas_python-0.16.0/docs/read-path.md +157 -0
  43. atlas_python-0.16.0/docs/write-path.md +126 -0
  44. atlas_python-0.16.0/examples/lifecycle.rs +110 -0
  45. atlas_python-0.16.0/examples/sensor_fleet.rs +122 -0
  46. atlas_python-0.16.0/examples/weather_store.rs +167 -0
  47. {atlas_python-0.14.0 → atlas_python-0.16.0}/pyproject.toml +5 -7
  48. atlas_python-0.16.0/python/atlas/__init__.py +44 -0
  49. atlas_python-0.16.0/python/atlas/__init__.pyi +234 -0
  50. atlas_python-0.16.0/python/atlas/_cli.py +423 -0
  51. atlas_python-0.16.0/python/atlas/_ops.py +337 -0
  52. atlas_python-0.16.0/python/atlas/_source.py +93 -0
  53. atlas_python-0.16.0/python/atlas/xarray.py +500 -0
  54. atlas_python-0.16.0/src/config.rs +47 -0
  55. atlas_python-0.16.0/src/error.rs +76 -0
  56. atlas_python-0.16.0/src/format/footer.rs +688 -0
  57. atlas_python-0.16.0/src/format/mask.rs +152 -0
  58. atlas_python-0.16.0/src/format/mod.rs +209 -0
  59. atlas_python-0.16.0/src/format/segment_store.rs +338 -0
  60. atlas_python-0.16.0/src/lib.rs +180 -0
  61. atlas_python-0.16.0/src/reader/mod.rs +602 -0
  62. atlas_python-0.16.0/src/schema/array.rs +128 -0
  63. atlas_python-0.16.0/src/schema/attr.rs +127 -0
  64. atlas_python-0.16.0/src/schema/dtype.rs +144 -0
  65. atlas_python-0.16.0/src/schema/mod.rs +26 -0
  66. atlas_python-0.16.0/src/writer/mod.rs +500 -0
  67. atlas_python-0.16.0/tests/cross_fixture.rs +204 -0
  68. atlas_python-0.16.0/tests/fixtures/from_python/data.atlas +0 -0
  69. atlas_python-0.16.0/tests/fixtures/golden_v1/data.atlas +0 -0
  70. atlas_python-0.16.0/tests/golden.rs +218 -0
  71. atlas_python-0.16.0/tests/integration.rs +1340 -0
  72. atlas_python-0.14.0/CONTRIBUTING.md +0 -218
  73. atlas_python-0.14.0/PKG-INFO +0 -331
  74. atlas_python-0.14.0/README.md +0 -439
  75. atlas_python-0.14.0/atlas-python/README.md +0 -298
  76. atlas_python-0.14.0/atlas-python/benchmarks/README.md +0 -249
  77. atlas_python-0.14.0/atlas-python/benchmarks/_common.py +0 -258
  78. atlas_python-0.14.0/atlas-python/benchmarks/bench_collection.py +0 -757
  79. atlas_python-0.14.0/atlas-python/benchmarks/generate_bench_charts.py +0 -141
  80. atlas_python-0.14.0/atlas-python/docs/assets/bench_gridded.svg +0 -1622
  81. atlas_python-0.14.0/atlas-python/docs/assets/bench_profile.svg +0 -1570
  82. atlas_python-0.14.0/atlas-python/docs/benchmarks.md +0 -187
  83. atlas_python-0.14.0/atlas-python/docs/examples.md +0 -37
  84. atlas_python-0.14.0/atlas-python/docs/guides/attributes.md +0 -115
  85. atlas_python-0.14.0/atlas-python/docs/guides/bulk-reads.md +0 -107
  86. atlas_python-0.14.0/atlas-python/docs/guides/cloud-storage.md +0 -151
  87. atlas_python-0.14.0/atlas-python/docs/guides/codecs-and-meta.md +0 -95
  88. atlas_python-0.14.0/atlas-python/docs/guides/dask.md +0 -100
  89. atlas_python-0.14.0/atlas-python/docs/guides/datasets-and-arrays.md +0 -208
  90. atlas_python-0.14.0/atlas-python/docs/guides/dtypes.md +0 -96
  91. atlas_python-0.14.0/atlas-python/docs/guides/durability.md +0 -98
  92. atlas_python-0.14.0/atlas-python/docs/guides/shared-arrays.md +0 -87
  93. atlas_python-0.14.0/atlas-python/docs/guides/stats.md +0 -177
  94. atlas_python-0.14.0/atlas-python/docs/guides/xarray.md +0 -168
  95. atlas_python-0.14.0/atlas-python/docs/index.md +0 -87
  96. atlas_python-0.14.0/atlas-python/docs/installation.md +0 -94
  97. atlas_python-0.14.0/atlas-python/docs/quickstart.md +0 -64
  98. atlas_python-0.14.0/atlas-python/docs/reference/atlas.md +0 -10
  99. atlas_python-0.14.0/atlas-python/docs/reference/dataset-view.md +0 -10
  100. atlas_python-0.14.0/atlas-python/docs/reference/xarray-accessor.md +0 -42
  101. atlas_python-0.14.0/atlas-python/docs/vs-zarr-netcdf.md +0 -139
  102. atlas_python-0.14.0/atlas-python/examples/01_basics.py +0 -67
  103. atlas_python-0.14.0/atlas-python/examples/02_xarray.py +0 -75
  104. atlas_python-0.14.0/atlas-python/examples/03_dask_streaming.py +0 -57
  105. atlas_python-0.14.0/atlas-python/examples/04_meta_formats.py +0 -83
  106. atlas_python-0.14.0/atlas-python/examples/05_codecs.py +0 -91
  107. atlas_python-0.14.0/atlas-python/examples/06_stats_scan.py +0 -77
  108. atlas_python-0.14.0/atlas-python/examples/07_shared_arrays.py +0 -82
  109. atlas_python-0.14.0/atlas-python/examples/08_object_store.py +0 -142
  110. atlas_python-0.14.0/atlas-python/examples/09_missing_data.py +0 -101
  111. atlas_python-0.14.0/atlas-python/src/dataset.rs +0 -654
  112. atlas_python-0.14.0/atlas-python/src/store.rs +0 -637
  113. atlas_python-0.14.0/atlas-python/tests/test_smoke.py +0 -599
  114. atlas_python-0.14.0/atlas-python/tests/test_xarray.py +0 -686
  115. atlas_python-0.14.0/docs/README.md +0 -67
  116. atlas_python-0.14.0/docs/architecture.md +0 -104
  117. atlas_python-0.14.0/docs/data-model.md +0 -108
  118. atlas_python-0.14.0/docs/metadata.md +0 -90
  119. atlas_python-0.14.0/docs/pruning-index.md +0 -159
  120. atlas_python-0.14.0/docs/python-xarray.md +0 -89
  121. atlas_python-0.14.0/docs/storage-layout.md +0 -98
  122. atlas_python-0.14.0/docs/write-path.md +0 -101
  123. atlas_python-0.14.0/examples/bench_pruning.rs +0 -75
  124. atlas_python-0.14.0/examples/lifecycle.rs +0 -213
  125. atlas_python-0.14.0/examples/sensor_fleet.rs +0 -154
  126. atlas_python-0.14.0/examples/weather_store.rs +0 -340
  127. atlas_python-0.14.0/python/atlas/__init__.py +0 -5
  128. atlas_python-0.14.0/python/atlas/__init__.pyi +0 -458
  129. atlas_python-0.14.0/python/atlas/store.py +0 -106
  130. atlas_python-0.14.0/python/atlas/xarray.py +0 -775
  131. atlas_python-0.14.0/src/array.rs +0 -144
  132. atlas_python-0.14.0/src/config.rs +0 -164
  133. atlas_python-0.14.0/src/dataset/cache.rs +0 -54
  134. atlas_python-0.14.0/src/dataset/mod.rs +0 -11
  135. atlas_python-0.14.0/src/dataset/pending_attrs.rs +0 -244
  136. atlas_python-0.14.0/src/dataset/view.rs +0 -1027
  137. atlas_python-0.14.0/src/error.rs +0 -97
  138. atlas_python-0.14.0/src/lib.rs +0 -201
  139. atlas_python-0.14.0/src/meta/mod.rs +0 -21
  140. atlas_python-0.14.0/src/meta/persist.rs +0 -449
  141. atlas_python-0.14.0/src/meta/schema.rs +0 -230
  142. atlas_python-0.14.0/src/meta/store_meta.rs +0 -668
  143. atlas_python-0.14.0/src/meta/type_index.rs +0 -175
  144. atlas_python-0.14.0/src/pruning/bitmap.rs +0 -84
  145. atlas_python-0.14.0/src/pruning/column.rs +0 -392
  146. atlas_python-0.14.0/src/pruning/mod.rs +0 -264
  147. atlas_python-0.14.0/src/pruning/value.rs +0 -157
  148. atlas_python-0.14.0/src/schema/array.rs +0 -43
  149. atlas_python-0.14.0/src/schema/attr.rs +0 -252
  150. atlas_python-0.14.0/src/schema/dtype.rs +0 -277
  151. atlas_python-0.14.0/src/schema/mod.rs +0 -17
  152. atlas_python-0.14.0/src/store/bulk_read.rs +0 -228
  153. atlas_python-0.14.0/src/store/durability.rs +0 -178
  154. atlas_python-0.14.0/src/store/mod.rs +0 -877
  155. atlas_python-0.14.0/tests/integration.rs +0 -1526
  156. {atlas_python-0.14.0 → atlas_python-0.16.0}/.github/workflows/atlas-python-docs.yaml +0 -0
  157. {atlas_python-0.14.0 → atlas_python-0.16.0}/.github/workflows/atlas-python-release.yaml +0 -0
  158. {atlas_python-0.14.0 → atlas_python-0.16.0}/.github/workflows/atlas-rust-release.yaml +0 -0
  159. {atlas_python-0.14.0 → atlas_python-0.16.0}/.github/workflows/ci.yaml +0 -0
  160. {atlas_python-0.14.0 → atlas_python-0.16.0}/.gitignore +0 -0
  161. {atlas_python-0.14.0 → atlas_python-0.16.0}/LICENSE +0 -0
  162. {atlas_python-0.14.0 → atlas_python-0.16.0}/atlas-python/.python-version +0 -0
  163. {atlas_python-0.14.0 → atlas_python-0.16.0}/atlas-python/LICENSE +0 -0
  164. {atlas_python-0.14.0 → atlas_python-0.16.0}/atlas-python/src/runtime.rs +0 -0
  165. {atlas_python-0.14.0 → atlas_python-0.16.0}/atlas-python/tests/GL_PR_BO_JLKU.nc +0 -0
  166. {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.14.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.14.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.14.0"
8
+ version = "0.16.0"
9
9
  edition = "2024"
10
- description = "Directory-based store for thousands of N-dimensional datasets local or remote using object storage."
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
- array-format = "0.12.0"
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
- tokio = { version = "1", features = ["fs", "sync", "rt"] }
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
+