ondio 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.
- ondio-0.1.0/.gitignore +62 -0
- ondio-0.1.0/LICENSE.md +13 -0
- ondio-0.1.0/PKG-INFO +314 -0
- ondio-0.1.0/README.md +294 -0
- ondio-0.1.0/ondio/__init__.py +53 -0
- ondio-0.1.0/ondio/_log.py +46 -0
- ondio-0.1.0/ondio/backends/aws.py +161 -0
- ondio-0.1.0/ondio/backends/gcs.py +159 -0
- ondio-0.1.0/ondio/backends/http.py +127 -0
- ondio-0.1.0/ondio/backends/local.py +139 -0
- ondio-0.1.0/ondio/backends/protocol.py +170 -0
- ondio-0.1.0/ondio/dispatcher.py +317 -0
- ondio-0.1.0/ondio/flac.py +486 -0
- ondio-0.1.0/ondio/parquet.py +89 -0
- ondio-0.1.0/ondio/registry.py +48 -0
- ondio-0.1.0/ondio/types.py +45 -0
- ondio-0.1.0/pyproject.toml +92 -0
- ondio-0.1.0/tests/conftest.py +82 -0
- ondio-0.1.0/tests/unit/test_aws_backend.py +133 -0
- ondio-0.1.0/tests/unit/test_flac.py +388 -0
- ondio-0.1.0/tests/unit/test_gcs_backend.py +199 -0
- ondio-0.1.0/tests/unit/test_http_backend.py +144 -0
- ondio-0.1.0/tests/unit/test_local_dispatcher.py +170 -0
- ondio-0.1.0/tests/unit/test_log.py +28 -0
- ondio-0.1.0/tests/unit/test_parquet.py +58 -0
- ondio-0.1.0/tests/unit/test_registry.py +41 -0
- ondio-0.1.0/tests/unit/test_types.py +47 -0
ondio-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#***************************************************************************
|
|
2
|
+
# Codebase
|
|
3
|
+
#***************************************************************************
|
|
4
|
+
# -- ignore files/folders that begin with playground/scratch/Untitled
|
|
5
|
+
playground*
|
|
6
|
+
scratch*
|
|
7
|
+
Untitled*
|
|
8
|
+
dev*
|
|
9
|
+
# -- ignore notebooks by default : use git add -f filename.ipynb to add
|
|
10
|
+
*.ipynb
|
|
11
|
+
# -- ignore directories
|
|
12
|
+
tmp/
|
|
13
|
+
downloads/
|
|
14
|
+
logs/
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
#***************************************************************************
|
|
18
|
+
# Mac
|
|
19
|
+
#***************************************************************************
|
|
20
|
+
.DS_Store
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
#***************************************************************************
|
|
24
|
+
# SUBLIME
|
|
25
|
+
#***************************************************************************
|
|
26
|
+
*sftp-config.json*
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
#***************************************************************************
|
|
30
|
+
# Python / Tests
|
|
31
|
+
#***************************************************************************
|
|
32
|
+
*.pyc
|
|
33
|
+
pip-selfcheck.json
|
|
34
|
+
man
|
|
35
|
+
.hypothesis/
|
|
36
|
+
__pycache__/
|
|
37
|
+
.coverage
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
#***************************************************************************
|
|
41
|
+
# Distribution / packaging
|
|
42
|
+
#***************************************************************************
|
|
43
|
+
.Python
|
|
44
|
+
.ipynb_checkpoints
|
|
45
|
+
build/
|
|
46
|
+
develop-eggs/
|
|
47
|
+
dist/
|
|
48
|
+
MANIFEST
|
|
49
|
+
downloads/
|
|
50
|
+
eggs/
|
|
51
|
+
.eggs/
|
|
52
|
+
lib/
|
|
53
|
+
lib64/
|
|
54
|
+
bin/
|
|
55
|
+
include
|
|
56
|
+
parts/
|
|
57
|
+
sdist/
|
|
58
|
+
var/
|
|
59
|
+
wheels/
|
|
60
|
+
*.egg-info/
|
|
61
|
+
.installed.cfg
|
|
62
|
+
*.egg
|
ondio-0.1.0/LICENSE.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025, Regents of the University of California (Schmidt Center for Data Science and Environment at UC Berkeley)
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
|
6
|
+
|
|
7
|
+
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
|
8
|
+
|
|
9
|
+
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
|
10
|
+
|
|
11
|
+
Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
|
12
|
+
|
|
13
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
ondio-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ondio
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Uniform IO of audio data and bioacoustic model results across S3, GCS, HTTP, and local filesystems
|
|
5
|
+
Author-email: Michael Catchen <mdcatchen@gmail.com>
|
|
6
|
+
License-Expression: BSD-3-Clause
|
|
7
|
+
License-File: LICENSE.md
|
|
8
|
+
Requires-Python: >=3.11
|
|
9
|
+
Requires-Dist: audioop-lts; python_version >= '3.13'
|
|
10
|
+
Requires-Dist: boto3<2,>=1.42
|
|
11
|
+
Requires-Dist: numpy
|
|
12
|
+
Requires-Dist: pydub
|
|
13
|
+
Provides-Extra: gcs
|
|
14
|
+
Requires-Dist: google-cloud-storage; extra == 'gcs'
|
|
15
|
+
Provides-Extra: http
|
|
16
|
+
Requires-Dist: requests; extra == 'http'
|
|
17
|
+
Provides-Extra: parquet
|
|
18
|
+
Requires-Dist: pyarrow; extra == 'parquet'
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# ondio
|
|
22
|
+
|
|
23
|
+
`ondio` --- _ondas_ (Spanish for waves) + io --- is a Python package for uniform IO of
|
|
24
|
+
audio data (specifically just `.flac` at the moment) and results derived from
|
|
25
|
+
bioacoustic models (specifically `.json` and `.parquet` files) across a variety of
|
|
26
|
+
sources: AWS S3, GCS, HTTP/HTTPS, and local filesystems.
|
|
27
|
+
|
|
28
|
+
Every public function takes a URI; the platform is inferred from the URI scheme and
|
|
29
|
+
the call is dispatched to the matching backend, so the same code runs unchanged
|
|
30
|
+
against an S3 bucket, a GCS bucket, a web server, or a local directory.
|
|
31
|
+
|
|
32
|
+
`ondio` is a spin-off of [`soundhub_utils`](https://github.com/SchmidtDSE/soundhub_utils), with just the lightweight components to handle IO from generic backends.
|
|
33
|
+
|
|
34
|
+
## Naming: `read`/`write` vs `download`/`upload`
|
|
35
|
+
|
|
36
|
+
The API draws one distinction consistently:
|
|
37
|
+
|
|
38
|
+
- **`read` / `write`** move bytes **in and out of memory**. `read(uri)` returns
|
|
39
|
+
`bytes`; `write(uri, data)` takes them. Nothing touches the local filesystem.
|
|
40
|
+
- **`download` / `upload`** move **files on disk**. `download(uri, out_path)`
|
|
41
|
+
streams the object to a local path (creating parent directories);
|
|
42
|
+
`upload(uri, source_path)` pushes a local file to the URI. The URI always
|
|
43
|
+
comes first, the local path second.
|
|
44
|
+
|
|
45
|
+
The format helpers follow the same rule: `read_flac` returns decoded samples or
|
|
46
|
+
FLAC bytes in memory, while `download_flac` writes a local `.flac` file;
|
|
47
|
+
`write_json` serializes a Python object straight to a URI, and `download_json`
|
|
48
|
+
fetches and parses one.
|
|
49
|
+
|
|
50
|
+
## Objects, files, and JSON
|
|
51
|
+
|
|
52
|
+
The same calls work with `s3://`, `gs://`, `http(s)://`, `file://`, or plain
|
|
53
|
+
local paths:
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
import ondio
|
|
57
|
+
|
|
58
|
+
# moving objects into/from memory
|
|
59
|
+
data = ondio.read("s3://my-bucket/audio/rec.flac") # -> bytes
|
|
60
|
+
ondio.write("s3://my-bucket/results/example.txt", b"done")
|
|
61
|
+
|
|
62
|
+
# JSON <-> Python objects, no local file involved
|
|
63
|
+
ondio.write_json("s3://my-bucket/results//summary.json", {"detections": 118})
|
|
64
|
+
summary = ondio.download_json("s3://my-bucket/results/summary.json")
|
|
65
|
+
|
|
66
|
+
# moving files on/off disk
|
|
67
|
+
ondio.download("gs://my-bucket/audio/rec.flac", "data/rec.flac")
|
|
68
|
+
ondio.upload("s3://my-bucket/audio/rec.flac", "data/rec.flac")
|
|
69
|
+
|
|
70
|
+
# listing and management — filename filters compose with max_items
|
|
71
|
+
uris = ondio.list_files("s3://my-bucket/results/")
|
|
72
|
+
flacs = ondio.list_files("s3://my-bucket/audio/", required_ext="flac")
|
|
73
|
+
chunks = ondio.list_files("s3://my-bucket/audio/", required_prefix="chunk_", max_items=100)
|
|
74
|
+
n_flacs = ondio.object_count("s3://my-bucket/audio/", required_ext="flac")
|
|
75
|
+
if ondio.exists("s3://my-bucket/tmp/scratch.json"):
|
|
76
|
+
ondio.delete("s3://my-bucket/tmp/scratch.json")
|
|
77
|
+
|
|
78
|
+
# keyword arguments are forwarded to the backend constructor
|
|
79
|
+
# (for s3:// that's boto3.Session — e.g. picking an AWS profile)
|
|
80
|
+
ondio.list_files("s3://my-bucket/audio/", profile_name="dse")
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## FLAC
|
|
84
|
+
|
|
85
|
+
FLAC-specific IO lives behind the same read/download split. Decoding requires
|
|
86
|
+
the ffmpeg CLI on PATH (pydub and numpy are core dependencies); header parsing
|
|
87
|
+
and the whole-file `decode=False` path don't touch ffmpeg.
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
import ondio
|
|
91
|
+
|
|
92
|
+
# parse STREAMINFO fetching only header bytes — never the audio
|
|
93
|
+
header = ondio.extract_flac_header("s3://my-bucket/audio/rec.flac")
|
|
94
|
+
header.sample_rate, header.channels, header.duration
|
|
95
|
+
|
|
96
|
+
# in memory: decode a 3 s window to PCM — a float32 array of shape
|
|
97
|
+
# (frames, channels) in [-1, 1] plus the sample rate, like libsndfile
|
|
98
|
+
samples, rate = ondio.read_flac("s3://my-bucket/audio/rec.flac", 60.0, 63.0)
|
|
99
|
+
|
|
100
|
+
# in memory: the same window as bytes of a standalone .flac (lossless re-encode)
|
|
101
|
+
clip = ondio.read_flac("s3://my-bucket/audio/rec.flac", 60.0, 63.0, decode=False)
|
|
102
|
+
|
|
103
|
+
# in memory: whole file with decode=False is the original bytes exactly, no ffmpeg
|
|
104
|
+
raw = ondio.read_flac("s3://my-bucket/audio/rec.flac", decode=False)
|
|
105
|
+
|
|
106
|
+
# on disk: cut a clip straight to a local .flac file
|
|
107
|
+
ondio.download_flac("s3://my-bucket/audio/rec.flac", "clips/rec_60-63.flac", 60.0, 63.0)
|
|
108
|
+
|
|
109
|
+
# on disk: no window means a plain byte-for-byte download
|
|
110
|
+
ondio.download_flac("s3://my-bucket/audio/rec.flac", "data/rec.flac")
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Windowed reads of remote files
|
|
114
|
+
|
|
115
|
+
Windowed reads of large remote files fetch only an estimated byte range around
|
|
116
|
+
the window instead of the whole object. Using byte ranges path is chosen automatically
|
|
117
|
+
for files over 50 MB when the window is under a third of the stream duration;
|
|
118
|
+
`use_range=True`/`False` forces either path. `padding_ratio` (default 0.25)
|
|
119
|
+
controls how much extra audio is fetched on each side of the window to absorb
|
|
120
|
+
byte-estimate error.
|
|
121
|
+
|
|
122
|
+
The byte estimate positions the window using the header's duration, so ranged
|
|
123
|
+
slices are aligned only approximately (within tens of milliseconds) — but they are
|
|
124
|
+
never silently wrong in length: a ranged fetch that fails to decode or comes
|
|
125
|
+
back too short to cover the window raises `OndioError` naming `use_range=False`
|
|
126
|
+
as the exact fallback, rather than returning short or fabricated audio. A
|
|
127
|
+
header that lies about the duration (truncated recorder files exist) can still
|
|
128
|
+
place a covering window at the wrong position without an error — pass
|
|
129
|
+
`use_range=False` whenever the header cannot be trusted; the full download
|
|
130
|
+
reads the window exactly.
|
|
131
|
+
|
|
132
|
+
## Parquet
|
|
133
|
+
|
|
134
|
+
Model results go out as hive-partitioned parquet datasets via
|
|
135
|
+
`ondio.parquet.upload_parquet` (requires the `parquet` extra for pyarrow). The
|
|
136
|
+
dataset is written to a local temp directory with pyarrow, then uploaded
|
|
137
|
+
file-by-file through the backend — pyarrow's native S3/GCS filesystems are
|
|
138
|
+
deliberately not used (this would require extra credential config), so credentials
|
|
139
|
+
follow the same path as every other `ondio` call.
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
import pyarrow as pa
|
|
143
|
+
from ondio.parquet import upload_parquet
|
|
144
|
+
|
|
145
|
+
table = pa.table({
|
|
146
|
+
"site": ["A", "A", "B"],
|
|
147
|
+
"date": ["2026-04-02", "2026-04-02", "2026-04-03"],
|
|
148
|
+
"species": ["GRSP", "WEME", "GRSP"],
|
|
149
|
+
"confidence": [0.91, 0.87, 0.66],
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
# writes s3://my-bucket/results/run-1/site=A/date=2026-04-02/part-0.parquet, ...
|
|
153
|
+
upload_parquet("s3://my-bucket/results/run-1", table, partition_cols=["site", "date"])
|
|
154
|
+
|
|
155
|
+
# partition_cols=[] writes a flat (unpartitioned) dataset under the prefix
|
|
156
|
+
upload_parquet("s3://my-bucket/results/run-2/", table, partition_cols=[])
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Details of use:
|
|
160
|
+
|
|
161
|
+
- **`overwrite`** — by default (`overwrite=False`) the call refuses with
|
|
162
|
+
`OndioError` if any objects already exist under the prefix, so two runs can
|
|
163
|
+
never silently merge into one dataset. `overwrite=True` deletes everything
|
|
164
|
+
under the prefix first, then writes.
|
|
165
|
+
- **`compression`** — parquet codec, default `"snappy"`.
|
|
166
|
+
- **`max_workers`** — the per-file uploads run in a thread pool; this caps its
|
|
167
|
+
size (default lets the executor decide).
|
|
168
|
+
- Backend constructor kwargs pass through as everywhere else
|
|
169
|
+
(e.g. `profile_name="fieldwork"` for S3).
|
|
170
|
+
|
|
171
|
+
There is no `download_parquet`: readers should point their query engine
|
|
172
|
+
(pyarrow, DuckDB, polars) at the dataset directly, or fetch individual files
|
|
173
|
+
with `ondio.download` / `ondio.list_files`.
|
|
174
|
+
|
|
175
|
+
## Known differences from soundhub_utils
|
|
176
|
+
|
|
177
|
+
`ondio` is not a drop-in port; the differences below are deliberate. The FLAC
|
|
178
|
+
ones were established empirically by running both implementations side by side
|
|
179
|
+
on synthetic fixtures and on real field recordings.
|
|
180
|
+
|
|
181
|
+
### API shape
|
|
182
|
+
|
|
183
|
+
- One URI-first dispatcher replaces the per-platform modules (`io.aws`,
|
|
184
|
+
`io.gcs`, `io.url`): the URI scheme picks the backend, and platform SDKs and
|
|
185
|
+
their types never leak through the public API.
|
|
186
|
+
- The `read`/`write` (memory) vs `download`/`upload` (disk) naming rule is
|
|
187
|
+
applied consistently. Legacy's `io.read_flac(src, dest, ...)` actually wrote
|
|
188
|
+
a file to disk; its counterpart here is `download_flac(uri, out_path, ...)`.
|
|
189
|
+
- Failures raise typed exceptions (`OndioError`, `ObjectNotFoundError`,
|
|
190
|
+
`AuthError`, ...) instead of only being logged: logging (via cocina's
|
|
191
|
+
`Printer`, as in soundhub_utils) narrates progress and decisions, but is
|
|
192
|
+
never the sole signal that something went wrong.
|
|
193
|
+
|
|
194
|
+
### Ranged (windowed) FLAC reads
|
|
195
|
+
|
|
196
|
+
- **Ranged blobs are decoded behind a reconstructed header.** `ondio` prepends
|
|
197
|
+
a fresh `fLaC` marker plus the file's own STREAMINFO to every fetched byte
|
|
198
|
+
range before handing it to ffmpeg. Legacy passed the bare mid-stream blob,
|
|
199
|
+
leaving ffmpeg to blind-scan for a frame sync: measured on clean files,
|
|
200
|
+
every legacy ranged read came back silently misaligned by 1–18 ms, and
|
|
201
|
+
24-bit blobs could be probed as a video stream and crash the decoder.
|
|
202
|
+
- **Undershoots are loud.** A ranged fetch that fails to decode, or comes back
|
|
203
|
+
too short to cover the requested window, raises `OndioError` naming
|
|
204
|
+
`use_range=False` as the exact fallback — there is no silent short result
|
|
205
|
+
and no hidden retry or implicit full download. Legacy's only length check
|
|
206
|
+
was a log message with a 1-second tolerance: it silently returned short
|
|
207
|
+
audio, and on files whose header overstates the duration (truncated recorder
|
|
208
|
+
files exist) it returned fabricated audio for windows past the real end of
|
|
209
|
+
the stream and wrong-position audio for valid windows.
|
|
210
|
+
- **Ranged fetches are optional and bounded.** Legacy always byte-ranged
|
|
211
|
+
partial requests, with no full-file fallback. `ondio` ranges automatically
|
|
212
|
+
only for files over 50 MB with windows under a third of the duration, and
|
|
213
|
+
`use_range=False` always forces the exact full-download path.
|
|
214
|
+
- **The byte estimate skips the metadata.** Byte positions are interpolated
|
|
215
|
+
from the first audio byte onward, not from byte 0, so header and artwork
|
|
216
|
+
bytes no longer skew the time→byte map.
|
|
217
|
+
- **Millisecond offsets are rounded, not truncated.** Float subtraction
|
|
218
|
+
artifacts (`1.4 - 0.4 == 0.9999…`) shaved a millisecond — tens of samples —
|
|
219
|
+
off legacy slices.
|
|
220
|
+
|
|
221
|
+
One caveat is shared with legacy: a ranged window that decodes and covers the
|
|
222
|
+
requested length, but was positioned using a header that lies about the
|
|
223
|
+
duration, can still come from the wrong part of the stream with no error —
|
|
224
|
+
only undershoots are caught. Pass `use_range=False` whenever the header cannot
|
|
225
|
+
be trusted.
|
|
226
|
+
|
|
227
|
+
### FLAC header parsing
|
|
228
|
+
|
|
229
|
+
- Legacy's `extract_header` mis-parses the STREAMINFO bit fields: stereo files
|
|
230
|
+
report 1 channel, and 16- and 24-bit files both report 17 bits per sample.
|
|
231
|
+
`ondio.extract_flac_header` parses the block per the FLAC spec (verified
|
|
232
|
+
against libsndfile) and fetches only header bytes, skipping metadata block
|
|
233
|
+
bodies such as embedded artwork instead of downloading them.
|
|
234
|
+
|
|
235
|
+
## Architecture
|
|
236
|
+
|
|
237
|
+
`ondio` defines a unified interface for reading/writing files across different platforms, where the target platform is inferred based on the structure of the URI. The URI structure is used to dispatch to platform specific implementations, combining the [strategy](https://en.wikipedia.org/wiki/Strategy_pattern) and [registry](https://martinfowler.com/eaaCatalog/registry.html) design patterns.
|
|
238
|
+
|
|
239
|
+
### Layers
|
|
240
|
+
|
|
241
|
+
`ondio` follows a layered structure. The primary layer users interact with is the
|
|
242
|
+
public API: generic read/write methods plus format-specific ones.
|
|
243
|
+
|
|
244
|
+
The second layer is the registry, which maps URI schemes to approriate backends, and
|
|
245
|
+
specific helpers for `.flac` and `.parquet` files. File-format specific logic only lives at
|
|
246
|
+
this layer --- the backends only operate on bytes and arbitrary files on disk, and never
|
|
247
|
+
implement format-specific logic.
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
| Function | Description |
|
|
251
|
+
|-|-|
|
|
252
|
+
| `read(uri)` | Read the full object at `uri` into memory as `bytes` |
|
|
253
|
+
| `write(uri, data)` | Write in-memory `bytes` to `uri` |
|
|
254
|
+
| `write_json(uri, obj)` / `download_json(uri)` | JSON objects, serialized/parsed |
|
|
255
|
+
| `download(uri, out_path)` | Download the object at `uri` to a local file |
|
|
256
|
+
| `upload(uri, source_path)` | Upload a local file to `uri` |
|
|
257
|
+
| `extract_flac_header(uri)` | Parse FLAC STREAMINFO, fetching only header bytes |
|
|
258
|
+
| `read_flac(uri, start_sec, end_sec, decode=…)` | FLAC (whole or windowed) → PCM array or FLAC bytes |
|
|
259
|
+
| `download_flac(uri, out_path, start_sec, end_sec)` | FLAC (whole or windowed) → local `.flac` file |
|
|
260
|
+
| `upload_parquet(uri, table, partition_cols)` | `pyarrow.Table` → hive-partitioned dataset (in `ondio.parquet`) |
|
|
261
|
+
| `list_files(uri_prefix)` | Full URIs under a prefix, sorted; optional filename prefix/extension filters |
|
|
262
|
+
| `object_count(uri_prefix)` | Count objects under a prefix without listing them; same filters |
|
|
263
|
+
| `exists(uri)` | Whether the object exists |
|
|
264
|
+
| `delete(uri)` | Delete the object; missing objects are a no-op |
|
|
265
|
+
|
|
266
|
+
Each call resolves a backend from the URI scheme and delegates to it:
|
|
267
|
+
|
|
268
|
+
```
|
|
269
|
+
caller
|
|
270
|
+
"s3://…" "gs://…" "https://…" "file://…" "/path"
|
|
271
|
+
│
|
|
272
|
+
▼
|
|
273
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
274
|
+
│ dispatcher — the public, URI-first API │
|
|
275
|
+
│ │
|
|
276
|
+
│ bytes/objects: read · write · download · upload │
|
|
277
|
+
│ exists · delete · list_files · object_count │
|
|
278
|
+
│ json/parquet: write_json · download_json · upload_parquet │
|
|
279
|
+
│ flac: extract_flac_header · read_flac │
|
|
280
|
+
│ download_flac │
|
|
281
|
+
└──────────────┬───────────────────────────────┬──────────────────┘
|
|
282
|
+
│ get_backend(uri) │ passes (backend, uri)
|
|
283
|
+
▼ ▼
|
|
284
|
+
┌──────────────────────────┐ ┌─────────────────────────────────┐
|
|
285
|
+
│ registry │ │ format helpers │
|
|
286
|
+
│ │ │ │
|
|
287
|
+
│ URI scheme → platform │ │ flac.py header parsing + │
|
|
288
|
+
│ → backend factory │ │ partial reads │
|
|
289
|
+
│ │ │ │
|
|
290
|
+
│ │ │ parquet.py upload_parquet │
|
|
291
|
+
│ │ │ (pyarrow) │
|
|
292
|
+
└──────────────┬───────────┘ └───────────────┬─────────────────┘
|
|
293
|
+
│ backend-agnostic: built on the protocol
|
|
294
|
+
│ │
|
|
295
|
+
│ │
|
|
296
|
+
▼ ▼
|
|
297
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
298
|
+
│ backends — the StorageBackend protocol │
|
|
299
|
+
│ │
|
|
300
|
+
│ read · read_range · size · write · download · list_files │
|
|
301
|
+
│ object_count · exists · delete │
|
|
302
|
+
│ │
|
|
303
|
+
│ ┌─────────┐ ┌───────────┐ ┌───────────┐ ┌─────────────┐ │
|
|
304
|
+
│ │ local │ │ aws (s3:) │ │ gcs (gs:) │ │ url (http:) │ │
|
|
305
|
+
│ └────┬────┘ └─────┬─────┘ └─────┬─────┘ └──────┬──────┘ │
|
|
306
|
+
└────────┼──────────────┼───────────────┼────────────────┼────────┘
|
|
307
|
+
▼ ▼ ▼ ▼
|
|
308
|
+
filesystem boto3 google-cloud-storage requests
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
- **dispatcher** — what callers import: every function takes a URI, infers the platform, and delegates. No platform types leak out.
|
|
312
|
+
- **registry** — the strategy lookup: maps the URI scheme to a platform name and lazily constructs that platform's backend.
|
|
313
|
+
- **format helpers** — FLAC and Parquet logic written once against the backend protocol, so partial FLAC reads work identically on S3, GCS, HTTP, and local files. FLAC decoding/slicing shells out to ffmpeg via pydub (requires the ffmpeg CLI on PATH).
|
|
314
|
+
- **backends** — one class per platform implementing the byte-level protocol (`read_range` is what makes partial reads cheap on remote files); each wraps its platform SDK and translates its errors to ondio's exception types.
|