smart-data-engine-sdk 0.1.0.dev0__tar.gz → 0.1.0rc1__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.
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/PKG-INFO +31 -9
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/README.md +27 -6
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/pyproject.toml +8 -3
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/src/sde/__init__.py +94 -2
- smart_data_engine_sdk-0.1.0rc1/src/sde/_cutover_project.py +179 -0
- smart_data_engine_sdk-0.1.0rc1/src/sde/_local_state.py +188 -0
- smart_data_engine_sdk-0.1.0rc1/src/sde/_operator_deadline.py +50 -0
- smart_data_engine_sdk-0.1.0rc1/src/sde/_usage.py +314 -0
- smart_data_engine_sdk-0.1.0rc1/src/sde/bulk.py +79 -0
- smart_data_engine_sdk-0.1.0rc1/src/sde/cutover.py +286 -0
- smart_data_engine_sdk-0.1.0rc1/src/sde/engines/_clickhouse_connection.py +224 -0
- smart_data_engine_sdk-0.1.0rc1/src/sde/engines/_index_build.py +294 -0
- smart_data_engine_sdk-0.1.0rc1/src/sde/engines/_operator.py +394 -0
- smart_data_engine_sdk-0.1.0rc1/src/sde/engines/_staging.py +222 -0
- smart_data_engine_sdk-0.1.0rc1/src/sde/engines/_storage.py +22 -0
- smart_data_engine_sdk-0.1.0rc1/src/sde/engines/_write_fences.py +271 -0
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/src/sde/engines/clickhouse.py +479 -53
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/src/sde/engines/orderbook.py +4 -1
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/src/sde/engines/postgres.py +332 -37
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/src/sde/errors.py +15 -0
- smart_data_engine_sdk-0.1.0rc1/src/sde/frozen_verification.py +152 -0
- smart_data_engine_sdk-0.1.0rc1/src/sde/generation.py +131 -0
- smart_data_engine_sdk-0.1.0rc1/src/sde/index_build.py +313 -0
- smart_data_engine_sdk-0.1.0rc1/src/sde/index_operator.py +347 -0
- smart_data_engine_sdk-0.1.0rc1/src/sde/inspection.py +62 -0
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/src/sde/layout.py +9 -0
- smart_data_engine_sdk-0.1.0rc1/src/sde/local_cutover.py +801 -0
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/src/sde/logging.py +11 -0
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/src/sde/migration.py +46 -10
- smart_data_engine_sdk-0.1.0rc1/src/sde/physical.py +531 -0
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/src/sde/placement.py +221 -29
- smart_data_engine_sdk-0.1.0rc1/src/sde/provisioning.py +63 -0
- smart_data_engine_sdk-0.1.0rc1/src/sde/query.py +521 -0
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/src/sde/schema.py +120 -24
- smart_data_engine_sdk-0.1.0rc1/src/sde/session.py +993 -0
- smart_data_engine_sdk-0.1.0rc1/src/sde/staging.py +264 -0
- smart_data_engine_sdk-0.1.0rc1/src/sde/staging_operator.py +393 -0
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/src/sde/telemetry.py +371 -20
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/src/sde/testing/memory.py +14 -1
- smart_data_engine_sdk-0.1.0rc1/src/sde/verification.py +220 -0
- smart_data_engine_sdk-0.1.0rc1/src/sde/write_fence.py +283 -0
- smart_data_engine_sdk-0.1.0rc1/src/sde_demo/__init__.py +1 -0
- smart_data_engine_sdk-0.1.0rc1/src/sde_demo/__main__.py +183 -0
- smart_data_engine_sdk-0.1.0rc1/src/sde_demo/diagnostics.py +92 -0
- smart_data_engine_sdk-0.1.0rc1/src/sde_demo/model.py +75 -0
- smart_data_engine_sdk-0.1.0rc1/src/sde_demo/project.py +312 -0
- smart_data_engine_sdk-0.1.0rc1/src/sde_demo/py.typed +0 -0
- smart_data_engine_sdk-0.1.0rc1/src/sde_demo/query_count.py +301 -0
- smart_data_engine_sdk-0.1.0rc1/src/sde_demo/resources.py +969 -0
- smart_data_engine_sdk-0.1.0rc1/src/sde_demo/runtime.py +419 -0
- smart_data_engine_sdk-0.1.0rc1/src/sde_demo/verification.py +242 -0
- smart_data_engine_sdk-0.1.0rc1/src/sde_operator/__init__.py +1 -0
- smart_data_engine_sdk-0.1.0rc1/src/sde_operator/__main__.py +210 -0
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/_claims.py +20 -6
- smart_data_engine_sdk-0.1.0rc1/tests/_cutover_driver.py +43 -0
- smart_data_engine_sdk-0.1.0rc1/tests/_cutover_worker.py +71 -0
- smart_data_engine_sdk-0.1.0rc1/tests/_generation.py +47 -0
- smart_data_engine_sdk-0.1.0rc1/tests/_index_worker.py +64 -0
- smart_data_engine_sdk-0.1.0rc1/tests/_staging_driver.py +49 -0
- smart_data_engine_sdk-0.1.0rc1/tests/_staging_worker.py +85 -0
- smart_data_engine_sdk-0.1.0rc1/tests/_weather_fixture.py +67 -0
- smart_data_engine_sdk-0.1.0rc1/tests/_write_fence.py +56 -0
- smart_data_engine_sdk-0.1.0rc1/tests/bulk_vectors.py +66 -0
- smart_data_engine_sdk-0.1.0rc1/tests/datetime_query_peer.py +31 -0
- smart_data_engine_sdk-0.1.0rc1/tests/fence_peer.py +44 -0
- smart_data_engine_sdk-0.1.0rc1/tests/qualification/__init__.py +1 -0
- smart_data_engine_sdk-0.1.0rc1/tests/qualification/metrics.py +115 -0
- smart_data_engine_sdk-0.1.0rc1/tests/qualification/operation_metrics.py +77 -0
- smart_data_engine_sdk-0.1.0rc1/tests/qualification/operation_worker.py +170 -0
- smart_data_engine_sdk-0.1.0rc1/tests/qualification/operations.py +221 -0
- smart_data_engine_sdk-0.1.0rc1/tests/qualification/operator_worker.py +38 -0
- smart_data_engine_sdk-0.1.0rc1/tests/qualification/packets.py +110 -0
- smart_data_engine_sdk-0.1.0rc1/tests/qualification/project.py +181 -0
- smart_data_engine_sdk-0.1.0rc1/tests/qualification/run.py +322 -0
- smart_data_engine_sdk-0.1.0rc1/tests/qualification/transition.py +145 -0
- smart_data_engine_sdk-0.1.0rc1/tests/qualification/weather_worker.py +236 -0
- smart_data_engine_sdk-0.1.0rc1/tests/test_bulk.py +313 -0
- smart_data_engine_sdk-0.1.0rc1/tests/test_bulk_live.py +289 -0
- smart_data_engine_sdk-0.1.0rc1/tests/test_clickhouse_datetime_queries.py +178 -0
- smart_data_engine_sdk-0.1.0rc1/tests/test_clickhouse_tls.py +435 -0
- smart_data_engine_sdk-0.1.0rc1/tests/test_clickhouse_transport.py +79 -0
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_conformance.py +250 -1
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_copy_freshness.py +3 -1
- smart_data_engine_sdk-0.1.0rc1/tests/test_cutover_plan.py +110 -0
- smart_data_engine_sdk-0.1.0rc1/tests/test_cutover_project.py +234 -0
- smart_data_engine_sdk-0.1.0rc1/tests/test_demo_query_count.py +505 -0
- smart_data_engine_sdk-0.1.0rc1/tests/test_demo_resources.py +634 -0
- smart_data_engine_sdk-0.1.0rc1/tests/test_demo_starter.py +707 -0
- smart_data_engine_sdk-0.1.0rc1/tests/test_demo_starter_live.py +145 -0
- smart_data_engine_sdk-0.1.0rc1/tests/test_demo_verification.py +436 -0
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_dual_write.py +3 -1
- smart_data_engine_sdk-0.1.0rc1/tests/test_enrollment_durability.py +81 -0
- smart_data_engine_sdk-0.1.0rc1/tests/test_frozen_verification_live.py +162 -0
- smart_data_engine_sdk-0.1.0rc1/tests/test_generation_map.py +91 -0
- smart_data_engine_sdk-0.1.0rc1/tests/test_generation_migration_live.py +137 -0
- smart_data_engine_sdk-0.1.0rc1/tests/test_generation_session_live.py +159 -0
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_implementer_documents.py +1 -1
- smart_data_engine_sdk-0.1.0rc1/tests/test_index_change_live.py +401 -0
- smart_data_engine_sdk-0.1.0rc1/tests/test_index_operator_live.py +1084 -0
- smart_data_engine_sdk-0.1.0rc1/tests/test_index_plan.py +95 -0
- smart_data_engine_sdk-0.1.0rc1/tests/test_integration_guard.py +48 -0
- smart_data_engine_sdk-0.1.0rc1/tests/test_local_cutover_live.py +995 -0
- smart_data_engine_sdk-0.1.0rc1/tests/test_map_unicode.py +101 -0
- smart_data_engine_sdk-0.1.0rc1/tests/test_map_unicode_live.py +26 -0
- smart_data_engine_sdk-0.1.0rc1/tests/test_measure_storage.py +164 -0
- smart_data_engine_sdk-0.1.0rc1/tests/test_measure_storage_live.py +64 -0
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_migration.py +6 -4
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_migration_live.py +3 -1
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_no_account.py +15 -2
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_no_retry.py +9 -3
- smart_data_engine_sdk-0.1.0rc1/tests/test_operation_metrics.py +102 -0
- smart_data_engine_sdk-0.1.0rc1/tests/test_operation_oracle.py +46 -0
- smart_data_engine_sdk-0.1.0rc1/tests/test_operator_cli.py +141 -0
- smart_data_engine_sdk-0.1.0rc1/tests/test_operator_deadline.py +60 -0
- smart_data_engine_sdk-0.1.0rc1/tests/test_operator_native_live.py +154 -0
- smart_data_engine_sdk-0.1.0rc1/tests/test_owned_session.py +179 -0
- smart_data_engine_sdk-0.1.0rc1/tests/test_owned_session_live.py +60 -0
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_packaging.py +69 -2
- smart_data_engine_sdk-0.1.0rc1/tests/test_physical.py +413 -0
- smart_data_engine_sdk-0.1.0rc1/tests/test_physical_live.py +349 -0
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_placement_and_routing.py +6 -1
- smart_data_engine_sdk-0.1.0rc1/tests/test_postgres_tls.py +117 -0
- smart_data_engine_sdk-0.1.0rc1/tests/test_qualification_cleanup.py +27 -0
- smart_data_engine_sdk-0.1.0rc1/tests/test_qualification_metrics.py +168 -0
- smart_data_engine_sdk-0.1.0rc1/tests/test_qualification_workers.py +30 -0
- smart_data_engine_sdk-0.1.0rc1/tests/test_query_live.py +447 -0
- smart_data_engine_sdk-0.1.0rc1/tests/test_query_plan.py +159 -0
- smart_data_engine_sdk-0.1.0rc1/tests/test_query_session.py +130 -0
- smart_data_engine_sdk-0.1.0rc1/tests/test_query_summary.py +75 -0
- smart_data_engine_sdk-0.1.0rc1/tests/test_query_vectors.py +87 -0
- smart_data_engine_sdk-0.1.0rc1/tests/test_release.py +473 -0
- smart_data_engine_sdk-0.1.0rc1/tests/test_runtime_privileges_live.py +366 -0
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_schema_statements.py +1 -1
- smart_data_engine_sdk-0.1.0rc1/tests/test_session_ownership_live.py +364 -0
- smart_data_engine_sdk-0.1.0rc1/tests/test_staging_abandon_live.py +414 -0
- smart_data_engine_sdk-0.1.0rc1/tests/test_staging_native_live.py +125 -0
- smart_data_engine_sdk-0.1.0rc1/tests/test_staging_operator_live.py +696 -0
- smart_data_engine_sdk-0.1.0rc1/tests/test_staging_plan.py +84 -0
- smart_data_engine_sdk-0.1.0rc1/tests/test_storage_sizes_live.py +78 -0
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_telemetry.py +136 -8
- smart_data_engine_sdk-0.1.0rc1/tests/test_telemetry_storage_time.py +372 -0
- smart_data_engine_sdk-0.1.0rc1/tests/test_verification_request.py +190 -0
- smart_data_engine_sdk-0.1.0rc1/tests/test_write_fence.py +172 -0
- smart_data_engine_sdk-0.1.0rc1/tests/test_write_fence_live.py +232 -0
- smart_data_engine_sdk-0.1.0rc1/tests/timestamp_peer.py +55 -0
- smart_data_engine_sdk-0.1.0rc1/tests/tls_certificates.py +143 -0
- smart_data_engine_sdk-0.1.0rc1/tests/verification_peer.py +31 -0
- smart_data_engine_sdk-0.1.0rc1/tests/weather_starter_peer.py +39 -0
- smart_data_engine_sdk-0.1.0.dev0/src/sde/session.py +0 -507
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/.gitignore +0 -0
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/LICENSE +0 -0
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/NOTICE +0 -0
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/src/sde/canonical.py +0 -0
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/src/sde/capabilities.py +0 -0
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/src/sde/engines/__init__.py +0 -0
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/src/sde/entity.py +0 -0
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/src/sde/explain.py +0 -0
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/src/sde/groups.py +0 -0
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/src/sde/hashing.py +0 -0
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/src/sde/infer.py +0 -0
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/src/sde/internal.py +0 -0
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/src/sde/model.py +0 -0
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/src/sde/py.typed +0 -0
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/src/sde/routing.py +0 -0
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/src/sde/shapes.py +0 -0
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/src/sde/testing/__init__.py +0 -0
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/src/sde/testing/loader.py +0 -0
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/src/sde/types.py +0 -0
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/src/sde/watermark.py +0 -0
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_canonical.py +0 -0
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_clickhouse_slice.py +0 -0
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_compatibility_views.py +0 -0
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_denormalized_layout.py +0 -0
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_engine_agreement.py +0 -0
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_explain_live.py +0 -0
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_failure_semantics.py +0 -0
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_groups_and_shapes.py +0 -0
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_hashing.py +0 -0
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_infer.py +0 -0
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_internal_failures.py +0 -0
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_layout_dialects.py +0 -0
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_model.py +0 -0
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_neutral_declaration.py +0 -0
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_no_account_live.py +0 -0
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_no_expiry.py +0 -0
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_orderbook_adapter.py +0 -0
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_orderbook_shape.py +0 -0
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_orderbook_slice.py +0 -0
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_overhead.py +0 -0
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_platforms.py +0 -0
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_postgres_slice.py +0 -0
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_readme_claims.py +0 -0
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_rollback_protection.py +0 -0
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_rollback_protection_live.py +0 -0
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_schema_vectors_live.py +0 -0
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_session.py +0 -0
- {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_session_hashed.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: smart-data-engine-sdk
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.0rc1
|
|
4
4
|
Summary: Smart Data Engine client library: declare a data model, we place it and move it
|
|
5
5
|
Project-URL: Homepage, https://github.com/Smart-Data-Engines/smart-data-engine-sdk
|
|
6
6
|
Project-URL: Repository, https://github.com/Smart-Data-Engines/smart-data-engine-sdk
|
|
@@ -17,11 +17,12 @@ Classifier: Intended Audience :: Developers
|
|
|
17
17
|
Classifier: Programming Language :: Python :: 3.11
|
|
18
18
|
Classifier: Programming Language :: Python :: 3.12
|
|
19
19
|
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
21
|
Classifier: Topic :: Database
|
|
21
22
|
Classifier: Typing :: Typed
|
|
22
|
-
Requires-Python: <3.
|
|
23
|
+
Requires-Python: <3.15,>=3.11
|
|
23
24
|
Provides-Extra: clickhouse
|
|
24
|
-
Requires-Dist: clickhouse-connect>=
|
|
25
|
+
Requires-Dist: clickhouse-connect>=1.7.2; extra == 'clickhouse'
|
|
25
26
|
Provides-Extra: dev
|
|
26
27
|
Requires-Dist: mypy>=1.11; extra == 'dev'
|
|
27
28
|
Requires-Dist: pytest>=8; extra == 'dev'
|
|
@@ -130,12 +131,9 @@ with zero releases — PyPI compares names after stripping `._-` and folding `l`
|
|
|
130
131
|
`0`, and both reduce to `smartdataeng1ne`. The suffix changes the reduction, and it matches the
|
|
131
132
|
repository name, which is the consistency worth having regardless.
|
|
132
133
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
someone else registers the name before we do, a person who is already debugging gets told, by code
|
|
137
|
-
they had decided to trust, to install a stranger's package.
|
|
138
|
-
[`../docs/publishing.md`](../docs/publishing.md) is what closes this.
|
|
134
|
+
Published to PyPI on 12 September 2026. Until then this section was the one instruction in this
|
|
135
|
+
repository that was knowingly untrue, and it carried a warning saying so; the warning is gone because
|
|
136
|
+
the commands work.
|
|
139
137
|
|
|
140
138
|
## Conformance
|
|
141
139
|
|
|
@@ -150,3 +148,27 @@ its own. If it is not, that is a bug in the document.
|
|
|
150
148
|
## Licence
|
|
151
149
|
|
|
152
150
|
Apache-2.0.
|
|
151
|
+
|
|
152
|
+
## Bound migration verification
|
|
153
|
+
|
|
154
|
+
Managed cutover uses a `VerificationRequest` from the controller. Configure `Session(...,
|
|
155
|
+
project_id=...)` from your local enrollment manifest, load the request with
|
|
156
|
+
`VerificationRequest.from_record`, and pass it to `verify(..., request=request)`. The returned
|
|
157
|
+
`as_record()` includes the request and counts, while row-level differences remain local.
|
|
158
|
+
See [the request protocol](../docs/format-contract.md#7b-verification-requests-and-bound-reports).
|
|
159
|
+
Loaded placement maps are immutable; load a new document when their layout or routing changes.
|
|
160
|
+
|
|
161
|
+
Batch operations: [logical bulk writes](../docs/bulk-writes.md) document `save_many` / `saveMany`,
|
|
162
|
+
bounds, source/copy ordering and uncertain outcomes.
|
|
163
|
+
|
|
164
|
+
[Logical reads and exact summaries](../docs/logical-reads.md) cover scan, count and summarize,
|
|
165
|
+
including portable keyset pages, typed values and explicit failure semantics.
|
|
166
|
+
|
|
167
|
+
## Local Weather starter
|
|
168
|
+
|
|
169
|
+
The [Weather starter](https://github.com/Smart-Data-Engines/smart-data-engine-sdk/blob/main/docs/weather-starter.md)
|
|
170
|
+
is part of this package, as the `sde-weather` command. It exercises logical operations with
|
|
171
|
+
restricted runtime credentials, and it includes local setup, telemetry, operator handoffs and an
|
|
172
|
+
ownership-checked reset.
|
|
173
|
+
|
|
174
|
+
Engine credentials and verified TLS configuration: [connection guide](../docs/engine-connections.md).
|
|
@@ -96,12 +96,9 @@ with zero releases — PyPI compares names after stripping `._-` and folding `l`
|
|
|
96
96
|
`0`, and both reduce to `smartdataeng1ne`. The suffix changes the reduction, and it matches the
|
|
97
97
|
repository name, which is the consistency worth having regardless.
|
|
98
98
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
someone else registers the name before we do, a person who is already debugging gets told, by code
|
|
103
|
-
they had decided to trust, to install a stranger's package.
|
|
104
|
-
[`../docs/publishing.md`](../docs/publishing.md) is what closes this.
|
|
99
|
+
Published to PyPI on 12 September 2026. Until then this section was the one instruction in this
|
|
100
|
+
repository that was knowingly untrue, and it carried a warning saying so; the warning is gone because
|
|
101
|
+
the commands work.
|
|
105
102
|
|
|
106
103
|
## Conformance
|
|
107
104
|
|
|
@@ -116,3 +113,27 @@ its own. If it is not, that is a bug in the document.
|
|
|
116
113
|
## Licence
|
|
117
114
|
|
|
118
115
|
Apache-2.0.
|
|
116
|
+
|
|
117
|
+
## Bound migration verification
|
|
118
|
+
|
|
119
|
+
Managed cutover uses a `VerificationRequest` from the controller. Configure `Session(...,
|
|
120
|
+
project_id=...)` from your local enrollment manifest, load the request with
|
|
121
|
+
`VerificationRequest.from_record`, and pass it to `verify(..., request=request)`. The returned
|
|
122
|
+
`as_record()` includes the request and counts, while row-level differences remain local.
|
|
123
|
+
See [the request protocol](../docs/format-contract.md#7b-verification-requests-and-bound-reports).
|
|
124
|
+
Loaded placement maps are immutable; load a new document when their layout or routing changes.
|
|
125
|
+
|
|
126
|
+
Batch operations: [logical bulk writes](../docs/bulk-writes.md) document `save_many` / `saveMany`,
|
|
127
|
+
bounds, source/copy ordering and uncertain outcomes.
|
|
128
|
+
|
|
129
|
+
[Logical reads and exact summaries](../docs/logical-reads.md) cover scan, count and summarize,
|
|
130
|
+
including portable keyset pages, typed values and explicit failure semantics.
|
|
131
|
+
|
|
132
|
+
## Local Weather starter
|
|
133
|
+
|
|
134
|
+
The [Weather starter](https://github.com/Smart-Data-Engines/smart-data-engine-sdk/blob/main/docs/weather-starter.md)
|
|
135
|
+
is part of this package, as the `sde-weather` command. It exercises logical operations with
|
|
136
|
+
restricted runtime credentials, and it includes local setup, telemetry, operator handoffs and an
|
|
137
|
+
ownership-checked reset.
|
|
138
|
+
|
|
139
|
+
Engine credentials and verified TLS configuration: [connection guide](../docs/engine-connections.md).
|
|
@@ -24,7 +24,7 @@ name = "smart-data-engine-sdk"
|
|
|
24
24
|
dynamic = ["version"]
|
|
25
25
|
description = "Smart Data Engine client library: declare a data model, we place it and move it"
|
|
26
26
|
readme = "README.md"
|
|
27
|
-
requires-python = ">=3.11,<3.
|
|
27
|
+
requires-python = ">=3.11,<3.15"
|
|
28
28
|
license = "Apache-2.0"
|
|
29
29
|
# Apache-2.0 section 4 asks that the licence travel with the work, and section 4(d) says the same
|
|
30
30
|
# about a NOTICE file where one exists. Both were missing from the wheel and the sdist until this
|
|
@@ -47,6 +47,7 @@ classifiers = [
|
|
|
47
47
|
"Programming Language :: Python :: 3.11",
|
|
48
48
|
"Programming Language :: Python :: 3.12",
|
|
49
49
|
"Programming Language :: Python :: 3.13",
|
|
50
|
+
"Programming Language :: Python :: 3.14",
|
|
50
51
|
"Topic :: Database",
|
|
51
52
|
# The wheel carries `py.typed`, so this row is a fact the artefact can be checked against.
|
|
52
53
|
"Typing :: Typed",
|
|
@@ -68,13 +69,17 @@ Issues = "https://github.com/Smart-Data-Engines/smart-data-engine-sdk/issues"
|
|
|
68
69
|
"Format contract" = "https://github.com/Smart-Data-Engines/smart-data-engine-sdk/blob/main/docs/format-contract.md"
|
|
69
70
|
"Failure semantics" = "https://github.com/Smart-Data-Engines/smart-data-engine-sdk/blob/main/docs/failure-semantics.md"
|
|
70
71
|
|
|
72
|
+
[project.scripts]
|
|
73
|
+
sde-operator = "sde_operator.__main__:main"
|
|
74
|
+
sde-weather = "sde_demo.__main__:main"
|
|
75
|
+
|
|
71
76
|
[project.optional-dependencies]
|
|
72
77
|
signed = ["cryptography>=42"]
|
|
73
78
|
postgres = ["psycopg[binary]>=3.1"]
|
|
74
79
|
# The official ClickHouse client, over HTTP. Chosen over the native-protocol driver because HTTP is
|
|
75
80
|
# what a client is most likely to already have open through a proxy or a load balancer, and because
|
|
76
81
|
# the second engine should not also introduce a second network story.
|
|
77
|
-
clickhouse = ["clickhouse-connect>=
|
|
82
|
+
clickhouse = ["clickhouse-connect>=1.7.2"]
|
|
78
83
|
dev = ["pytest>=8", "mypy>=1.11", "ruff>=0.6"]
|
|
79
84
|
|
|
80
85
|
[tool.hatch.version]
|
|
@@ -84,7 +89,7 @@ path = "src/sde/__init__.py"
|
|
|
84
89
|
# otherwise looks for a directory named after the project and fails - which it did, and better here
|
|
85
90
|
# than at publish time.
|
|
86
91
|
[tool.hatch.build.targets.wheel]
|
|
87
|
-
packages = ["src/sde"]
|
|
92
|
+
packages = ["src/sde", "src/sde_operator", "src/sde_demo"]
|
|
88
93
|
# `src/sde/py.typed` needs no entry here: hatchling includes it because it lives inside the package
|
|
89
94
|
# directory, which was checked by building the wheel both ways rather than assumed. A `force-include`
|
|
90
95
|
# for it was written first and then removed - dead configuration that looks load-bearing is worse than
|
|
@@ -33,22 +33,37 @@ mode, not a loophole.
|
|
|
33
33
|
|
|
34
34
|
from __future__ import annotations
|
|
35
35
|
|
|
36
|
+
from .bulk import MAX_BATCH_ROWS, MAX_BATCH_VALUES, BulkWritable
|
|
36
37
|
from .canonical import CanonicalError, canonical_bytes, canonical_str, digest16
|
|
37
38
|
from .capabilities import members_of, satisfies
|
|
39
|
+
from .cutover import CUTOVER_PROTOCOL, CUTOVER_RELAYOUT_PROTOCOL, CutoverPlan, load_cutover_plan
|
|
38
40
|
from .entity import Ref, clear_registry, entity, registry
|
|
39
41
|
from .errors import (
|
|
42
|
+
BulkWriteRefused,
|
|
40
43
|
DeclarationError,
|
|
41
44
|
EngineError,
|
|
42
45
|
MapError,
|
|
43
46
|
MapRolledBack,
|
|
44
47
|
MigrationRefused,
|
|
45
48
|
ModelPlanningError,
|
|
49
|
+
ResourceBusy,
|
|
50
|
+
ResourceClosed,
|
|
46
51
|
SdeError,
|
|
47
52
|
)
|
|
48
53
|
from .explain import Cost, Explains, PlanFinding, QueryPlan, QueryPlanRefused, explain
|
|
54
|
+
from .frozen_verification import FrozenVerifyReport, verify_frozen
|
|
49
55
|
from .groups import Group, colocation_groups, group_of
|
|
50
56
|
from .hashing import NameMap, hash_identifiers, load_or_create_salt
|
|
57
|
+
from .index_build import (
|
|
58
|
+
INDEX_CHANGE_PROTOCOL,
|
|
59
|
+
INDEX_PROTOCOL,
|
|
60
|
+
IndexPlan,
|
|
61
|
+
IndexReceipt,
|
|
62
|
+
index_build_name,
|
|
63
|
+
load_index_plan,
|
|
64
|
+
)
|
|
51
65
|
from .infer import InferredModel, Note, infer_model, infer_models
|
|
66
|
+
from .inspection import InspectionContext
|
|
52
67
|
from .internal import internal_failures, reset_internal_failures
|
|
53
68
|
from .layout import (
|
|
54
69
|
DIALECTS,
|
|
@@ -65,6 +80,7 @@ from .layout import (
|
|
|
65
80
|
snake_case,
|
|
66
81
|
stored_types,
|
|
67
82
|
)
|
|
83
|
+
from .local_cutover import CutoverReceipt, CutoverRecoveryRequired, LocalCutover, load_local_map
|
|
68
84
|
from .migration import (
|
|
69
85
|
BACKFILL_TABLE,
|
|
70
86
|
CHUNK_ROWS,
|
|
@@ -80,6 +96,7 @@ from .migration import (
|
|
|
80
96
|
verify,
|
|
81
97
|
)
|
|
82
98
|
from .model import CONTRACT, LogicalModel, build_model, neutral_declaration
|
|
99
|
+
from .physical import PHYSICAL_DESIGN_SINCE, PhysicalFinding
|
|
83
100
|
from .placement import (
|
|
84
101
|
ALSO_WRITE_SINCE,
|
|
85
102
|
MAP_CONTRACT,
|
|
@@ -91,10 +108,28 @@ from .placement import (
|
|
|
91
108
|
PlacementMap,
|
|
92
109
|
load_map,
|
|
93
110
|
)
|
|
111
|
+
from .provisioning import prepare_schema
|
|
112
|
+
from .query import (
|
|
113
|
+
MAX_PAGE_ROWS,
|
|
114
|
+
NumericSummary,
|
|
115
|
+
Queryable,
|
|
116
|
+
QueryRefused,
|
|
117
|
+
Range,
|
|
118
|
+
ScanPage,
|
|
119
|
+
Summarizable,
|
|
120
|
+
)
|
|
94
121
|
from .routing import Router, resolve
|
|
95
122
|
from .schema import CompatibilityViews, compatibility_views, schema_is_fixed, schema_statements
|
|
96
|
-
from .session import Engine, Session
|
|
123
|
+
from .session import Engine, ManagedEngine, Session
|
|
97
124
|
from .shapes import SHAPE_KINDS, WRITE_KINDS, OperationShape, enumerate_shapes
|
|
125
|
+
from .staging import (
|
|
126
|
+
STAGING_PROTOCOL,
|
|
127
|
+
STAGING_RELAYOUT_PROTOCOL,
|
|
128
|
+
StagingPlan,
|
|
129
|
+
StagingReceipt,
|
|
130
|
+
load_staging_plan,
|
|
131
|
+
staging_table_name,
|
|
132
|
+
)
|
|
98
133
|
from .telemetry import (
|
|
99
134
|
MEASURED_FIELDS,
|
|
100
135
|
CopyFreshness,
|
|
@@ -103,8 +138,12 @@ from .telemetry import (
|
|
|
103
138
|
Histogram,
|
|
104
139
|
Recorder,
|
|
105
140
|
ShapeStats,
|
|
141
|
+
StorageMeasurement,
|
|
142
|
+
StorageSample,
|
|
143
|
+
StorageSize,
|
|
106
144
|
Window,
|
|
107
145
|
has_time_dimension,
|
|
146
|
+
time_fields,
|
|
108
147
|
)
|
|
109
148
|
from .types import Float32, Int32, Json, Timestamp, precision
|
|
110
149
|
from .watermark import (
|
|
@@ -114,33 +153,51 @@ from .watermark import (
|
|
|
114
153
|
WatermarkStore,
|
|
115
154
|
enforce_forward_only,
|
|
116
155
|
)
|
|
156
|
+
from .write_fence import EPOCH_COLUMN as WRITE_EPOCH_COLUMN
|
|
157
|
+
from .write_fence import FenceState, WriteFence
|
|
117
158
|
|
|
118
|
-
__version__ = "0.1.
|
|
159
|
+
__version__ = "0.1.0rc1"
|
|
119
160
|
|
|
120
161
|
__all__ = [
|
|
121
162
|
"ALSO_WRITE_SINCE",
|
|
122
163
|
"BACKFILL_TABLE",
|
|
123
164
|
"CHUNK_ROWS",
|
|
124
165
|
"CONTRACT",
|
|
166
|
+
"CUTOVER_PROTOCOL",
|
|
167
|
+
"CUTOVER_RELAYOUT_PROTOCOL",
|
|
125
168
|
"DIALECTS",
|
|
126
169
|
"DIALECT_PRECISION",
|
|
127
170
|
"FIXED_SCHEMA",
|
|
171
|
+
"INDEX_CHANGE_PROTOCOL",
|
|
172
|
+
"INDEX_PROTOCOL",
|
|
128
173
|
"MAP_CONTRACT",
|
|
129
174
|
"MAP_CONTRACT_FLOOR",
|
|
175
|
+
"MAX_BATCH_ROWS",
|
|
176
|
+
"MAX_BATCH_VALUES",
|
|
177
|
+
"MAX_PAGE_ROWS",
|
|
130
178
|
"MEASURED_FIELDS",
|
|
131
179
|
"ORDERBOOK_KEY",
|
|
132
180
|
"ORDERBOOK_SHAPE",
|
|
133
181
|
"ORDERBOOK_TABLE",
|
|
182
|
+
"PHYSICAL_DESIGN_SINCE",
|
|
134
183
|
"PRECISION_INDEPENDENT",
|
|
135
184
|
"RESERVED_TABLES",
|
|
136
185
|
"SHAPE_KINDS",
|
|
186
|
+
"STAGING_PROTOCOL",
|
|
187
|
+
"STAGING_RELAYOUT_PROTOCOL",
|
|
137
188
|
"WATERMARK_TABLE",
|
|
189
|
+
"WRITE_EPOCH_COLUMN",
|
|
138
190
|
"WRITE_KINDS",
|
|
139
191
|
"BackfillProgress",
|
|
192
|
+
"BulkWritable",
|
|
193
|
+
"BulkWriteRefused",
|
|
140
194
|
"CanonicalError",
|
|
141
195
|
"CompatibilityViews",
|
|
142
196
|
"CopyFreshness",
|
|
143
197
|
"Cost",
|
|
198
|
+
"CutoverPlan",
|
|
199
|
+
"CutoverReceipt",
|
|
200
|
+
"CutoverRecoveryRequired",
|
|
144
201
|
"DeclarationError",
|
|
145
202
|
"DerivedLayout",
|
|
146
203
|
"Difference",
|
|
@@ -149,15 +206,22 @@ __all__ = [
|
|
|
149
206
|
"EntityProgress",
|
|
150
207
|
"Explains",
|
|
151
208
|
"FanOutStats",
|
|
209
|
+
"FenceState",
|
|
152
210
|
"Float32",
|
|
211
|
+
"FrozenVerifyReport",
|
|
153
212
|
"Group",
|
|
154
213
|
"GroupFeatures",
|
|
155
214
|
"GroupPlacement",
|
|
156
215
|
"Histogram",
|
|
216
|
+
"IndexPlan",
|
|
217
|
+
"IndexReceipt",
|
|
157
218
|
"InferredModel",
|
|
219
|
+
"InspectionContext",
|
|
158
220
|
"Int32",
|
|
159
221
|
"Json",
|
|
222
|
+
"LocalCutover",
|
|
160
223
|
"LogicalModel",
|
|
224
|
+
"ManagedEngine",
|
|
161
225
|
"MapError",
|
|
162
226
|
"MapRolledBack",
|
|
163
227
|
"Materialization",
|
|
@@ -166,24 +230,40 @@ __all__ = [
|
|
|
166
230
|
"ModelPlanningError",
|
|
167
231
|
"NameMap",
|
|
168
232
|
"Note",
|
|
233
|
+
"NumericSummary",
|
|
169
234
|
"OperationShape",
|
|
235
|
+
"PhysicalFinding",
|
|
170
236
|
"PhysicalLayout",
|
|
171
237
|
"PlacementMap",
|
|
172
238
|
"PlanFinding",
|
|
173
239
|
"Protection",
|
|
174
240
|
"QueryPlan",
|
|
175
241
|
"QueryPlanRefused",
|
|
242
|
+
"QueryRefused",
|
|
243
|
+
"Queryable",
|
|
244
|
+
"Range",
|
|
176
245
|
"Recorder",
|
|
177
246
|
"Ref",
|
|
247
|
+
"ResourceBusy",
|
|
248
|
+
"ResourceClosed",
|
|
178
249
|
"Router",
|
|
250
|
+
"ScanPage",
|
|
179
251
|
"SdeError",
|
|
180
252
|
"Session",
|
|
181
253
|
"ShapeStats",
|
|
254
|
+
"StagingPlan",
|
|
255
|
+
"StagingReceipt",
|
|
256
|
+
"StorageMeasurement",
|
|
257
|
+
"StorageSample",
|
|
258
|
+
"StorageSize",
|
|
259
|
+
"Summarizable",
|
|
182
260
|
"Timestamp",
|
|
261
|
+
"VerificationRequest",
|
|
183
262
|
"VerifyReport",
|
|
184
263
|
"WatermarkCheck",
|
|
185
264
|
"WatermarkStore",
|
|
186
265
|
"Window",
|
|
266
|
+
"WriteFence",
|
|
187
267
|
"__version__",
|
|
188
268
|
"backfill",
|
|
189
269
|
"build_model",
|
|
@@ -205,15 +285,21 @@ __all__ = [
|
|
|
205
285
|
"group_of",
|
|
206
286
|
"has_time_dimension",
|
|
207
287
|
"hash_identifiers",
|
|
288
|
+
"index_build_name",
|
|
208
289
|
"infer_model",
|
|
209
290
|
"infer_models",
|
|
210
291
|
"internal_failures",
|
|
292
|
+
"load_cutover_plan",
|
|
293
|
+
"load_index_plan",
|
|
294
|
+
"load_local_map",
|
|
211
295
|
"load_map",
|
|
212
296
|
"load_or_create_salt",
|
|
297
|
+
"load_staging_plan",
|
|
213
298
|
"members_of",
|
|
214
299
|
"neutral_declaration",
|
|
215
300
|
"precision",
|
|
216
301
|
"precision_refusal",
|
|
302
|
+
"prepare_schema",
|
|
217
303
|
"registry",
|
|
218
304
|
"reset_internal_failures",
|
|
219
305
|
"resolve",
|
|
@@ -221,6 +307,12 @@ __all__ = [
|
|
|
221
307
|
"schema_is_fixed",
|
|
222
308
|
"schema_statements",
|
|
223
309
|
"snake_case",
|
|
310
|
+
"staging_table_name",
|
|
224
311
|
"stored_types",
|
|
312
|
+
"time_fields",
|
|
313
|
+
"verification_request",
|
|
225
314
|
"verify",
|
|
315
|
+
"verify_frozen",
|
|
226
316
|
]
|
|
317
|
+
|
|
318
|
+
from .verification import VerificationRequest, verification_request
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"""Durable project state for a client-side cutover; no rows or credentials are stored here."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import hashlib
|
|
6
|
+
import json
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from . import _local_state
|
|
11
|
+
from .errors import MigrationRefused
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def encode(value: Any) -> bytes:
|
|
15
|
+
# Native principal/namespace names are exact strings, not canonical map identifiers.
|
|
16
|
+
# Keep their spelling; the state checksum is over this explicitly versioned raw-JSON form.
|
|
17
|
+
return json.dumps(
|
|
18
|
+
value, ensure_ascii=False, sort_keys=True, separators=(",", ":"), allow_nan=False
|
|
19
|
+
).encode("utf-8")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _abandoned_stages(payload: Any) -> bool:
|
|
23
|
+
stages = payload.get("stages") if isinstance(payload, dict) else None
|
|
24
|
+
if not isinstance(stages, dict):
|
|
25
|
+
return False
|
|
26
|
+
return any(
|
|
27
|
+
isinstance(record, dict)
|
|
28
|
+
and isinstance(record.get("receipt"), dict)
|
|
29
|
+
and record["receipt"].get("outcome") == "abandoned"
|
|
30
|
+
for record in stages.values()
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _index_changes(payload: Any) -> bool:
|
|
35
|
+
"""Whether an in-place index record - executing or kept - also removes indexes (protocol 2)."""
|
|
36
|
+
if not isinstance(payload, dict):
|
|
37
|
+
return False
|
|
38
|
+
records: list[Any] = []
|
|
39
|
+
history = payload.get("indexes")
|
|
40
|
+
if isinstance(history, dict):
|
|
41
|
+
records.extend(history.values())
|
|
42
|
+
execution = payload.get("execution")
|
|
43
|
+
if isinstance(execution, dict) and execution.get("kind") == "index":
|
|
44
|
+
records.append(execution)
|
|
45
|
+
return any(
|
|
46
|
+
isinstance(record, dict)
|
|
47
|
+
and isinstance(record.get("plan"), dict)
|
|
48
|
+
and record["plan"].get("protocol") == 2
|
|
49
|
+
for record in records
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class ProjectState:
|
|
54
|
+
"""One POSIX directory and lock, shared by all local executors for this project."""
|
|
55
|
+
|
|
56
|
+
def __init__(self, root: Path, project_id: str, model_version: str) -> None:
|
|
57
|
+
self.root = root.resolve()
|
|
58
|
+
self.project_id = project_id
|
|
59
|
+
self.model_version = model_version
|
|
60
|
+
self.path = self.root / "project.json"
|
|
61
|
+
self.map_path = self.root / "active-map.json"
|
|
62
|
+
|
|
63
|
+
def lock(self) -> Any:
|
|
64
|
+
return _local_state.transaction(self.root)
|
|
65
|
+
|
|
66
|
+
def read(self) -> dict[str, Any]:
|
|
67
|
+
try:
|
|
68
|
+
envelope = json.loads(self.path.read_bytes())
|
|
69
|
+
if not isinstance(envelope, dict) or set(envelope) != {
|
|
70
|
+
"storage_contract",
|
|
71
|
+
"payload",
|
|
72
|
+
"sha256",
|
|
73
|
+
}:
|
|
74
|
+
raise ValueError("invalid state envelope")
|
|
75
|
+
if type(envelope["storage_contract"]) is not int or envelope[
|
|
76
|
+
"storage_contract"
|
|
77
|
+
] not in (1, 2, 3, 4, 5):
|
|
78
|
+
raise ValueError("unsupported state storage contract")
|
|
79
|
+
payload = envelope["payload"]
|
|
80
|
+
if (
|
|
81
|
+
not isinstance(payload, dict)
|
|
82
|
+
or hashlib.sha256(encode(payload)).hexdigest() != envelope["sha256"]
|
|
83
|
+
):
|
|
84
|
+
raise ValueError("state checksum mismatch")
|
|
85
|
+
if (
|
|
86
|
+
payload.get("project_id") != self.project_id
|
|
87
|
+
or payload.get("model_version") != self.model_version
|
|
88
|
+
):
|
|
89
|
+
raise ValueError("state belongs to another local project/model")
|
|
90
|
+
fields = {
|
|
91
|
+
"project_id",
|
|
92
|
+
"model_version",
|
|
93
|
+
"active_map",
|
|
94
|
+
"execution",
|
|
95
|
+
"completed",
|
|
96
|
+
"retired_names",
|
|
97
|
+
}
|
|
98
|
+
if envelope["storage_contract"] >= 2:
|
|
99
|
+
fields.add("stages")
|
|
100
|
+
if not isinstance(payload.get("stages"), dict):
|
|
101
|
+
raise ValueError("staging history must be an object")
|
|
102
|
+
if envelope["storage_contract"] >= 3:
|
|
103
|
+
# In-place index builds: a reader of contracts 1 and 2 refuses this state rather
|
|
104
|
+
# than ignoring a history it would not know to keep.
|
|
105
|
+
fields.add("indexes")
|
|
106
|
+
if not isinstance(payload.get("indexes"), dict):
|
|
107
|
+
raise ValueError("index build history must be an object")
|
|
108
|
+
if envelope["storage_contract"] < 4 and _abandoned_stages(payload):
|
|
109
|
+
# Contract 4 is what makes an older operator refuse an abandoned staging's record
|
|
110
|
+
# instead of reading a receipt whose tables may name no identity.
|
|
111
|
+
raise ValueError("an abandoned staging needs state storage contract 4")
|
|
112
|
+
if envelope["storage_contract"] < 5 and _index_changes(payload):
|
|
113
|
+
# Contract 5 is what makes an older operator refuse a build that also removes
|
|
114
|
+
# indexes, instead of resuming it with no idea that removals follow the decision.
|
|
115
|
+
raise ValueError("an index change needs state storage contract 5")
|
|
116
|
+
if set(payload) != fields:
|
|
117
|
+
raise ValueError("unknown or missing project state fields")
|
|
118
|
+
return payload
|
|
119
|
+
except (OSError, ValueError, TypeError) as exc:
|
|
120
|
+
raise MigrationRefused(
|
|
121
|
+
"local cutover state is missing or corrupt; "
|
|
122
|
+
"restore verified state before proceeding"
|
|
123
|
+
) from exc
|
|
124
|
+
|
|
125
|
+
def confirm(self) -> None:
|
|
126
|
+
# Call under the project lock. Neither reading matching bytes nor an in-memory receipt
|
|
127
|
+
# confirms the directory fsync that may have failed after an earlier publication.
|
|
128
|
+
_local_state.confirm_file(self.map_path)
|
|
129
|
+
_local_state.confirm_file(self.path)
|
|
130
|
+
|
|
131
|
+
def write(self, payload: dict[str, Any]) -> None:
|
|
132
|
+
body = encode(payload)
|
|
133
|
+
envelope = {
|
|
134
|
+
"storage_contract": 5
|
|
135
|
+
if _index_changes(payload)
|
|
136
|
+
else 4
|
|
137
|
+
if _abandoned_stages(payload)
|
|
138
|
+
else 3
|
|
139
|
+
if "indexes" in payload
|
|
140
|
+
else 2
|
|
141
|
+
if "stages" in payload
|
|
142
|
+
else 1,
|
|
143
|
+
"payload": payload,
|
|
144
|
+
"sha256": hashlib.sha256(body).hexdigest(),
|
|
145
|
+
}
|
|
146
|
+
_local_state.write_bytes(self.path, encode(envelope))
|
|
147
|
+
|
|
148
|
+
def enroll(self, document: dict[str, Any], map_payload: bytes) -> None:
|
|
149
|
+
with self.lock():
|
|
150
|
+
if self.map_path.exists() and self.map_path.read_bytes() != map_payload:
|
|
151
|
+
raise MigrationRefused("an existing active map differs from the enrolled state")
|
|
152
|
+
if self.path.exists():
|
|
153
|
+
state = self.read()
|
|
154
|
+
if (
|
|
155
|
+
state["active_map"] != document
|
|
156
|
+
or state["execution"] is not None
|
|
157
|
+
or state["completed"]
|
|
158
|
+
):
|
|
159
|
+
raise MigrationRefused(
|
|
160
|
+
"local project is already enrolled; use its current state"
|
|
161
|
+
)
|
|
162
|
+
else:
|
|
163
|
+
state = {
|
|
164
|
+
"project_id": self.project_id,
|
|
165
|
+
"model_version": self.model_version,
|
|
166
|
+
"active_map": document,
|
|
167
|
+
"execution": None,
|
|
168
|
+
"completed": {},
|
|
169
|
+
"retired_names": [],
|
|
170
|
+
}
|
|
171
|
+
self.write(state)
|
|
172
|
+
if not self.map_path.exists():
|
|
173
|
+
_local_state.write_bytes(self.map_path, map_payload, replace=False)
|
|
174
|
+
# Matching visible files may come from a publication whose directory fsync failed.
|
|
175
|
+
# An identical retry must confirm their durability before enrollment succeeds.
|
|
176
|
+
self.confirm()
|
|
177
|
+
|
|
178
|
+
def publish(self, payload: bytes) -> None:
|
|
179
|
+
_local_state.write_bytes(self.map_path, payload)
|