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.
Files changed (209) hide show
  1. continuo_python_runtime-0.10.0/.python-version +1 -0
  2. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/CHANGELOG.md +106 -0
  3. continuo_python_runtime-0.10.0/CLAUDE.md +67 -0
  4. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/Dockerfile.duckdb +1 -1
  5. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/Dockerfile.postgres +1 -1
  6. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/Dockerfile.trino +1 -1
  7. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/PKG-INFO +86 -13
  8. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/README.md +84 -11
  9. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/duckdb/continuo_duckdb_adapter/adapter.py +3 -3
  10. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/duckdb/continuo_duckdb_adapter/application/ports.py +15 -3
  11. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/duckdb/continuo_duckdb_adapter/application/warehouse.py +64 -7
  12. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/duckdb/continuo_duckdb_adapter/domain/columns.py +1 -1
  13. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/duckdb/continuo_duckdb_adapter/domain/layout.py +2 -2
  14. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/duckdb/continuo_duckdb_adapter/infrastructure/ddl.py +44 -4
  15. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/duckdb/continuo_duckdb_adapter/infrastructure/extensions.py +2 -2
  16. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/duckdb/continuo_duckdb_adapter/infrastructure/session.py +32 -8
  17. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/duckdb/pyproject.toml +2 -2
  18. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/duckdb/tests/conftest.py +8 -0
  19. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/duckdb/tests/test_adapter_duckdb_runtime.py +63 -0
  20. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/duckdb/tests/test_infra_duckdb_session.py +54 -0
  21. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/duckdb/tests/test_infra_duckdb_settings_ddl.py +34 -0
  22. continuo_python_runtime-0.10.0/adapters/duckdb/tests/test_integration_duckdb_conformance.py +27 -0
  23. continuo_python_runtime-0.10.0/adapters/duckdb/tests/test_integration_duckdb_nullability_probe.py +35 -0
  24. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/duckdb/tests/test_integration_duckdb_runtime.py +78 -0
  25. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/postgres/continuo_postgres_adapter/adapter.py +181 -41
  26. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/postgres/pyproject.toml +2 -2
  27. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/postgres/tests/test_adapter_runtime_postgres.py +86 -0
  28. continuo_python_runtime-0.10.0/adapters/postgres/tests/test_integration_postgres_conformance.py +103 -0
  29. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/postgres/tests/test_integration_runtime_postgres.py +108 -0
  30. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/trino/continuo_trino_adapter/adapter.py +183 -24
  31. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/trino/pyproject.toml +2 -2
  32. continuo_python_runtime-0.10.0/adapters/trino/tests/test_adapter_trino_writes.py +231 -0
  33. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/trino/tests/test_integration_runtime_trino.py +86 -0
  34. continuo_python_runtime-0.10.0/adapters/trino/tests/test_integration_trino_conformance.py +77 -0
  35. continuo_python_runtime-0.10.0/adapters/trino/tests/test_integration_trino_merge_probe.py +98 -0
  36. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/continuo_python_runtime/contract/kinds.py +12 -5
  37. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/continuo_python_runtime/contract/loader.py +98 -1
  38. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/continuo_python_runtime/contract/merge.py +15 -2
  39. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/continuo_python_runtime/contract/model.py +8 -0
  40. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/continuo_python_runtime/harness.py +100 -4
  41. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/continuo_python_runtime/hashing.py +8 -3
  42. continuo_python_runtime-0.10.0/continuo_python_runtime/shape.py +95 -0
  43. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/contract/README.md +10 -2
  44. continuo_python_runtime-0.10.0/contract/continuo_engine_contract/keys.py +87 -0
  45. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/contract/continuo_engine_contract/port.py +98 -2
  46. continuo_python_runtime-0.10.0/contract/continuo_engine_contract/testing/__init__.py +4 -0
  47. continuo_python_runtime-0.10.0/contract/continuo_engine_contract/testing/conformance.py +306 -0
  48. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/contract/pyproject.toml +7 -1
  49. continuo_python_runtime-0.10.0/contract/tests/test_conformance_suite.py +168 -0
  50. continuo_python_runtime-0.10.0/contract/tests/test_contract_keys.py +95 -0
  51. continuo_python_runtime-0.10.0/contract/tests/test_contract_packaging.py +26 -0
  52. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/contract/tests/test_port.py +122 -2
  53. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/docs/boundary-contract.md +57 -14
  54. continuo_python_runtime-0.10.0/image-requirements-duckdb.txt +3 -0
  55. continuo_python_runtime-0.10.0/image-requirements-postgres.txt +3 -0
  56. continuo_python_runtime-0.10.0/image-requirements-trino.txt +3 -0
  57. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/pyproject.toml +2 -2
  58. continuo_python_runtime-0.10.0/template/.github/workflows/release.yml +188 -0
  59. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/template/Dockerfile +1 -1
  60. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/template/Dockerfile.pip +1 -1
  61. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/template/README.md +30 -11
  62. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/conftest.py +48 -0
  63. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/contract/test_kinds.py +16 -1
  64. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/contract/test_loader.py +170 -0
  65. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/contract/test_merge.py +111 -2
  66. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/contract/test_model.py +32 -1
  67. continuo_python_runtime-0.10.0/tests/contract/test_wire_contract_write_modes.py +53 -0
  68. continuo_python_runtime-0.10.0/tests/fixtures/wire_contract_write_modes.yml +53 -0
  69. continuo_python_runtime-0.10.0/tests/test_harness_write_modes.py +259 -0
  70. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/test_hashing.py +7 -0
  71. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/test_kind_registries.py +5 -1
  72. continuo_python_runtime-0.10.0/tests/test_python_version_consistency.py +85 -0
  73. continuo_python_runtime-0.10.0/tests/test_shape.py +60 -0
  74. continuo_python_runtime-0.10.0/tests/test_template.py +248 -0
  75. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/uv.lock +17 -6
  76. continuo_python_runtime-0.8.0/CLAUDE.md +0 -37
  77. continuo_python_runtime-0.8.0/image-requirements-duckdb.txt +0 -3
  78. continuo_python_runtime-0.8.0/image-requirements-postgres.txt +0 -3
  79. continuo_python_runtime-0.8.0/image-requirements-trino.txt +0 -3
  80. continuo_python_runtime-0.8.0/template/.github/workflows/release.yml +0 -66
  81. continuo_python_runtime-0.8.0/tests/test_template.py +0 -82
  82. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/.claude/agents/ddd-architecture-auditor.md +0 -0
  83. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/.dockerignore +0 -0
  84. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/.github/CODEOWNERS +0 -0
  85. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  86. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  87. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  88. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  89. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/.github/dependabot.yml +0 -0
  90. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/.github/workflows/ci.yml +0 -0
  91. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/.github/workflows/dco.yml +0 -0
  92. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/.github/workflows/image-build.yml +0 -0
  93. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/.github/workflows/image-smoke.yml +0 -0
  94. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/.github/workflows/images.yml +0 -0
  95. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/.github/workflows/publish-pypi.yml +0 -0
  96. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/.github/workflows/release.yml +0 -0
  97. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/.github/workflows/security.yml +0 -0
  98. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/.github/workflows/verify-packages.yml +0 -0
  99. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/.gitignore +0 -0
  100. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/.gitleaks.toml +0 -0
  101. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/CODE_OF_CONDUCT.md +0 -0
  102. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/CONTRIBUTING.md +0 -0
  103. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/DCO +0 -0
  104. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/LICENSE +0 -0
  105. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/NOTICE +0 -0
  106. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/SECURITY.md +0 -0
  107. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/duckdb/README.md +0 -0
  108. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/duckdb/continuo_duckdb_adapter/__init__.py +0 -0
  109. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/duckdb/continuo_duckdb_adapter/application/__init__.py +0 -0
  110. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/duckdb/continuo_duckdb_adapter/domain/__init__.py +0 -0
  111. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/duckdb/continuo_duckdb_adapter/domain/identifiers.py +0 -0
  112. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/duckdb/continuo_duckdb_adapter/infrastructure/__init__.py +0 -0
  113. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/duckdb/continuo_duckdb_adapter/infrastructure/passfile.py +0 -0
  114. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/duckdb/continuo_duckdb_adapter/infrastructure/settings.py +0 -0
  115. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/duckdb/tests/__init__.py +0 -0
  116. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/duckdb/tests/test_adapter_duckdb_proxy.py +0 -0
  117. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/duckdb/tests/test_adapter_duckdb_validation.py +0 -0
  118. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/duckdb/tests/test_adapter_duckdb_wiring.py +0 -0
  119. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/duckdb/tests/test_domain_duckdb_columns.py +0 -0
  120. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/duckdb/tests/test_domain_duckdb_identifiers.py +0 -0
  121. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/duckdb/tests/test_domain_duckdb_layout.py +0 -0
  122. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/duckdb/tests/test_integration_duckdb_layout.py +0 -0
  123. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/duckdb/tests/test_integration_duckdb_stack.py +0 -0
  124. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/duckdb/tests/test_integration_duckdb_validation.py +0 -0
  125. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/postgres/README.md +0 -0
  126. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/postgres/continuo_postgres_adapter/__init__.py +0 -0
  127. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/postgres/tests/__init__.py +0 -0
  128. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/postgres/tests/conftest.py +0 -0
  129. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/postgres/tests/test_adapter_postgres_validation.py +0 -0
  130. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/postgres/tests/test_integration_postgres_validation.py +0 -0
  131. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/trino/README.md +0 -0
  132. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/trino/continuo_trino_adapter/__init__.py +0 -0
  133. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/trino/tests/__init__.py +0 -0
  134. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/trino/tests/test_adapter_runtime_trino.py +0 -0
  135. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/trino/tests/test_adapter_trino_validation.py +0 -0
  136. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/adapters/trino/tests/test_integration_trino_validation.py +0 -0
  137. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/continuo_python_runtime/__init__.py +0 -0
  138. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/continuo_python_runtime/cli.py +0 -0
  139. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/continuo_python_runtime/closure.py +0 -0
  140. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/continuo_python_runtime/conform.py +0 -0
  141. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/continuo_python_runtime/context.py +0 -0
  142. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/continuo_python_runtime/contract/__init__.py +0 -0
  143. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/continuo_python_runtime/contract/paths.py +0 -0
  144. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/continuo_python_runtime/contract/secret_ref.py +0 -0
  145. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/continuo_python_runtime/csv_loader.py +0 -0
  146. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/continuo_python_runtime/csv_readers/__init__.py +0 -0
  147. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/continuo_python_runtime/csv_readers/https.py +0 -0
  148. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/continuo_python_runtime/csv_readers/s3.py +0 -0
  149. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/continuo_python_runtime/csv_source.py +0 -0
  150. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/continuo_python_runtime/errors.py +0 -0
  151. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/continuo_python_runtime/lint.py +0 -0
  152. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/continuo_python_runtime/types.py +0 -0
  153. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/continuo_python_runtime/validation/__init__.py +0 -0
  154. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/continuo_python_runtime/validation/runner.py +0 -0
  155. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/continuo_python_runtime/validation/s3.py +0 -0
  156. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/contract/LICENSE +0 -0
  157. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/contract/NOTICE +0 -0
  158. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/contract/continuo_engine_contract/__init__.py +0 -0
  159. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/contract/continuo_engine_contract/config.py +0 -0
  160. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/contract/continuo_engine_contract/result.py +0 -0
  161. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/contract/continuo_engine_contract/sql.py +0 -0
  162. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/contract/continuo_engine_contract/types.py +0 -0
  163. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/contract/tests/__init__.py +0 -0
  164. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/contract/tests/test_config.py +0 -0
  165. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/contract/tests/test_contract_types.py +0 -0
  166. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/contract/tests/test_result.py +0 -0
  167. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/contract/tests/test_sql.py +0 -0
  168. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/scripts/apply_rehearsal_version.py +0 -0
  169. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/scripts/bake_duckdb_extensions.py +0 -0
  170. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/scripts/check_version_bumps.py +0 -0
  171. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/scripts/security-scan.sh +0 -0
  172. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/scripts/verify_installed_packages.py +0 -0
  173. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/template/contracts/example.yml +0 -0
  174. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/template/contracts/example_csv.yml +0 -0
  175. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/template/requirements.lock +0 -0
  176. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/template/scripts/example.py +0 -0
  177. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/__init__.py +0 -0
  178. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/contract/__init__.py +0 -0
  179. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/smoke/duckdb-stack/docker-compose.yml +0 -0
  180. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/smoke/node_smoke/contracts/smoke.yml +0 -0
  181. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/smoke/node_smoke/scripts/smoke.py +0 -0
  182. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/smoke/postgres-stack/docker-compose.yml +0 -0
  183. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/smoke/trino-stack/catalog/iceberg.properties +0 -0
  184. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/smoke/trino-stack/docker-compose.yml +0 -0
  185. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/test_adapter_naming.py +0 -0
  186. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/test_check_version_bumps_tags.py +0 -0
  187. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/test_ci_test_coverage.py +0 -0
  188. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/test_cli.py +0 -0
  189. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/test_closure.py +0 -0
  190. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/test_conform.py +0 -0
  191. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/test_context.py +0 -0
  192. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/test_contract_loader.py +0 -0
  193. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/test_contract_pin_consistency.py +0 -0
  194. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/test_csv_loader.py +0 -0
  195. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/test_csv_readers_https.py +0 -0
  196. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/test_csv_readers_integration.py +0 -0
  197. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/test_csv_source.py +0 -0
  198. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/test_errors.py +0 -0
  199. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/test_harness.py +0 -0
  200. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/test_image_requirements_sync.py +0 -0
  201. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/test_image_smoke_validation.py +0 -0
  202. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/test_lint.py +0 -0
  203. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/test_no_legacy_names.py +0 -0
  204. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/test_package.py +0 -0
  205. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/test_rehearsal_version.py +0 -0
  206. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/test_release_pipeline_structure.py +0 -0
  207. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/test_types.py +0 -0
  208. {continuo_python_runtime-0.8.0 → continuo_python_runtime-0.10.0}/tests/test_validation_runner.py +0 -0
  209. {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.8.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.7.3
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`) and validation (`ensure_schema` / `drop_schema` /
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 release
125
- webhook endpoint). `RELEASE_ENDPOINT` is the **base URL** of the Continuo
126
- API (no `/releases` suffix) — the workflow appends `/releases` itself.
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, or a VARCHAR/CHAR overflow. | The script's output shape, or the `output_columns` declaration. |
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.8.0
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-trino:v0.8.0
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` or
299
- `continuo-trino-adapter` package built from this repo's `adapters/postgres/`
300
- or `adapters/trino/` source (see the table above) — registered under the
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`) and validation (`ensure_schema` / `drop_schema` /
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 release
105
- webhook endpoint). `RELEASE_ENDPOINT` is the **base URL** of the Continuo
106
- API (no `/releases` suffix) — the workflow appends `/releases` itself.
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, or a VARCHAR/CHAR overflow. | The script's output shape, or the `output_columns` declaration. |
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.8.0
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-trino:v0.8.0
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` or
279
- `continuo-trino-adapter` package built from this repo's `adapters/postgres/`
280
- or `adapters/trino/` source (see the table above) — registered under the
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 ..domain.columns import ColumnDefinition
10
- from ..domain.identifiers import Identifier, QualifiedTable
11
- from ..domain.layout import TableLayout
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: ...