continuo-python-runtime 0.9.0__tar.gz → 0.11.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.11.0/.python-version +1 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/CHANGELOG.md +93 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/CLAUDE.md +10 -8
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/Dockerfile.duckdb +1 -1
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/Dockerfile.postgres +1 -1
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/Dockerfile.trino +1 -1
- continuo_python_runtime-0.9.0/README.md → continuo_python_runtime-0.11.0/PKG-INFO +36 -4
- continuo_python_runtime-0.9.0/PKG-INFO → continuo_python_runtime-0.11.0/README.md +16 -24
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/adapters/duckdb/README.md +12 -3
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/adapters/duckdb/continuo_duckdb_adapter/infrastructure/session.py +47 -2
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/adapters/duckdb/pyproject.toml +7 -7
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/adapters/duckdb/tests/test_infra_duckdb_session.py +66 -3
- continuo_python_runtime-0.11.0/adapters/duckdb/tests/test_integration_duckdb_conformance.py +51 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/adapters/duckdb/tests/test_integration_duckdb_stack.py +18 -6
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/adapters/postgres/README.md +7 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/adapters/postgres/continuo_postgres_adapter/adapter.py +69 -12
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/adapters/postgres/pyproject.toml +6 -6
- continuo_python_runtime-0.11.0/adapters/postgres/tests/test_adapter_postgres_connect.py +209 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/adapters/postgres/tests/test_integration_postgres_conformance.py +23 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/adapters/postgres/tests/test_integration_runtime_postgres.py +29 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/adapters/trino/README.md +11 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/adapters/trino/continuo_trino_adapter/adapter.py +84 -2
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/adapters/trino/pyproject.toml +6 -6
- continuo_python_runtime-0.11.0/adapters/trino/tests/test_adapter_trino_connect.py +217 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/adapters/trino/tests/test_integration_trino_conformance.py +24 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/continuo_python_runtime/contract/loader.py +17 -1
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/continuo_python_runtime/contract/merge.py +4 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/continuo_python_runtime/contract/model.py +1 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/continuo_python_runtime/harness.py +8 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/continuo_python_runtime/hashing.py +8 -3
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/contract/README.md +1 -1
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/contract/continuo_engine_contract/keys.py +3 -3
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/contract/continuo_engine_contract/port.py +9 -1
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/contract/continuo_engine_contract/testing/conformance.py +130 -5
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/contract/pyproject.toml +3 -3
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/contract/tests/test_contract_packaging.py +1 -1
- continuo_python_runtime-0.11.0/contract/tests/test_startup_conformance.py +167 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/docs/boundary-contract.md +17 -3
- continuo_python_runtime-0.11.0/image-requirements-duckdb.txt +3 -0
- continuo_python_runtime-0.11.0/image-requirements-postgres.txt +3 -0
- continuo_python_runtime-0.11.0/image-requirements-trino.txt +3 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/pyproject.toml +7 -7
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/template/Dockerfile +1 -1
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/template/Dockerfile.pip +1 -1
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/template/README.md +1 -1
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/tests/contract/test_loader.py +20 -1
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/tests/contract/test_merge.py +22 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/tests/test_harness.py +99 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/tests/test_hashing.py +7 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/tests/test_rehearsal_version.py +1 -1
- continuo_python_runtime-0.11.0/uv.lock +920 -0
- continuo_python_runtime-0.9.0/.python-version +0 -1
- continuo_python_runtime-0.9.0/adapters/duckdb/tests/test_integration_duckdb_conformance.py +0 -27
- continuo_python_runtime-0.9.0/image-requirements-duckdb.txt +0 -3
- continuo_python_runtime-0.9.0/image-requirements-postgres.txt +0 -3
- continuo_python_runtime-0.9.0/image-requirements-trino.txt +0 -3
- continuo_python_runtime-0.9.0/uv.lock +0 -895
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/.claude/agents/ddd-architecture-auditor.md +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/.dockerignore +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/.github/CODEOWNERS +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/.github/dependabot.yml +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/.github/workflows/ci.yml +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/.github/workflows/dco.yml +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/.github/workflows/image-build.yml +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/.github/workflows/image-smoke.yml +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/.github/workflows/images.yml +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/.github/workflows/publish-pypi.yml +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/.github/workflows/release.yml +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/.github/workflows/security.yml +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/.github/workflows/verify-packages.yml +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/.gitignore +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/.gitleaks.toml +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/CODE_OF_CONDUCT.md +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/CONTRIBUTING.md +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/DCO +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/LICENSE +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/NOTICE +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/SECURITY.md +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/adapters/duckdb/continuo_duckdb_adapter/__init__.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/adapters/duckdb/continuo_duckdb_adapter/adapter.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/adapters/duckdb/continuo_duckdb_adapter/application/__init__.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/adapters/duckdb/continuo_duckdb_adapter/application/ports.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/adapters/duckdb/continuo_duckdb_adapter/application/warehouse.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/adapters/duckdb/continuo_duckdb_adapter/domain/__init__.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/adapters/duckdb/continuo_duckdb_adapter/domain/columns.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/adapters/duckdb/continuo_duckdb_adapter/domain/identifiers.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/adapters/duckdb/continuo_duckdb_adapter/domain/layout.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/adapters/duckdb/continuo_duckdb_adapter/infrastructure/__init__.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/adapters/duckdb/continuo_duckdb_adapter/infrastructure/ddl.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/adapters/duckdb/continuo_duckdb_adapter/infrastructure/extensions.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/adapters/duckdb/continuo_duckdb_adapter/infrastructure/passfile.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/adapters/duckdb/continuo_duckdb_adapter/infrastructure/settings.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/adapters/duckdb/tests/__init__.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/adapters/duckdb/tests/conftest.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/adapters/duckdb/tests/test_adapter_duckdb_proxy.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/adapters/duckdb/tests/test_adapter_duckdb_runtime.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/adapters/duckdb/tests/test_adapter_duckdb_validation.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/adapters/duckdb/tests/test_adapter_duckdb_wiring.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/adapters/duckdb/tests/test_domain_duckdb_columns.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/adapters/duckdb/tests/test_domain_duckdb_identifiers.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/adapters/duckdb/tests/test_domain_duckdb_layout.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/adapters/duckdb/tests/test_infra_duckdb_settings_ddl.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/adapters/duckdb/tests/test_integration_duckdb_layout.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/adapters/duckdb/tests/test_integration_duckdb_nullability_probe.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/adapters/duckdb/tests/test_integration_duckdb_runtime.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/adapters/duckdb/tests/test_integration_duckdb_validation.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/adapters/postgres/continuo_postgres_adapter/__init__.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/adapters/postgres/tests/__init__.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/adapters/postgres/tests/conftest.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/adapters/postgres/tests/test_adapter_postgres_validation.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/adapters/postgres/tests/test_adapter_runtime_postgres.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/adapters/postgres/tests/test_integration_postgres_validation.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/adapters/trino/continuo_trino_adapter/__init__.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/adapters/trino/tests/__init__.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/adapters/trino/tests/test_adapter_runtime_trino.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/adapters/trino/tests/test_adapter_trino_validation.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/adapters/trino/tests/test_adapter_trino_writes.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/adapters/trino/tests/test_integration_runtime_trino.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/adapters/trino/tests/test_integration_trino_merge_probe.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/adapters/trino/tests/test_integration_trino_validation.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/continuo_python_runtime/__init__.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/continuo_python_runtime/cli.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/continuo_python_runtime/closure.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/continuo_python_runtime/conform.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/continuo_python_runtime/context.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/continuo_python_runtime/contract/__init__.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/continuo_python_runtime/contract/kinds.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/continuo_python_runtime/contract/paths.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/continuo_python_runtime/contract/secret_ref.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/continuo_python_runtime/csv_loader.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/continuo_python_runtime/csv_readers/__init__.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/continuo_python_runtime/csv_readers/https.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/continuo_python_runtime/csv_readers/s3.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/continuo_python_runtime/csv_source.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/continuo_python_runtime/errors.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/continuo_python_runtime/lint.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/continuo_python_runtime/shape.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/continuo_python_runtime/types.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/continuo_python_runtime/validation/__init__.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/continuo_python_runtime/validation/runner.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/continuo_python_runtime/validation/s3.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/contract/LICENSE +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/contract/NOTICE +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/contract/continuo_engine_contract/__init__.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/contract/continuo_engine_contract/config.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/contract/continuo_engine_contract/result.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/contract/continuo_engine_contract/sql.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/contract/continuo_engine_contract/testing/__init__.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/contract/continuo_engine_contract/types.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/contract/tests/__init__.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/contract/tests/test_config.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/contract/tests/test_conformance_suite.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/contract/tests/test_contract_keys.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/contract/tests/test_contract_types.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/contract/tests/test_port.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/contract/tests/test_result.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/contract/tests/test_sql.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/scripts/apply_rehearsal_version.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/scripts/bake_duckdb_extensions.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/scripts/check_version_bumps.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/scripts/security-scan.sh +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/scripts/verify_installed_packages.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/template/.github/workflows/release.yml +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/template/contracts/example.yml +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/template/contracts/example_csv.yml +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/template/requirements.lock +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/template/scripts/example.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/tests/__init__.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/tests/conftest.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/tests/contract/__init__.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/tests/contract/test_kinds.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/tests/contract/test_model.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/tests/contract/test_wire_contract_write_modes.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/tests/fixtures/wire_contract_write_modes.yml +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/tests/smoke/duckdb-stack/docker-compose.yml +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/tests/smoke/node_smoke/contracts/smoke.yml +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/tests/smoke/node_smoke/scripts/smoke.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/tests/smoke/postgres-stack/docker-compose.yml +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/tests/smoke/trino-stack/catalog/iceberg.properties +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/tests/smoke/trino-stack/docker-compose.yml +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/tests/test_adapter_naming.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/tests/test_check_version_bumps_tags.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/tests/test_ci_test_coverage.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/tests/test_cli.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/tests/test_closure.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/tests/test_conform.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/tests/test_context.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/tests/test_contract_loader.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/tests/test_contract_pin_consistency.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/tests/test_csv_loader.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/tests/test_csv_readers_https.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/tests/test_csv_readers_integration.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/tests/test_csv_source.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/tests/test_errors.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/tests/test_harness_write_modes.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/tests/test_image_requirements_sync.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/tests/test_image_smoke_validation.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/tests/test_kind_registries.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/tests/test_lint.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/tests/test_no_legacy_names.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/tests/test_package.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/tests/test_python_version_consistency.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/tests/test_release_pipeline_structure.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/tests/test_shape.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/tests/test_template.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/tests/test_types.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/tests/test_validation_runner.py +0 -0
- {continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/tests/test_validation_s3.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.14.8
|
|
@@ -5,6 +5,99 @@ follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [0.11.0] - 2026-11-04
|
|
9
|
+
|
|
10
|
+
**TL;DR**
|
|
11
|
+
|
|
12
|
+
- A failed python node's log now says why it failed.
|
|
13
|
+
- Every engine adapter waits up to about 10 s for a warehouse that refuses
|
|
14
|
+
the first connections, as a new Kubernetes pod sees until the cluster's
|
|
15
|
+
network policy admits it. That used to fail the attempt at once.
|
|
16
|
+
- The trino adapter now connects when it is created, so a wrong Trino
|
|
17
|
+
password fails at start-up instead of at the first read.
|
|
18
|
+
- Adapter authors: `StartupConformance` is a new conformance class every
|
|
19
|
+
adapter's integration suite runs.
|
|
20
|
+
|
|
21
|
+
**Upgrade**
|
|
22
|
+
|
|
23
|
+
1. Build your domain image `FROM` the `v0.11.0` engine image (or pin
|
|
24
|
+
`continuo-python-runtime==0.11.0` and your adapter's new version).
|
|
25
|
+
2. Bring-your-own adapter: subclass `StartupConformance` in your integration
|
|
26
|
+
suite and provide the `warehouse_address` and `adapter_from_env` fixtures.
|
|
27
|
+
|
|
28
|
+
Versions: runtime 0.11.0, engine contract 0.9.0, postgres adapter 0.3.2,
|
|
29
|
+
trino adapter 0.4.0, duckdb adapter 0.2.2.
|
|
30
|
+
|
|
31
|
+
### Fixed
|
|
32
|
+
|
|
33
|
+
- A failed python node's stored log names its failure. The harness logs every
|
|
34
|
+
runtime error at ERROR on stderr, with the traceback of the exception it
|
|
35
|
+
wraps, as well as printing it in the result block. Continuo cuts the result
|
|
36
|
+
block out of the pod log before storing it, so a failure only printed there,
|
|
37
|
+
such as `LoadError: adapter construction failed: … Connection refused`, was
|
|
38
|
+
missing from the stored log.
|
|
39
|
+
- Every adapter's `from_env` waits out a warehouse that refuses connections
|
|
40
|
+
for the first few seconds. A Job pod's first connection can be refused until
|
|
41
|
+
the cluster's NetworkPolicy engine (k3s's kube-router rejects) has admitted
|
|
42
|
+
the pod; that cost a task retry and a failed Job. A connection that no server
|
|
43
|
+
answered (every address refused, had no route or sat on an unreachable
|
|
44
|
+
network) is tried up to 5 times, 2 s apart, each retry logged. An
|
|
45
|
+
authentication failure, a timeout on any address and every other failure are
|
|
46
|
+
raised at once.
|
|
47
|
+
- postgres: around `psycopg2.connect`.
|
|
48
|
+
- duckdb: around the catalog attach.
|
|
49
|
+
- trino: `from_env` now sends `SELECT 1`, since the trino DBAPI opens no
|
|
50
|
+
connection until the first request. A refusal is retried only if it failed
|
|
51
|
+
within 5 s: urllib3 reports only the last address it tried, so a slower
|
|
52
|
+
one hides an address that timed out. A failed authentication now fails
|
|
53
|
+
`from_env` (a harness `LoadError`) rather than the first read or write.
|
|
54
|
+
|
|
55
|
+
### Added
|
|
56
|
+
|
|
57
|
+
- `StartupConformance` in `continuo_engine_contract.testing.conformance`: every
|
|
58
|
+
adapter's integration suite runs it. It puts the real engine behind a
|
|
59
|
+
loopback port that refuses connections for 3 s (`LateOpeningPort`), then
|
|
60
|
+
checks that `from_env` returns an adapter that answers `SELECT 1`. The new
|
|
61
|
+
fixtures are `warehouse_address` and `adapter_from_env`.
|
|
62
|
+
|
|
63
|
+
### Changed
|
|
64
|
+
|
|
65
|
+
- The python-node env contract in `docs/boundary-contract.md` lists
|
|
66
|
+
`TASK_ATTEMPT`, the 0-based number of the task's attempt, which the executor
|
|
67
|
+
sets on every production Job. The harness does not read it; a node script
|
|
68
|
+
may. Documentation only: no package changes.
|
|
69
|
+
- The engine images build on `python:3.14.8-slim` (was `3.14.2`), and
|
|
70
|
+
`.python-version` and the `template/` Dockerfile follow.
|
|
71
|
+
- `pyarrow` is pinned at `26.0.0` (was `25.0.1`) in the runtime, the engine
|
|
72
|
+
contract's `testing` extra and the three adapters, so they keep resolving
|
|
73
|
+
together.
|
|
74
|
+
- Dependency pins move together across the workspace: `ruff` 0.17.1, `mypy`
|
|
75
|
+
2.4.0, `boto3` 1.43.114, `sqlglot` 30.23.0 (runtime and engine contract),
|
|
76
|
+
`trino` 0.340.0 and `psycopg2-binary` 2.9.13. `urllib3` moves to 2.8.0 in
|
|
77
|
+
`uv.lock` (CVE-2026-97687, CVE-2026-97689).
|
|
78
|
+
|
|
79
|
+
## [0.10.0] - 2026-10-22
|
|
80
|
+
|
|
81
|
+
### Added
|
|
82
|
+
|
|
83
|
+
- Contracts may set `timeout_minutes` on any node: an integer from 1 to 10,080
|
|
84
|
+
that is the node's task timeout. Other values are refused as a contract
|
|
85
|
+
error naming the node. The merged wire entry carries the key only when set,
|
|
86
|
+
and it is the one wire key outside `config_hash`: changing it alone leaves
|
|
87
|
+
`config_hash` and `content_hash` unchanged. A node that does not set it has
|
|
88
|
+
the wire entry it always had.
|
|
89
|
+
|
|
90
|
+
### Changed
|
|
91
|
+
|
|
92
|
+
- The "mode_type not allowed for kind" contract error now quotes the offending
|
|
93
|
+
`mode_type` and `kind` (`repr`), matching the sibling "must be one of" error,
|
|
94
|
+
so an empty or whitespace value is legible. Docstring fixes only alongside:
|
|
95
|
+
the postgres `ensure_table` port-signature reference (0.6.0 → 0.8.0) and the
|
|
96
|
+
`ensure_unique_key` message-order claim (pyarrow grouping order is not
|
|
97
|
+
contractual).
|
|
98
|
+
- The trino adapter (0.3.1) and duckdb adapter (0.2.1) release with their
|
|
99
|
+
`continuo-engine-contract` pin moved to 0.8.1; no behavior change.
|
|
100
|
+
|
|
8
101
|
## [0.9.0] - 2026-10-11
|
|
9
102
|
|
|
10
103
|
### Added
|
|
@@ -28,15 +28,17 @@ merged PRs before adding anything new.
|
|
|
28
28
|
conformance suite for engine adapter authors. Its dependencies are installed
|
|
29
29
|
with `continuo-engine-contract[testing]`.
|
|
30
30
|
|
|
31
|
-
- Every adapter MUST satisfy the engine contract and pass
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
`
|
|
35
|
-
|
|
36
|
-
|
|
31
|
+
- Every adapter MUST satisfy the engine contract and pass `StartupConformance`
|
|
32
|
+
(`from_env` waits out a warehouse that refuses connections at first) and the
|
|
33
|
+
shared conformance classes for every capability it declares:
|
|
34
|
+
`IntrospectionConformance` for `TableIntrospection`, `AppendConformance` for
|
|
35
|
+
`AppendCapable`, and `UpsertConformance` for `UpsertCapable`. Append and
|
|
36
|
+
upsert adapters must also run the introspection class. An adapter need not
|
|
37
|
+
declare capabilities its engine cannot support.
|
|
37
38
|
- Each adapter's integration suite MUST subclass the applicable classes,
|
|
38
|
-
supply connected `adapter` and temporary `schema` fixtures
|
|
39
|
-
|
|
39
|
+
supply connected `adapter` and temporary `schema` fixtures (and, for
|
|
40
|
+
`StartupConformance`, `warehouse_address` and `adapter_from_env`), and run
|
|
41
|
+
them against its real warehouse in CI. In-memory reference tests alone do not
|
|
40
42
|
establish an adapter's conformance. Do not skip or weaken shared checks to
|
|
41
43
|
accommodate an adapter that violates the contract.
|
|
42
44
|
- Every new or changed engine-independent contract requirement, including
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
FROM python:3.14.
|
|
1
|
+
FROM python:3.14.8-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.
|
|
1
|
+
FROM python:3.14.8-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.
|
|
1
|
+
FROM python:3.14.8-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,3 +1,23 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: continuo-python-runtime
|
|
3
|
+
Version: 0.11.0
|
|
4
|
+
Summary: Runtime harness, contract tooling, and CI lint for Continuo python nodes.
|
|
5
|
+
Author: Simone Carolini
|
|
6
|
+
Maintainer: Simone Carolini
|
|
7
|
+
License-Expression: Apache-2.0
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
License-File: NOTICE
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
13
|
+
Requires-Python: >=3.14
|
|
14
|
+
Requires-Dist: boto3==1.43.114
|
|
15
|
+
Requires-Dist: continuo-engine-contract==0.9.0
|
|
16
|
+
Requires-Dist: pyarrow==26.0.0
|
|
17
|
+
Requires-Dist: pyyaml==6.0.3
|
|
18
|
+
Requires-Dist: sqlglot==30.23.0
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
1
21
|
# Continuo Python Runtime
|
|
2
22
|
|
|
3
23
|
[](https://github.com/carolsimone/continuo-python-runtime/actions/workflows/ci.yml)
|
|
@@ -176,6 +196,15 @@ produced, never how it is checked or written.
|
|
|
176
196
|
script reads with `os.environ[...]`. The contract never holds a secret value
|
|
177
197
|
or env var name; the Secret itself is created in the cluster by the operator.
|
|
178
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
|
+
|
|
179
208
|
## Write modes
|
|
180
209
|
|
|
181
210
|
`mode_type:` chooses how the harness writes a node's table on each run:
|
|
@@ -311,7 +340,10 @@ declared `output_columns` on the table `run()` returned, in this order:
|
|
|
311
340
|
Every runtime failure is one of five `HarnessError` subclasses
|
|
312
341
|
(`continuo_python_runtime/errors.py`). The sentinel result block's message
|
|
313
342
|
is prefixed `<ErrorClass>: ` so failures can be triaged without parsing free
|
|
314
|
-
text.
|
|
343
|
+
text. The harness also logs the same message at ERROR on stderr, with the
|
|
344
|
+
traceback of the exception it wraps (a driver error, the script's own
|
|
345
|
+
exception), so the stored log of a failed run names its failure: Continuo cuts
|
|
346
|
+
the result block out of the log before storing it.
|
|
315
347
|
|
|
316
348
|
| Class | Meaning | Typical fix target |
|
|
317
349
|
| --- | --- | --- |
|
|
@@ -327,11 +359,11 @@ A domain repo picks its warehouse engine by which base image it builds
|
|
|
327
359
|
`FROM`:
|
|
328
360
|
|
|
329
361
|
```dockerfile
|
|
330
|
-
FROM ghcr.io/carolsimone/continuo-python-runtime-postgres:v0.
|
|
362
|
+
FROM ghcr.io/carolsimone/continuo-python-runtime-postgres:v0.11.0
|
|
331
363
|
# or
|
|
332
|
-
FROM ghcr.io/carolsimone/continuo-python-runtime-trino:v0.
|
|
364
|
+
FROM ghcr.io/carolsimone/continuo-python-runtime-trino:v0.11.0
|
|
333
365
|
# or
|
|
334
|
-
FROM ghcr.io/carolsimone/continuo-python-runtime-duckdb:v0.
|
|
366
|
+
FROM ghcr.io/carolsimone/continuo-python-runtime-duckdb:v0.11.0
|
|
335
367
|
```
|
|
336
368
|
|
|
337
369
|
The engine is part of the image **name**; the tag is the bare version, so
|
|
@@ -1,23 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.5
|
|
2
|
-
Name: continuo-python-runtime
|
|
3
|
-
Version: 0.9.0
|
|
4
|
-
Summary: Runtime harness, contract tooling, and CI lint for Continuo python nodes.
|
|
5
|
-
Author: Simone Carolini
|
|
6
|
-
Maintainer: Simone Carolini
|
|
7
|
-
License-Expression: Apache-2.0
|
|
8
|
-
License-File: LICENSE
|
|
9
|
-
License-File: NOTICE
|
|
10
|
-
Classifier: Development Status :: 4 - Beta
|
|
11
|
-
Classifier: Intended Audience :: Developers
|
|
12
|
-
Classifier: Programming Language :: Python :: 3.14
|
|
13
|
-
Requires-Python: >=3.14
|
|
14
|
-
Requires-Dist: boto3==1.43.85
|
|
15
|
-
Requires-Dist: continuo-engine-contract==0.8.0
|
|
16
|
-
Requires-Dist: pyarrow==25.0.1
|
|
17
|
-
Requires-Dist: pyyaml==6.0.3
|
|
18
|
-
Requires-Dist: sqlglot==30.17.0
|
|
19
|
-
Description-Content-Type: text/markdown
|
|
20
|
-
|
|
21
1
|
# Continuo Python Runtime
|
|
22
2
|
|
|
23
3
|
[](https://github.com/carolsimone/continuo-python-runtime/actions/workflows/ci.yml)
|
|
@@ -196,6 +176,15 @@ produced, never how it is checked or written.
|
|
|
196
176
|
script reads with `os.environ[...]`. The contract never holds a secret value
|
|
197
177
|
or env var name; the Secret itself is created in the cluster by the operator.
|
|
198
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
|
+
|
|
199
188
|
## Write modes
|
|
200
189
|
|
|
201
190
|
`mode_type:` chooses how the harness writes a node's table on each run:
|
|
@@ -331,7 +320,10 @@ declared `output_columns` on the table `run()` returned, in this order:
|
|
|
331
320
|
Every runtime failure is one of five `HarnessError` subclasses
|
|
332
321
|
(`continuo_python_runtime/errors.py`). The sentinel result block's message
|
|
333
322
|
is prefixed `<ErrorClass>: ` so failures can be triaged without parsing free
|
|
334
|
-
text.
|
|
323
|
+
text. The harness also logs the same message at ERROR on stderr, with the
|
|
324
|
+
traceback of the exception it wraps (a driver error, the script's own
|
|
325
|
+
exception), so the stored log of a failed run names its failure: Continuo cuts
|
|
326
|
+
the result block out of the log before storing it.
|
|
335
327
|
|
|
336
328
|
| Class | Meaning | Typical fix target |
|
|
337
329
|
| --- | --- | --- |
|
|
@@ -347,11 +339,11 @@ A domain repo picks its warehouse engine by which base image it builds
|
|
|
347
339
|
`FROM`:
|
|
348
340
|
|
|
349
341
|
```dockerfile
|
|
350
|
-
FROM ghcr.io/carolsimone/continuo-python-runtime-postgres:v0.
|
|
342
|
+
FROM ghcr.io/carolsimone/continuo-python-runtime-postgres:v0.11.0
|
|
351
343
|
# or
|
|
352
|
-
FROM ghcr.io/carolsimone/continuo-python-runtime-trino:v0.
|
|
344
|
+
FROM ghcr.io/carolsimone/continuo-python-runtime-trino:v0.11.0
|
|
353
345
|
# or
|
|
354
|
-
FROM ghcr.io/carolsimone/continuo-python-runtime-duckdb:v0.
|
|
346
|
+
FROM ghcr.io/carolsimone/continuo-python-runtime-duckdb:v0.11.0
|
|
355
347
|
```
|
|
356
348
|
|
|
357
349
|
The engine is part of the image **name**; the tag is the bare version, so
|
|
@@ -47,9 +47,18 @@ contains a line break, when `PGPASSWORD` is set in the environment (libpq would
|
|
|
47
47
|
prefer it to any passfile), or when no temp file can be created. The `aws`
|
|
48
48
|
extension is only loaded for the AWS credential-chain S3 path.
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
The attach is retried, up to 5 attempts in all, for two failures only:
|
|
51
|
+
|
|
52
|
+
- a first attach to a brand-new catalog from several Jobs at once can race on
|
|
53
|
+
DuckLake's metadata creation;
|
|
54
|
+
- a catalog connection that no server answered (every address of the host
|
|
55
|
+
refused, had no route or sat on an unreachable network) is tried again 2 s
|
|
56
|
+
later, each retry logged: a Job pod's first connection can be refused for a
|
|
57
|
+
few seconds after the pod starts, until the cluster's NetworkPolicy engine
|
|
58
|
+
has admitted the pod.
|
|
59
|
+
|
|
60
|
+
Every other connection error, an authentication failure or a connect timeout
|
|
61
|
+
on any address among them, surfaces immediately.
|
|
53
62
|
|
|
54
63
|
## Physical layout (`config`)
|
|
55
64
|
|
|
@@ -13,6 +13,7 @@ from __future__ import annotations
|
|
|
13
13
|
|
|
14
14
|
import contextlib
|
|
15
15
|
import logging
|
|
16
|
+
import re
|
|
16
17
|
import time
|
|
17
18
|
import uuid
|
|
18
19
|
from collections.abc import Callable, Iterator, Sequence
|
|
@@ -52,13 +53,30 @@ logger = logging.getLogger("continuo_duckdb_adapter")
|
|
|
52
53
|
|
|
53
54
|
CATALOG_ALIAS = "lake"
|
|
54
55
|
|
|
56
|
+
# An ATTACH is retried on a fresh connection, up to _ATTACH_ATTEMPTS in all, for
|
|
57
|
+
# two failures only; every other failure is raised at once.
|
|
58
|
+
#
|
|
55
59
|
# DuckLake creates its metadata tables on the first ATTACH of a fresh catalog
|
|
56
60
|
# and does not guard that against a concurrent first ATTACH (several Jobs of one
|
|
57
61
|
# run starting together): all but one fail on the duplicate CREATE. The loser
|
|
58
|
-
# retries
|
|
59
|
-
#
|
|
62
|
+
# retries and then finds the catalog initialised.
|
|
63
|
+
#
|
|
64
|
+
# A Job pod's first connection to the catalog can be refused for a few seconds
|
|
65
|
+
# after the pod starts, until the cluster's NetworkPolicy engine has admitted
|
|
66
|
+
# the pod: an engine that rejects (k3s's kube-router) refuses at once instead
|
|
67
|
+
# of dropping the packets. So a connection no server answered, refused or
|
|
68
|
+
# unroutable, is retried. A connect timeout is not: an engine that drops
|
|
69
|
+
# packets only delays the first connection, which TCP's own retransmissions
|
|
70
|
+
# carry through, so a timeout means the catalog stayed silent for the whole
|
|
71
|
+
# timeout and every retry would wait that long again.
|
|
60
72
|
_ATTACH_ATTEMPTS = 5
|
|
61
73
|
_ATTACH_BACKOFF_SECONDS = 0.2
|
|
74
|
+
_UNREACHABLE_RETRY_SECONDS = 2.0
|
|
75
|
+
# The ATTACH error carries libpq's text for the catalog connection: one line per
|
|
76
|
+
# address it tried, the reason after "failed: " (libpq 14 and later) or after
|
|
77
|
+
# "could not connect to server: " (libpq 13 and earlier).
|
|
78
|
+
_ADDRESS_FAILURE = re.compile(r"(?:failed|could not connect to server): ([^\n]*)")
|
|
79
|
+
_UNREACHABLE_REASONS = frozenset({"Connection refused", "No route to host", "Network is unreachable"})
|
|
62
80
|
|
|
63
81
|
|
|
64
82
|
def _is_first_attach_race(exc: duckdb.Error) -> bool:
|
|
@@ -68,6 +86,25 @@ def _is_first_attach_race(exc: duckdb.Error) -> bool:
|
|
|
68
86
|
)
|
|
69
87
|
|
|
70
88
|
|
|
89
|
+
def _is_catalog_unreachable(exc: duckdb.Error) -> bool:
|
|
90
|
+
"""Whether *exc* says that every address of the catalog host refused or could not be routed to.
|
|
91
|
+
|
|
92
|
+
A host name can have several addresses, and libpq reports each one it
|
|
93
|
+
tried. One that timed out means a retry would wait out its timeout again;
|
|
94
|
+
a FATAL line came from a server that answered (a failed authentication, an
|
|
95
|
+
unknown database). libpq 13 and earlier print a timeout as a bare
|
|
96
|
+
"timeout expired" line, with no prefix.
|
|
97
|
+
"""
|
|
98
|
+
message = str(exc)
|
|
99
|
+
reasons = [reason.strip() for reason in _ADDRESS_FAILURE.findall(message)]
|
|
100
|
+
return (
|
|
101
|
+
bool(reasons)
|
|
102
|
+
and all(reason in _UNREACHABLE_REASONS for reason in reasons)
|
|
103
|
+
and "FATAL:" not in message
|
|
104
|
+
and "timeout expired" not in message
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
|
|
71
108
|
def _load_extension(con: "duckdb.DuckDBPyConnection", name: str) -> None:
|
|
72
109
|
try:
|
|
73
110
|
con.execute(load_extension(name))
|
|
@@ -135,6 +172,14 @@ class DuckLakeSession(LakeGateway):
|
|
|
135
172
|
logger.info("first attach of a fresh catalog raced (attempt %d); retrying", attempt)
|
|
136
173
|
sleep(_ATTACH_BACKOFF_SECONDS * attempt)
|
|
137
174
|
continue
|
|
175
|
+
if attempt < _ATTACH_ATTEMPTS and _is_catalog_unreachable(exc):
|
|
176
|
+
logger.warning(
|
|
177
|
+
"catalog not reachable yet (attempt %d of %d), retrying in %.0f s: %s",
|
|
178
|
+
attempt, _ATTACH_ATTEMPTS, _UNREACHABLE_RETRY_SECONDS,
|
|
179
|
+
_redact(exc, secrets).strip(),
|
|
180
|
+
)
|
|
181
|
+
sleep(_UNREACHABLE_RETRY_SECONDS)
|
|
182
|
+
continue
|
|
138
183
|
raise _translate(exc, secrets) from None
|
|
139
184
|
return cls(con, DdlRenderer(CATALOG_ALIAS), secrets=secrets, passfile=passfile)
|
|
140
185
|
except BaseException:
|
{continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/adapters/duckdb/pyproject.toml
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
[project]
|
|
2
|
-
version = "0.2.
|
|
2
|
+
version = "0.2.2"
|
|
3
3
|
name = "continuo-duckdb-adapter"
|
|
4
4
|
description = "DuckDB (DuckLake) warehouse adapter for Continuo: validation and python-node runtime."
|
|
5
5
|
authors = [{ name = "Simone Carolini" }]
|
|
@@ -10,9 +10,9 @@ dependencies = [
|
|
|
10
10
|
# Pinned exactly for the same reason as the postgres adapter: the
|
|
11
11
|
# [tool.uv.sources] redirect below is uv-only and never reaches built
|
|
12
12
|
# metadata, and this adapter subclasses the contract's port directly.
|
|
13
|
-
"continuo-engine-contract==0.
|
|
13
|
+
"continuo-engine-contract==0.9.0",
|
|
14
14
|
"duckdb==1.5.6",
|
|
15
|
-
"pyarrow==
|
|
15
|
+
"pyarrow==26.0.0",
|
|
16
16
|
]
|
|
17
17
|
classifiers = [
|
|
18
18
|
"Development Status :: 4 - Beta",
|
|
@@ -25,16 +25,16 @@ duckdb = "continuo_duckdb_adapter.adapter:DuckDBAdapter"
|
|
|
25
25
|
|
|
26
26
|
[dependency-groups]
|
|
27
27
|
dev = [
|
|
28
|
-
"ruff==0.
|
|
29
|
-
"mypy==2.
|
|
28
|
+
"ruff==0.17.1",
|
|
29
|
+
"mypy==2.4.0",
|
|
30
30
|
]
|
|
31
31
|
test = [
|
|
32
32
|
"pytest==9.1.1",
|
|
33
33
|
"pytest-cov==7.1.0",
|
|
34
34
|
# Integration tests read the DuckLake catalog (postgres) and the data
|
|
35
35
|
# files (MinIO) directly to prove layout was really applied.
|
|
36
|
-
"psycopg2-binary==2.9.
|
|
37
|
-
"boto3==1.43.
|
|
36
|
+
"psycopg2-binary==2.9.13",
|
|
37
|
+
"boto3==1.43.114",
|
|
38
38
|
]
|
|
39
39
|
|
|
40
40
|
[build-system]
|
|
@@ -298,12 +298,75 @@ def test_a_persistent_init_race_surfaces_after_bounded_attempts(connect_with):
|
|
|
298
298
|
assert all(c.closed for c in script.connections)
|
|
299
299
|
|
|
300
300
|
|
|
301
|
+
# --- a catalog the pod cannot reach yet --------------------------------------
|
|
302
|
+
#
|
|
303
|
+
# A Job pod's first connection can be refused for a few seconds after the pod
|
|
304
|
+
# starts, while the cluster's NetworkPolicy engine has not yet admitted it. The
|
|
305
|
+
# ATTACH reports libpq's text for the catalog connection; these are DuckDB's
|
|
306
|
+
# messages as it prints them.
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def _catalog_unreachable(reason: str) -> duckdb.IOException:
|
|
310
|
+
conninfo = "host=catalog port=5432 dbname=catalog user=continuo"
|
|
311
|
+
return duckdb.IOException(
|
|
312
|
+
f'IO Error: Failed to attach DuckLake MetaData "__ducklake_metadata_lake" at path + '
|
|
313
|
+
f'"postgres:{conninfo}"Unable to connect to Postgres at "{conninfo}": connection to '
|
|
314
|
+
f'server at "catalog" (10.43.0.7), port 5432 failed: {reason}\n'
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
REFUSED = "Connection refused\n\tIs the server running on that host and accepting TCP/IP connections?"
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
@pytest.mark.parametrize("reason", [
|
|
322
|
+
REFUSED, "No route to host", "Network is unreachable",
|
|
323
|
+
f'{REFUSED}\nconnection to server at "catalog" (10.43.0.8), port 5432 failed: {REFUSED}',
|
|
324
|
+
], ids=["refused", "no-route", "net-unreachable", "refused-twice"])
|
|
325
|
+
def test_an_unreachable_catalog_is_retried_on_a_fresh_connection(connect_with, reason):
|
|
326
|
+
script, sleeps, connect = connect_with([_catalog_unreachable(reason)] * 2 + [None])
|
|
327
|
+
session = connect()
|
|
328
|
+
assert len(script.connections) == 3
|
|
329
|
+
assert [c.closed for c in script.connections] == [True, True, False]
|
|
330
|
+
assert sleeps == [2.0, 2.0]
|
|
331
|
+
session.close()
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def test_a_catalog_that_stays_unreachable_fails_after_bounded_attempts(connect_with):
|
|
335
|
+
from continuo_duckdb_adapter.infrastructure import session as session_module
|
|
336
|
+
|
|
337
|
+
script, sleeps, connect = connect_with([_catalog_unreachable(REFUSED)] * 50)
|
|
338
|
+
with pytest.raises(duckdb.IOException, match="Connection refused"):
|
|
339
|
+
connect()
|
|
340
|
+
assert len(script.connections) == session_module._ATTACH_ATTEMPTS == 5
|
|
341
|
+
assert sleeps == [2.0, 2.0, 2.0, 2.0]
|
|
342
|
+
assert all(c.closed for c in script.connections)
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def test_each_catalog_retry_is_logged_redacted(connect_with, caplog):
|
|
346
|
+
leaking = duckdb.IOException(f"{_catalog_unreachable(REFUSED)} password={PASSWORD}")
|
|
347
|
+
_, _, connect = connect_with([leaking, leaking, None])
|
|
348
|
+
caplog.set_level(logging.INFO, logger="continuo_duckdb_adapter")
|
|
349
|
+
connect().close()
|
|
350
|
+
warnings = [r for r in caplog.records if r.levelno == logging.WARNING]
|
|
351
|
+
assert len(warnings) == 2
|
|
352
|
+
assert all("port 5432 failed: Connection refused" in r.getMessage() for r in warnings)
|
|
353
|
+
assert PASSWORD not in caplog.text
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
# A catalog host with two addresses, one refused and one silent: the ATTACH
|
|
357
|
+
# error carries both of libpq's lines.
|
|
358
|
+
REFUSED_THEN_TIMED_OUT = (
|
|
359
|
+
f'{REFUSED}\nconnection to server at "catalog" (10.43.0.8), port 5432 failed: timeout expired'
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
|
|
301
363
|
@pytest.mark.parametrize("error", [
|
|
302
|
-
|
|
303
|
-
|
|
364
|
+
_catalog_unreachable('FATAL: password authentication failed for user "continuo"'),
|
|
365
|
+
_catalog_unreachable("timeout expired"),
|
|
366
|
+
_catalog_unreachable(REFUSED_THEN_TIMED_OUT),
|
|
304
367
|
duckdb.Error("Failed to initialize DuckLake: disk full"),
|
|
305
368
|
duckdb.CatalogException("something else"),
|
|
306
|
-
])
|
|
369
|
+
], ids=["auth-failed", "timed-out", "refused-then-timed-out", "init-failed", "catalog-error"])
|
|
307
370
|
def test_every_other_attach_error_is_not_retried(connect_with, error):
|
|
308
371
|
script, sleeps, connect = connect_with([error])
|
|
309
372
|
with pytest.raises(type(error)):
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""The engine-contract conformance suite against a real DuckLake (tests/smoke/duckdb-stack).
|
|
2
|
+
|
|
3
|
+
The `adapter`, `schema` and `lake_env` fixtures come from this directory's
|
|
4
|
+
conftest.py. The first connection ``from_env`` opens is the catalog's, so the
|
|
5
|
+
startup case puts the catalog behind the refusing port.
|
|
6
|
+
Run the stack first:
|
|
7
|
+
docker compose -f tests/smoke/duckdb-stack/docker-compose.yml up -d --wait
|
|
8
|
+
"""
|
|
9
|
+
import pytest
|
|
10
|
+
|
|
11
|
+
from continuo_engine_contract.testing.conformance import (
|
|
12
|
+
AppendConformance,
|
|
13
|
+
IntrospectionConformance,
|
|
14
|
+
StartupConformance,
|
|
15
|
+
UpsertConformance,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
pytestmark = pytest.mark.integration
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@pytest.fixture
|
|
22
|
+
def warehouse_address(lake_env):
|
|
23
|
+
return "127.0.0.1", int(lake_env["DUCKDB_CATALOG_PORT"])
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@pytest.fixture
|
|
27
|
+
def adapter_from_env(lake_env, monkeypatch):
|
|
28
|
+
from continuo_duckdb_adapter.adapter import DuckDBAdapter
|
|
29
|
+
|
|
30
|
+
def build(host, port):
|
|
31
|
+
for key, value in {**lake_env, "DUCKDB_CATALOG_HOST": host, "DUCKDB_CATALOG_PORT": str(port)}.items():
|
|
32
|
+
monkeypatch.setenv(key, value)
|
|
33
|
+
return DuckDBAdapter.from_env()
|
|
34
|
+
|
|
35
|
+
return build
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class TestDuckDBStartup(StartupConformance):
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class TestDuckDBIntrospection(IntrospectionConformance):
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class TestDuckDBAppend(AppendConformance):
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class TestDuckDBUpsert(UpsertConformance):
|
|
51
|
+
pass
|
|
@@ -20,14 +20,26 @@ def test_ensure_schema_creates_a_schema_visible_to_another_connection(adapter, a
|
|
|
20
20
|
assert other.fetch(f'SELECT count(*) AS n FROM "{schema}"."t"').to_pylist() == [{"n": 0}]
|
|
21
21
|
|
|
22
22
|
|
|
23
|
-
def
|
|
24
|
-
with
|
|
25
|
-
|
|
23
|
+
def _connect(lake_env, sleeps, **overrides):
|
|
24
|
+
"""DuckDBAdapter.from_env's session, with its retry waits recorded instead of slept."""
|
|
25
|
+
from continuo_duckdb_adapter.infrastructure.session import DuckLakeSession
|
|
26
|
+
from continuo_duckdb_adapter.infrastructure.settings import DuckLakeSettings
|
|
27
|
+
|
|
28
|
+
return DuckLakeSession.connect(DuckLakeSettings.from_env({**lake_env, **overrides}), sleep=sleeps.append)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_unreachable_catalog_is_retried_then_raises_a_clear_error(lake_env):
|
|
32
|
+
sleeps: list[float] = []
|
|
33
|
+
with pytest.raises(duckdb.Error, match="Connection refused"):
|
|
34
|
+
_connect(lake_env, sleeps, DUCKDB_CATALOG_PORT="1")
|
|
35
|
+
assert sleeps == [2.0, 2.0, 2.0, 2.0]
|
|
26
36
|
|
|
27
37
|
|
|
28
|
-
def
|
|
29
|
-
|
|
30
|
-
|
|
38
|
+
def test_wrong_catalog_password_raises_a_clear_error_at_once(lake_env):
|
|
39
|
+
sleeps: list[float] = []
|
|
40
|
+
with pytest.raises(duckdb.Error, match="password authentication failed"):
|
|
41
|
+
_connect(lake_env, sleeps, DUCKDB_CATALOG_PASSWORD="definitely-wrong")
|
|
42
|
+
assert sleeps == []
|
|
31
43
|
|
|
32
44
|
|
|
33
45
|
def test_failed_attach_does_not_echo_the_password(adapter_factory):
|
{continuo_python_runtime-0.9.0 → continuo_python_runtime-0.11.0}/adapters/postgres/README.md
RENAMED
|
@@ -12,6 +12,13 @@ registers itself under entry-point group `continuo_engine.adapters` as
|
|
|
12
12
|
Connection env: `POSTGRES_HOST`, `POSTGRES_DB`, `POSTGRES_USER` (required);
|
|
13
13
|
`POSTGRES_PORT` (default 5432), `POSTGRES_PASSWORD` (default empty).
|
|
14
14
|
|
|
15
|
+
`from_env` tries a connection that no server answered (every address of the
|
|
16
|
+
host refused, had no route or sat on an unreachable network) up to 5 times,
|
|
17
|
+
2 s apart, logging each retry: a Job pod's first connection can be refused
|
|
18
|
+
for a few seconds after the pod starts, until the cluster's NetworkPolicy
|
|
19
|
+
engine has admitted the pod. Every other failure, an authentication failure or
|
|
20
|
+
a connect timeout on any address among them, is raised at once.
|
|
21
|
+
|
|
15
22
|
The connection runs with `autocommit = True`; every method owns its
|
|
16
23
|
transaction explicitly. `load()` and `ensure_table()` open an explicit
|
|
17
24
|
`BEGIN` around their statements and `COMMIT`/`ROLLBACK` themselves, so a
|