dlt-source-aquabyte
An installable dlt source package that loads aquaculture data from the Aquabyte API v3 into any dlt destination: sites, biomass, lice counts, welfare scores, behaviour and environmental readings.
Records land as the API returns them — nothing renamed, nothing dropped, no invented child tables. What is added is named up front rather than discovered later: _dlt_valid_from and _dlt_valid_to on the versioned sites table. Column names are the API's own field names, in dlt's usual snake_case.
Everything else is mechanics: auth, pagination, envelope unwrapping, incremental cursors, and overridable key and write-disposition defaults. Reshaping belongs in your transform layer, where you can change it without waiting for a release. Its only dependency is dlt itself — destination, orchestrator, secrets manager and log routing stay your choices.
Install
uv add dlt-source-aquabyte "dlt[duckdb]" # any dlt destination works; DuckDB is the one below
Quick start
Put the API base URL and your key in a .dlt/ directory beside the script you are about to run.
.dlt/config.toml:
[sources.aquabyte]
base_url = "https://api.aquabyte.ai/v3/"
initial_date = "2020-01-01" # first-run start for the date-based cursors
initial_time = "2020-01-01T00:00:00Z" # first-run start for the time-based cursors
.dlt/secrets.toml:
[sources.aquabyte]
api_key = "your-api-key-here"
Then load every resource:
import dlt
from dlt_source_aquabyte import aquabyte_source
pipeline = dlt.pipeline(
pipeline_name="aquabyte",
destination="duckdb", # any dlt destination
dataset_name="aquabyte_data",
)
print(pipeline.run(aquabyte_source()))
The two initial_* values are the first-run start for the resources that keep an incremental cursor; a run of only sites or environmental_latest needs neither, and a cursor resource missing one fails with an error naming it. How far back your data goes differs per endpoint and per account, and setting a start earlier than that costs empty requests, not errors.
What it loads
| Resource | Endpoint | Load strategy | Key |
|---|---|---|---|
sites |
GET /sites, GET /sites/{siteId} |
merge, scd2 |
id (merge key) |
environmental |
GET /environmental |
merge | penId, fromTime, toTime |
environmental_latest |
GET /environmental/latest |
replace | — |
biomass |
GET /biomass |
merge | penId, date |
harvest_report |
GET /biomass/harvestReport |
merge | penId, slaughterStartDate, mainReport, asOfDate |
lice_count |
GET /liceCount |
merge | penId, date |
behaviour_swim_speed |
GET /behaviour/swimSpeed |
merge | penId, fromTime, toTime |
behaviour_breathing_index |
GET /behaviour/breathingIndex |
merge | penId, fromTime |
welfare_scores |
GET /welfareScores |
merge | penId, date |
sites reads every site, and each site record carries its pens the way the API nests them — every pen, active or not. There is no separate pens table, because the API serves no pens endpoint; where pen history lives, and what the source does and does not promise about a pen or site that stops being reported. sites is versioned rather than replaced: a row is retired, never deleted, because a pen leaves /sites as soon as it is emptied (what that means for your queries). Nested objects land as one JSON column each, and welfare_scores is not unpivoted (why, and how to override it).
Configuring a resource
Each resource takes its endpoint's params in snake_case, except the window ones, which the incremental cursor drives:
source = aquabyte_source()
source.sites.bind(site_id="site-001") # switches to GET /sites/{siteId}
source.biomass.bind(pen_id="pen-abc", bucket_size=250)
pipeline.run(source)
pen_iddefaults to"all"— the API's own value for "every pen", in one request. Pass one id to read a single pen.site_idis the one path param, not a query param: binding it movessitesto the per-site endpoint, and both write the same table.- The window is the incremental cursor's, not a parameter of its own: a daily run resumes where it left off, and a backfill binds the window on the resource's
incremental_*argument — see the reference. paramsis on every resource and merged into the query string last: the escape hatch for a query param the API grows later, no release needed. It wins over every named param,penIdincluded.
Params can also be set in config, per resource:
[sources.aquabyte.environmental]
period = "15min"
Two params decide the grain of the data itself
period and bucket_size change what the API computes for you, not which rows you ask for. Both are worth deciding before the first load: a coarse setting is not wrong, but the detail under it never lands, and getting it later means re-loading that history the backfill way.
| Param | Resource | Values | API default | What it decides |
|---|---|---|---|---|
period |
environmental |
h, D, 15min |
D |
Row grain: h is 24× the rows of D, 15min is 96× |
period |
behaviour_swim_speed |
h, D |
D |
As above. 15min here is a 422 — only environmental takes it |
bucket_size |
biomass |
integer grams | 1000 |
Bucket width of the nested weightDist histogram — no extra rows |
⚠️ Changing period later leaves both grains in the table. The key is penId + fromTime + toTime, so hourly rows do not merge over the daily ones they cover — both sit there. Pick a period per resource and keep it, or re-load the history behind the change.
bucket_size adds no rows. weightDist covers only the weights observed, so a pen of smolt returns a couple of buckets and a harvest-size pen at 250 g a few dozen — one JSON column either way (what the arrays hold).
The package emits no log records of its own. dlt logs the window each run asked for and every request it made, on its own dlt logger, and routing them is dlt's [runtime] settings rather than anything here: what to set, and dlt's own logging documentation.
Examples
One concept each, readable on GitHub. From a checkout, run one with python examples/<name>.py.
| Example | The one concept |
|---|---|
quickstart.py |
Load every resource into DuckDB |
daily_load.py |
Re-running resumes from the stored cursor |
backfill.py |
Re-load a window, stored cursor untouched |
Compatibility
dlt-source-aquabyte |
Aquabyte API |
|---|---|
| 0.1.x | v3.1 |
The two numbers are unrelated — the package version is ordinary SemVer and never mirrors the API's. Built against that version's specs/openapi.json and run against the live API (last on 2026-08-17). A later backwards-compatible version is expected to work and is not verified here; run the suite first.
Read next
- Reference — the versioned site registry, nesting, backfilling, column types, and what the source deliberately does not expose.
- API quirks worth knowing — where the live API departs from its own OpenAPI document, including which identifiers to join on. Some of them change what a correct query looks like, so read it before your first one.
- Changelog and contributing.
License
Apache-2.0. specs/openapi.json is Aquabyte's own OpenAPI document, included as the spec this package is built against; it is their material, and the licence does not extend to it.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file dlt_source_aquabyte-0.1.0.tar.gz.
File metadata
- Download URL: dlt_source_aquabyte-0.1.0.tar.gz
- Upload date:
- Size: 32.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f4447b8e8e9aef09339f0c4675c8b1b768e574e6eafe1bb8c57854b0e7161bcd
|
|
| MD5 |
2c3be3944cace243f51ca8609335e29c
|
|
| BLAKE2b-256 |
5b48f8f7e9e977d9b66fb9ce257e34f4f8cc79e8b64d568ee923d7756615fbd4
|
Provenance
The following attestation bundles were made for dlt_source_aquabyte-0.1.0.tar.gz:
Publisher:
release.yml on Havbruksdataforeningen/dlt-sources
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
dlt_source_aquabyte-0.1.0.tar.gz -
Subject digest:
f4447b8e8e9aef09339f0c4675c8b1b768e574e6eafe1bb8c57854b0e7161bcd - Sigstore transparency entry: 2535540082
- Sigstore integration time:
-
Permalink:
Havbruksdataforeningen/dlt-sources@783e4f9fea561a2ec1c6ca1a90d83edb1b0130c2 -
Branch / Tag:
refs/tags/dlt-source-aquabyte/v0.1.0 - Owner: https://github.com/Havbruksdataforeningen
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@783e4f9fea561a2ec1c6ca1a90d83edb1b0130c2 -
Trigger Event:
push
-
Statement type:
File details
Details for the file dlt_source_aquabyte-0.1.0-py3-none-any.whl.
File metadata
- Download URL: dlt_source_aquabyte-0.1.0-py3-none-any.whl
- Upload date:
- Size: 13.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8ab0f567f800f45796c2ad17af2a6295012c156eb1ed2688db348d7c0dce48de
|
|
| MD5 |
280244e35206abc5918ccb635400b0ea
|
|
| BLAKE2b-256 |
5fc7997e082fde651376b8435d9d7b7926e17847e362388e08d9e61db7c56582
|
Provenance
The following attestation bundles were made for dlt_source_aquabyte-0.1.0-py3-none-any.whl:
Publisher:
release.yml on Havbruksdataforeningen/dlt-sources
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
dlt_source_aquabyte-0.1.0-py3-none-any.whl -
Subject digest:
8ab0f567f800f45796c2ad17af2a6295012c156eb1ed2688db348d7c0dce48de - Sigstore transparency entry: 2535540156
- Sigstore integration time:
-
Permalink:
Havbruksdataforeningen/dlt-sources@783e4f9fea561a2ec1c6ca1a90d83edb1b0130c2 -
Branch / Tag:
refs/tags/dlt-source-aquabyte/v0.1.0 - Owner: https://github.com/Havbruksdataforeningen
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@783e4f9fea561a2ec1c6ca1a90d83edb1b0130c2 -
Trigger Event:
push
-
Statement type: