vgi-python 0.26.0__tar.gz → 0.28.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.28.0}/PKG-INFO +2 -2
- vgi_python-0.28.0/RELEASE-RUNBOOK.md +69 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/docs/column-statistics.md +24 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/docs/how-to/function-patterns.md +25 -0
- vgi_python-0.28.0/examples/batch_index_worker.py +142 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/pyproject.toml +1 -1
- vgi_python-0.28.0/tests/catalog/test_attach_option.py +192 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/catalog/test_column_statistics.py +86 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/client/test_broken_pipe.py +20 -9
- vgi_python-0.28.0/tests/client/test_stop_force.py +101 -0
- vgi_python-0.28.0/tests/client/test_table_column_statistics.py +73 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/client/test_worker_debug.py +15 -13
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/table_in_out/test_client.py +10 -6
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/test_examples_workers.py +20 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/test_serve.py +10 -5
- {vgi_python-0.26.0 → vgi_python-0.28.0}/uv.lock +525 -336
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/attach_options.py +34 -3
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/http_server.py +5 -14
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/catalog/__init__.py +7 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/catalog/_descriptor_spec.py +39 -2
- vgi_python-0.28.0/vgi/catalog/attach_option.py +157 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/catalog/catalog_interface.py +68 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/client/catalog_mixin.py +53 -3
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/client/client.py +82 -26
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/http/landing.html +260 -18
- vgi_python-0.28.0/vgi/http/landing_page.py +112 -0
- vgi_python-0.28.0/vgi/http/vgi-client.js +23 -0
- {vgi_python-0.26.0 → vgi_python-0.28.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.28.0}/.gitattributes +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/.github/dependabot.yml +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/.github/styles/config/vocabularies/VGI/accept.txt +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/.github/workflows/ci.yml +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/.github/workflows/docs.yml +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/.github/workflows/integration.yml +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/.github/workflows/release.yml +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/.gitignore +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/.python-version +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/.vale.ini +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/CLAUDE.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/DOCS_ACCEPTANCE_CRITERIA.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/DOCS_REVIEW_RUBRIC.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/DOCS_USABILITY_TEST.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/LICENSE +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/README.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/SECURITY.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/ci/README.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/ci/preprocess-require.awk +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/ci/run-integration.sh +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/dist-vgi/.gitignore +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/docs/aggregate-functions.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/docs/api/arguments.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/docs/api/auth.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/docs/api/catalogs.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/docs/api/client.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/docs/api/exceptions.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/docs/api/filters.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/docs/api/functions.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/docs/api/http.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/docs/api/index.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/docs/api/metadata.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/docs/api/observability.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/docs/api/storage.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/docs/api/transactor.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/docs/api/worker.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/docs/argument-serialization.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/docs/assets/apple-touch-icon.png +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/docs/assets/favicon-16x16.png +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/docs/assets/favicon-32x32.png +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/docs/assets/favicon.ico +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/docs/assets/kinds/aggregate.svg +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/docs/assets/kinds/buffering.svg +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/docs/assets/kinds/scalar.svg +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/docs/assets/kinds/table-in-out.svg +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/docs/assets/kinds/table.svg +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/docs/assets/logo.png +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/docs/assets/social-card.png +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/docs/authentication.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/docs/catalog-interface.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/docs/cli.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/docs/concepts/index.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/docs/contributing-docs.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/docs/filter-pushdown.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/docs/generator-api.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/docs/global-functions.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/docs/how-to/catalogs.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/docs/how-to/http-auth.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/docs/how-to/index.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/docs/how-to/pushdown-and-statistics.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/docs/how-to/state-storage.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/docs/index.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/docs/lifecycle.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/docs/metadata.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/docs/overrides/main.html +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/docs/robots.txt +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/docs/shared-storage.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/docs/stylesheets/extra.css +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/docs/tutorial/index.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/docs/tutorial/scalar.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/docs/tutorial/table.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/docs/vgi-logo.png +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/examples/calc_scalar_worker.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/examples/calc_worker.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/examples/filter_worker.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/examples/greeting_scalar_worker.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/examples/row_count_worker.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/examples/series_streaming_worker.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/examples/sum_worker.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/mkdocs.yml +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/packages/vgi-fixtures/LICENSE +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/packages/vgi-fixtures/README.md +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/packages/vgi-fixtures/docker/Dockerfile +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/packages/vgi-fixtures/docker/docker-entrypoint.sh +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/packages/vgi-fixtures/pyproject.toml +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/scripts/measure_startup.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/scripts/run_all_tests.sh +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/test-data/generate.sh +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/__init__.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/_http_fixtures.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/catalog/__init__.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/catalog/test_catalog_interface.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/catalog/test_client_catalog.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/catalog/test_declarative.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/catalog/test_example_worker_catalog.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/catalog/test_integration.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/catalog/test_required_filters.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/catalog/test_scan_branches.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/catalog/test_serialization.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/catalog/test_setting.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/catalog/test_storage.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/catalog/test_time_travel.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/catalog/test_writable_table.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/client/__init__.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/client/test_cli.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/client/test_cli_catalog_functions.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/conformance/__init__.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/conformance/_stub.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/conformance/conftest.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/conformance/test_accumulate.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/conformance/test_aggregate.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/conformance/test_attach.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/conformance/test_bearer_auth.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/conformance/test_const_param_enforcement.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/conformance/test_container.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/conformance/test_copy_from.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/conformance/test_copy_to.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/conformance/test_directory_parity.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/conformance/test_function_inventory.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/conformance/test_github.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/conformance/test_http_client.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/conformance/test_http_external_location.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/conformance/test_http_upload_url.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/conformance/test_macro.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/conformance/test_overload.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/conformance/test_protocol_inventory.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/conformance/test_protocol_version.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/conformance/test_resumable_scan.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/conformance/test_scalar.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/conformance/test_scalar_attach_opaque_data.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/conformance/test_secret.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/conformance/test_settings.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/conformance/test_table.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/conformance/test_table_in_out.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/conformance/test_view.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/conformance/test_writable.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/conftest.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/scalar/__init__.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/scalar/test_bernoulli_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/scalar/test_binary_packet_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/scalar/test_client.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/scalar/test_conditional_message_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/scalar/test_hash_seed_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/scalar/test_multiply_by_setting_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/scalar/test_multiply_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/scalar/test_random_bytes_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/scalar/test_return_secret_value_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/table/__init__.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/table/generator/__init__.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/table/generator/test_constant_columns_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/table/generator/test_double_sequence_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/table/generator/test_exception_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/table/generator/test_filter_echo_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/table/generator/test_logging_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/table/generator/test_nested_sequence_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/table/generator/test_partitioned_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/table/generator/test_projected_data_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/table/generator/test_sequence_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/table/generator/test_settings_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/table/generator/test_struct_settings_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/table/generator/test_ten_thousand_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/table/test_wide_columns.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/table_in_out/__init__.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/table_in_out/generator/__init__.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/table_in_out/generator/test_buffer_input_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/table_in_out/generator/test_echo_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/table_in_out/generator/test_exception_functions.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/table_in_out/generator/test_filter_by_setting_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/table_in_out/generator/test_repeat_inputs_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/table_in_out/generator/test_substream_partial_sum_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/table_in_out/generator/test_sum_all_columns_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/table_in_out/test_blended_metadata.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/test_access_log_audit.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/test_aggregate_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/test_argument_spec.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/test_auth.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/test_bind_exceptions.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/test_bind_request_at_clause.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/test_bound_storage_conformance.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/test_cache_control.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/test_call_state_attach.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/test_catalog_auth_binding.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/test_copy_from_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/test_copy_to_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/test_docstrings.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/test_documentation_examples.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/test_example_function_arg_types.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/test_exception_handling.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/test_exceptions.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/test_filter_pushdown.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/test_filter_pushdown_extension.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/test_function_storage.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/test_function_storage_azure_sql.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/test_function_storage_cf_do.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/test_function_storage_cf_do_integration.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/test_function_storage_conformance.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/test_generated_cpp_constants.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/test_generated_cpp_protocol_version.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/test_generated_cpp_request_builders.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/test_generated_cpp_schemas.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/test_generated_cpp_secret.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/test_generated_go_schemas.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/test_generated_protocol_version.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/test_generated_schemas_cross_lang.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/test_generated_ts_client.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/test_generated_ts_schemas.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/test_http_demo_storage.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/test_http_s3_offload_input.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/test_http_s3_offload_output.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/test_metadata.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/test_mypy_consolidated.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/test_nest_tensor.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/test_otel.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/test_projection_enforcement.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/test_projection_repro.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/test_protocol_classes.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/test_resolved_secrets.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/test_schema_scoped_functions.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/test_schema_utils.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/test_setting_secret_annotations.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/test_signing_key.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/test_table_buffering_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/test_table_function_dynamic_to_string.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/test_tcp_transport.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/test_type_bounds.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/test_union_argument.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/test_worker.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/test_worker_cli.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/test_worker_page.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/transactor/__init__.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/tests/transactor/test_transactor.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/__init__.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_duckdb.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_storage_profile.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/__init__.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/accumulate/__init__.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/accumulate/worker.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/aggregate/__init__.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/aggregate/_common.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/aggregate/basic.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/aggregate/dynamic.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/aggregate/generic.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/aggregate/listagg.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/aggregate/percentile.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/aggregate/same_name.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/aggregate/secret_typed.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/aggregate/streaming.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/aggregate/varargs.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/aggregate/window.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/bad_enum.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/bad_protocol.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/cancellable.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/catalog.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/companion.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/copy_from.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/copy_to.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/global_functions.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/narrow_bind/__init__.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/narrow_bind/worker.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/nest_tensor.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/orchard_catalog.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/projection_repro/__init__.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/projection_repro/worker.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/scalar/__init__.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/scalar/_common.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/scalar/arithmetic.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/scalar/bench_ladder.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/scalar/binary.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/scalar/formatting.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/scalar/geo.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/scalar/null_handling.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/scalar/random_demo.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/scalar/same_name.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/scalar/settings_secrets.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/scalar/type_info.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/schema_reconcile/__init__.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/schema_reconcile/worker.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/simple_writable.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/table/__init__.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/table/_common.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/table/batch_index.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/table/batch_index_broken.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/table/cache.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/table/catalog_scans.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/table/filters.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/table/late_materialization.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/table/make_series.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/table/misc.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/table/order_modes.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/table/pairs.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/table/partition_columns.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/table/partition_columns_broken.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/table/profiling_example.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/table/required_filters.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/table/same_name_cached.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/table/sequence.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/table/settings.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/table/transaction_storage.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/table/tt_pushdown.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/table/typed_probe.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/table/versioned.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/table/wide.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/table_in_out.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/table_in_out_same_name.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/twin_catalogs.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/versioned.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/versioned_tables.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/worker.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/writable/__init__.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/writable/generic.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/writable/table.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/_test_fixtures/writable/worker.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/aggregate_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/argument_spec.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/arguments.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/attach_header.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/auth.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/cache_control.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/catalog/descriptors.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/catalog/duckdb_statistics.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/catalog/secret_type.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/catalog/setting.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/catalog/storage.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/client/__init__.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/client/cli.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/client/cli_catalog.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/client/cli_schema.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/client/cli_table.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/client/cli_transaction.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/client/cli_utils.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/client/cli_view.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/codegen/__init__.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/codegen/_common.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/codegen/cpp_constants.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/codegen/cpp_protocol_version.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/codegen/cpp_request_builders.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/codegen/cpp_schemas.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/codegen/cpp_secret_protocol_version.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/codegen/cpp_secret_request_builders.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/codegen/cpp_secret_schemas.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/codegen/go_schemas.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/codegen/protocol_version.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/codegen/ts_client.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/codegen/ts_schemas.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/copy_from_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/copy_to_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/exceptions.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/function_storage.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/function_storage_azure_sql.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/function_storage_cf_do.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/http/__init__.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/http/demo_storage.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/http/worker_page.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/invocation.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/logging_config.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/meta_worker.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/metadata.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/otel.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/profiling.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/protocol.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/protocol_version.txt +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/py.typed +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/scalar_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/schema_utils.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/secret_protocol.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/secret_service.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/table_buffering_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/table_filter_pushdown.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/table_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/table_in_out_function.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/transactor/__init__.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/transactor/_duckdb_compat.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/transactor/client.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/transactor/protocol.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/transactor/server.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/vgi/worker.py +0 -0
- {vgi_python-0.26.0 → vgi_python-0.28.0}/wrangler.jsonc +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: vgi-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.28.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.
|
|
@@ -180,6 +180,30 @@ Statistics are transmitted via the `catalog_table_column_statistics_get` RPC met
|
|
|
180
180
|
|
|
181
181
|
Cache TTL is carried as IPC batch `custom_metadata` with key `cache_max_age_seconds`.
|
|
182
182
|
|
|
183
|
+
## Reading Statistics From a Client
|
|
184
|
+
|
|
185
|
+
DuckDB is not the only caller. A pure-Python [`Client`][vgi.client.Client] reads the same statistics with
|
|
186
|
+
`table_column_statistics`, which issues the RPC and decodes the sparse-union batch for you:
|
|
187
|
+
|
|
188
|
+
```python test="skip"
|
|
189
|
+
stats = client.table_column_statistics(
|
|
190
|
+
attach_opaque_data=attach.attach_opaque_data,
|
|
191
|
+
schema_name="main",
|
|
192
|
+
name="events",
|
|
193
|
+
)
|
|
194
|
+
for column in stats:
|
|
195
|
+
print(column.column_name, column.min, column.max, column.distinct_count)
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Workers may also inline statistics on `TableInfo.column_statistics` rather than serving them
|
|
199
|
+
lazily; `TableInfo.supports_column_statistics` advertises which. Prefer the inlined copy when
|
|
200
|
+
it is present and fall back to the call above.
|
|
201
|
+
|
|
202
|
+
To decode bytes you already hold, use `deserialize_column_statistics` — the inverse of
|
|
203
|
+
`serialize_column_statistics`. The `cache_max_age_seconds` TTL travels in the batch's custom
|
|
204
|
+
metadata rather than in the statistics, so read it with `deserialize_record_batch` if you
|
|
205
|
+
need it.
|
|
206
|
+
|
|
183
207
|
## Capability Flags
|
|
184
208
|
|
|
185
209
|
Statistics are opt-in at two levels:
|
|
@@ -213,6 +213,31 @@ ATTACH 'buffers' (TYPE vgi, LOCATION 'uv run row_count_worker.py');
|
|
|
213
213
|
SELECT * FROM buffers.row_count((SELECT * FROM big_table));
|
|
214
214
|
```
|
|
215
215
|
|
|
216
|
+
### Getting the input in order
|
|
217
|
+
|
|
218
|
+
The sink runs in parallel across DuckDB threads, so `process` sees batches in no particular order.
|
|
219
|
+
When output depends on input *order* — a running total, a row pattern match — set
|
|
220
|
+
`Meta.requires_input_batch_index = True` and each `process` call receives `params.batch_index`, a
|
|
221
|
+
monotonic ordinal you can sort the buffered batches by in `finalize`.
|
|
222
|
+
|
|
223
|
+
Not every source can supply one (a base table scan can; `range()` and `VALUES` cannot). When it
|
|
224
|
+
cannot, the extension serializes the sink and numbers the batches itself, so a worker always gets a
|
|
225
|
+
valid index and callers never have to wrap their input. This example reports the indices it saw, one
|
|
226
|
+
row per buffered batch:
|
|
227
|
+
|
|
228
|
+
```python
|
|
229
|
+
--8<-- "examples/batch_index_worker.py"
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
```sql
|
|
233
|
+
ATTACH 'bi' (TYPE vgi, LOCATION 'uv run batch_index_worker.py');
|
|
234
|
+
SELECT * FROM bi.batch_indexes((SELECT * FROM range(5000))) ORDER BY batch_index;
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
The alternative, `Meta.sink_order_dependent = True`, delivers ordered input by forcing a
|
|
238
|
+
single-threaded sink. It works with any source but gives up parallel ingest, which is usually the
|
|
239
|
+
bulk of a buffering query's wall time. The two flags are mutually exclusive.
|
|
240
|
+
|
|
216
241
|
??? info "Buffering vs. table-in-out"
|
|
217
242
|
Both consume a relation, but a **table-in-out** function emits *per input batch* and never
|
|
218
243
|
holds the whole input — use it for streaming transforms (filter, enrich, reshape). Reach for
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# /// script
|
|
2
|
+
# requires-python = ">=3.13"
|
|
3
|
+
# dependencies = ["vgi-python"]
|
|
4
|
+
# ///
|
|
5
|
+
"""A buffering function that asks for input order, and reports what it got.
|
|
6
|
+
|
|
7
|
+
A buffering sink normally runs in parallel across DuckDB threads, so batches
|
|
8
|
+
arrive in no particular order. Setting ``Meta.requires_input_batch_index`` asks
|
|
9
|
+
for DuckDB's per-chunk index alongside each batch, which is what lets a worker
|
|
10
|
+
put the input back in order — the prerequisite for anything order-sensitive,
|
|
11
|
+
like row pattern matching or a running total.
|
|
12
|
+
|
|
13
|
+
Not every source can supply one: a base table scan can, while ``range()`` and
|
|
14
|
+
``VALUES`` cannot. When it cannot, the extension serializes the sink and numbers
|
|
15
|
+
the batches itself, so a worker sees a valid monotonic index either way and never
|
|
16
|
+
has to care which route produced it.
|
|
17
|
+
|
|
18
|
+
This function emits one row per buffered batch — its index and its row count — so
|
|
19
|
+
the guarantee is directly observable:
|
|
20
|
+
|
|
21
|
+
ATTACH 'bi' (TYPE vgi, LOCATION 'uv run batch_index_worker.py');
|
|
22
|
+
SELECT * FROM bi.batch_indexes((SELECT * FROM range(5000))) ORDER BY batch_index;
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from dataclasses import dataclass
|
|
26
|
+
from typing import Annotated
|
|
27
|
+
|
|
28
|
+
import pyarrow as pa
|
|
29
|
+
from vgi_rpc import ArrowSerializableDataclass
|
|
30
|
+
|
|
31
|
+
from vgi import Arg, Worker
|
|
32
|
+
from vgi.arguments import TableInput
|
|
33
|
+
from vgi.catalog import Catalog, Schema
|
|
34
|
+
from vgi.invocation import BindResponse
|
|
35
|
+
from vgi.table_buffering_function import OutputCollector, TableBufferingFunction, TableBufferingParams
|
|
36
|
+
from vgi.table_function import BindParams
|
|
37
|
+
|
|
38
|
+
_RESULT = pa.schema([("batch_index", pa.int64()), ("rows", pa.int64())])
|
|
39
|
+
|
|
40
|
+
# One log entry per buffered batch: the index DuckDB gave us, and the batch size.
|
|
41
|
+
_NS = b"batches"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass(slots=True, frozen=True, kw_only=True)
|
|
45
|
+
class BatchIndexArgs:
|
|
46
|
+
"""Arguments: the input table whose batches should be reported."""
|
|
47
|
+
|
|
48
|
+
data: Annotated[TableInput, Arg(0, doc="Input table")]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass(kw_only=True)
|
|
52
|
+
class DrainState(ArrowSerializableDataclass):
|
|
53
|
+
"""Per-finalize-stream cursor: emit the report once, then finish."""
|
|
54
|
+
|
|
55
|
+
done: bool = False
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class BatchIndexes(TableBufferingFunction[BatchIndexArgs, DrainState]):
|
|
59
|
+
"""Report the batch index and row count of every buffered input batch."""
|
|
60
|
+
|
|
61
|
+
class Meta:
|
|
62
|
+
"""Function metadata."""
|
|
63
|
+
|
|
64
|
+
name = "batch_indexes"
|
|
65
|
+
# Ask for DuckDB's per-chunk index. Mutually exclusive with
|
|
66
|
+
# sink_order_dependent, which orders the input by serializing the sink
|
|
67
|
+
# instead of by numbering it.
|
|
68
|
+
requires_input_batch_index = True
|
|
69
|
+
|
|
70
|
+
@classmethod
|
|
71
|
+
def on_bind(cls, params: BindParams[BatchIndexArgs]) -> BindResponse:
|
|
72
|
+
"""Output shape is fixed: one row per input batch."""
|
|
73
|
+
return BindResponse(output_schema=_RESULT)
|
|
74
|
+
|
|
75
|
+
@classmethod
|
|
76
|
+
def process(cls, batch: pa.RecordBatch, params: TableBufferingParams[BatchIndexArgs]) -> bytes:
|
|
77
|
+
"""Sink: record this batch's index and size.
|
|
78
|
+
|
|
79
|
+
``params.batch_index`` is populated because ``Meta`` asked for it; -1
|
|
80
|
+
stands in for the absent case so an older host that does not supply one
|
|
81
|
+
degrades to a visible marker instead of a crash.
|
|
82
|
+
"""
|
|
83
|
+
index = params.batch_index if params.batch_index is not None else -1
|
|
84
|
+
payload = index.to_bytes(8, "little", signed=True) + batch.num_rows.to_bytes(8, "little")
|
|
85
|
+
params.storage.state_append(_NS, b"", payload)
|
|
86
|
+
return params.execution_id
|
|
87
|
+
|
|
88
|
+
@classmethod
|
|
89
|
+
def combine(cls, state_ids: list[bytes], params: TableBufferingParams[BatchIndexArgs]) -> list[bytes]:
|
|
90
|
+
"""Nothing to reduce: the log already holds one entry per batch."""
|
|
91
|
+
return [params.execution_id]
|
|
92
|
+
|
|
93
|
+
@classmethod
|
|
94
|
+
def initial_finalize_state(
|
|
95
|
+
cls, finalize_state_id: bytes, params: TableBufferingParams[BatchIndexArgs]
|
|
96
|
+
) -> DrainState:
|
|
97
|
+
"""One cursor per finalize stream."""
|
|
98
|
+
return DrainState()
|
|
99
|
+
|
|
100
|
+
@classmethod
|
|
101
|
+
def finalize(
|
|
102
|
+
cls,
|
|
103
|
+
params: TableBufferingParams[BatchIndexArgs],
|
|
104
|
+
finalize_state_id: bytes,
|
|
105
|
+
state: DrainState,
|
|
106
|
+
out: OutputCollector,
|
|
107
|
+
) -> None:
|
|
108
|
+
"""Source: emit the report, ordered by batch index."""
|
|
109
|
+
if state.done:
|
|
110
|
+
out.finish()
|
|
111
|
+
return
|
|
112
|
+
entries = [
|
|
113
|
+
(
|
|
114
|
+
int.from_bytes(value[:8], "little", signed=True),
|
|
115
|
+
int.from_bytes(value[8:16], "little"),
|
|
116
|
+
)
|
|
117
|
+
for _id, value in params.storage.state_log_scan(_NS, b"")
|
|
118
|
+
]
|
|
119
|
+
entries.sort()
|
|
120
|
+
out.emit(
|
|
121
|
+
pa.RecordBatch.from_pydict(
|
|
122
|
+
{
|
|
123
|
+
"batch_index": [index for index, _rows in entries],
|
|
124
|
+
"rows": [rows for _index, rows in entries],
|
|
125
|
+
},
|
|
126
|
+
schema=params.output_schema,
|
|
127
|
+
)
|
|
128
|
+
)
|
|
129
|
+
state.done = True
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class BatchIndexWorker(Worker):
|
|
133
|
+
"""A worker exposing the ``bi`` catalog."""
|
|
134
|
+
|
|
135
|
+
catalog = Catalog(
|
|
136
|
+
name="bi",
|
|
137
|
+
schemas=[Schema(name="main", functions=[BatchIndexes])],
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
if __name__ == "__main__":
|
|
142
|
+
BatchIndexWorker().run()
|
|
@@ -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
|
|
@@ -12,6 +12,7 @@ from vgi_rpc.utils import deserialize_record_batch
|
|
|
12
12
|
from vgi.catalog.catalog_interface import (
|
|
13
13
|
ColumnStatistics,
|
|
14
14
|
TableColumnStatisticsResult,
|
|
15
|
+
deserialize_column_statistics,
|
|
15
16
|
serialize_column_statistics,
|
|
16
17
|
)
|
|
17
18
|
from vgi.catalog.descriptors import ColumnStatisticsInput, Table
|
|
@@ -199,6 +200,91 @@ class TestSerializeColumnStatistics:
|
|
|
199
200
|
assert min_type.num_fields == 1
|
|
200
201
|
|
|
201
202
|
|
|
203
|
+
# ============================================================================
|
|
204
|
+
# deserialize_column_statistics
|
|
205
|
+
# ============================================================================
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def _roundtrip(stats: list[ColumnStatistics]) -> list[ColumnStatistics]:
|
|
209
|
+
return deserialize_column_statistics(serialize_column_statistics(stats))
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
class TestDeserializeColumnStatistics:
|
|
213
|
+
"""Tests that deserialization is a faithful inverse of serialization."""
|
|
214
|
+
|
|
215
|
+
def test_empty_batch(self) -> None:
|
|
216
|
+
assert _roundtrip([]) == []
|
|
217
|
+
|
|
218
|
+
def test_int_column_roundtrips(self) -> None:
|
|
219
|
+
stats = [
|
|
220
|
+
ColumnStatistics(
|
|
221
|
+
column_name="id",
|
|
222
|
+
min=pa.scalar(1, pa.int64()),
|
|
223
|
+
max=pa.scalar(100, pa.int64()),
|
|
224
|
+
has_null=False,
|
|
225
|
+
has_not_null=True,
|
|
226
|
+
distinct_count=100,
|
|
227
|
+
)
|
|
228
|
+
]
|
|
229
|
+
(got,) = _roundtrip(stats)
|
|
230
|
+
assert got.column_name == "id"
|
|
231
|
+
assert got.min.as_py() == 1 # type: ignore[union-attr]
|
|
232
|
+
assert got.max.as_py() == 100 # type: ignore[union-attr]
|
|
233
|
+
assert got.has_null is False
|
|
234
|
+
assert got.has_not_null is True
|
|
235
|
+
assert got.distinct_count == 100
|
|
236
|
+
|
|
237
|
+
def test_string_column_roundtrips_with_string_fields(self) -> None:
|
|
238
|
+
stats = [
|
|
239
|
+
ColumnStatistics(
|
|
240
|
+
column_name="name",
|
|
241
|
+
min=pa.scalar("Alice"),
|
|
242
|
+
max=pa.scalar("Zoe"),
|
|
243
|
+
contains_unicode=True,
|
|
244
|
+
max_string_length=5,
|
|
245
|
+
)
|
|
246
|
+
]
|
|
247
|
+
(got,) = _roundtrip(stats)
|
|
248
|
+
assert got.min.as_py() == "Alice" # type: ignore[union-attr]
|
|
249
|
+
assert got.max.as_py() == "Zoe" # type: ignore[union-attr]
|
|
250
|
+
assert got.contains_unicode is True
|
|
251
|
+
assert got.max_string_length == 5
|
|
252
|
+
|
|
253
|
+
def test_unknown_min_max_stays_none(self) -> None:
|
|
254
|
+
"""A column with no min/max must come back as None, not a null scalar."""
|
|
255
|
+
(got,) = _roundtrip([ColumnStatistics(column_name="opaque")])
|
|
256
|
+
assert got.min is None
|
|
257
|
+
assert got.max is None
|
|
258
|
+
assert got.distinct_count is None
|
|
259
|
+
assert got.contains_unicode is None
|
|
260
|
+
assert got.max_string_length is None
|
|
261
|
+
|
|
262
|
+
def test_mixed_types_keep_their_own_union_arm(self) -> None:
|
|
263
|
+
"""Several column types share one sparse union; each must decode to its own type."""
|
|
264
|
+
stats = [
|
|
265
|
+
ColumnStatistics(column_name="i", min=pa.scalar(1, pa.int64()), max=pa.scalar(9, pa.int64())),
|
|
266
|
+
ColumnStatistics(column_name="s", min=pa.scalar("a"), max=pa.scalar("z")),
|
|
267
|
+
ColumnStatistics(column_name="f", min=pa.scalar(0.5, pa.float64()), max=pa.scalar(9.5, pa.float64())),
|
|
268
|
+
]
|
|
269
|
+
got = _roundtrip(stats)
|
|
270
|
+
assert [s.column_name for s in got] == ["i", "s", "f"]
|
|
271
|
+
assert got[0].min.as_py() == 1 # type: ignore[union-attr]
|
|
272
|
+
assert got[1].min.as_py() == "a" # type: ignore[union-attr]
|
|
273
|
+
assert got[2].max.as_py() == 9.5 # type: ignore[union-attr]
|
|
274
|
+
|
|
275
|
+
def test_order_is_preserved(self) -> None:
|
|
276
|
+
stats = [ColumnStatistics(column_name=f"c{i}", min=pa.scalar(i, pa.int64())) for i in range(6)]
|
|
277
|
+
assert [s.column_name for s in _roundtrip(stats)] == [f"c{i}" for i in range(6)]
|
|
278
|
+
|
|
279
|
+
def test_cache_ttl_rides_in_batch_metadata_not_the_stats(self) -> None:
|
|
280
|
+
"""The TTL is out-of-band; deserialization returns only the statistics."""
|
|
281
|
+
data = serialize_column_statistics([ColumnStatistics(column_name="a")], cache_max_age_seconds=60)
|
|
282
|
+
_, metadata = deserialize_record_batch(data)
|
|
283
|
+
assert metadata is not None
|
|
284
|
+
assert metadata[b"cache_max_age_seconds"] == b"60"
|
|
285
|
+
assert len(deserialize_column_statistics(data)) == 1
|
|
286
|
+
|
|
287
|
+
|
|
202
288
|
# ============================================================================
|
|
203
289
|
# ColumnStatisticsInput
|
|
204
290
|
# ============================================================================
|
|
@@ -9,6 +9,8 @@ input or producing output).
|
|
|
9
9
|
|
|
10
10
|
from __future__ import annotations
|
|
11
11
|
|
|
12
|
+
import sys
|
|
13
|
+
|
|
12
14
|
import pyarrow as pa
|
|
13
15
|
import pytest
|
|
14
16
|
|
|
@@ -17,6 +19,16 @@ from vgi.client.catalog_mixin import CatalogClientError
|
|
|
17
19
|
from vgi.client.client import Client, ClientError
|
|
18
20
|
|
|
19
21
|
|
|
22
|
+
def _exits_with(code: int) -> list[str]:
|
|
23
|
+
"""Argv for a worker that exits immediately with ``code``, reading no input.
|
|
24
|
+
|
|
25
|
+
A real process rather than the shell's ``exit`` builtin: workers are spawned
|
|
26
|
+
without a shell, so a builtin would not resolve. Returned as argv because no
|
|
27
|
+
one string quoting convention survives both POSIX and Windows.
|
|
28
|
+
"""
|
|
29
|
+
return [sys.executable, "-c", f"raise SystemExit({code})"]
|
|
30
|
+
|
|
31
|
+
|
|
20
32
|
def _make_test_batch() -> pa.RecordBatch:
|
|
21
33
|
"""Create a simple test batch for invoking functions."""
|
|
22
34
|
return pa.RecordBatch.from_pydict({"x": [1, 2, 3]})
|
|
@@ -27,9 +39,9 @@ class TestClientWorkerExitHandling:
|
|
|
27
39
|
|
|
28
40
|
def test_client_raises_error_when_worker_exits_immediately(self) -> None:
|
|
29
41
|
"""Client raises ClientError when worker exits before accepting input."""
|
|
30
|
-
#
|
|
31
|
-
# pool=None
|
|
32
|
-
client = Client(
|
|
42
|
+
# A worker that terminates without ever reading stdin.
|
|
43
|
+
# pool=None: the pool is for real, reusable workers.
|
|
44
|
+
client = Client(_exits_with(1), pool=None)
|
|
33
45
|
|
|
34
46
|
# Worker exits before sending any output - detected either via BrokenPipeError
|
|
35
47
|
# or via early exit check when reading fails
|
|
@@ -48,9 +60,8 @@ class TestClientWorkerExitHandling:
|
|
|
48
60
|
|
|
49
61
|
def test_client_raises_error_with_useful_message(self) -> None:
|
|
50
62
|
"""Client error message includes useful debugging information."""
|
|
51
|
-
# Use a specific exit code
|
|
52
|
-
|
|
53
|
-
client = Client("exit 42", pool=None)
|
|
63
|
+
# Use a specific exit code so the message can be checked for it.
|
|
64
|
+
client = Client(_exits_with(42), pool=None)
|
|
54
65
|
|
|
55
66
|
try:
|
|
56
67
|
client.start()
|
|
@@ -84,8 +95,8 @@ class TestCatalogMixinWorkerExitHandling:
|
|
|
84
95
|
|
|
85
96
|
def test_catalog_invoke_raises_error_when_worker_exits(self) -> None:
|
|
86
97
|
"""Catalog invoke raises CatalogClientError when worker exits."""
|
|
87
|
-
#
|
|
88
|
-
client = Client(
|
|
98
|
+
# A worker that terminates without ever reading stdin.
|
|
99
|
+
client = Client(_exits_with(1))
|
|
89
100
|
|
|
90
101
|
# Worker exits - detected either via BrokenPipeError (shows "terminated
|
|
91
102
|
# unexpectedly") or via read failure (shows "Failed to read catalog result")
|
|
@@ -94,7 +105,7 @@ class TestCatalogMixinWorkerExitHandling:
|
|
|
94
105
|
|
|
95
106
|
def test_catalog_invoke_error_includes_useful_info(self) -> None:
|
|
96
107
|
"""Catalog error message includes useful debugging information."""
|
|
97
|
-
client = Client(
|
|
108
|
+
client = Client(_exits_with(42))
|
|
98
109
|
|
|
99
110
|
try:
|
|
100
111
|
list(client.catalogs())
|