continuo-python-runtime 0.8.0__tar.gz → 0.10.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.
- continuo_python_runtime-0.10.0/.python-version +1 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/CHANGELOG.md +106 -0
- continuo_python_runtime-0.10.0/CLAUDE.md +67 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/Dockerfile.duckdb +1 -1
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/Dockerfile.postgres +1 -1
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/Dockerfile.trino +1 -1
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/PKG-INFO +86 -13
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/README.md +84 -11
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/duckdb/continuo_duckdb_adapter/adapter.py +3 -3
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/duckdb/continuo_duckdb_adapter/application/ports.py +15 -3
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/duckdb/continuo_duckdb_adapter/application/warehouse.py +64 -7
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/duckdb/continuo_duckdb_adapter/domain/columns.py +1 -1
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/duckdb/continuo_duckdb_adapter/domain/layout.py +2 -2
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/duckdb/continuo_duckdb_adapter/infrastructure/ddl.py +44 -4
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/duckdb/continuo_duckdb_adapter/infrastructure/extensions.py +2 -2
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/duckdb/continuo_duckdb_adapter/infrastructure/session.py +32 -8
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/duckdb/pyproject.toml +2 -2
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/duckdb/tests/conftest.py +8 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/duckdb/tests/test_adapter_duckdb_runtime.py +63 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/duckdb/tests/test_infra_duckdb_session.py +54 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/duckdb/tests/test_infra_duckdb_settings_ddl.py +34 -0
- continuo_python_runtime-0.10.0/adapters/duckdb/tests/test_integration_duckdb_conformance.py +27 -0
- continuo_python_runtime-0.10.0/adapters/duckdb/tests/test_integration_duckdb_nullability_probe.py +35 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/duckdb/tests/test_integration_duckdb_runtime.py +78 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/postgres/continuo_postgres_adapter/adapter.py +181 -41
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/postgres/pyproject.toml +2 -2
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/postgres/tests/test_adapter_runtime_postgres.py +86 -0
- continuo_python_runtime-0.10.0/adapters/postgres/tests/test_integration_postgres_conformance.py +103 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/postgres/tests/test_integration_runtime_postgres.py +108 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/trino/continuo_trino_adapter/adapter.py +183 -24
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/trino/pyproject.toml +2 -2
- continuo_python_runtime-0.10.0/adapters/trino/tests/test_adapter_trino_writes.py +231 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/trino/tests/test_integration_runtime_trino.py +86 -0
- continuo_python_runtime-0.10.0/adapters/trino/tests/test_integration_trino_conformance.py +77 -0
- continuo_python_runtime-0.10.0/adapters/trino/tests/test_integration_trino_merge_probe.py +98 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/continuo_python_runtime/contract/kinds.py +12 -5
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/continuo_python_runtime/contract/loader.py +98 -1
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/continuo_python_runtime/contract/merge.py +15 -2
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/continuo_python_runtime/contract/model.py +8 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/continuo_python_runtime/harness.py +100 -4
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/continuo_python_runtime/hashing.py +8 -3
- continuo_python_runtime-0.10.0/continuo_python_runtime/shape.py +95 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/contract/README.md +10 -2
- continuo_python_runtime-0.10.0/contract/continuo_engine_contract/keys.py +87 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/contract/continuo_engine_contract/port.py +98 -2
- continuo_python_runtime-0.10.0/contract/continuo_engine_contract/testing/__init__.py +4 -0
- continuo_python_runtime-0.10.0/contract/continuo_engine_contract/testing/conformance.py +306 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/contract/pyproject.toml +7 -1
- continuo_python_runtime-0.10.0/contract/tests/test_conformance_suite.py +168 -0
- continuo_python_runtime-0.10.0/contract/tests/test_contract_keys.py +95 -0
- continuo_python_runtime-0.10.0/contract/tests/test_contract_packaging.py +26 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/contract/tests/test_port.py +122 -2
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/docs/boundary-contract.md +57 -14
- continuo_python_runtime-0.10.0/image-requirements-duckdb.txt +3 -0
- continuo_python_runtime-0.10.0/image-requirements-postgres.txt +3 -0
- continuo_python_runtime-0.10.0/image-requirements-trino.txt +3 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/pyproject.toml +2 -2
- continuo_python_runtime-0.10.0/template/.github/workflows/release.yml +188 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/template/Dockerfile +1 -1
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/template/Dockerfile.pip +1 -1
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/template/README.md +30 -11
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/conftest.py +48 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/contract/test_kinds.py +16 -1
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/contract/test_loader.py +170 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/contract/test_merge.py +111 -2
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/contract/test_model.py +32 -1
- continuo_python_runtime-0.10.0/tests/contract/test_wire_contract_write_modes.py +53 -0
- continuo_python_runtime-0.10.0/tests/fixtures/wire_contract_write_modes.yml +53 -0
- continuo_python_runtime-0.10.0/tests/test_harness_write_modes.py +259 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/test_hashing.py +7 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/test_kind_registries.py +5 -1
- continuo_python_runtime-0.10.0/tests/test_python_version_consistency.py +85 -0
- continuo_python_runtime-0.10.0/tests/test_shape.py +60 -0
- continuo_python_runtime-0.10.0/tests/test_template.py +248 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/uv.lock +17 -6
- continuo_python_runtime-0.8.0/CLAUDE.md +0 -37
- continuo_python_runtime-0.8.0/image-requirements-duckdb.txt +0 -3
- continuo_python_runtime-0.8.0/image-requirements-postgres.txt +0 -3
- continuo_python_runtime-0.8.0/image-requirements-trino.txt +0 -3
- continuo_python_runtime-0.8.0/template/.github/workflows/release.yml +0 -66
- continuo_python_runtime-0.8.0/tests/test_template.py +0 -82
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/.claude/agents/ddd-architecture-auditor.md +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/.dockerignore +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/.github/CODEOWNERS +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/.github/dependabot.yml +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/.github/workflows/ci.yml +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/.github/workflows/dco.yml +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/.github/workflows/image-build.yml +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/.github/workflows/image-smoke.yml +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/.github/workflows/images.yml +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/.github/workflows/publish-pypi.yml +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/.github/workflows/release.yml +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/.github/workflows/security.yml +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/.github/workflows/verify-packages.yml +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/.gitignore +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/.gitleaks.toml +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/CODE_OF_CONDUCT.md +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/CONTRIBUTING.md +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/DCO +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/LICENSE +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/NOTICE +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/SECURITY.md +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/duckdb/README.md +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/duckdb/continuo_duckdb_adapter/__init__.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/duckdb/continuo_duckdb_adapter/application/__init__.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/duckdb/continuo_duckdb_adapter/domain/__init__.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/duckdb/continuo_duckdb_adapter/domain/identifiers.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/duckdb/continuo_duckdb_adapter/infrastructure/__init__.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/duckdb/continuo_duckdb_adapter/infrastructure/passfile.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/duckdb/continuo_duckdb_adapter/infrastructure/settings.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/duckdb/tests/__init__.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/duckdb/tests/test_adapter_duckdb_proxy.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/duckdb/tests/test_adapter_duckdb_validation.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/duckdb/tests/test_adapter_duckdb_wiring.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/duckdb/tests/test_domain_duckdb_columns.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/duckdb/tests/test_domain_duckdb_identifiers.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/duckdb/tests/test_domain_duckdb_layout.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/duckdb/tests/test_integration_duckdb_layout.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/duckdb/tests/test_integration_duckdb_stack.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/duckdb/tests/test_integration_duckdb_validation.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/postgres/README.md +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/postgres/continuo_postgres_adapter/__init__.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/postgres/tests/__init__.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/postgres/tests/conftest.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/postgres/tests/test_adapter_postgres_validation.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/postgres/tests/test_integration_postgres_validation.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/trino/README.md +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/trino/continuo_trino_adapter/__init__.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/trino/tests/__init__.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/trino/tests/test_adapter_runtime_trino.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/trino/tests/test_adapter_trino_validation.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/trino/tests/test_integration_trino_validation.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/continuo_python_runtime/__init__.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/continuo_python_runtime/cli.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/continuo_python_runtime/closure.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/continuo_python_runtime/conform.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/continuo_python_runtime/context.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/continuo_python_runtime/contract/__init__.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/continuo_python_runtime/contract/paths.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/continuo_python_runtime/contract/secret_ref.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/continuo_python_runtime/csv_loader.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/continuo_python_runtime/csv_readers/__init__.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/continuo_python_runtime/csv_readers/https.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/continuo_python_runtime/csv_readers/s3.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/continuo_python_runtime/csv_source.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/continuo_python_runtime/errors.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/continuo_python_runtime/lint.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/continuo_python_runtime/types.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/continuo_python_runtime/validation/__init__.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/continuo_python_runtime/validation/runner.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/continuo_python_runtime/validation/s3.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/contract/LICENSE +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/contract/NOTICE +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/contract/continuo_engine_contract/__init__.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/contract/continuo_engine_contract/config.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/contract/continuo_engine_contract/result.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/contract/continuo_engine_contract/sql.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/contract/continuo_engine_contract/types.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/contract/tests/__init__.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/contract/tests/test_config.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/contract/tests/test_contract_types.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/contract/tests/test_result.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/contract/tests/test_sql.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/scripts/apply_rehearsal_version.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/scripts/bake_duckdb_extensions.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/scripts/check_version_bumps.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/scripts/security-scan.sh +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/scripts/verify_installed_packages.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/template/contracts/example.yml +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/template/contracts/example_csv.yml +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/template/requirements.lock +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/template/scripts/example.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/__init__.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/contract/__init__.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/smoke/duckdb-stack/docker-compose.yml +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/smoke/node_smoke/contracts/smoke.yml +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/smoke/node_smoke/scripts/smoke.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/smoke/postgres-stack/docker-compose.yml +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/smoke/trino-stack/catalog/iceberg.properties +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/smoke/trino-stack/docker-compose.yml +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/test_adapter_naming.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/test_check_version_bumps_tags.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/test_ci_test_coverage.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/test_cli.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/test_closure.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/test_conform.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/test_context.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/test_contract_loader.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/test_contract_pin_consistency.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/test_csv_loader.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/test_csv_readers_https.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/test_csv_readers_integration.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/test_csv_source.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/test_errors.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/test_harness.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/test_image_requirements_sync.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/test_image_smoke_validation.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/test_lint.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/test_no_legacy_names.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/test_package.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/test_rehearsal_version.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/test_release_pipeline_structure.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/test_types.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/test_validation_runner.py +0 -0
- {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/test_validation_s3.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.14.2
|
|
@@ -5,6 +5,112 @@ follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [0.10.0] - 2026-10-22
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Contracts may set `timeout_minutes` on any node: an integer from 1 to 10,080
|
|
13
|
+
that is the node's task timeout. Other values are refused as a contract
|
|
14
|
+
error naming the node. The merged wire entry carries the key only when set,
|
|
15
|
+
and it is the one wire key outside `config_hash`: changing it alone leaves
|
|
16
|
+
`config_hash` and `content_hash` unchanged. A node that does not set it has
|
|
17
|
+
the wire entry it always had.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- The "mode_type not allowed for kind" contract error now quotes the offending
|
|
22
|
+
`mode_type` and `kind` (`repr`), matching the sibling "must be one of" error,
|
|
23
|
+
so an empty or whitespace value is legible. Docstring fixes only alongside:
|
|
24
|
+
the postgres `ensure_table` port-signature reference (0.6.0 → 0.8.0) and the
|
|
25
|
+
`ensure_unique_key` message-order claim (pyarrow grouping order is not
|
|
26
|
+
contractual).
|
|
27
|
+
- The trino adapter (0.3.1) and duckdb adapter (0.2.1) release with their
|
|
28
|
+
`continuo-engine-contract` pin moved to 0.8.1; no behavior change.
|
|
29
|
+
|
|
30
|
+
## [0.9.0] - 2026-10-11
|
|
31
|
+
|
|
32
|
+
### Added
|
|
33
|
+
|
|
34
|
+
- Contracts may set `mode_type` — `replace` (the default), `append` or
|
|
35
|
+
`upsert` — and, for `upsert` only, `primary_key`: a non-empty list of output
|
|
36
|
+
columns, each spelled exactly as in `output_columns` and declared
|
|
37
|
+
`nullable: false`, names unique case-insensitively, and none a
|
|
38
|
+
floating-point (`DOUBLE PRECISION`) column — the warehouse collapses `0.0`
|
|
39
|
+
and `-0.0`, so a float key is rejected at validation rather than failing at
|
|
40
|
+
write time.
|
|
41
|
+
`python-csv` nodes allow only `replace`. `continuo-runtime validate`, `merge`
|
|
42
|
+
and `run` enforce these rules; the harness's write path for the new modes
|
|
43
|
+
follows in a later entry.
|
|
44
|
+
- The merged wire entry carries `mode_type` when it is not `replace` and
|
|
45
|
+
`primary_key` (authored order) for `upsert`; both are part of
|
|
46
|
+
`config_hash`. Entries of nodes that do not set them, and their hashes, are
|
|
47
|
+
unchanged.
|
|
48
|
+
- `continuo-engine-contract`: opt-in write capabilities on top of
|
|
49
|
+
`WarehouseAdapter` — `TableIntrospection` (`table_columns`, returning
|
|
50
|
+
`LiveColumn(name, nullable)`; `LookupError` for a missing table),
|
|
51
|
+
`AppendCapable` (`append`) and `UpsertCapable` (`upsert`, which runs
|
|
52
|
+
`ensure_unique_key` and then the engine's `_upsert`). `ensure_table` takes an
|
|
53
|
+
optional keyword-only `primary_key`. `continuo_engine_contract.keys` holds
|
|
54
|
+
`ensure_unique_key` and `DuplicateKeyError(ValueError)`.
|
|
55
|
+
- `continuo_engine_contract.testing.conformance`: a pytest conformance suite for the
|
|
56
|
+
write capabilities (`IntrospectionConformance`, `AppendConformance`,
|
|
57
|
+
`UpsertConformance`). Adapter integration suites subclass the reusable
|
|
58
|
+
classes and run them against their real engine, providing `adapter` and
|
|
59
|
+
`schema` fixtures. Install with `continuo-engine-contract[testing]`.
|
|
60
|
+
- `continuo-postgres-adapter`: `append` (batched inserts in one transaction),
|
|
61
|
+
`upsert` (`INSERT ... ON CONFLICT (key) DO UPDATE`, `DO NOTHING` when every
|
|
62
|
+
column is in the key) and `table_columns`; `ensure_table` declares an
|
|
63
|
+
upsert key as the table's `PRIMARY KEY`. `load` now shares the insert path
|
|
64
|
+
(no behavior change).
|
|
65
|
+
- `continuo-duckdb-adapter`: `append` (one transaction), `upsert` (`MERGE`
|
|
66
|
+
from the registered Arrow data, one transaction) and `table_columns`.
|
|
67
|
+
`ensure_table` accepts and ignores an upsert key: DuckLake has no
|
|
68
|
+
constraints, so the key is guaranteed by the write path.
|
|
69
|
+
- `continuo-trino-adapter`: `append` and `upsert` through a stage table
|
|
70
|
+
shaped like the target, then one `INSERT ... SELECT` or one `MERGE` — one
|
|
71
|
+
Iceberg commit per write; the stage is dropped best-effort afterwards.
|
|
72
|
+
`table_columns` reads `information_schema`. `ensure_table` accepts an
|
|
73
|
+
upsert key (no constraint DDL) and rejects it with `format_version: 1`.
|
|
74
|
+
`load` now shares the stage helpers (no behavior change).
|
|
75
|
+
- The harness writes each node with its `mode_type`: `load` for `replace`,
|
|
76
|
+
`append`, or `upsert` with the node's `primary_key` (also passed to
|
|
77
|
+
`ensure_table`). An adapter without the needed capability fails with
|
|
78
|
+
`LoadError: <Adapter> does not support <mode>` before the script runs; an
|
|
79
|
+
`upsert` whose rows repeat a key fails with `ConformError` and writes
|
|
80
|
+
nothing. Before an `append` or `upsert` the live table's columns and
|
|
81
|
+
nullability are compared with `output_columns`; any difference is a
|
|
82
|
+
`LoadError` naming each one. See "Write modes" in the README.
|
|
83
|
+
|
|
84
|
+
### Changed
|
|
85
|
+
|
|
86
|
+
- The Python version is pinned in one place, `.python-version` (`3.14.2`), the
|
|
87
|
+
single source of truth: the engine images' `Dockerfile`s pin the same exact
|
|
88
|
+
patch (`python:3.14.2-slim`), and `requires-python`, mypy `python_version`,
|
|
89
|
+
the trove classifiers and `setup-python` inputs track its major.minor. A new
|
|
90
|
+
consistency guard (`tests/test_python_version_consistency.py`) scans every
|
|
91
|
+
`pyproject.toml`, `Dockerfile*` and workflow and fails on any drift.
|
|
92
|
+
- `continuo-engine-contract` 0.7.3 -> 0.8.0 (the write capabilities above).
|
|
93
|
+
The runtime and the three adapters pin it exactly, as do the engine images'
|
|
94
|
+
`image-requirements-*.txt`.
|
|
95
|
+
- **Breaking for newly copied templates.** The `template/` release workflow
|
|
96
|
+
calls continuo's public release API instead of an unauthenticated webhook:
|
|
97
|
+
- `RELEASE_ENDPOINT` keeps its name but is now the origin of continuo's
|
|
98
|
+
`auth.publicUrl` (`scheme://host[:port]`, no path), which is also the OIDC
|
|
99
|
+
audience.
|
|
100
|
+
- The repository must be bound to the service in continuo's
|
|
101
|
+
`ciAuth.bindings`.
|
|
102
|
+
- The first release of a service is an operator bootstrap; the workflow never
|
|
103
|
+
sends `bootstrap`.
|
|
104
|
+
|
|
105
|
+
The workflow checks the endpoint and does an authenticated read of
|
|
106
|
+
`/api/v1/current-prod` before building, so a wrong URL, audience or missing
|
|
107
|
+
binding fails early. It then submits to `<RELEASE_ENDPOINT>/api/v1/releases`
|
|
108
|
+
with a fresh GitHub Actions OIDC token per call and polls
|
|
109
|
+
`GET /api/v1/releases/{id}` for up to about 50 minutes (`timeout-minutes: 60`),
|
|
110
|
+
failing on `rejected` and `superseded`. A newer push no longer cancels a run
|
|
111
|
+
that is waiting for its release (`cancel-in-progress: false`). Workflows
|
|
112
|
+
already copied from the template are untouched.
|
|
113
|
+
|
|
8
114
|
## [0.8.0] - 2026-10-01
|
|
9
115
|
|
|
10
116
|
Packages in this release: `continuo-python-runtime` 0.8.0 (no runtime code
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# continuo-python-runtime
|
|
2
|
+
|
|
3
|
+
The validation/execution runtime that continuo's executor runs as a Kubernetes
|
|
4
|
+
Job. Python 3.12, uv workspace: the runtime (`continuo_python_runtime`), the
|
|
5
|
+
engine contract (`contract/`), and the engine adapters (`adapters/postgres`,
|
|
6
|
+
`adapters/trino`, `adapters/duckdb`).
|
|
7
|
+
|
|
8
|
+
## CHANGELOG is not optional
|
|
9
|
+
|
|
10
|
+
Every user-facing change keeps `CHANGELOG.md` current. Two steps, both required:
|
|
11
|
+
|
|
12
|
+
- **On every PR** that changes behavior, the CLI/op surface, a package version,
|
|
13
|
+
or a container: add a bullet under `## [Unreleased]` (Keep a Changelog style —
|
|
14
|
+
Added / Changed / Fixed / Removed). No entry, no merge.
|
|
15
|
+
- **On every release**: rename `## [Unreleased]` to `## [X.Y.Z] - YYYY-MM-DD`,
|
|
16
|
+
add a fresh empty `## [Unreleased]` above it, and tag. `release.yml` reads the
|
|
17
|
+
section matching the pushed tag for the GitHub Release notes, so a release with
|
|
18
|
+
no matching section ships with generic auto-notes and the changelog silently
|
|
19
|
+
loses that version.
|
|
20
|
+
|
|
21
|
+
If the changelog is missing a shipped version, backfill it from that tag's
|
|
22
|
+
merged PRs before adding anything new.
|
|
23
|
+
|
|
24
|
+
## Engine adapter conformance
|
|
25
|
+
|
|
26
|
+
`contract/continuo_engine_contract/testing/conformance.py`
|
|
27
|
+
(`continuo_engine_contract.testing.conformance`) is the shared, published
|
|
28
|
+
conformance suite for engine adapter authors. Its dependencies are installed
|
|
29
|
+
with `continuo-engine-contract[testing]`.
|
|
30
|
+
|
|
31
|
+
- Every adapter MUST satisfy the engine contract and pass the shared
|
|
32
|
+
conformance classes for every capability it declares: `IntrospectionConformance`
|
|
33
|
+
for `TableIntrospection`, `AppendConformance` for `AppendCapable`, and
|
|
34
|
+
`UpsertConformance` for `UpsertCapable`. Append and upsert adapters must also
|
|
35
|
+
run the introspection class. An adapter need not declare capabilities its
|
|
36
|
+
engine cannot support.
|
|
37
|
+
- Each adapter's integration suite MUST subclass the applicable classes,
|
|
38
|
+
supply connected `adapter` and temporary `schema` fixtures, and run them
|
|
39
|
+
against its real warehouse in CI. In-memory reference tests alone do not
|
|
40
|
+
establish an adapter's conformance. Do not skip or weaken shared checks to
|
|
41
|
+
accommodate an adapter that violates the contract.
|
|
42
|
+
- Every new or changed engine-independent contract requirement, including
|
|
43
|
+
bug regressions, MUST have coverage in this shared testing module. Extend
|
|
44
|
+
the applicable conformance class, or add a class for a new capability, so
|
|
45
|
+
all adapters declaring it are checked by the same cases. Exercise the
|
|
46
|
+
reusable cases in `contract/tests/test_conformance_suite.py` as well.
|
|
47
|
+
- Engine-specific implementation details, such as SQL generation, driver
|
|
48
|
+
behavior and authentication, belong in the owning adapter's tests. Add a
|
|
49
|
+
shared case when the behavior expresses a requirement of the engine
|
|
50
|
+
contract; unrelated runtime features do not belong in this module.
|
|
51
|
+
- Before completing an adapter or contract change, run `contract/tests` and
|
|
52
|
+
the affected adapter's unit and real-engine integration suites in Docker.
|
|
53
|
+
|
|
54
|
+
## Releasing
|
|
55
|
+
|
|
56
|
+
A release is one `chore(release):` commit that bumps versions and updates the
|
|
57
|
+
changelog, then a `vX.Y.Z` tag on it. Bump the version of every package whose
|
|
58
|
+
source changed since the last tag — `scripts/check_version_bumps.py` fails the
|
|
59
|
+
PyPI publish otherwise. The tag builds and publishes the `-postgres` / `-trino` / `-duckdb`
|
|
60
|
+
images and the PyPI packages. See `CONTRIBUTING.md` for the full steps.
|
|
61
|
+
|
|
62
|
+
## Conventions
|
|
63
|
+
|
|
64
|
+
- Standard `logging` for diagnostics, never `print` — except the CLI's
|
|
65
|
+
sentinel-framed result blocks, which own stdout.
|
|
66
|
+
- In-repo packages are pinned exactly, not with ranges (see `pyproject.toml`).
|
|
67
|
+
- Commits are signed off (`git commit -s`); CI enforces the DCO.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
FROM python:3.14-slim
|
|
1
|
+
FROM python:3.14.2-slim
|
|
2
2
|
# One dual-role image per engine: the python-node runtime harness (default
|
|
3
3
|
# command `run`) and the blue/green validation runner (`validation-op`). It
|
|
4
4
|
# installs the PUBLISHED, versioned libraries, never the repo source:
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
FROM python:3.14-slim
|
|
1
|
+
FROM python:3.14.2-slim
|
|
2
2
|
# One dual-role image per engine — the python-node runtime harness (default
|
|
3
3
|
# command `run`) and the blue/green validation runner (`validation-op`). It
|
|
4
4
|
# installs the PUBLISHED, versioned libraries, never the repo source:
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
FROM python:3.14-slim
|
|
1
|
+
FROM python:3.14.2-slim
|
|
2
2
|
# One dual-role image per engine — the python-node runtime harness (default
|
|
3
3
|
# command `run`) and the blue/green validation runner (`validation-op`). It
|
|
4
4
|
# installs the PUBLISHED, versioned libraries, never the repo source:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: continuo-python-runtime
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.10.0
|
|
4
4
|
Summary: Runtime harness, contract tooling, and CI lint for Continuo python nodes.
|
|
5
5
|
Author: Simone Carolini
|
|
6
6
|
Maintainer: Simone Carolini
|
|
@@ -12,7 +12,7 @@ Classifier: Intended Audience :: Developers
|
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.14
|
|
13
13
|
Requires-Python: >=3.14
|
|
14
14
|
Requires-Dist: boto3==1.43.85
|
|
15
|
-
Requires-Dist: continuo-engine-contract==0.
|
|
15
|
+
Requires-Dist: continuo-engine-contract==0.8.1
|
|
16
16
|
Requires-Dist: pyarrow==25.0.1
|
|
17
17
|
Requires-Dist: pyyaml==6.0.3
|
|
18
18
|
Requires-Dist: sqlglot==30.17.0
|
|
@@ -73,7 +73,8 @@ engine adapters, the validation runner, and the node harness. The former
|
|
|
73
73
|
`continuo-validation` repository was merged in — there is no longer a separate
|
|
74
74
|
validation-side port, adapter class, entry-point group, or image. One
|
|
75
75
|
`WarehouseAdapter` per engine serves both the data plane (`fetch` /
|
|
76
|
-
`ensure_table` / `load
|
|
76
|
+
`ensure_table` / `load`, plus the opt-in `table_columns` / `append` / `upsert`
|
|
77
|
+
capabilities) and validation (`ensure_schema` / `drop_schema` /
|
|
77
78
|
`build_empty_from_sql` / `build_empty_from_columns` / `clone_empty_from_prod` /
|
|
78
79
|
`check_binds`), and one image per engine runs both roles.
|
|
79
80
|
|
|
@@ -121,9 +122,13 @@ the Go parser has not been taught is a production outage, not a refactor.
|
|
|
121
122
|
service name (one service name per domain repo).
|
|
122
123
|
3. Configure repository variables in GitHub (Settings → Secrets and
|
|
123
124
|
variables → Actions): `REGISTRY` (your Docker registry), `BUCKET` (your
|
|
124
|
-
S3 bucket for contract artifacts), `RELEASE_ENDPOINT` (the
|
|
125
|
-
|
|
126
|
-
|
|
125
|
+
S3 bucket for contract artifacts), `RELEASE_ENDPOINT` (the base
|
|
126
|
+
URL of your continuo install, `scheme://host[:port]` with no path, the origin of continuo's
|
|
127
|
+
`auth.publicUrl`). The
|
|
128
|
+
workflow calls `<RELEASE_ENDPOINT>/api/v1/releases` with its GitHub Actions
|
|
129
|
+
OIDC token, so the repository must be bound to your service in continuo's
|
|
130
|
+
`ciAuth.bindings` (see [Releasing from CI](https://github.com/carolsimone/continuo/blob/main/deploy/README.md#releasing-from-ci-github-actions)),
|
|
131
|
+
and an operator bootstraps the service's first release.
|
|
127
132
|
4. Configure repository secrets: `AWS_ACCESS_KEY_ID` and
|
|
128
133
|
`AWS_SECRET_ACCESS_KEY` for the S3 upload. The template workflow pushes
|
|
129
134
|
the built image to GHCR using the workflow's own `GITHUB_TOKEN` (granted
|
|
@@ -191,6 +196,71 @@ produced, never how it is checked or written.
|
|
|
191
196
|
script reads with `os.environ[...]`. The contract never holds a secret value
|
|
192
197
|
or env var name; the Secret itself is created in the cluster by the operator.
|
|
193
198
|
|
|
199
|
+
### Task timeout
|
|
200
|
+
|
|
201
|
+
Any node may set `timeout_minutes:` — an integer from 1 to 10,080 (one week) —
|
|
202
|
+
to declare how long its task may run before continuo gives up on it. A value
|
|
203
|
+
outside that range, or one that is not an integer, fails
|
|
204
|
+
`continuo-runtime validate`/`merge`/`lint`. It is the one key that is not part
|
|
205
|
+
of `config_hash`, so changing only the timeout never changes the node's
|
|
206
|
+
`content_hash`.
|
|
207
|
+
|
|
208
|
+
## Write modes
|
|
209
|
+
|
|
210
|
+
`mode_type:` chooses how the harness writes a node's table on each run:
|
|
211
|
+
|
|
212
|
+
| `mode_type` | Each run | Rows from earlier runs |
|
|
213
|
+
| --- | --- | --- |
|
|
214
|
+
| `replace` (default) | empties the table, inserts this run's rows | gone |
|
|
215
|
+
| `append` | inserts this run's rows | kept |
|
|
216
|
+
| `upsert` | inserts rows with a new key, overwrites rows whose key exists | kept unless overwritten; never deleted |
|
|
217
|
+
|
|
218
|
+
```yaml
|
|
219
|
+
- schema: fx
|
|
220
|
+
table: fx_rates
|
|
221
|
+
kind: python-api
|
|
222
|
+
mode_type: upsert
|
|
223
|
+
primary_key: [rate_date, base, quote]
|
|
224
|
+
output_columns:
|
|
225
|
+
- {name: rate_date, type: DATE, nullable: false}
|
|
226
|
+
- {name: base, type: VARCHAR(3), nullable: false}
|
|
227
|
+
- {name: quote, type: VARCHAR(3), nullable: false}
|
|
228
|
+
- {name: rate, type: DECIMAL(12,6)}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
- `python-csv` nodes only `replace`.
|
|
232
|
+
- `primary_key` is required for `upsert` and rejected otherwise: a non-empty
|
|
233
|
+
list of output columns declared `nullable: false` (a NULL key never
|
|
234
|
+
matches), each spelled exactly as in `output_columns`, names unique
|
|
235
|
+
case-insensitively. A Postgres `replace` table that
|
|
236
|
+
wants uniqueness uses `config.indexes` with `unique: true`.
|
|
237
|
+
- Every write is atomic: all of the run's rows or none. An `append` or
|
|
238
|
+
`upsert` run with zero rows writes nothing (a `replace` run with zero rows
|
|
239
|
+
still empties the table).
|
|
240
|
+
- `append` is a plain insert: a run that committed but was reported failed
|
|
241
|
+
and is retried stores its rows twice. `upsert` is idempotent.
|
|
242
|
+
- An `upsert` run whose own rows repeat a key fails with `ConformError` and
|
|
243
|
+
writes nothing.
|
|
244
|
+
- Before an `append` or `upsert` the harness compares the live table with
|
|
245
|
+
`output_columns` — names case-insensitively, nullability exactly; order and
|
|
246
|
+
types are not compared — and fails with `LoadError` naming every missing
|
|
247
|
+
column, unexpected column and nullability mismatch. Changing an `append` or
|
|
248
|
+
`upsert` table (columns, types, nullability, key or mode) needs a new table
|
|
249
|
+
name (`_v2`) under a new contract; continuo rejects the release otherwise.
|
|
250
|
+
Adding a nullable column is no exception: the existing rows cannot be
|
|
251
|
+
backfilled.
|
|
252
|
+
- The installed engine must support the mode; all three shipped adapters do.
|
|
253
|
+
A bring-your-own adapter declares `AppendCapable` / `UpsertCapable` from
|
|
254
|
+
`continuo-engine-contract` (and can run its conformance suite); without them
|
|
255
|
+
an `append`/`upsert` node fails with `LoadError: <Adapter> does not support
|
|
256
|
+
<mode>` before its script runs.
|
|
257
|
+
- Engines: on Postgres the key is the table's `PRIMARY KEY`, created with the
|
|
258
|
+
table; DuckLake and Trino/Iceberg have no constraints, so the key is
|
|
259
|
+
guaranteed by the write path only. On Trino an `upsert` table needs Iceberg
|
|
260
|
+
`format_version` 2 (the default).
|
|
261
|
+
- `full_refresh` stays refused for every python node: a rebuild would destroy
|
|
262
|
+
an accumulating table.
|
|
263
|
+
|
|
194
264
|
## The script API
|
|
195
265
|
|
|
196
266
|
A node script is a Python file with exactly one required entry point:
|
|
@@ -277,8 +347,8 @@ text.
|
|
|
277
347
|
| `ContractError` | Contract missing or invalid, node not found for `NODE_ID`, or the declared script is missing/unreachable. | The contract yaml or the `script:` path. |
|
|
278
348
|
| `ReadError` | `ctx.read()` was called with an undeclared name, or a declared read failed at the warehouse. | The `reads:` map, or the upstream query/warehouse access. |
|
|
279
349
|
| `ScriptError` | `run()` raised, has no callable `run`, or returned a value that isn't Arrow-convertible. | The node script. |
|
|
280
|
-
| `ConformError` | Structural mismatch (extra/missing/duplicate columns), a strict-cast failure, a not-null violation,
|
|
281
|
-
| `LoadError` | Adapter construction failed, or the DDL/INSERT failed at the warehouse during the write. | Warehouse connectivity/permissions, or the target table. |
|
|
350
|
+
| `ConformError` | Structural mismatch (extra/missing/duplicate columns), a strict-cast failure, a not-null violation, a VARCHAR/CHAR overflow, or an `upsert` run whose rows repeat a primary key. | The script's output shape, or the `output_columns` declaration. |
|
|
351
|
+
| `LoadError` | Adapter construction failed, the installed engine does not support the node's `mode_type`, the live table's shape differs from `output_columns` (append/upsert), or the DDL/INSERT failed at the warehouse during the write. | Warehouse connectivity/permissions, the engine image, or the target table. |
|
|
282
352
|
|
|
283
353
|
## Engine selection
|
|
284
354
|
|
|
@@ -286,18 +356,21 @@ A domain repo picks its warehouse engine by which base image it builds
|
|
|
286
356
|
`FROM`:
|
|
287
357
|
|
|
288
358
|
```dockerfile
|
|
289
|
-
FROM ghcr.io/carolsimone/continuo-python-runtime-postgres:v0.
|
|
359
|
+
FROM ghcr.io/carolsimone/continuo-python-runtime-postgres:v0.10.0
|
|
360
|
+
# or
|
|
361
|
+
FROM ghcr.io/carolsimone/continuo-python-runtime-trino:v0.10.0
|
|
290
362
|
# or
|
|
291
|
-
FROM ghcr.io/carolsimone/continuo-python-runtime-
|
|
363
|
+
FROM ghcr.io/carolsimone/continuo-python-runtime-duckdb:v0.10.0
|
|
292
364
|
```
|
|
293
365
|
|
|
294
366
|
The engine is part of the image **name**; the tag is the bare version, so
|
|
295
367
|
Continuo's Helm chart can pin an image as `<name>:vX.Y.Z@sha256:<digest>`.
|
|
296
368
|
|
|
297
369
|
Each image bakes in exactly one `WarehouseAdapter` for that engine — the
|
|
298
|
-
pinned PyPI version of the `continuo-postgres-adapter
|
|
299
|
-
`continuo-trino-adapter` package built from this
|
|
300
|
-
|
|
370
|
+
pinned PyPI version of the `continuo-postgres-adapter`,
|
|
371
|
+
`continuo-trino-adapter` or `continuo-duckdb-adapter` package built from this
|
|
372
|
+
repo's `adapters/postgres/`, `adapters/trino/` or `adapters/duckdb/` source
|
|
373
|
+
(see the table above) — registered under the
|
|
301
374
|
`continuo_engine.adapters` entry-point group (entry names `postgres` /
|
|
302
375
|
`trino` / `duckdb`). The runtime discovers it via `discover_adapter()` at run time, so a
|
|
303
376
|
single image serves every node in the service and the release-time
|
|
@@ -53,7 +53,8 @@ engine adapters, the validation runner, and the node harness. The former
|
|
|
53
53
|
`continuo-validation` repository was merged in — there is no longer a separate
|
|
54
54
|
validation-side port, adapter class, entry-point group, or image. One
|
|
55
55
|
`WarehouseAdapter` per engine serves both the data plane (`fetch` /
|
|
56
|
-
`ensure_table` / `load
|
|
56
|
+
`ensure_table` / `load`, plus the opt-in `table_columns` / `append` / `upsert`
|
|
57
|
+
capabilities) and validation (`ensure_schema` / `drop_schema` /
|
|
57
58
|
`build_empty_from_sql` / `build_empty_from_columns` / `clone_empty_from_prod` /
|
|
58
59
|
`check_binds`), and one image per engine runs both roles.
|
|
59
60
|
|
|
@@ -101,9 +102,13 @@ the Go parser has not been taught is a production outage, not a refactor.
|
|
|
101
102
|
service name (one service name per domain repo).
|
|
102
103
|
3. Configure repository variables in GitHub (Settings → Secrets and
|
|
103
104
|
variables → Actions): `REGISTRY` (your Docker registry), `BUCKET` (your
|
|
104
|
-
S3 bucket for contract artifacts), `RELEASE_ENDPOINT` (the
|
|
105
|
-
|
|
106
|
-
|
|
105
|
+
S3 bucket for contract artifacts), `RELEASE_ENDPOINT` (the base
|
|
106
|
+
URL of your continuo install, `scheme://host[:port]` with no path, the origin of continuo's
|
|
107
|
+
`auth.publicUrl`). The
|
|
108
|
+
workflow calls `<RELEASE_ENDPOINT>/api/v1/releases` with its GitHub Actions
|
|
109
|
+
OIDC token, so the repository must be bound to your service in continuo's
|
|
110
|
+
`ciAuth.bindings` (see [Releasing from CI](https://github.com/carolsimone/continuo/blob/main/deploy/README.md#releasing-from-ci-github-actions)),
|
|
111
|
+
and an operator bootstraps the service's first release.
|
|
107
112
|
4. Configure repository secrets: `AWS_ACCESS_KEY_ID` and
|
|
108
113
|
`AWS_SECRET_ACCESS_KEY` for the S3 upload. The template workflow pushes
|
|
109
114
|
the built image to GHCR using the workflow's own `GITHUB_TOKEN` (granted
|
|
@@ -171,6 +176,71 @@ produced, never how it is checked or written.
|
|
|
171
176
|
script reads with `os.environ[...]`. The contract never holds a secret value
|
|
172
177
|
or env var name; the Secret itself is created in the cluster by the operator.
|
|
173
178
|
|
|
179
|
+
### Task timeout
|
|
180
|
+
|
|
181
|
+
Any node may set `timeout_minutes:` — an integer from 1 to 10,080 (one week) —
|
|
182
|
+
to declare how long its task may run before continuo gives up on it. A value
|
|
183
|
+
outside that range, or one that is not an integer, fails
|
|
184
|
+
`continuo-runtime validate`/`merge`/`lint`. It is the one key that is not part
|
|
185
|
+
of `config_hash`, so changing only the timeout never changes the node's
|
|
186
|
+
`content_hash`.
|
|
187
|
+
|
|
188
|
+
## Write modes
|
|
189
|
+
|
|
190
|
+
`mode_type:` chooses how the harness writes a node's table on each run:
|
|
191
|
+
|
|
192
|
+
| `mode_type` | Each run | Rows from earlier runs |
|
|
193
|
+
| --- | --- | --- |
|
|
194
|
+
| `replace` (default) | empties the table, inserts this run's rows | gone |
|
|
195
|
+
| `append` | inserts this run's rows | kept |
|
|
196
|
+
| `upsert` | inserts rows with a new key, overwrites rows whose key exists | kept unless overwritten; never deleted |
|
|
197
|
+
|
|
198
|
+
```yaml
|
|
199
|
+
- schema: fx
|
|
200
|
+
table: fx_rates
|
|
201
|
+
kind: python-api
|
|
202
|
+
mode_type: upsert
|
|
203
|
+
primary_key: [rate_date, base, quote]
|
|
204
|
+
output_columns:
|
|
205
|
+
- {name: rate_date, type: DATE, nullable: false}
|
|
206
|
+
- {name: base, type: VARCHAR(3), nullable: false}
|
|
207
|
+
- {name: quote, type: VARCHAR(3), nullable: false}
|
|
208
|
+
- {name: rate, type: DECIMAL(12,6)}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
- `python-csv` nodes only `replace`.
|
|
212
|
+
- `primary_key` is required for `upsert` and rejected otherwise: a non-empty
|
|
213
|
+
list of output columns declared `nullable: false` (a NULL key never
|
|
214
|
+
matches), each spelled exactly as in `output_columns`, names unique
|
|
215
|
+
case-insensitively. A Postgres `replace` table that
|
|
216
|
+
wants uniqueness uses `config.indexes` with `unique: true`.
|
|
217
|
+
- Every write is atomic: all of the run's rows or none. An `append` or
|
|
218
|
+
`upsert` run with zero rows writes nothing (a `replace` run with zero rows
|
|
219
|
+
still empties the table).
|
|
220
|
+
- `append` is a plain insert: a run that committed but was reported failed
|
|
221
|
+
and is retried stores its rows twice. `upsert` is idempotent.
|
|
222
|
+
- An `upsert` run whose own rows repeat a key fails with `ConformError` and
|
|
223
|
+
writes nothing.
|
|
224
|
+
- Before an `append` or `upsert` the harness compares the live table with
|
|
225
|
+
`output_columns` — names case-insensitively, nullability exactly; order and
|
|
226
|
+
types are not compared — and fails with `LoadError` naming every missing
|
|
227
|
+
column, unexpected column and nullability mismatch. Changing an `append` or
|
|
228
|
+
`upsert` table (columns, types, nullability, key or mode) needs a new table
|
|
229
|
+
name (`_v2`) under a new contract; continuo rejects the release otherwise.
|
|
230
|
+
Adding a nullable column is no exception: the existing rows cannot be
|
|
231
|
+
backfilled.
|
|
232
|
+
- The installed engine must support the mode; all three shipped adapters do.
|
|
233
|
+
A bring-your-own adapter declares `AppendCapable` / `UpsertCapable` from
|
|
234
|
+
`continuo-engine-contract` (and can run its conformance suite); without them
|
|
235
|
+
an `append`/`upsert` node fails with `LoadError: <Adapter> does not support
|
|
236
|
+
<mode>` before its script runs.
|
|
237
|
+
- Engines: on Postgres the key is the table's `PRIMARY KEY`, created with the
|
|
238
|
+
table; DuckLake and Trino/Iceberg have no constraints, so the key is
|
|
239
|
+
guaranteed by the write path only. On Trino an `upsert` table needs Iceberg
|
|
240
|
+
`format_version` 2 (the default).
|
|
241
|
+
- `full_refresh` stays refused for every python node: a rebuild would destroy
|
|
242
|
+
an accumulating table.
|
|
243
|
+
|
|
174
244
|
## The script API
|
|
175
245
|
|
|
176
246
|
A node script is a Python file with exactly one required entry point:
|
|
@@ -257,8 +327,8 @@ text.
|
|
|
257
327
|
| `ContractError` | Contract missing or invalid, node not found for `NODE_ID`, or the declared script is missing/unreachable. | The contract yaml or the `script:` path. |
|
|
258
328
|
| `ReadError` | `ctx.read()` was called with an undeclared name, or a declared read failed at the warehouse. | The `reads:` map, or the upstream query/warehouse access. |
|
|
259
329
|
| `ScriptError` | `run()` raised, has no callable `run`, or returned a value that isn't Arrow-convertible. | The node script. |
|
|
260
|
-
| `ConformError` | Structural mismatch (extra/missing/duplicate columns), a strict-cast failure, a not-null violation,
|
|
261
|
-
| `LoadError` | Adapter construction failed, or the DDL/INSERT failed at the warehouse during the write. | Warehouse connectivity/permissions, or the target table. |
|
|
330
|
+
| `ConformError` | Structural mismatch (extra/missing/duplicate columns), a strict-cast failure, a not-null violation, a VARCHAR/CHAR overflow, or an `upsert` run whose rows repeat a primary key. | The script's output shape, or the `output_columns` declaration. |
|
|
331
|
+
| `LoadError` | Adapter construction failed, the installed engine does not support the node's `mode_type`, the live table's shape differs from `output_columns` (append/upsert), or the DDL/INSERT failed at the warehouse during the write. | Warehouse connectivity/permissions, the engine image, or the target table. |
|
|
262
332
|
|
|
263
333
|
## Engine selection
|
|
264
334
|
|
|
@@ -266,18 +336,21 @@ A domain repo picks its warehouse engine by which base image it builds
|
|
|
266
336
|
`FROM`:
|
|
267
337
|
|
|
268
338
|
```dockerfile
|
|
269
|
-
FROM ghcr.io/carolsimone/continuo-python-runtime-postgres:v0.
|
|
339
|
+
FROM ghcr.io/carolsimone/continuo-python-runtime-postgres:v0.10.0
|
|
340
|
+
# or
|
|
341
|
+
FROM ghcr.io/carolsimone/continuo-python-runtime-trino:v0.10.0
|
|
270
342
|
# or
|
|
271
|
-
FROM ghcr.io/carolsimone/continuo-python-runtime-
|
|
343
|
+
FROM ghcr.io/carolsimone/continuo-python-runtime-duckdb:v0.10.0
|
|
272
344
|
```
|
|
273
345
|
|
|
274
346
|
The engine is part of the image **name**; the tag is the bare version, so
|
|
275
347
|
Continuo's Helm chart can pin an image as `<name>:vX.Y.Z@sha256:<digest>`.
|
|
276
348
|
|
|
277
349
|
Each image bakes in exactly one `WarehouseAdapter` for that engine — the
|
|
278
|
-
pinned PyPI version of the `continuo-postgres-adapter
|
|
279
|
-
`continuo-trino-adapter` package built from this
|
|
280
|
-
|
|
350
|
+
pinned PyPI version of the `continuo-postgres-adapter`,
|
|
351
|
+
`continuo-trino-adapter` or `continuo-duckdb-adapter` package built from this
|
|
352
|
+
repo's `adapters/postgres/`, `adapters/trino/` or `adapters/duckdb/` source
|
|
353
|
+
(see the table above) — registered under the
|
|
281
354
|
`continuo_engine.adapters` entry-point group (entry names `postgres` /
|
|
282
355
|
`trino` / `duckdb`). The runtime discovers it via `discover_adapter()` at run time, so a
|
|
283
356
|
single image serves every node in the service and the release-time
|
|
@@ -6,9 +6,9 @@ port leaves to the concrete engine, ``required_env`` and ``from_env``.
|
|
|
6
6
|
"""
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
|
-
from .application.warehouse import LakeWarehouse
|
|
10
|
-
from .infrastructure.session import DuckLakeSession
|
|
11
|
-
from .infrastructure.settings import REQUIRED_ENV, DuckLakeSettings
|
|
9
|
+
from continuo_duckdb_adapter.application.warehouse import LakeWarehouse
|
|
10
|
+
from continuo_duckdb_adapter.infrastructure.session import DuckLakeSession
|
|
11
|
+
from continuo_duckdb_adapter.infrastructure.settings import REQUIRED_ENV, DuckLakeSettings
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class DuckDBAdapter(LakeWarehouse):
|
|
@@ -6,9 +6,11 @@ from collections.abc import Sequence
|
|
|
6
6
|
from contextlib import AbstractContextManager
|
|
7
7
|
from typing import TYPE_CHECKING
|
|
8
8
|
|
|
9
|
-
from
|
|
10
|
-
|
|
11
|
-
from
|
|
9
|
+
from continuo_engine_contract.port import LiveColumn # type: ignore[import-untyped]
|
|
10
|
+
|
|
11
|
+
from continuo_duckdb_adapter.domain.columns import ColumnDefinition
|
|
12
|
+
from continuo_duckdb_adapter.domain.identifiers import Identifier, QualifiedTable
|
|
13
|
+
from continuo_duckdb_adapter.domain.layout import TableLayout
|
|
12
14
|
|
|
13
15
|
if TYPE_CHECKING: # pragma: no cover
|
|
14
16
|
import pyarrow # type: ignore[import-untyped]
|
|
@@ -35,6 +37,10 @@ class LakeGateway(ABC):
|
|
|
35
37
|
@abstractmethod
|
|
36
38
|
def table_exists(self, table: QualifiedTable) -> bool: ...
|
|
37
39
|
|
|
40
|
+
@abstractmethod
|
|
41
|
+
def columns(self, table: QualifiedTable) -> "list[LiveColumn]":
|
|
42
|
+
"""Name and nullability of every column of *table*, in table order; [] when absent."""
|
|
43
|
+
|
|
38
44
|
@abstractmethod
|
|
39
45
|
def drop_table_if_exists(self, table: QualifiedTable) -> None: ...
|
|
40
46
|
|
|
@@ -73,5 +79,11 @@ class LakeGateway(ABC):
|
|
|
73
79
|
def insert_arrow(self, table: QualifiedTable, data: "pyarrow.Table") -> None:
|
|
74
80
|
"""Append *data* in its own column order."""
|
|
75
81
|
|
|
82
|
+
@abstractmethod
|
|
83
|
+
def merge_arrow(
|
|
84
|
+
self, table: QualifiedTable, data: "pyarrow.Table", key: Sequence[str]
|
|
85
|
+
) -> None:
|
|
86
|
+
"""Insert *data*'s rows with a new *key*, overwrite rows whose key exists."""
|
|
87
|
+
|
|
76
88
|
@abstractmethod
|
|
77
89
|
def close(self) -> None: ...
|