dbt-opteryx
dbt adapter for Opteryx Cloud. It runs dbt Core 1.x against
Opteryx's HTTP jobs API, using the DBAPI from
opteryx-sqlalchemy.
Install
pip install dbt-opteryx
Profile
dbt's database is an Opteryx workspace and schema is a collection
(workspace / collection are accepted as aliases). Every account has a personal
space, personal.<your user>, which needs no setup:
my_project:
target: cloud
outputs:
cloud:
type: opteryx
host: opteryx.app
port: 443
ssl: true
user: "{{ env_var('OPTERYX_USER') }}"
token: "{{ env_var('OPTERYX_TOKEN') }}"
database: personal # workspace
schema: "{{ env_var('OPTERYX_USER') }}" # collection
threads: 1
Put this in dbt_project.yml (the leading-comment fix in opteryx-core has not been re-checked against opteryx.app):
query-comment:
append: true
Behaviour to know about
- No transactions. Opteryx has none; every statement commits on its own. The adapter sends nothing for dbt's BEGIN/COMMIT.
- No temporary tables. dbt's
__dbt_tmpintermediates are real tables, which dbt drops when the model succeeds. A failed model can leave one behind. - Views are built with
CREATE OR REPLACE VIEW, not dbt's rename swap. - Seeds are inserted as typed literals (
CAST('…' AS <type>)), not bind parameters. - Incremental strategies:
append,mergeanddelete+insert. - Model contracts are refused, with an error naming the model: the adapter does not enforce them yet, and ignoring one silently would claim a guarantee it does not keep.
- Queries cannot be cancelled from dbt.
- Empty relations filter on
WHERE 1 = 0. Opteryx refuses a bare literal as a WHERE clause, so the adapter replaces dbt'sWHERE falsein--emptyrefs and in the schema-only subquery dbt uses for unit tests and column introspection. - Snapshots do not work. dbt's snapshot SQL selects
*, <columns>, which Opteryx refuses (SELECT t.*, <columns>is accepted). See issue 14.
Integration tests
tests/integration/project is a dbt project covering seeds, sources (including
freshness), view, table, ephemeral and incremental models (all three strategies),
snapshots, hooks, contracts, generic, singular and unit tests, show,
run-operation, --empty and docs generation. Run it against opteryx.app:
CLIENT_ID=… CLIENT_SECRET=… python tests/integration/run.py
Every step runs, and every node must pass except those listed in
EXPECTED_FAILURES in run.py, which must fail. The run fails on an unexpected
failure and also on an expected failure that passes, so the list is pruned as
Opteryx gaps close.
It writes into personal.$CLIENT_ID (override with OPTERYX_WORKSPACE /
OPTERYX_COLLECTION). CI runs it on push (.github/workflows/integration.yaml),
reading CLIENT_ID / CLIENT_SECRET from the OPTERYX_TEST_CLIENT /
OPTERYX_TEST_TOKEN repository secrets. That is a service account, which has no
personal space, so CI writes into opteryx_ci.dbt.
Offline checks (no connection) run with python -m pytest tests/unit.
CI
| Workflow | Runs on | Does |
|---|---|---|
regression-tests.yaml |
push | tests/unit on Python 3.10–3.14 × dbt-core 1.10 (floor) and latest; 1.10 is skipped on 3.14, where it cannot import |
integration.yaml |
push | tests/integration/run.py against opteryx.app; known gaps are expected failures |
semgrep_scan.yaml |
push | Semgrep scan, findings uploaded to opteryx.ops.sast_findings |
release.yaml |
v* tag |
Builds and publishes to PyPI by trusted publishing |
Repository secrets: OPTERYX_TEST_CLIENT, OPTERYX_TEST_TOKEN (integration),
UPLOAD_CLIENT, UPLOAD_TOKEN (Semgrep upload).
Releasing
Bump version in dbt/adapters/opteryx/__version__.py (the package version is read
from it), then push a matching vX.Y.Z tag. PyPI must list this repository's
release.yaml as a trusted publisher for dbt-opteryx; for the first release that
is a pending publisher, set up on PyPI before the tag is pushed.
Verification against opteryx.app (2026-11-09, re-run 2026-11-13)
dbt-core 1.12.6, dbt-adapters 1.24.5, opteryx-sqlalchemy 0.0.13.
| Feature | Result | Notes |
|---|---|---|
dbt debug |
PASS | |
| Seeds, first load | PASS | Needs the typed-literal loader in this adapter |
| Seeds, reload over an existing table | PASS | Verified 2026-11-10 after the TRUNCATE fix deployed |
Seeds, --full-refresh |
PASS | Drops and recreates |
Sources: source() reads |
PASS | samples.tpch_sf001.nation |
| Sources: tests | PASS | not_null, unique |
Sources: dbt source freshness |
PASS | |
| View materialization | PASS | Created and replaced |
| Table materialization | PASS | Created; rebuilt through the rename swap |
Incremental, append |
PASS | Second run appends only new rows (checked 4 → 5) |
Incremental, merge, first build |
PASS | |
Incremental, merge, incremental run |
PASS | Verified 2026-11-13 (0.9.202) |
| Tests: not_null, unique, singular | PASS | |
| Tests: accepted_values, relationships | PASS | Verified 2026-11-13 |
dbt docs generate |
PASS | Verified 2026-11-13: views now list their columns |
Schema creation (CREATE COLLECTION) |
PASS with workaround | query-comment: {append: true}. Fixed in opteryx-core, awaiting deploy |
Incremental, delete+insert, first build |
PASS | |
Incremental, delete+insert, incremental run |
PASS | Verified 2026-11-10 |
| Ephemeral models | FAIL | Not re-tested: the test model int_active_customers filters on a bare boolean (WHERE active), which Opteryx refuses, so the issue 13 CTAS fix is masked |
| Snapshots (timestamp and check strategies) | FAIL | Issue 14 |
| Unit tests | PASS | Issues 15 and 16 fixed in this adapter; verified 2026-11-13 |
--empty |
PASS | Issue 15 fixed in this adapter; verified 2026-11-13 |
| Model contracts | Refused | The adapter raises (see above) |
| Pre/post hooks | PASS | Hook statements ran, checked in hook_log |
dbt show |
PASS | |
dbt run-operation with run_query |
PASS |
Issues found
| # | Issue | Class | Proposed fix |
|---|---|---|---|
| 10 | ALTER TABLE x RENAME TO y with a bare target is read as a move to another workspace. |
Behaviour (documented) | None needed: the adapter always passes the qualified name. |
| 14 | SELECT *, x is refused by design ("SELECT * cannot coexist with additional columns"); SELECT t.*, x works. dbt's snapshot SQL uses SELECT *, …. |
Opteryx dialect rule | Left as it is (ruled 2026-11-12). Lifting the engine's refusal is not enough: a bare star beside other columns expands computed columns twice. Snapshots stay unsupported unless the adapter overrides dbt's snapshot macros. |
| 15 | WHERE false is refused by design ("WHERE clause cannot be a bare literal"); WHERE 1 = 0 works. dbt's --empty renders refs as (SELECT * FROM x WHERE false LIMIT 0), and its schema-only subquery uses the same pattern. |
Opteryx dialect rule | Fixed in this adapter 2026-11-12: OpteryxRelation.render_limited and opteryx__get_empty_subquery_sql render WHERE 1 = 0. The dialect rule is unchanged. |
| 16 | dbt's unit-test fixture SQL casts string columns to character varying (dbt's base Column.string_type), which Opteryx's CAST does not accept. |
Adapter gap | Fixed in this adapter 2026-11-13: OpteryxColumn.string_type returns VARCHAR. |
Metadata
Release files for dbt-opteryx 0.1.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| dbt_opteryx-0.1.2.tar.gz | 16.1 kB | With provenance | Details for dbt_opteryx-0.1.2.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| dbt_opteryx-0.1.2-py3-none-any.whl | Python 3 | none | any | Details for dbt_opteryx-0.1.2-py3-none-any.whl |
Total release size: 31.6 kB
Release files / dbt_opteryx-0.1.2.tar.gz
| Download URL | dbt_opteryx-0.1.2.tar.gz |
|---|---|
| Size | 16.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e5fb943979ba6fddb33c5e4e71e157821d5fd3e8461109580ab3282ef5924b97
|
|
BLAKE2b-256 checksum How to use checksums |
565088324b71276f9f82b6cf34b05027915a839a19c13be0b084db6fb1de1213
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 13, 2026.
Transparency logRelease files / dbt_opteryx-0.1.2-py3-none-any.whl
| Download URL | dbt_opteryx-0.1.2-py3-none-any.whl |
|---|---|
| Size | 15.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
47bed6da23340c803e987c658c4a9e3e72b1d87bc454143a915faf85baeb8f72
|
|
BLAKE2b-256 checksum How to use checksums |
c21cb045914e0b0ea455552b40ae817129234c6e0f0cc4c423e5ac46e913db2f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 13, 2026.
Transparency logSecurity for dbt-opteryx 0.1.2
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
Both files in this release have been uploaded with provenance metadata.
Attestation for dbt_opteryx-0.1.2.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 13, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.