vgi-python 0.26.0__tar.gz → 0.27.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.
- {vgi_python-0.26.0 → vgi_python-0.27.0}/PKG-INFO +1 -1
- vgi_python-0.27.0/RELEASE-RUNBOOK.md +69 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/pyproject.toml +1 -1
- vgi_python-0.27.0/tests/catalog/test_attach_option.py +192 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/test_serve.py +10 -5
- {vgi_python-0.26.0 → vgi_python-0.27.0}/uv.lock +1 -1
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/attach_options.py +34 -3
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/http_server.py +5 -14
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/catalog/_descriptor_spec.py +39 -2
- vgi_python-0.27.0/vgi/catalog/attach_option.py +157 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/catalog/catalog_interface.py +3 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/http/landing.html +260 -18
- vgi_python-0.27.0/vgi/http/landing_page.py +112 -0
- vgi_python-0.27.0/vgi/http/vgi-client.js +23 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/serve.py +3 -10
- vgi_python-0.26.0/tests/landing/_checker.py +0 -153
- vgi_python-0.26.0/tests/landing/describe.expected.json +0 -3442
- vgi_python-0.26.0/tests/landing/describe.schema.json +0 -402
- vgi_python-0.26.0/tests/test_landing_conformance.py +0 -93
- vgi_python-0.26.0/vgi/catalog/attach_option.py +0 -49
- vgi_python-0.26.0/vgi/http/describe_json.py +0 -461
- vgi_python-0.26.0/vgi/http/landing_page.py +0 -95
- {vgi_python-0.26.0 → vgi_python-0.27.0}/.gitattributes +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/.github/dependabot.yml +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/.github/styles/config/vocabularies/VGI/accept.txt +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/.github/workflows/ci.yml +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/.github/workflows/docs.yml +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/.github/workflows/integration.yml +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/.github/workflows/release.yml +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/.gitignore +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/.python-version +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/.vale.ini +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/CLAUDE.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/DOCS_ACCEPTANCE_CRITERIA.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/DOCS_REVIEW_RUBRIC.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/DOCS_USABILITY_TEST.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/LICENSE +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/README.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/SECURITY.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/ci/README.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/ci/preprocess-require.awk +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/ci/run-integration.sh +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/dist-vgi/.gitignore +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/docs/aggregate-functions.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/docs/api/arguments.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/docs/api/auth.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/docs/api/catalogs.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/docs/api/client.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/docs/api/exceptions.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/docs/api/filters.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/docs/api/functions.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/docs/api/http.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/docs/api/index.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/docs/api/metadata.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/docs/api/observability.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/docs/api/storage.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/docs/api/transactor.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/docs/api/worker.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/docs/argument-serialization.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/docs/assets/apple-touch-icon.png +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/docs/assets/favicon-16x16.png +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/docs/assets/favicon-32x32.png +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/docs/assets/favicon.ico +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/docs/assets/kinds/aggregate.svg +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/docs/assets/kinds/buffering.svg +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/docs/assets/kinds/scalar.svg +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/docs/assets/kinds/table-in-out.svg +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/docs/assets/kinds/table.svg +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/docs/assets/logo.png +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/docs/assets/social-card.png +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/docs/authentication.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/docs/catalog-interface.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/docs/cli.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/docs/column-statistics.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/docs/concepts/index.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/docs/contributing-docs.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/docs/filter-pushdown.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/docs/generator-api.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/docs/global-functions.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/docs/how-to/catalogs.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/docs/how-to/function-patterns.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/docs/how-to/http-auth.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/docs/how-to/index.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/docs/how-to/pushdown-and-statistics.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/docs/how-to/state-storage.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/docs/index.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/docs/lifecycle.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/docs/metadata.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/docs/overrides/main.html +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/docs/robots.txt +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/docs/shared-storage.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/docs/stylesheets/extra.css +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/docs/tutorial/index.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/docs/tutorial/scalar.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/docs/tutorial/table.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/docs/vgi-logo.png +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/examples/calc_scalar_worker.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/examples/calc_worker.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/examples/filter_worker.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/examples/greeting_scalar_worker.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/examples/row_count_worker.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/examples/series_streaming_worker.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/examples/sum_worker.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/mkdocs.yml +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/packages/vgi-fixtures/LICENSE +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/packages/vgi-fixtures/README.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/packages/vgi-fixtures/docker/Dockerfile +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/packages/vgi-fixtures/docker/docker-entrypoint.sh +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/packages/vgi-fixtures/pyproject.toml +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/scripts/measure_startup.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/scripts/run_all_tests.sh +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/test-data/generate.sh +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/__init__.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/_http_fixtures.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/catalog/__init__.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/catalog/test_catalog_interface.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/catalog/test_client_catalog.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/catalog/test_column_statistics.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/catalog/test_declarative.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/catalog/test_example_worker_catalog.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/catalog/test_integration.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/catalog/test_required_filters.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/catalog/test_scan_branches.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/catalog/test_serialization.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/catalog/test_setting.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/catalog/test_storage.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/catalog/test_time_travel.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/catalog/test_writable_table.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/client/__init__.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/client/test_broken_pipe.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/client/test_cli.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/client/test_cli_catalog_functions.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/client/test_worker_debug.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/conformance/__init__.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/conformance/_stub.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/conformance/conftest.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/conformance/test_accumulate.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/conformance/test_aggregate.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/conformance/test_attach.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/conformance/test_bearer_auth.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/conformance/test_const_param_enforcement.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/conformance/test_container.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/conformance/test_copy_from.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/conformance/test_copy_to.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/conformance/test_directory_parity.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/conformance/test_function_inventory.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/conformance/test_github.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/conformance/test_http_client.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/conformance/test_http_external_location.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/conformance/test_http_upload_url.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/conformance/test_macro.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/conformance/test_overload.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/conformance/test_protocol_inventory.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/conformance/test_protocol_version.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/conformance/test_resumable_scan.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/conformance/test_scalar.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/conformance/test_scalar_attach_opaque_data.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/conformance/test_secret.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/conformance/test_settings.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/conformance/test_table.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/conformance/test_table_in_out.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/conformance/test_view.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/conformance/test_writable.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/conftest.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/scalar/__init__.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/scalar/test_bernoulli_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/scalar/test_binary_packet_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/scalar/test_client.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/scalar/test_conditional_message_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/scalar/test_hash_seed_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/scalar/test_multiply_by_setting_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/scalar/test_multiply_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/scalar/test_random_bytes_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/scalar/test_return_secret_value_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/table/__init__.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/table/generator/__init__.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/table/generator/test_constant_columns_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/table/generator/test_double_sequence_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/table/generator/test_exception_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/table/generator/test_filter_echo_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/table/generator/test_logging_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/table/generator/test_nested_sequence_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/table/generator/test_partitioned_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/table/generator/test_projected_data_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/table/generator/test_sequence_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/table/generator/test_settings_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/table/generator/test_struct_settings_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/table/generator/test_ten_thousand_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/table/test_wide_columns.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/table_in_out/__init__.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/table_in_out/generator/__init__.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/table_in_out/generator/test_buffer_input_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/table_in_out/generator/test_echo_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/table_in_out/generator/test_exception_functions.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/table_in_out/generator/test_filter_by_setting_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/table_in_out/generator/test_repeat_inputs_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/table_in_out/generator/test_substream_partial_sum_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/table_in_out/generator/test_sum_all_columns_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/table_in_out/test_blended_metadata.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/table_in_out/test_client.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/test_access_log_audit.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/test_aggregate_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/test_argument_spec.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/test_auth.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/test_bind_exceptions.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/test_bind_request_at_clause.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/test_bound_storage_conformance.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/test_cache_control.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/test_call_state_attach.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/test_catalog_auth_binding.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/test_copy_from_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/test_copy_to_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/test_docstrings.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/test_documentation_examples.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/test_example_function_arg_types.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/test_examples_workers.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/test_exception_handling.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/test_exceptions.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/test_filter_pushdown.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/test_filter_pushdown_extension.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/test_function_storage.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/test_function_storage_azure_sql.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/test_function_storage_cf_do.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/test_function_storage_cf_do_integration.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/test_function_storage_conformance.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/test_generated_cpp_constants.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/test_generated_cpp_protocol_version.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/test_generated_cpp_request_builders.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/test_generated_cpp_schemas.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/test_generated_cpp_secret.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/test_generated_go_schemas.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/test_generated_protocol_version.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/test_generated_schemas_cross_lang.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/test_generated_ts_client.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/test_generated_ts_schemas.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/test_http_demo_storage.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/test_http_s3_offload_input.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/test_http_s3_offload_output.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/test_metadata.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/test_mypy_consolidated.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/test_nest_tensor.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/test_otel.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/test_projection_enforcement.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/test_projection_repro.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/test_protocol_classes.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/test_resolved_secrets.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/test_schema_scoped_functions.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/test_schema_utils.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/test_setting_secret_annotations.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/test_signing_key.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/test_table_buffering_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/test_table_function_dynamic_to_string.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/test_tcp_transport.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/test_type_bounds.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/test_union_argument.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/test_worker.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/test_worker_cli.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/test_worker_page.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/transactor/__init__.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/tests/transactor/test_transactor.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/__init__.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_duckdb.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_storage_profile.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/__init__.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/accumulate/__init__.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/accumulate/worker.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/aggregate/__init__.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/aggregate/_common.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/aggregate/basic.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/aggregate/dynamic.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/aggregate/generic.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/aggregate/listagg.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/aggregate/percentile.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/aggregate/same_name.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/aggregate/secret_typed.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/aggregate/streaming.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/aggregate/varargs.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/aggregate/window.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/bad_enum.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/bad_protocol.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/cancellable.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/catalog.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/companion.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/copy_from.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/copy_to.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/global_functions.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/narrow_bind/__init__.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/narrow_bind/worker.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/nest_tensor.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/orchard_catalog.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/projection_repro/__init__.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/projection_repro/worker.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/scalar/__init__.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/scalar/_common.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/scalar/arithmetic.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/scalar/bench_ladder.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/scalar/binary.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/scalar/formatting.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/scalar/geo.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/scalar/null_handling.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/scalar/random_demo.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/scalar/same_name.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/scalar/settings_secrets.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/scalar/type_info.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/schema_reconcile/__init__.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/schema_reconcile/worker.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/simple_writable.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/table/__init__.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/table/_common.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/table/batch_index.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/table/batch_index_broken.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/table/cache.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/table/catalog_scans.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/table/filters.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/table/late_materialization.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/table/make_series.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/table/misc.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/table/order_modes.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/table/pairs.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/table/partition_columns.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/table/partition_columns_broken.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/table/profiling_example.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/table/required_filters.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/table/same_name_cached.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/table/sequence.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/table/settings.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/table/transaction_storage.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/table/tt_pushdown.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/table/typed_probe.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/table/versioned.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/table/wide.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/table_in_out.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/table_in_out_same_name.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/twin_catalogs.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/versioned.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/versioned_tables.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/worker.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/writable/__init__.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/writable/generic.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/writable/table.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/_test_fixtures/writable/worker.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/aggregate_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/argument_spec.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/arguments.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/attach_header.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/auth.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/cache_control.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/catalog/__init__.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/catalog/descriptors.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/catalog/duckdb_statistics.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/catalog/secret_type.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/catalog/setting.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/catalog/storage.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/client/__init__.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/client/catalog_mixin.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/client/cli.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/client/cli_catalog.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/client/cli_schema.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/client/cli_table.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/client/cli_transaction.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/client/cli_utils.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/client/cli_view.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/client/client.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/codegen/__init__.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/codegen/_common.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/codegen/cpp_constants.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/codegen/cpp_protocol_version.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/codegen/cpp_request_builders.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/codegen/cpp_schemas.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/codegen/cpp_secret_protocol_version.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/codegen/cpp_secret_request_builders.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/codegen/cpp_secret_schemas.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/codegen/go_schemas.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/codegen/protocol_version.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/codegen/ts_client.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/codegen/ts_schemas.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/copy_from_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/copy_to_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/exceptions.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/function_storage.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/function_storage_azure_sql.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/function_storage_cf_do.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/http/__init__.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/http/demo_storage.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/http/worker_page.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/invocation.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/logging_config.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/meta_worker.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/metadata.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/otel.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/profiling.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/protocol.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/protocol_version.txt +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/py.typed +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/scalar_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/schema_utils.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/secret_protocol.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/secret_service.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/table_buffering_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/table_filter_pushdown.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/table_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/table_in_out_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/transactor/__init__.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/transactor/_duckdb_compat.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/transactor/client.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/transactor/protocol.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/transactor/server.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/vgi/worker.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.27.0}/wrangler.jsonc +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: vgi-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.27.0
|
|
4
4
|
Summary: Vector Gateway Interface - Connect DuckDB to external programs via Apache Arrow
|
|
5
5
|
Project-URL: Homepage, https://query.farm
|
|
6
6
|
Project-URL: Repository, https://github.com/Query-farm/vgi-python
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Release runbook — describe.json retirement + attach-option `required`
|
|
2
|
+
|
|
3
|
+
All code is committed on `main` in each repo and green. Nothing is pushed.
|
|
4
|
+
This is the ordering and the per-repo trigger; fill in `X.Y.Z` per package.
|
|
5
|
+
|
|
6
|
+
**Version decision needed first.** Three transports drop `DescribeProvider`
|
|
7
|
+
from their public API (breaking): `@query-farm/vgi-rpc`, `vgi-rpc` (Rust),
|
|
8
|
+
`farm.query:vgirpc`. Choose major or minor per your compatibility policy — it
|
|
9
|
+
cascades into the implementation pins below.
|
|
10
|
+
|
|
11
|
+
## 1. Transports (must publish first)
|
|
12
|
+
|
|
13
|
+
| Repo | Trigger | Version lives in |
|
|
14
|
+
|---|---|---|
|
|
15
|
+
| vgi-rpc-typescript | `gh release create vX.Y.Z` (release: published) | `package.json` (+ `bun install` to sync `bun.lock`) |
|
|
16
|
+
| vgi-rpc-rust | tag push — `gh release create vX.Y.Z` | `[workspace.package] version`; `cargo update -w` |
|
|
17
|
+
| vgi-rpc-java | `gh release create vX.Y.Z` (release: published) | `build.gradle.kts` |
|
|
18
|
+
| vgi-rpc-go | tag push — `gh release create vX.Y.Z` | none (module proxy reads the tag) |
|
|
19
|
+
|
|
20
|
+
Then wait for index propagation: npm, `https://index.crates.io/3/v/vgi-rpc`,
|
|
21
|
+
Maven Central, Go proxy.
|
|
22
|
+
|
|
23
|
+
## 2. Implementation dependency pins
|
|
24
|
+
|
|
25
|
+
These currently cannot build against published transports:
|
|
26
|
+
|
|
27
|
+
- **vgi-typescript** — peer range is `>=0.18.1 <0.19.0`; widen to the new
|
|
28
|
+
transport version. `LandingInfo` resolves only once the transport publishes.
|
|
29
|
+
- **vgi-rust** — workspace dep already bumped to `vgi-rpc 0.20.0`; adjust if you
|
|
30
|
+
pick a different number. The `[patch.crates-io]` block stays uncommitted (it
|
|
31
|
+
is documented in `Cargo.toml` for local co-development only).
|
|
32
|
+
- **vgi-java** — bump `api("farm.query:vgirpc:X")` and the `VGI_RPC_JAVA_REF`
|
|
33
|
+
pin in `.github/workflows/integration.yml`. (`landing.yml` was deleted with
|
|
34
|
+
the contract it checked.)
|
|
35
|
+
- **vgi-go** — self-contained; no pin, but it needs the new vgi-rpc-go tag.
|
|
36
|
+
|
|
37
|
+
## 3. Implementations
|
|
38
|
+
|
|
39
|
+
| Repo | Trigger | Version lives in |
|
|
40
|
+
|---|---|---|
|
|
41
|
+
| vgi-python | `gh release create vX.Y.Z --target main` (PyPI on release) | root `pyproject.toml`; `uv lock` |
|
|
42
|
+
| vgi-go | `gh release create vX.Y.Z` (tag → module proxy) | none |
|
|
43
|
+
| vgi-rust | `gh release create vX.Y.Z` (tag → crates.io) | `[workspace.package] version` |
|
|
44
|
+
| vgi-typescript | **`git tag vX.Y.Z && git push origin vX.Y.Z`** — pushing the bump to main does NOT publish and fails silently | `package.json`; `bun install` |
|
|
45
|
+
| vgi-java | `gh release create vX.Y.Z` (Maven Central on release) | `build.gradle.kts` |
|
|
46
|
+
|
|
47
|
+
## 4. Frontend
|
|
48
|
+
|
|
49
|
+
- **vgi-web-frontend** — auto-deploys on push to `main`. Push last, so no worker
|
|
50
|
+
is serving a page newer than its vendored bundle.
|
|
51
|
+
|
|
52
|
+
## Verified before release
|
|
53
|
+
|
|
54
|
+
- vgi-python 2175 pass / 99 skip; ruff + mypy clean
|
|
55
|
+
- vgi-go `go test ./...` clean; vet + gofmt clean
|
|
56
|
+
- vgi-rust tests pass; clippy `-D warnings` clean
|
|
57
|
+
- vgi-java `:vgi:test` passes
|
|
58
|
+
- vgi-rpc-typescript 715 pass / 26 fail — exactly the pre-change baseline
|
|
59
|
+
(confirmed by running the suite at the parent commit in a worktree)
|
|
60
|
+
- Landing page verified in a browser against all four language workers:
|
|
61
|
+
Python 221 functions, Go 221, Rust 654, Java 219 — each 2 schemas, 59 tables,
|
|
62
|
+
3 views, with lazy columns loading.
|
|
63
|
+
|
|
64
|
+
## Not done, tracked
|
|
65
|
+
|
|
66
|
+
- `vgi-rpc-typescript` has 26 pre-existing test failures (integration/streaming;
|
|
67
|
+
unrelated to this work, present before it).
|
|
68
|
+
- `~/vgi-java/CLAUDE.md` still references the landing-conformance workflow in
|
|
69
|
+
its historical notes; the workflow itself is deleted.
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# Copyright 2025, 2026 Query Farm LLC - https://query.farm
|
|
2
|
+
|
|
3
|
+
"""Tests for the AttachOption descriptor, its ``required`` flag, and validation."""
|
|
4
|
+
|
|
5
|
+
from typing import Annotated
|
|
6
|
+
|
|
7
|
+
import pyarrow as pa
|
|
8
|
+
import pytest
|
|
9
|
+
from vgi_rpc.utils import deserialize_record_batch, serialize_record_batch_bytes
|
|
10
|
+
|
|
11
|
+
from vgi.catalog._descriptor_spec import _SpecBase
|
|
12
|
+
from vgi.catalog.attach_option import (
|
|
13
|
+
AttachOption,
|
|
14
|
+
AttachOptionSpec,
|
|
15
|
+
MissingAttachOptionsError,
|
|
16
|
+
extract_attach_option_specs,
|
|
17
|
+
validate_required_attach_options,
|
|
18
|
+
)
|
|
19
|
+
from vgi.catalog.catalog_interface import ReadOnlyCatalogInterface
|
|
20
|
+
from vgi.catalog.setting import SettingSpec
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class _Options:
|
|
24
|
+
"""Attach options mixing defaulted and required entries."""
|
|
25
|
+
|
|
26
|
+
region: Annotated[str, AttachOption(desc="AWS region")] = "us-east-1"
|
|
27
|
+
# No class-level assignment: nothing to fall back to, so the caller must supply it.
|
|
28
|
+
api_key: Annotated[str, AttachOption(desc="API key", required=True)]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _roundtrip(spec: AttachOptionSpec) -> AttachOptionSpec:
|
|
32
|
+
batch, _ = deserialize_record_batch(spec.serialize())
|
|
33
|
+
return AttachOptionSpec.deserialize(batch)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class TestRequiredExtraction:
|
|
37
|
+
"""Extraction of the ``required`` flag from Annotated declarations."""
|
|
38
|
+
|
|
39
|
+
def test_defaults_to_false(self) -> None:
|
|
40
|
+
"""An option that isn't declared required isn't required."""
|
|
41
|
+
specs = {s.name: s for s in extract_attach_option_specs(_Options)}
|
|
42
|
+
assert specs["region"].required is False
|
|
43
|
+
assert specs["region"].default == "us-east-1"
|
|
44
|
+
|
|
45
|
+
def test_required_option_extracted(self) -> None:
|
|
46
|
+
"""``required=True`` survives extraction, with no default."""
|
|
47
|
+
specs = {s.name: s for s in extract_attach_option_specs(_Options)}
|
|
48
|
+
assert specs["api_key"].required is True
|
|
49
|
+
assert specs["api_key"].default is None
|
|
50
|
+
|
|
51
|
+
def test_required_with_default_rejected(self) -> None:
|
|
52
|
+
"""A required option that also defaults is contradictory."""
|
|
53
|
+
with pytest.raises(ValueError, match="required but also declares a default"):
|
|
54
|
+
AttachOptionSpec(name="api_key", desc="", type=pa.string(), default="fallback", required=True)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class TestWireFormat:
|
|
58
|
+
"""Serialization of the added ``required`` column."""
|
|
59
|
+
|
|
60
|
+
@pytest.mark.parametrize("required", [True, False], ids=["required", "optional"])
|
|
61
|
+
def test_roundtrip_preserves_required(self, required: bool) -> None:
|
|
62
|
+
"""``required`` survives an Arrow IPC round trip in both states."""
|
|
63
|
+
spec = AttachOptionSpec(name="api_key", desc="API key", type=pa.string(), default=None, required=required)
|
|
64
|
+
assert _roundtrip(spec).required is required
|
|
65
|
+
|
|
66
|
+
def test_roundtrip_preserves_shared_fields(self) -> None:
|
|
67
|
+
"""Widening the schema doesn't disturb the four shared columns."""
|
|
68
|
+
spec = AttachOptionSpec(name="region", desc="AWS region", type=pa.string(), default="us-east-1")
|
|
69
|
+
restored = _roundtrip(spec)
|
|
70
|
+
assert (restored.name, restored.desc, restored.type, restored.default) == (
|
|
71
|
+
"region",
|
|
72
|
+
"AWS region",
|
|
73
|
+
pa.string(),
|
|
74
|
+
"us-east-1",
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
def test_setting_wire_format_unchanged(self) -> None:
|
|
78
|
+
"""``Setting`` keeps the original four columns; only AttachOption widened."""
|
|
79
|
+
assert SettingSpec.ARROW_SCHEMA.names == ["name", "description", "type", "default_value"]
|
|
80
|
+
assert AttachOptionSpec.ARROW_SCHEMA.names == [
|
|
81
|
+
"name",
|
|
82
|
+
"description",
|
|
83
|
+
"type",
|
|
84
|
+
"default_value",
|
|
85
|
+
"required",
|
|
86
|
+
]
|
|
87
|
+
|
|
88
|
+
def test_reads_batch_without_required_column(self) -> None:
|
|
89
|
+
"""A peer that predates the column yields ``required=False``, not an error."""
|
|
90
|
+
type_bytes = pa.schema([pa.field("value", pa.string())]).serialize().to_pybytes()
|
|
91
|
+
legacy = pa.RecordBatch.from_pylist(
|
|
92
|
+
[{"name": "region", "description": "AWS region", "type": type_bytes, "default_value": None}],
|
|
93
|
+
schema=_SpecBase.ARROW_SCHEMA,
|
|
94
|
+
)
|
|
95
|
+
restored = AttachOptionSpec.deserialize(legacy)
|
|
96
|
+
assert restored.required is False
|
|
97
|
+
assert restored.name == "region"
|
|
98
|
+
|
|
99
|
+
def test_older_reader_ignores_required_column(self) -> None:
|
|
100
|
+
"""A four-column reader still reads a five-column batch (forward compatible)."""
|
|
101
|
+
spec = AttachOptionSpec(name="api_key", desc="API key", type=pa.string(), default=None, required=True)
|
|
102
|
+
batch, _ = deserialize_record_batch(spec.serialize())
|
|
103
|
+
legacy_read = SettingSpec.deserialize(batch)
|
|
104
|
+
assert legacy_read.name == "api_key"
|
|
105
|
+
assert legacy_read.default is None
|
|
106
|
+
|
|
107
|
+
def test_null_required_reads_as_false(self) -> None:
|
|
108
|
+
"""An explicit null in the nullable column means "not required"."""
|
|
109
|
+
type_bytes = pa.schema([pa.field("value", pa.string())]).serialize().to_pybytes()
|
|
110
|
+
batch = pa.RecordBatch.from_pylist(
|
|
111
|
+
[
|
|
112
|
+
{
|
|
113
|
+
"name": "region",
|
|
114
|
+
"description": "",
|
|
115
|
+
"type": type_bytes,
|
|
116
|
+
"default_value": None,
|
|
117
|
+
"required": None,
|
|
118
|
+
}
|
|
119
|
+
],
|
|
120
|
+
schema=AttachOptionSpec.ARROW_SCHEMA,
|
|
121
|
+
)
|
|
122
|
+
# Round-trip through IPC the way a real peer's bytes would arrive.
|
|
123
|
+
restored, _ = deserialize_record_batch(serialize_record_batch_bytes(batch))
|
|
124
|
+
assert AttachOptionSpec.deserialize(restored).required is False
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class TestValidateRequiredAttachOptions:
|
|
128
|
+
"""The shared validation helper."""
|
|
129
|
+
|
|
130
|
+
SPECS = [
|
|
131
|
+
AttachOptionSpec(name="region", desc="", type=pa.string(), default="us-east-1"),
|
|
132
|
+
AttachOptionSpec(name="api_key", desc="", type=pa.string(), default=None, required=True),
|
|
133
|
+
]
|
|
134
|
+
|
|
135
|
+
def test_missing_required_raises(self) -> None:
|
|
136
|
+
"""Omitting a required option raises with the names attached."""
|
|
137
|
+
with pytest.raises(MissingAttachOptionsError) as exc:
|
|
138
|
+
validate_required_attach_options("demo", self.SPECS, {"region": "eu-west-1"})
|
|
139
|
+
assert exc.value.missing == ["api_key"]
|
|
140
|
+
assert "'api_key'" in str(exc.value)
|
|
141
|
+
|
|
142
|
+
def test_supplied_required_passes(self) -> None:
|
|
143
|
+
"""Supplying the required option is enough; defaults cover the rest."""
|
|
144
|
+
validate_required_attach_options("demo", self.SPECS, {"api_key": "secret"})
|
|
145
|
+
|
|
146
|
+
def test_case_insensitive_match(self) -> None:
|
|
147
|
+
"""DuckDB lowercases ATTACH option keys; matching follows."""
|
|
148
|
+
validate_required_attach_options("demo", self.SPECS, {"API_KEY": "secret"})
|
|
149
|
+
|
|
150
|
+
def test_no_required_specs_always_passes(self) -> None:
|
|
151
|
+
"""A catalog with no required options accepts an empty mapping."""
|
|
152
|
+
validate_required_attach_options("demo", [self.SPECS[0]], {})
|
|
153
|
+
|
|
154
|
+
def test_multiple_missing_reported_together(self) -> None:
|
|
155
|
+
"""Every missing option is reported in one error, not one at a time."""
|
|
156
|
+
specs = [
|
|
157
|
+
AttachOptionSpec(name="api_key", desc="", type=pa.string(), default=None, required=True),
|
|
158
|
+
AttachOptionSpec(name="tenant", desc="", type=pa.string(), default=None, required=True),
|
|
159
|
+
]
|
|
160
|
+
with pytest.raises(MissingAttachOptionsError) as exc:
|
|
161
|
+
validate_required_attach_options("demo", specs, {})
|
|
162
|
+
assert exc.value.missing == ["api_key", "tenant"]
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class _GatedCatalog(ReadOnlyCatalogInterface):
|
|
166
|
+
"""Read-only catalog that cannot be attached without ``api_key``."""
|
|
167
|
+
|
|
168
|
+
catalog_name = "gated"
|
|
169
|
+
attach_option_specs = [
|
|
170
|
+
AttachOptionSpec(name="api_key", desc="API key", type=pa.string(), default=None, required=True),
|
|
171
|
+
]
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
class TestCatalogAttachEnforcement:
|
|
175
|
+
"""The default ``catalog_attach`` refuses an attach missing required options."""
|
|
176
|
+
|
|
177
|
+
def test_attach_without_required_option_raises(self) -> None:
|
|
178
|
+
"""The attach fails loudly rather than yielding an empty-looking catalog."""
|
|
179
|
+
with pytest.raises(MissingAttachOptionsError, match="api_key"):
|
|
180
|
+
_GatedCatalog().catalog_attach(
|
|
181
|
+
name="gated", options={}, data_version_spec=None, implementation_version=None
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
def test_attach_with_required_option_succeeds(self) -> None:
|
|
185
|
+
"""Supplying the option attaches normally."""
|
|
186
|
+
result = _GatedCatalog().catalog_attach(
|
|
187
|
+
name="gated",
|
|
188
|
+
options={"api_key": "secret"},
|
|
189
|
+
data_version_spec=None,
|
|
190
|
+
implementation_version=None,
|
|
191
|
+
)
|
|
192
|
+
assert result.attach_opaque_data is not None
|
|
@@ -698,8 +698,9 @@ class TestCLI:
|
|
|
698
698
|
# Give server a moment to start
|
|
699
699
|
time.sleep(0.5)
|
|
700
700
|
|
|
701
|
-
# The landing surface: GET / serves the shared static page
|
|
702
|
-
#
|
|
701
|
+
# The landing surface: GET / serves the shared static page, which
|
|
702
|
+
# reads the catalog over the protocol using the client bundle the
|
|
703
|
+
# worker serves beside it. Worker identity rides on the status doc.
|
|
703
704
|
import json as _json
|
|
704
705
|
import urllib.request
|
|
705
706
|
|
|
@@ -709,10 +710,14 @@ class TestCLI:
|
|
|
709
710
|
assert resp.status == 200
|
|
710
711
|
assert b"vgi-landing-asset" in body
|
|
711
712
|
|
|
712
|
-
with urllib.request.urlopen(f"{url}
|
|
713
|
+
with urllib.request.urlopen(f"{url}?format=json", timeout=5) as resp:
|
|
713
714
|
doc = _json.loads(resp.read())
|
|
714
|
-
assert doc["worker"]
|
|
715
|
-
assert doc["
|
|
715
|
+
assert doc["worker"] == "ExampleWorker"
|
|
716
|
+
assert doc["lang"] == "python"
|
|
717
|
+
|
|
718
|
+
with urllib.request.urlopen(f"{url}vgi-client.js", timeout=5) as resp:
|
|
719
|
+
assert resp.status == 200
|
|
720
|
+
assert resp.headers["Content-Type"].startswith("text/javascript")
|
|
716
721
|
finally:
|
|
717
722
|
proc.terminate()
|
|
718
723
|
proc.wait(timeout=5)
|
|
@@ -31,7 +31,12 @@ from vgi_rpc import ArrowSerializableDataclass
|
|
|
31
31
|
from vgi_rpc.rpc import CallContext, OutputCollector
|
|
32
32
|
from vgi_rpc.utils import deserialize_record_batch, serialize_record_batch_bytes
|
|
33
33
|
|
|
34
|
-
from vgi.catalog.attach_option import
|
|
34
|
+
from vgi.catalog.attach_option import (
|
|
35
|
+
AttachOption,
|
|
36
|
+
AttachOptionSpec,
|
|
37
|
+
extract_attach_option_specs,
|
|
38
|
+
validate_required_attach_options,
|
|
39
|
+
)
|
|
35
40
|
from vgi.catalog.catalog_interface import (
|
|
36
41
|
AttachOpaqueData,
|
|
37
42
|
CatalogAttachResult,
|
|
@@ -53,6 +58,10 @@ __all__ = [
|
|
|
53
58
|
|
|
54
59
|
|
|
55
60
|
CATALOG_NAME = "attach_options"
|
|
61
|
+
# A second catalog from the same worker, declaring one option the caller must
|
|
62
|
+
# supply. Kept separate from CATALOG_NAME so the defaults-and-round-trip
|
|
63
|
+
# coverage above can keep attaching with no options at all.
|
|
64
|
+
REQUIRED_CATALOG_NAME = "attach_options_required"
|
|
56
65
|
_ATTACH_ID_SEP = b"\x00"
|
|
57
66
|
_UUID_BYTES = 16
|
|
58
67
|
|
|
@@ -109,9 +118,19 @@ class AttachOptions:
|
|
|
109
118
|
] = {"a": 1, "b": "x"}
|
|
110
119
|
|
|
111
120
|
|
|
121
|
+
class RequiredAttachOptions:
|
|
122
|
+
"""Attach-time options for the catalog that refuses an anonymous ATTACH."""
|
|
123
|
+
|
|
124
|
+
# No class-level assignment: there is no default to fall back on, so the
|
|
125
|
+
# caller has to supply it.
|
|
126
|
+
api_key: Annotated[str, AttachOption(desc="API key", required=True)]
|
|
127
|
+
region: Annotated[str, AttachOption(desc="Region")] = "us-east-1"
|
|
128
|
+
|
|
129
|
+
|
|
112
130
|
# Resolve once at import time; used both to build the echo function's output schema
|
|
113
131
|
# and to backfill defaults in catalog_attach.
|
|
114
132
|
_ATTACH_OPTION_SPECS: list[AttachOptionSpec] = extract_attach_option_specs(AttachOptions)
|
|
133
|
+
_REQUIRED_OPTION_SPECS: list[AttachOptionSpec] = extract_attach_option_specs(RequiredAttachOptions)
|
|
115
134
|
|
|
116
135
|
_ECHO_SCHEMA: pa.Schema = schema({spec.name: spec.type for spec in _ATTACH_OPTION_SPECS})
|
|
117
136
|
|
|
@@ -244,8 +263,14 @@ class AttachOptionsCatalog(ReadOnlyCatalogInterface):
|
|
|
244
263
|
ctx: CallContext | None = None,
|
|
245
264
|
) -> CatalogAttachResult:
|
|
246
265
|
del data_version_spec, implementation_version, ctx
|
|
247
|
-
if name
|
|
248
|
-
raise ValueError(f"Unknown catalog: {name!r}. Available: {CATALOG_NAME}")
|
|
266
|
+
if name not in (CATALOG_NAME, REQUIRED_CATALOG_NAME):
|
|
267
|
+
raise ValueError(f"Unknown catalog: {name!r}. Available: {CATALOG_NAME}, {REQUIRED_CATALOG_NAME}")
|
|
268
|
+
|
|
269
|
+
if name == REQUIRED_CATALOG_NAME:
|
|
270
|
+
# This interface overrides catalog_attach, so the base class's check
|
|
271
|
+
# never runs — an overrider that declares required options calls the
|
|
272
|
+
# shared validator itself.
|
|
273
|
+
validate_required_attach_options(name, _REQUIRED_OPTION_SPECS, options)
|
|
249
274
|
|
|
250
275
|
attach_opaque_data = _encode_attach_opaque_data(options)
|
|
251
276
|
|
|
@@ -270,6 +295,12 @@ class AttachOptionsCatalog(ReadOnlyCatalogInterface):
|
|
|
270
295
|
data_version_spec=None,
|
|
271
296
|
attach_option_specs=[spec.serialize() for spec in _ATTACH_OPTION_SPECS],
|
|
272
297
|
),
|
|
298
|
+
CatalogInfo(
|
|
299
|
+
name=REQUIRED_CATALOG_NAME,
|
|
300
|
+
implementation_version=None,
|
|
301
|
+
data_version_spec=None,
|
|
302
|
+
attach_option_specs=[spec.serialize() for spec in _REQUIRED_OPTION_SPECS],
|
|
303
|
+
),
|
|
273
304
|
]
|
|
274
305
|
|
|
275
306
|
def catalog_version(
|
|
@@ -412,25 +412,16 @@ def main() -> None:
|
|
|
412
412
|
mw._exempt_prefixes = (*mw._exempt_prefixes, f"{prefix}/__blobs__")
|
|
413
413
|
|
|
414
414
|
if describe:
|
|
415
|
-
# Standardized landing surface
|
|
416
|
-
#
|
|
417
|
-
from vgi.http.landing_page import
|
|
418
|
-
ColumnsResource,
|
|
419
|
-
DescribeJsonResource,
|
|
420
|
-
LandingPageResource,
|
|
421
|
-
)
|
|
415
|
+
# Standardized landing surface: the shared static page plus the
|
|
416
|
+
# browser client build it reads the catalog with.
|
|
417
|
+
from vgi.http.landing_page import ClientBundleResource, LandingPageResource
|
|
422
418
|
|
|
423
419
|
oauth_active = oauth_metadata is not None and getattr(oauth_metadata, "client_id", None) is not None
|
|
424
420
|
server_id = getattr(server, "server_id", "")
|
|
425
|
-
wsgi_app.add_route(prefix or "/", LandingPageResource(server_id=server_id))
|
|
426
|
-
wsgi_app.add_route(
|
|
427
|
-
f"{prefix}/describe.json",
|
|
428
|
-
DescribeJsonResource(ExampleWorker, oauth=oauth_active, server_id=server_id),
|
|
429
|
-
)
|
|
430
421
|
wsgi_app.add_route(
|
|
431
|
-
|
|
432
|
-
ColumnsResource(ExampleWorker),
|
|
422
|
+
prefix or "/", LandingPageResource(ExampleWorker, server_id=server_id, oauth=oauth_active)
|
|
433
423
|
)
|
|
424
|
+
wsgi_app.add_route(f"{prefix}/vgi-client.js", ClientBundleResource())
|
|
434
425
|
|
|
435
426
|
# vgi_rpc's make_wsgi_app already advertises VGI-Max-Request-Bytes and
|
|
436
427
|
# installs a Falcon middleware enforcing it with a structured 413 that
|
|
@@ -5,9 +5,16 @@
|
|
|
5
5
|
[`Setting`][vgi.catalog.setting.Setting] (session-level, resent every call) and
|
|
6
6
|
[`AttachOption`][vgi.catalog.attach_option.AttachOption] (delivered once at
|
|
7
7
|
``catalog_attach``) are declared the same way — an `Annotated`-hint descriptor
|
|
8
|
-
plus a serializable spec
|
|
8
|
+
plus a serializable spec sharing the same four Arrow IPC columns. This module
|
|
9
9
|
holds the machinery both share; the two public modules subclass it so their
|
|
10
|
-
names
|
|
10
|
+
names and behaviour are unchanged.
|
|
11
|
+
|
|
12
|
+
A subclass may widen its wire format with additional columns — `AttachOption`
|
|
13
|
+
adds ``required`` — via :meth:`_SpecBase._extra_row` /
|
|
14
|
+
:meth:`_SpecBase._extra_kwargs` and :meth:`_DescriptorBase.extra_spec_kwargs`.
|
|
15
|
+
Because both directions read by column name, the widening is compatible in both
|
|
16
|
+
directions: a reader ignores columns it doesn't know, and a reader whose peer
|
|
17
|
+
never wrote the column falls back to the field default.
|
|
11
18
|
"""
|
|
12
19
|
|
|
13
20
|
from __future__ import annotations
|
|
@@ -97,6 +104,24 @@ class _SpecBase:
|
|
|
97
104
|
] # type: ignore[arg-type]
|
|
98
105
|
)
|
|
99
106
|
|
|
107
|
+
def _extra_row(self) -> dict[str, Any]:
|
|
108
|
+
"""Extra wire columns this spec contributes beyond the shared four.
|
|
109
|
+
|
|
110
|
+
Subclasses that widen :data:`ARROW_SCHEMA` return the matching values
|
|
111
|
+
here. Keys must exist in the subclass's schema.
|
|
112
|
+
"""
|
|
113
|
+
return {}
|
|
114
|
+
|
|
115
|
+
@classmethod
|
|
116
|
+
def _extra_kwargs(cls, row: dict[str, object]) -> dict[str, Any]:
|
|
117
|
+
"""Constructor kwargs read from the extra wire columns.
|
|
118
|
+
|
|
119
|
+
``row`` is built from the *batch's* schema, so a column a peer never
|
|
120
|
+
wrote is simply absent — read with ``.get()`` and supply the default.
|
|
121
|
+
"""
|
|
122
|
+
del row
|
|
123
|
+
return {}
|
|
124
|
+
|
|
100
125
|
def serialize(self) -> bytes:
|
|
101
126
|
"""Serialize to Arrow IPC bytes."""
|
|
102
127
|
# Serialize type as a single-field schema
|
|
@@ -116,6 +141,7 @@ class _SpecBase:
|
|
|
116
141
|
"description": self.desc,
|
|
117
142
|
"type": type_bytes,
|
|
118
143
|
"default_value": default_bytes,
|
|
144
|
+
**self._extra_row(),
|
|
119
145
|
}
|
|
120
146
|
],
|
|
121
147
|
schema=self.ARROW_SCHEMA,
|
|
@@ -147,6 +173,7 @@ class _SpecBase:
|
|
|
147
173
|
desc=cast(str, row["description"]),
|
|
148
174
|
type=data_type,
|
|
149
175
|
default=default,
|
|
176
|
+
**cls._extra_kwargs(row),
|
|
150
177
|
)
|
|
151
178
|
|
|
152
179
|
|
|
@@ -179,6 +206,15 @@ class _DescriptorBase:
|
|
|
179
206
|
return self
|
|
180
207
|
return getattr(type(obj), self._name, None)
|
|
181
208
|
|
|
209
|
+
def extra_spec_kwargs(self) -> dict[str, Any]:
|
|
210
|
+
"""Extra keyword arguments this descriptor passes to its spec factory.
|
|
211
|
+
|
|
212
|
+
The base declaration carries only ``name``/``desc``/``type``/``default``;
|
|
213
|
+
a subclass adding a field returns it here so ``_extract_specs`` stays
|
|
214
|
+
shared.
|
|
215
|
+
"""
|
|
216
|
+
return {}
|
|
217
|
+
|
|
182
218
|
|
|
183
219
|
def _extract_specs[D: _DescriptorBase, S: _SpecBase](
|
|
184
220
|
declaring_cls: type,
|
|
@@ -240,6 +276,7 @@ def _extract_specs[D: _DescriptorBase, S: _SpecBase](
|
|
|
240
276
|
desc=descriptor.desc,
|
|
241
277
|
type=arrow_type,
|
|
242
278
|
default=default,
|
|
279
|
+
**descriptor.extra_spec_kwargs(),
|
|
243
280
|
)
|
|
244
281
|
)
|
|
245
282
|
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# Copyright 2025, 2026 Query Farm LLC - https://query.farm
|
|
2
|
+
|
|
3
|
+
"""AttachOption descriptor for declarative worker attach-time options.
|
|
4
|
+
|
|
5
|
+
This module provides the AttachOption descriptor class for declaring options
|
|
6
|
+
that workers accept at ATTACH time (delivered once via catalog_attach, distinct
|
|
7
|
+
from session-level Settings resent on every call).
|
|
8
|
+
|
|
9
|
+
The declaration mirrors ``vgi.catalog.setting.Setting`` — same Arrow IPC spec
|
|
10
|
+
format, same Python type → Arrow mapping, same extractor shape — so both share
|
|
11
|
+
the machinery in ``vgi.catalog._descriptor_spec``.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from dataclasses import dataclass
|
|
15
|
+
from typing import Any, ClassVar
|
|
16
|
+
|
|
17
|
+
import pyarrow as pa
|
|
18
|
+
|
|
19
|
+
from vgi.catalog._descriptor_spec import _DescriptorBase, _extract_specs, _SpecBase
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"AttachOption",
|
|
23
|
+
"AttachOptionSpec",
|
|
24
|
+
"MissingAttachOptionsError",
|
|
25
|
+
"extract_attach_option_specs",
|
|
26
|
+
"validate_required_attach_options",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass(frozen=True)
|
|
31
|
+
class AttachOptionSpec(_SpecBase):
|
|
32
|
+
"""Extracted attach-option metadata for catalog discovery serialization.
|
|
33
|
+
|
|
34
|
+
The resolved form of an `AttachOption`. See
|
|
35
|
+
`_SpecBase` (in `vgi.catalog._descriptor_spec`) for the shared fields and
|
|
36
|
+
wire format.
|
|
37
|
+
|
|
38
|
+
Attributes:
|
|
39
|
+
required: The caller must supply this option at ``ATTACH`` time. A
|
|
40
|
+
catalog that cannot be attached without it advertises that fact at
|
|
41
|
+
discovery, so a client can say so before attempting the attach
|
|
42
|
+
rather than surfacing a failure that reads like an empty catalog.
|
|
43
|
+
Mutually exclusive with ``default`` — an option that falls back to
|
|
44
|
+
a value is by definition satisfiable without the caller.
|
|
45
|
+
ARROW_SCHEMA: The shared four columns plus ``required``.
|
|
46
|
+
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
required: bool = False
|
|
50
|
+
|
|
51
|
+
ARROW_SCHEMA: ClassVar[pa.Schema] = pa.schema(
|
|
52
|
+
[
|
|
53
|
+
*_SpecBase.ARROW_SCHEMA,
|
|
54
|
+
# Nullable, and appended last: a peer that predates this column
|
|
55
|
+
# reads the batch by name and simply doesn't see it.
|
|
56
|
+
pa.field("required", pa.bool_(), nullable=True),
|
|
57
|
+
]
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
def __post_init__(self) -> None:
|
|
61
|
+
"""Reject the contradictory ``required`` + ``default`` combination."""
|
|
62
|
+
if self.required and self.default is not None:
|
|
63
|
+
raise ValueError(
|
|
64
|
+
f"Attach option {self.name!r} is required but also declares a default "
|
|
65
|
+
f"({self.default!r}); an option with a default is always satisfiable "
|
|
66
|
+
"without the caller. Drop one."
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
def _extra_row(self) -> dict[str, Any]:
|
|
70
|
+
return {"required": self.required}
|
|
71
|
+
|
|
72
|
+
@classmethod
|
|
73
|
+
def _extra_kwargs(cls, row: dict[str, object]) -> dict[str, Any]:
|
|
74
|
+
# Absent column (older peer) and explicit null both mean "not required".
|
|
75
|
+
return {"required": bool(row.get("required") or False)}
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@dataclass
|
|
79
|
+
class AttachOption(_DescriptorBase):
|
|
80
|
+
"""Descriptor for declarative attach-option definitions using Annotated.
|
|
81
|
+
|
|
82
|
+
Use with Annotated type hints to declare options in a Worker's
|
|
83
|
+
AttachOptions inner class. The Arrow type is resolved from the base type
|
|
84
|
+
in the Annotated hint. See
|
|
85
|
+
`_DescriptorBase` (in `vgi.catalog._descriptor_spec`) for the
|
|
86
|
+
``desc`` and ``arrow_type`` attributes.
|
|
87
|
+
|
|
88
|
+
Declare a required option by omitting the class-level assignment that would
|
|
89
|
+
otherwise supply its default::
|
|
90
|
+
|
|
91
|
+
class AttachOptions:
|
|
92
|
+
region: Annotated[str, AttachOption(desc="AWS region")] = "us-east-1"
|
|
93
|
+
api_key: Annotated[str, AttachOption(desc="API key", required=True)]
|
|
94
|
+
|
|
95
|
+
Attributes:
|
|
96
|
+
required: The caller must supply this option at ``ATTACH`` time.
|
|
97
|
+
|
|
98
|
+
"""
|
|
99
|
+
|
|
100
|
+
required: bool = False
|
|
101
|
+
|
|
102
|
+
def extra_spec_kwargs(self) -> dict[str, Any]:
|
|
103
|
+
"""Carry ``required`` through to the `AttachOptionSpec`."""
|
|
104
|
+
return {"required": self.required}
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class MissingAttachOptionsError(ValueError):
|
|
108
|
+
"""Raised when ``catalog_attach`` omits options declared ``required``.
|
|
109
|
+
|
|
110
|
+
Carries the option names in :attr:`missing` so a caller can act on them
|
|
111
|
+
without parsing the message.
|
|
112
|
+
|
|
113
|
+
Attributes:
|
|
114
|
+
missing: Names of the required options that were not supplied.
|
|
115
|
+
|
|
116
|
+
"""
|
|
117
|
+
|
|
118
|
+
missing: list[str]
|
|
119
|
+
|
|
120
|
+
def __init__(self, catalog_name: str, missing: list[str]) -> None:
|
|
121
|
+
"""Build the error from the catalog name and the missing option names."""
|
|
122
|
+
self.missing = missing
|
|
123
|
+
joined = ", ".join(repr(name) for name in missing)
|
|
124
|
+
super().__init__(
|
|
125
|
+
f"Catalog {catalog_name!r} cannot be attached without the required "
|
|
126
|
+
f"option{'s' if len(missing) > 1 else ''} {joined}."
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def validate_required_attach_options(
|
|
131
|
+
catalog_name: str,
|
|
132
|
+
specs: list[AttachOptionSpec],
|
|
133
|
+
options: dict[str, Any],
|
|
134
|
+
) -> None:
|
|
135
|
+
"""Raise if any ``required`` spec has no corresponding entry in ``options``.
|
|
136
|
+
|
|
137
|
+
Option names are matched case-insensitively, mirroring DuckDB's handling of
|
|
138
|
+
``ATTACH`` option keys.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
catalog_name: Catalog being attached, for the error message.
|
|
142
|
+
specs: The attach-option specs the catalog declares.
|
|
143
|
+
options: Options the caller supplied at ``ATTACH`` time.
|
|
144
|
+
|
|
145
|
+
Raises:
|
|
146
|
+
MissingAttachOptionsError: If a required option was not supplied.
|
|
147
|
+
|
|
148
|
+
"""
|
|
149
|
+
supplied = {key.lower() for key in options}
|
|
150
|
+
missing = [spec.name for spec in specs if spec.required and spec.name.lower() not in supplied]
|
|
151
|
+
if missing:
|
|
152
|
+
raise MissingAttachOptionsError(catalog_name, missing)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def extract_attach_option_specs(options_cls: type) -> list[AttachOptionSpec]:
|
|
156
|
+
"""Extract AttachOptionSpec objects from an AttachOptions class."""
|
|
157
|
+
return _extract_specs(options_cls, descriptor_type=AttachOption, spec_factory=AttachOptionSpec)
|