vgi-python 0.24.0__tar.gz → 0.25.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.24.0 → vgi_python-0.25.0}/.github/workflows/ci.yml +5 -5
- {vgi_python-0.24.0 → vgi_python-0.25.0}/.github/workflows/docs.yml +1 -1
- {vgi_python-0.24.0 → vgi_python-0.25.0}/.github/workflows/integration.yml +3 -3
- {vgi_python-0.24.0 → vgi_python-0.25.0}/CLAUDE.md +95 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/PKG-INFO +2 -2
- {vgi_python-0.24.0 → vgi_python-0.25.0}/pyproject.toml +2 -2
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/test_serve.py +197 -0
- vgi_python-0.25.0/tests/test_worker_cli.py +200 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/uv.lock +5 -5
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/http_server.py +9 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/auth.py +13 -1
- vgi_python-0.25.0/vgi/logging_config.py +319 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/serve.py +197 -1
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/worker.py +75 -0
- vgi_python-0.24.0/tests/test_worker_cli.py +0 -112
- vgi_python-0.24.0/vgi/logging_config.py +0 -161
- {vgi_python-0.24.0 → vgi_python-0.25.0}/.gitattributes +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/.github/dependabot.yml +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/.github/styles/config/vocabularies/VGI/accept.txt +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/.github/workflows/release.yml +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/.gitignore +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/.python-version +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/.vale.ini +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/DOCS_ACCEPTANCE_CRITERIA.md +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/DOCS_REVIEW_RUBRIC.md +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/DOCS_USABILITY_TEST.md +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/LICENSE +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/README.md +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/SECURITY.md +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/ci/README.md +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/ci/preprocess-require.awk +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/ci/run-integration.sh +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/dist-vgi/.gitignore +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/docs/aggregate-functions.md +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/docs/api/arguments.md +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/docs/api/auth.md +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/docs/api/catalogs.md +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/docs/api/client.md +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/docs/api/exceptions.md +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/docs/api/filters.md +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/docs/api/functions.md +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/docs/api/http.md +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/docs/api/index.md +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/docs/api/metadata.md +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/docs/api/observability.md +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/docs/api/storage.md +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/docs/api/transactor.md +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/docs/api/worker.md +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/docs/argument-serialization.md +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/docs/assets/apple-touch-icon.png +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/docs/assets/favicon-16x16.png +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/docs/assets/favicon-32x32.png +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/docs/assets/favicon.ico +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/docs/assets/kinds/aggregate.svg +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/docs/assets/kinds/buffering.svg +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/docs/assets/kinds/scalar.svg +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/docs/assets/kinds/table-in-out.svg +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/docs/assets/kinds/table.svg +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/docs/assets/logo.png +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/docs/assets/social-card.png +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/docs/authentication.md +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/docs/catalog-interface.md +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/docs/cli.md +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/docs/column-statistics.md +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/docs/concepts/index.md +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/docs/contributing-docs.md +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/docs/filter-pushdown.md +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/docs/generator-api.md +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/docs/global-functions.md +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/docs/how-to/catalogs.md +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/docs/how-to/function-patterns.md +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/docs/how-to/http-auth.md +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/docs/how-to/index.md +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/docs/how-to/pushdown-and-statistics.md +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/docs/how-to/state-storage.md +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/docs/index.md +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/docs/lifecycle.md +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/docs/metadata.md +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/docs/overrides/main.html +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/docs/robots.txt +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/docs/shared-storage.md +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/docs/stylesheets/extra.css +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/docs/tutorial/index.md +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/docs/tutorial/scalar.md +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/docs/tutorial/table.md +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/docs/vgi-logo.png +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/examples/calc_scalar_worker.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/examples/calc_worker.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/examples/filter_worker.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/examples/greeting_scalar_worker.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/examples/row_count_worker.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/examples/series_streaming_worker.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/examples/sum_worker.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/mkdocs.yml +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/packages/vgi-fixtures/LICENSE +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/packages/vgi-fixtures/README.md +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/packages/vgi-fixtures/docker/Dockerfile +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/packages/vgi-fixtures/docker/docker-entrypoint.sh +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/packages/vgi-fixtures/pyproject.toml +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/scripts/measure_startup.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/scripts/run_all_tests.sh +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/test-data/generate.sh +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/__init__.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/_http_fixtures.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/catalog/__init__.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/catalog/test_catalog_interface.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/catalog/test_client_catalog.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/catalog/test_column_statistics.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/catalog/test_declarative.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/catalog/test_example_worker_catalog.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/catalog/test_integration.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/catalog/test_required_filters.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/catalog/test_scan_branches.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/catalog/test_serialization.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/catalog/test_setting.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/catalog/test_storage.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/catalog/test_time_travel.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/catalog/test_writable_table.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/client/__init__.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/client/test_broken_pipe.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/client/test_cli.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/client/test_cli_catalog_functions.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/client/test_worker_debug.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/conformance/__init__.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/conformance/_stub.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/conformance/conftest.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/conformance/test_accumulate.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/conformance/test_aggregate.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/conformance/test_attach.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/conformance/test_bearer_auth.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/conformance/test_const_param_enforcement.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/conformance/test_container.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/conformance/test_copy_from.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/conformance/test_copy_to.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/conformance/test_directory_parity.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/conformance/test_function_inventory.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/conformance/test_github.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/conformance/test_http_client.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/conformance/test_http_external_location.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/conformance/test_http_upload_url.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/conformance/test_macro.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/conformance/test_overload.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/conformance/test_protocol_inventory.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/conformance/test_protocol_version.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/conformance/test_resumable_scan.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/conformance/test_scalar.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/conformance/test_scalar_attach_opaque_data.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/conformance/test_secret.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/conformance/test_settings.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/conformance/test_table.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/conformance/test_table_in_out.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/conformance/test_view.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/conformance/test_writable.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/conftest.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/landing/_checker.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/landing/describe.expected.json +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/landing/describe.schema.json +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/scalar/__init__.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/scalar/test_bernoulli_function.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/scalar/test_binary_packet_function.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/scalar/test_client.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/scalar/test_conditional_message_function.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/scalar/test_hash_seed_function.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/scalar/test_multiply_by_setting_function.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/scalar/test_multiply_function.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/scalar/test_random_bytes_function.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/scalar/test_return_secret_value_function.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/table/__init__.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/table/generator/__init__.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/table/generator/test_constant_columns_function.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/table/generator/test_double_sequence_function.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/table/generator/test_exception_function.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/table/generator/test_filter_echo_function.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/table/generator/test_logging_function.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/table/generator/test_nested_sequence_function.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/table/generator/test_partitioned_function.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/table/generator/test_projected_data_function.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/table/generator/test_sequence_function.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/table/generator/test_settings_function.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/table/generator/test_struct_settings_function.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/table/generator/test_ten_thousand_function.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/table/test_wide_columns.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/table_in_out/__init__.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/table_in_out/generator/__init__.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/table_in_out/generator/test_buffer_input_function.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/table_in_out/generator/test_echo_function.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/table_in_out/generator/test_exception_functions.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/table_in_out/generator/test_filter_by_setting_function.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/table_in_out/generator/test_repeat_inputs_function.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/table_in_out/generator/test_substream_partial_sum_function.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/table_in_out/generator/test_sum_all_columns_function.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/table_in_out/test_blended_metadata.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/table_in_out/test_client.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/test_access_log_audit.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/test_aggregate_function.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/test_argument_spec.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/test_auth.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/test_bind_exceptions.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/test_bind_request_at_clause.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/test_bound_storage_conformance.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/test_cache_control.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/test_call_state_attach.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/test_catalog_auth_binding.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/test_copy_from_function.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/test_copy_to_function.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/test_docstrings.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/test_documentation_examples.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/test_example_function_arg_types.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/test_examples_workers.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/test_exception_handling.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/test_exceptions.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/test_filter_pushdown.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/test_filter_pushdown_extension.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/test_function_storage.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/test_function_storage_azure_sql.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/test_function_storage_cf_do.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/test_function_storage_cf_do_integration.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/test_function_storage_conformance.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/test_generated_cpp_constants.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/test_generated_cpp_protocol_version.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/test_generated_cpp_request_builders.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/test_generated_cpp_schemas.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/test_generated_cpp_secret.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/test_generated_go_schemas.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/test_generated_protocol_version.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/test_generated_schemas_cross_lang.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/test_generated_ts_client.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/test_generated_ts_schemas.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/test_http_demo_storage.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/test_http_s3_offload_input.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/test_http_s3_offload_output.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/test_landing_conformance.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/test_metadata.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/test_mypy_consolidated.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/test_nest_tensor.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/test_otel.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/test_projection_enforcement.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/test_projection_repro.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/test_protocol_classes.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/test_resolved_secrets.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/test_schema_scoped_functions.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/test_schema_utils.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/test_setting_secret_annotations.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/test_signing_key.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/test_table_buffering_function.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/test_table_function_dynamic_to_string.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/test_tcp_transport.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/test_type_bounds.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/test_union_argument.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/test_worker.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/test_worker_page.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/transactor/__init__.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/tests/transactor/test_transactor.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/__init__.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_duckdb.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_storage_profile.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/__init__.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/accumulate/__init__.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/accumulate/worker.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/aggregate/__init__.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/aggregate/_common.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/aggregate/basic.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/aggregate/dynamic.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/aggregate/generic.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/aggregate/listagg.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/aggregate/percentile.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/aggregate/same_name.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/aggregate/secret_typed.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/aggregate/streaming.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/aggregate/varargs.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/aggregate/window.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/attach_options.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/bad_enum.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/bad_protocol.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/cancellable.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/catalog.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/companion.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/copy_from.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/copy_to.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/global_functions.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/narrow_bind/__init__.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/narrow_bind/worker.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/nest_tensor.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/orchard_catalog.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/projection_repro/__init__.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/projection_repro/worker.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/scalar/__init__.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/scalar/_common.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/scalar/arithmetic.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/scalar/bench_ladder.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/scalar/binary.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/scalar/formatting.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/scalar/geo.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/scalar/null_handling.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/scalar/random_demo.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/scalar/same_name.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/scalar/settings_secrets.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/scalar/type_info.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/schema_reconcile/__init__.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/schema_reconcile/worker.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/simple_writable.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/table/__init__.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/table/_common.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/table/batch_index.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/table/batch_index_broken.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/table/cache.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/table/catalog_scans.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/table/filters.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/table/late_materialization.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/table/make_series.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/table/misc.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/table/order_modes.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/table/pairs.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/table/partition_columns.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/table/partition_columns_broken.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/table/profiling_example.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/table/required_filters.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/table/same_name_cached.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/table/sequence.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/table/settings.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/table/transaction_storage.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/table/tt_pushdown.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/table/typed_probe.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/table/versioned.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/table/wide.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/table_in_out.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/table_in_out_same_name.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/twin_catalogs.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/versioned.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/versioned_tables.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/worker.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/writable/__init__.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/writable/generic.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/writable/table.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/_test_fixtures/writable/worker.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/aggregate_function.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/argument_spec.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/arguments.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/attach_header.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/cache_control.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/catalog/__init__.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/catalog/_descriptor_spec.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/catalog/attach_option.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/catalog/catalog_interface.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/catalog/descriptors.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/catalog/duckdb_statistics.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/catalog/secret_type.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/catalog/setting.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/catalog/storage.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/client/__init__.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/client/catalog_mixin.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/client/cli.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/client/cli_catalog.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/client/cli_schema.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/client/cli_table.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/client/cli_transaction.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/client/cli_utils.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/client/cli_view.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/client/client.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/codegen/__init__.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/codegen/_common.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/codegen/cpp_constants.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/codegen/cpp_protocol_version.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/codegen/cpp_request_builders.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/codegen/cpp_schemas.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/codegen/cpp_secret_protocol_version.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/codegen/cpp_secret_request_builders.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/codegen/cpp_secret_schemas.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/codegen/go_schemas.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/codegen/protocol_version.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/codegen/ts_client.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/codegen/ts_schemas.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/copy_from_function.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/copy_to_function.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/exceptions.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/function.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/function_storage.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/function_storage_azure_sql.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/function_storage_cf_do.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/http/__init__.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/http/demo_storage.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/http/describe_json.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/http/landing.html +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/http/landing_page.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/http/worker_page.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/invocation.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/meta_worker.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/metadata.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/otel.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/profiling.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/protocol.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/protocol_version.txt +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/py.typed +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/scalar_function.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/schema_utils.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/secret_protocol.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/secret_service.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/table_buffering_function.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/table_filter_pushdown.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/table_function.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/table_in_out_function.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/transactor/__init__.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/transactor/_duckdb_compat.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/transactor/client.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/transactor/protocol.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/vgi/transactor/server.py +0 -0
- {vgi_python-0.24.0 → vgi_python-0.25.0}/wrangler.jsonc +0 -0
|
@@ -83,8 +83,8 @@ jobs:
|
|
|
83
83
|
- name: Install uv
|
|
84
84
|
uses: astral-sh/setup-uv@v8.2.0
|
|
85
85
|
|
|
86
|
-
- name: Set up Python 3.
|
|
87
|
-
run: uv python install 3.
|
|
86
|
+
- name: Set up Python 3.14
|
|
87
|
+
run: uv python install 3.14
|
|
88
88
|
|
|
89
89
|
- name: Install dependencies
|
|
90
90
|
run: uv sync --all-extras --group docs
|
|
@@ -141,8 +141,8 @@ jobs:
|
|
|
141
141
|
- name: Install uv
|
|
142
142
|
uses: astral-sh/setup-uv@v8.2.0
|
|
143
143
|
|
|
144
|
-
- name: Set up Python 3.
|
|
145
|
-
run: uv python install 3.
|
|
144
|
+
- name: Set up Python 3.14
|
|
145
|
+
run: uv python install 3.14
|
|
146
146
|
|
|
147
147
|
- name: Install test dependencies
|
|
148
148
|
run: |
|
|
@@ -150,7 +150,7 @@ jobs:
|
|
|
150
150
|
# local vgi-rpc checkout with the s3/external extras plus boto3.
|
|
151
151
|
# Subsequent uv run calls must pass --no-sync or uv re-syncs the
|
|
152
152
|
# env to the lockfile and drops the overlay.
|
|
153
|
-
uv sync --all-extras --python 3.
|
|
153
|
+
uv sync --all-extras --python 3.14
|
|
154
154
|
uv pip install './vgi-rpc[http,s3,external]' boto3
|
|
155
155
|
|
|
156
156
|
- name: Create test bucket in LocalStack
|
|
@@ -78,11 +78,11 @@ jobs:
|
|
|
78
78
|
- name: Install uv
|
|
79
79
|
uses: astral-sh/setup-uv@v8.2.0
|
|
80
80
|
|
|
81
|
-
- name: Set up Python 3.
|
|
82
|
-
run: uv python install 3.
|
|
81
|
+
- name: Set up Python 3.14
|
|
82
|
+
run: uv python install 3.14
|
|
83
83
|
|
|
84
84
|
- name: Install dependencies (vgi + fixtures + all extras)
|
|
85
|
-
run: uv sync --all-extras --python 3.
|
|
85
|
+
run: uv sync --all-extras --python 3.14
|
|
86
86
|
|
|
87
87
|
- name: Download haybarn-unittest
|
|
88
88
|
run: |
|
|
@@ -297,6 +297,11 @@ vgi-client --input data.parquet --function sum_all_columns --worker vgi-fixture-
|
|
|
297
297
|
| `VGI_OAUTH_DEVICE_CODE_CLIENT_ID` | Client ID for device-code flow (optional, URL-safe chars only) |
|
|
298
298
|
| `VGI_OAUTH_DEVICE_CODE_CLIENT_SECRET` | Client secret for device-code flow (optional, URL-safe chars only) |
|
|
299
299
|
| `VGI_OAUTH_USE_ID_TOKEN` | When `1`/`true`/`yes`, clients use OIDC `id_token` as Bearer instead of `access_token` |
|
|
300
|
+
| `VGI_INTROSPECT_PRINCIPALS` | Comma-separated principals permitted to call `__introspect_token__`. Required — no permissive default — whenever the worker implements `resolve_token()` (see below) |
|
|
301
|
+
| `VGI_INTROSPECT_RATE_LIMIT` | Introspection requests allowed per caller per second (default 20) |
|
|
302
|
+
| `VGI_WORKER_ACCESS_LOG_SAMPLE` | Fraction of *successful* calls to keep in the access log, `0.0`–`1.0`. Errors are always kept; the decision is per call, so every record of one stream shares a fate |
|
|
303
|
+
| `VGI_WORKER_ACCESS_LOG_ASYNC` | When `1`/`true`/`yes`, emit access-log records from a listener thread. Bounded queue; full means drop, and a crash loses whatever is queued |
|
|
304
|
+
| `VGI_WORKER_ACCESS_LOG_QUEUE_SIZE` | Bound on the async access-log queue (default 10000) |
|
|
300
305
|
| `VGI_OTEL_ENABLED` | Enable OpenTelemetry instrumentation (`1`/`true`/`yes`) |
|
|
301
306
|
| `VGI_OTEL_CUSTOM_ATTRIBUTES` | Comma-separated `key=value` pairs for custom span/metric attributes |
|
|
302
307
|
| `VGI_OTEL_CLAIM_ATTRIBUTES` | Comma-separated `claim_key=span_attr_name` pairs for claim extraction |
|
|
@@ -392,6 +397,96 @@ Set `SENTRY_DSN` (and install `vgi[sentry]`) to forward unhandled exceptions to
|
|
|
392
397
|
|
|
393
398
|
The same enrichment applies to OTel spans when `VGI_OTEL_ENABLED=1` — both backends read from the same `VgiTracer.set_current_span_attributes()` call sites in `vgi/otel.py`. Either, neither, or both can be active in a process.
|
|
394
399
|
|
|
400
|
+
### Serving Behind a Load Balancer
|
|
401
|
+
|
|
402
|
+
`--max-externalized-response-bytes` caps a single *externalized* response — the
|
|
403
|
+
payload uploaded to blob storage and replaced on the wire by a pointer. Set it
|
|
404
|
+
to whatever the load balancer, API gateway, or object-store policy in front of
|
|
405
|
+
the worker will actually carry.
|
|
406
|
+
|
|
407
|
+
```bash
|
|
408
|
+
vgi-serve my.worker:MyWorker --http --max-externalized-response-bytes 67108864
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
Unlike `--max-stream-response-bytes`, which governs the wire and is *soft* for
|
|
412
|
+
producer streams (a continuation token carries the overshoot to the next turn),
|
|
413
|
+
this cap is **hard on every method type with no continuation escape** — bytes
|
|
414
|
+
already uploaded cannot be un-uploaded. Default is no cap. When set, the value
|
|
415
|
+
is advertised as `VGI-Max-Externalized-Response-Bytes` so clients can size their
|
|
416
|
+
own expectations.
|
|
417
|
+
|
|
418
|
+
Also available on `Worker.main --http` and the fixture server. Under
|
|
419
|
+
`--server granian` it travels to worker processes through `export_serve_config`.
|
|
420
|
+
|
|
421
|
+
### Token Introspection (`resolve_token`)
|
|
422
|
+
|
|
423
|
+
A worker may optionally expose `POST {prefix}/__introspect_token__`, which
|
|
424
|
+
resolves an opaque bearer credential to a principal — for a reverse proxy that
|
|
425
|
+
terminates the only public listener and must know the caller's identity before
|
|
426
|
+
it can authorize anything.
|
|
427
|
+
|
|
428
|
+
```python
|
|
429
|
+
from vgi.auth import AuthUnavailableError, TokenIdentity
|
|
430
|
+
from vgi.worker import Worker
|
|
431
|
+
|
|
432
|
+
class MyWorker(Worker):
|
|
433
|
+
functions = [...]
|
|
434
|
+
|
|
435
|
+
@classmethod
|
|
436
|
+
def resolve_token(cls, token: str) -> TokenIdentity | None:
|
|
437
|
+
try:
|
|
438
|
+
row = api_keys.lookup(token) # your own store
|
|
439
|
+
except ConnectionError as exc:
|
|
440
|
+
# "I could not find out" — 503 + Retry-After, not 401.
|
|
441
|
+
raise AuthUnavailableError(str(exc)) from exc
|
|
442
|
+
if row is None:
|
|
443
|
+
return None # "the credential is unknown"
|
|
444
|
+
return TokenIdentity(principal=row.principal, token_name=row.label)
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
**The route does not exist until `resolve_token` is overridden** — absent, not
|
|
448
|
+
routed-and-refusing. That is what keeps a dependency upgrade from growing a
|
|
449
|
+
credential-to-identity oracle on every existing worker.
|
|
450
|
+
|
|
451
|
+
Enabling it also requires an allowlist of principals permitted to ask, via
|
|
452
|
+
`--introspect-principals` or `VGI_INTROSPECT_PRINCIPALS`. There is no permissive
|
|
453
|
+
default and a worker that overrides the hook without one **refuses to start**:
|
|
454
|
+
authenticating and introspecting are different capabilities, and "any
|
|
455
|
+
authenticated caller" lets any user resolve any other user's credential to its
|
|
456
|
+
owner. `--introspect-rate-limit` (default 20/caller/second) bounds, rather than
|
|
457
|
+
closes, the oracle an allowlisted-but-compromised caller still has.
|
|
458
|
+
|
|
459
|
+
Return `None` for "the store answered and this credential is unknown"; raise
|
|
460
|
+
`AuthUnavailableError` for "the answer is not knowable". A caller that
|
|
461
|
+
negative-caches the first must not cache the second. Never return claims — a
|
|
462
|
+
pass-through claims field would let a worker choose its caller's tenant routing
|
|
463
|
+
and policy branch.
|
|
464
|
+
|
|
465
|
+
`AuthUnavailableError` is worth reaching for in any custom `authenticate`
|
|
466
|
+
callback too, not just here: it is deliberately **not** a `ValueError`, because
|
|
467
|
+
`chain_authenticate` advances to the next authenticator on `ValueError` — so a
|
|
468
|
+
sidecar outage raised as one is read as "not my credential, try the next" and
|
|
469
|
+
ends up a 401 from the end of the chain, restarting every session in the fleet
|
|
470
|
+
over a thirty-second blip.
|
|
471
|
+
|
|
472
|
+
### Access Log Sampling and Async Emission
|
|
473
|
+
|
|
474
|
+
`--access-log-sample 0.05` keeps 5% of *successful* calls. Errors are always
|
|
475
|
+
kept, and the decision is made per call so every record belonging to one stream
|
|
476
|
+
shares a fate. `--access-log-async` moves formatting and writing to a listener
|
|
477
|
+
thread so disk latency stays off the request path; the queue is bounded
|
|
478
|
+
(`--access-log-queue-size`, default 10000), full means drop, and the next record
|
|
479
|
+
through carries `dropped_records` so a gap is never silent. A crash loses
|
|
480
|
+
whatever is still queued — that is the trade.
|
|
481
|
+
|
|
482
|
+
Both apply only to `vgi_rpc.access`, which gets its own handler for the purpose.
|
|
483
|
+
They are deliberately not applied to the diagnostic loggers: dropping half of a
|
|
484
|
+
traceback is not a saving.
|
|
485
|
+
|
|
486
|
+
**Trace correlation needs no configuration.** vgi-rpc reads whatever span is
|
|
487
|
+
current when it emits a record, so `trace_id` / `span_id` appear on every access
|
|
488
|
+
record as soon as `VGI_OTEL_ENABLED=1`.
|
|
489
|
+
|
|
395
490
|
### Key Constraints for Scalar Functions:
|
|
396
491
|
- **1:1 row mapping**: Output must have exactly the same number of rows as input
|
|
397
492
|
- **Single column output**: Output schema has exactly one column named "result"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: vgi-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.25.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
|
|
@@ -162,7 +162,7 @@ Requires-Dist: httpx>=0.24
|
|
|
162
162
|
Requires-Dist: platformdirs
|
|
163
163
|
Requires-Dist: pyarrow
|
|
164
164
|
Requires-Dist: typer>=0.9
|
|
165
|
-
Requires-Dist: vgi-rpc>=0.
|
|
165
|
+
Requires-Dist: vgi-rpc>=0.39.1
|
|
166
166
|
Provides-Extra: azure
|
|
167
167
|
Requires-Dist: azure-identity>=1.16.0; extra == 'azure'
|
|
168
168
|
Requires-Dist: pymssql>=2.3.0; extra == 'azure'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "vgi-python"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.25.0"
|
|
4
4
|
description = "Vector Gateway Interface - Connect DuckDB to external programs via Apache Arrow"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
keywords = [
|
|
@@ -40,7 +40,7 @@ dependencies = [
|
|
|
40
40
|
"pyarrow",
|
|
41
41
|
"typer>=0.9",
|
|
42
42
|
"platformdirs",
|
|
43
|
-
"vgi-rpc>=0.
|
|
43
|
+
"vgi-rpc>=0.39.1",
|
|
44
44
|
"httpx>=0.24",
|
|
45
45
|
]
|
|
46
46
|
|
|
@@ -270,6 +270,203 @@ class TestCreateApp:
|
|
|
270
270
|
assert isinstance(app, falcon.App)
|
|
271
271
|
|
|
272
272
|
|
|
273
|
+
def _capability_headers(app: object) -> dict[str, str]:
|
|
274
|
+
"""Read the capability headers a served app advertises on ``/health``.
|
|
275
|
+
|
|
276
|
+
Args:
|
|
277
|
+
app: The Falcon app under test.
|
|
278
|
+
|
|
279
|
+
Returns:
|
|
280
|
+
Response headers, lowercased.
|
|
281
|
+
|
|
282
|
+
"""
|
|
283
|
+
import falcon.testing
|
|
284
|
+
|
|
285
|
+
resp = falcon.testing.TestClient(app).simulate_get("/vgi/health")
|
|
286
|
+
assert resp.status_code == 200
|
|
287
|
+
return {k.lower(): v for k, v in resp.headers.items()}
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
class TestMaxExternalizedResponseBytes:
|
|
291
|
+
"""The externalized-response cap, for workers behind a size-limited proxy."""
|
|
292
|
+
|
|
293
|
+
def test_advertised_when_set(self) -> None:
|
|
294
|
+
"""The cap reaches the wire, so a client can size its own expectations."""
|
|
295
|
+
app = create_app(_SingleWorker, prefix="/vgi", describe=False, max_externalized_response_bytes=65536)
|
|
296
|
+
assert _capability_headers(app)["vgi-max-externalized-response-bytes"] == "65536"
|
|
297
|
+
|
|
298
|
+
def test_absent_by_default(self) -> None:
|
|
299
|
+
"""No cap unless one is configured.
|
|
300
|
+
|
|
301
|
+
The header's absence is what tells a client there is no ceiling; a
|
|
302
|
+
default value here would be a ceiling nobody chose.
|
|
303
|
+
"""
|
|
304
|
+
app = create_app(_SingleWorker, prefix="/vgi", describe=False)
|
|
305
|
+
assert "vgi-max-externalized-response-bytes" not in _capability_headers(app)
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
class _IntrospectingWorker(Worker):
|
|
309
|
+
"""Worker that implements the optional token-introspection hook."""
|
|
310
|
+
|
|
311
|
+
functions = [_DoubleFunc]
|
|
312
|
+
|
|
313
|
+
@classmethod
|
|
314
|
+
def resolve_token(cls, token: str) -> object | None:
|
|
315
|
+
from vgi.auth import TokenIdentity
|
|
316
|
+
|
|
317
|
+
if token == "good-token":
|
|
318
|
+
return TokenIdentity(principal="alice", token_name="deploy-key-1")
|
|
319
|
+
return None
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
class TestIntrospectResolverDetection:
|
|
323
|
+
"""``resolve_token`` presence is detected by override, not by a flag."""
|
|
324
|
+
|
|
325
|
+
def test_base_worker_has_no_resolver(self) -> None:
|
|
326
|
+
"""A worker that never wrote the lookup gets no endpoint."""
|
|
327
|
+
assert _SingleWorker._introspect_resolver() is None
|
|
328
|
+
|
|
329
|
+
def test_override_is_detected(self) -> None:
|
|
330
|
+
"""Implementing the hook is what enables it."""
|
|
331
|
+
resolver = _IntrospectingWorker._introspect_resolver()
|
|
332
|
+
assert resolver is not None
|
|
333
|
+
assert resolver("good-token") is not None
|
|
334
|
+
assert resolver("nope") is None
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
class TestTokenIntrospection:
|
|
338
|
+
"""``POST /__introspect_token__`` — absent unless a worker implements it."""
|
|
339
|
+
|
|
340
|
+
@staticmethod
|
|
341
|
+
def _client(worker_cls: type[Worker], **env: str) -> object:
|
|
342
|
+
import falcon.testing
|
|
343
|
+
|
|
344
|
+
from vgi.serve import _resolve_authenticate
|
|
345
|
+
|
|
346
|
+
with pytest.MonkeyPatch.context() as mp:
|
|
347
|
+
for key in ("VGI_INTROSPECT_PRINCIPALS", "VGI_INTROSPECT_RATE_LIMIT", "VGI_BEARER_TOKENS"):
|
|
348
|
+
mp.delenv(key, raising=False)
|
|
349
|
+
for key, value in env.items():
|
|
350
|
+
mp.setenv(key, value)
|
|
351
|
+
app = create_app(
|
|
352
|
+
worker_cls,
|
|
353
|
+
prefix="/vgi",
|
|
354
|
+
describe=False,
|
|
355
|
+
authenticate=_resolve_authenticate(),
|
|
356
|
+
)
|
|
357
|
+
return falcon.testing.TestClient(app)
|
|
358
|
+
|
|
359
|
+
def test_route_absent_without_hook(self) -> None:
|
|
360
|
+
"""Not "routed and refusing" — absent.
|
|
361
|
+
|
|
362
|
+
This is the property that keeps a dependency upgrade from silently
|
|
363
|
+
growing a credential-to-identity oracle on every existing worker.
|
|
364
|
+
"""
|
|
365
|
+
client = self._client(_SingleWorker)
|
|
366
|
+
resp = client.simulate_post("/vgi/__introspect_token__", json={"token": "x"}) # type: ignore[attr-defined]
|
|
367
|
+
assert resp.status_code == 404
|
|
368
|
+
|
|
369
|
+
def test_capability_not_advertised_without_hook(self) -> None:
|
|
370
|
+
"""A proxy preflighting at boot learns this worker cannot answer."""
|
|
371
|
+
app = create_app(_SingleWorker, prefix="/vgi", describe=False)
|
|
372
|
+
assert "vgi-token-introspection" not in _capability_headers(app)
|
|
373
|
+
|
|
374
|
+
def test_capability_advertised_with_hook(self) -> None:
|
|
375
|
+
"""A proxy can discover support at boot rather than at first login."""
|
|
376
|
+
with pytest.MonkeyPatch.context() as mp:
|
|
377
|
+
mp.setenv("VGI_INTROSPECT_PRINCIPALS", "proxy-a")
|
|
378
|
+
app = create_app(_IntrospectingWorker, prefix="/vgi", describe=False)
|
|
379
|
+
assert _capability_headers(app)["vgi-token-introspection"] == "true"
|
|
380
|
+
|
|
381
|
+
def test_allowlisted_caller_resolves(self) -> None:
|
|
382
|
+
"""The endpoint answers with a principal and never with claims."""
|
|
383
|
+
client = self._client(
|
|
384
|
+
_IntrospectingWorker,
|
|
385
|
+
VGI_INTROSPECT_PRINCIPALS="proxy-a",
|
|
386
|
+
VGI_BEARER_TOKENS="ptok=proxy-a",
|
|
387
|
+
)
|
|
388
|
+
resp = client.simulate_post( # type: ignore[attr-defined]
|
|
389
|
+
"/vgi/__introspect_token__",
|
|
390
|
+
json={"token": "good-token"},
|
|
391
|
+
headers={"Authorization": "Bearer ptok"},
|
|
392
|
+
)
|
|
393
|
+
assert resp.status_code == 200
|
|
394
|
+
assert resp.json == {"principal": "alice", "token_name": "deploy-key-1", "ttl_seconds": 300}
|
|
395
|
+
assert "claims" not in resp.json
|
|
396
|
+
|
|
397
|
+
def test_unresolvable_token_is_404(self) -> None:
|
|
398
|
+
"""Unknown credential and unknown route answer alike, on purpose."""
|
|
399
|
+
client = self._client(
|
|
400
|
+
_IntrospectingWorker,
|
|
401
|
+
VGI_INTROSPECT_PRINCIPALS="proxy-a",
|
|
402
|
+
VGI_BEARER_TOKENS="ptok=proxy-a",
|
|
403
|
+
)
|
|
404
|
+
resp = client.simulate_post( # type: ignore[attr-defined]
|
|
405
|
+
"/vgi/__introspect_token__",
|
|
406
|
+
json={"token": "nope"},
|
|
407
|
+
headers={"Authorization": "Bearer ptok"},
|
|
408
|
+
)
|
|
409
|
+
assert resp.status_code == 404
|
|
410
|
+
|
|
411
|
+
def test_non_allowlisted_caller_refused(self) -> None:
|
|
412
|
+
"""Authenticating is not the same capability as introspecting.
|
|
413
|
+
|
|
414
|
+
Without this, any user holding a valid credential could resolve any
|
|
415
|
+
other user's credential to its owner.
|
|
416
|
+
"""
|
|
417
|
+
client = self._client(
|
|
418
|
+
_IntrospectingWorker,
|
|
419
|
+
VGI_INTROSPECT_PRINCIPALS="proxy-a",
|
|
420
|
+
VGI_BEARER_TOKENS="ptok=proxy-a,utok=user-b",
|
|
421
|
+
)
|
|
422
|
+
resp = client.simulate_post( # type: ignore[attr-defined]
|
|
423
|
+
"/vgi/__introspect_token__",
|
|
424
|
+
json={"token": "good-token"},
|
|
425
|
+
headers={"Authorization": "Bearer utok"},
|
|
426
|
+
)
|
|
427
|
+
assert resp.status_code == 403
|
|
428
|
+
|
|
429
|
+
def test_hook_without_allowlist_refuses_to_start(self) -> None:
|
|
430
|
+
"""Fail closed and loud, rather than defaulting to an open oracle."""
|
|
431
|
+
with pytest.MonkeyPatch.context() as mp:
|
|
432
|
+
mp.delenv("VGI_INTROSPECT_PRINCIPALS", raising=False)
|
|
433
|
+
with pytest.raises(SystemExit) as exc:
|
|
434
|
+
create_app(_IntrospectingWorker, prefix="/vgi", describe=False)
|
|
435
|
+
assert exc.value.code == 1
|
|
436
|
+
|
|
437
|
+
@pytest.mark.parametrize("bad", ["nonsense", "0", "-5"])
|
|
438
|
+
def test_bad_rate_limit_refuses_to_start(self, bad: str) -> None:
|
|
439
|
+
"""A typo must not silently become "refuse everything" or "no bound"."""
|
|
440
|
+
with pytest.MonkeyPatch.context() as mp:
|
|
441
|
+
mp.setenv("VGI_INTROSPECT_PRINCIPALS", "proxy-a")
|
|
442
|
+
mp.setenv("VGI_INTROSPECT_RATE_LIMIT", bad)
|
|
443
|
+
with pytest.raises(SystemExit):
|
|
444
|
+
create_app(_IntrospectingWorker, prefix="/vgi", describe=False)
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
class TestAuthUnavailableReExport:
|
|
448
|
+
"""``AuthUnavailableError`` is reachable without naming a vgi_rpc module."""
|
|
449
|
+
|
|
450
|
+
def test_importable_from_vgi_auth(self) -> None:
|
|
451
|
+
"""Worker authors reach it from ``vgi.auth`` like every other auth type."""
|
|
452
|
+
from vgi_rpc.http import AuthUnavailableError as Upstream
|
|
453
|
+
|
|
454
|
+
from vgi.auth import AuthUnavailableError
|
|
455
|
+
|
|
456
|
+
assert AuthUnavailableError is Upstream
|
|
457
|
+
|
|
458
|
+
def test_is_not_a_value_error(self) -> None:
|
|
459
|
+
"""The whole point: ``chain_authenticate`` must not swallow it.
|
|
460
|
+
|
|
461
|
+
The chain advances to the next authenticator on ``ValueError``, so an
|
|
462
|
+
outage raised as one is read as "not my credential, try the next" and
|
|
463
|
+
ends up a 401 — restarting every session in the fleet over a blip.
|
|
464
|
+
"""
|
|
465
|
+
from vgi.auth import AuthUnavailableError
|
|
466
|
+
|
|
467
|
+
assert not issubclass(AuthUnavailableError, ValueError)
|
|
468
|
+
|
|
469
|
+
|
|
273
470
|
# ---------------------------------------------------------------------------
|
|
274
471
|
# Tests: env var helpers
|
|
275
472
|
# ---------------------------------------------------------------------------
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# Copyright 2025, 2026 Query Farm LLC - https://query.farm
|
|
2
|
+
|
|
3
|
+
"""Tests for Worker.main() CLI logging options."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
from typing import TYPE_CHECKING
|
|
9
|
+
|
|
10
|
+
import pytest
|
|
11
|
+
|
|
12
|
+
from vgi.logging_config import (
|
|
13
|
+
_KNOWN_LOGGERS,
|
|
14
|
+
ACCESS_LOGGER,
|
|
15
|
+
LogFormat,
|
|
16
|
+
LogLevel,
|
|
17
|
+
configure_worker_logging,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
from collections.abc import Iterator
|
|
22
|
+
|
|
23
|
+
# All known logger names from the registry
|
|
24
|
+
_ALL_LOGGER_NAMES = [name for name, _, _ in _KNOWN_LOGGERS]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@pytest.fixture(autouse=False)
|
|
28
|
+
def _reset_loggers() -> Iterator[None]:
|
|
29
|
+
"""Save and restore logger handlers, levels and propagation after each test.
|
|
30
|
+
|
|
31
|
+
``propagate`` is restored too because the access-log options turn it off on
|
|
32
|
+
``vgi_rpc.access``; leaving that set would silently suppress access records
|
|
33
|
+
for every later test in the session.
|
|
34
|
+
"""
|
|
35
|
+
saved: dict[str, tuple[int, list[logging.Handler], bool]] = {}
|
|
36
|
+
for name in _ALL_LOGGER_NAMES:
|
|
37
|
+
logger = logging.getLogger(name)
|
|
38
|
+
saved[name] = (logger.level, list(logger.handlers), logger.propagate)
|
|
39
|
+
yield
|
|
40
|
+
for name in _ALL_LOGGER_NAMES:
|
|
41
|
+
logger = logging.getLogger(name)
|
|
42
|
+
level, handlers, propagate = saved[name]
|
|
43
|
+
logger.handlers[:] = handlers
|
|
44
|
+
logger.setLevel(level)
|
|
45
|
+
logger.propagate = propagate
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class TestConfigureWorkerLogging:
|
|
49
|
+
"""Tests for configure_worker_logging() options."""
|
|
50
|
+
|
|
51
|
+
def test_debug_flag(self, _reset_loggers: None) -> None:
|
|
52
|
+
"""``--debug`` sets vgi + vgi_rpc loggers to DEBUG."""
|
|
53
|
+
configure_worker_logging(debug=True)
|
|
54
|
+
for name in ("vgi", "vgi_rpc"):
|
|
55
|
+
logger = logging.getLogger(name)
|
|
56
|
+
assert logger.level == logging.DEBUG
|
|
57
|
+
assert len(logger.handlers) == 1
|
|
58
|
+
|
|
59
|
+
def test_log_level_option(self, _reset_loggers: None) -> None:
|
|
60
|
+
"""``--log-level WARNING`` sets correct level."""
|
|
61
|
+
configure_worker_logging(log_level=LogLevel.WARNING)
|
|
62
|
+
for name in ("vgi", "vgi_rpc"):
|
|
63
|
+
logger = logging.getLogger(name)
|
|
64
|
+
assert logger.level == logging.WARNING
|
|
65
|
+
|
|
66
|
+
def test_log_logger_targeting(self, _reset_loggers: None) -> None:
|
|
67
|
+
"""``--log-logger vgi.worker`` targets only that logger."""
|
|
68
|
+
configure_worker_logging(log_level=LogLevel.DEBUG, log_loggers=["vgi.worker"])
|
|
69
|
+
target = logging.getLogger("vgi.worker")
|
|
70
|
+
assert target.level == logging.DEBUG
|
|
71
|
+
assert len(target.handlers) == 1
|
|
72
|
+
# Root vgi logger should not have been modified
|
|
73
|
+
root = logging.getLogger("vgi")
|
|
74
|
+
assert root.handlers == [] or root.level != logging.DEBUG
|
|
75
|
+
|
|
76
|
+
def test_log_format_json(self, _reset_loggers: None) -> None:
|
|
77
|
+
"""``--log-format json`` uses VgiJsonFormatter."""
|
|
78
|
+
from vgi_rpc.logging_utils import VgiJsonFormatter
|
|
79
|
+
|
|
80
|
+
configure_worker_logging(debug=True, log_format=LogFormat.json)
|
|
81
|
+
logger = logging.getLogger("vgi")
|
|
82
|
+
assert any(isinstance(h.formatter, VgiJsonFormatter) for h in logger.handlers)
|
|
83
|
+
|
|
84
|
+
def test_debug_overrides_log_level(self, _reset_loggers: None) -> None:
|
|
85
|
+
"""``--debug --log-level INFO`` resolves to DEBUG."""
|
|
86
|
+
configure_worker_logging(debug=True, log_level=LogLevel.INFO)
|
|
87
|
+
logger = logging.getLogger("vgi")
|
|
88
|
+
assert logger.level == logging.DEBUG
|
|
89
|
+
|
|
90
|
+
def test_unknown_logger_warning(self, _reset_loggers: None, capsys: pytest.CaptureFixture[str]) -> None:
|
|
91
|
+
"""Unrecognized logger name warns on stderr."""
|
|
92
|
+
configure_worker_logging(log_level=LogLevel.DEBUG, log_loggers=["not.a.real.logger"])
|
|
93
|
+
captured = capsys.readouterr()
|
|
94
|
+
assert "warning: unknown logger 'not.a.real.logger'" in captured.err
|
|
95
|
+
|
|
96
|
+
def test_configure_idempotent(self, _reset_loggers: None) -> None:
|
|
97
|
+
"""Calling configure_worker_logging() twice doesn't duplicate handlers."""
|
|
98
|
+
configure_worker_logging(debug=True)
|
|
99
|
+
configure_worker_logging(debug=True)
|
|
100
|
+
logger = logging.getLogger("vgi")
|
|
101
|
+
assert len(logger.handlers) == 1
|
|
102
|
+
|
|
103
|
+
def test_returns_effective_level(self, _reset_loggers: None) -> None:
|
|
104
|
+
"""Return value is the numeric log level."""
|
|
105
|
+
level = configure_worker_logging(log_level=LogLevel.WARNING)
|
|
106
|
+
assert level == logging.WARNING
|
|
107
|
+
|
|
108
|
+
level = configure_worker_logging(debug=True, log_level=LogLevel.WARNING)
|
|
109
|
+
assert level == logging.DEBUG
|
|
110
|
+
|
|
111
|
+
def test_text_format_has_timestamp(self, _reset_loggers: None) -> None:
|
|
112
|
+
"""Text format includes asctime in the formatter."""
|
|
113
|
+
configure_worker_logging(log_format=LogFormat.text)
|
|
114
|
+
logger = logging.getLogger("vgi")
|
|
115
|
+
assert len(logger.handlers) == 1
|
|
116
|
+
fmt = logger.handlers[0].formatter
|
|
117
|
+
assert fmt is not None
|
|
118
|
+
assert fmt._fmt is not None
|
|
119
|
+
assert "asctime" in fmt._fmt
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class TestAccessLogOptions:
|
|
123
|
+
"""Sampling and async emission for ``vgi_rpc.access``."""
|
|
124
|
+
|
|
125
|
+
def test_untouched_by_default(self, _reset_loggers: None) -> None:
|
|
126
|
+
"""No options means no dedicated handler — records still propagate."""
|
|
127
|
+
configure_worker_logging(debug=True)
|
|
128
|
+
access = logging.getLogger(ACCESS_LOGGER)
|
|
129
|
+
assert access.handlers == []
|
|
130
|
+
assert access.propagate is True
|
|
131
|
+
|
|
132
|
+
def test_sampling_attaches_filter(self, _reset_loggers: None) -> None:
|
|
133
|
+
"""A sample rate below 1.0 installs the sampler on the access logger."""
|
|
134
|
+
from vgi_rpc.logging_utils import AccessLogSampler
|
|
135
|
+
|
|
136
|
+
configure_worker_logging(debug=True, access_log_sample=0.25)
|
|
137
|
+
access = logging.getLogger(ACCESS_LOGGER)
|
|
138
|
+
assert len(access.handlers) == 1
|
|
139
|
+
assert any(isinstance(f, AccessLogSampler) for f in access.handlers[0].filters)
|
|
140
|
+
|
|
141
|
+
def test_sampling_does_not_touch_diagnostic_loggers(self, _reset_loggers: None) -> None:
|
|
142
|
+
"""Dropping a fraction of tracebacks is not a saving.
|
|
143
|
+
|
|
144
|
+
The access logger gets its own handler precisely so the sampler cannot
|
|
145
|
+
reach ``vgi`` / ``vgi_rpc``, which share the stderr handler.
|
|
146
|
+
"""
|
|
147
|
+
from vgi_rpc.logging_utils import AccessLogSampler
|
|
148
|
+
|
|
149
|
+
configure_worker_logging(debug=True, access_log_sample=0.25)
|
|
150
|
+
for name in ("vgi", "vgi_rpc"):
|
|
151
|
+
for handler in logging.getLogger(name).handlers:
|
|
152
|
+
assert not any(isinstance(f, AccessLogSampler) for f in handler.filters)
|
|
153
|
+
|
|
154
|
+
def test_sampling_stops_propagation(self, _reset_loggers: None) -> None:
|
|
155
|
+
"""Otherwise every surviving record is written twice."""
|
|
156
|
+
configure_worker_logging(debug=True, access_log_sample=0.5)
|
|
157
|
+
assert logging.getLogger(ACCESS_LOGGER).propagate is False
|
|
158
|
+
|
|
159
|
+
def test_async_uses_queue_handler(self, _reset_loggers: None) -> None:
|
|
160
|
+
"""Async emission puts a bounded queue on the request thread's path."""
|
|
161
|
+
from vgi_rpc.logging_utils import DroppingQueueHandler
|
|
162
|
+
|
|
163
|
+
configure_worker_logging(debug=True, access_log_async=True, access_log_queue_size=64)
|
|
164
|
+
access = logging.getLogger(ACCESS_LOGGER)
|
|
165
|
+
assert len(access.handlers) == 1
|
|
166
|
+
assert isinstance(access.handlers[0], DroppingQueueHandler)
|
|
167
|
+
|
|
168
|
+
def test_json_format_carries_to_access_handler(self, _reset_loggers: None) -> None:
|
|
169
|
+
"""The dedicated handler must not silently revert to text output."""
|
|
170
|
+
from vgi_rpc.logging_utils import VgiJsonFormatter
|
|
171
|
+
|
|
172
|
+
configure_worker_logging(debug=True, log_format=LogFormat.json, access_log_sample=0.5)
|
|
173
|
+
access = logging.getLogger(ACCESS_LOGGER)
|
|
174
|
+
assert isinstance(access.handlers[0].formatter, VgiJsonFormatter)
|
|
175
|
+
|
|
176
|
+
@pytest.mark.parametrize("rate", [-0.1, 1.5])
|
|
177
|
+
def test_out_of_range_sample_rate_exits(self, _reset_loggers: None, rate: float) -> None:
|
|
178
|
+
"""A typo here silently changes how much of the audit trail survives."""
|
|
179
|
+
with pytest.raises(SystemExit):
|
|
180
|
+
configure_worker_logging(debug=True, access_log_sample=rate)
|
|
181
|
+
|
|
182
|
+
def test_non_positive_queue_size_exits(self, _reset_loggers: None) -> None:
|
|
183
|
+
"""A zero-length queue would drop every record."""
|
|
184
|
+
with pytest.raises(SystemExit):
|
|
185
|
+
configure_worker_logging(debug=True, access_log_async=True, access_log_queue_size=0)
|
|
186
|
+
|
|
187
|
+
def test_env_overrides_win(self, _reset_loggers: None, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
188
|
+
"""Reachable without owning argv, like the other VGI_WORKER_LOG_* vars."""
|
|
189
|
+
from vgi_rpc.logging_utils import AccessLogSampler
|
|
190
|
+
|
|
191
|
+
monkeypatch.setenv("VGI_WORKER_ACCESS_LOG_SAMPLE", "0.1")
|
|
192
|
+
configure_worker_logging(debug=True)
|
|
193
|
+
access = logging.getLogger(ACCESS_LOGGER)
|
|
194
|
+
assert any(isinstance(f, AccessLogSampler) for f in access.handlers[0].filters)
|
|
195
|
+
|
|
196
|
+
def test_malformed_env_exits(self, _reset_loggers: None, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
197
|
+
"""A typo must not leave sampling silently off."""
|
|
198
|
+
monkeypatch.setenv("VGI_WORKER_ACCESS_LOG_SAMPLE", "half")
|
|
199
|
+
with pytest.raises(SystemExit):
|
|
200
|
+
configure_worker_logging(debug=True)
|
|
@@ -2352,7 +2352,7 @@ requires-dist = [
|
|
|
2352
2352
|
|
|
2353
2353
|
[[package]]
|
|
2354
2354
|
name = "vgi-python"
|
|
2355
|
-
version = "0.
|
|
2355
|
+
version = "0.25.0"
|
|
2356
2356
|
source = { editable = "." }
|
|
2357
2357
|
dependencies = [
|
|
2358
2358
|
{ name = "click" },
|
|
@@ -2467,7 +2467,7 @@ requires-dist = [
|
|
|
2467
2467
|
{ name = "vgi-python", extras = ["test-fixtures"], marker = "extra == 'test-fixtures-writable'" },
|
|
2468
2468
|
{ name = "vgi-python", extras = ["test-fixtures", "test-fixtures-writable", "http", "oauth", "otel", "sentry", "azure"], marker = "extra == 'dev'" },
|
|
2469
2469
|
{ name = "vgi-python", extras = ["transactor"], marker = "extra == 'test-fixtures-writable'" },
|
|
2470
|
-
{ name = "vgi-rpc", specifier = ">=0.
|
|
2470
|
+
{ name = "vgi-rpc", specifier = ">=0.39.1" },
|
|
2471
2471
|
{ name = "vgi-rpc", extras = ["conformance", "external", "http", "oauth", "otel", "sentry"], marker = "extra == 'dev'" },
|
|
2472
2472
|
{ name = "vgi-rpc", extras = ["http"], marker = "extra == 'granian'" },
|
|
2473
2473
|
{ name = "vgi-rpc", extras = ["http"], marker = "extra == 'http'" },
|
|
@@ -2497,7 +2497,7 @@ docs = [
|
|
|
2497
2497
|
|
|
2498
2498
|
[[package]]
|
|
2499
2499
|
name = "vgi-rpc"
|
|
2500
|
-
version = "0.
|
|
2500
|
+
version = "0.39.1"
|
|
2501
2501
|
source = { registry = "https://pypi.org/simple" }
|
|
2502
2502
|
dependencies = [
|
|
2503
2503
|
{ name = "docstring-parser" },
|
|
@@ -2505,9 +2505,9 @@ dependencies = [
|
|
|
2505
2505
|
{ name = "pywin32", marker = "sys_platform == 'win32'" },
|
|
2506
2506
|
{ name = "tzdata", marker = "sys_platform == 'win32'" },
|
|
2507
2507
|
]
|
|
2508
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
2508
|
+
sdist = { url = "https://files.pythonhosted.org/packages/72/7d/a4dc94670c7b29eb0d55f3965909d627e4a5e4beadf7787622a049197db7/vgi_rpc-0.39.1.tar.gz", hash = "sha256:3eb71657f34ddc608db817785b596f2653d35341ec57c40481a9fa37ceefe1e7", size = 1060176, upload-time = "2026-08-05T04:22:33.766Z" }
|
|
2509
2509
|
wheels = [
|
|
2510
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
2510
|
+
{ url = "https://files.pythonhosted.org/packages/60/18/d29ffcb0e3b69ca8d8d7950f99ba5ee6ad9c68e07ce70457bba6567f9859/vgi_rpc-0.39.1-py3-none-any.whl", hash = "sha256:dc2baab02dfc7df37456688e6d4c6cde7c7428bbbb2733e37511b1e950754aca", size = 482199, upload-time = "2026-08-05T04:22:31.935Z" },
|
|
2511
2511
|
]
|
|
2512
2512
|
|
|
2513
2513
|
[package.optional-dependencies]
|
|
@@ -179,6 +179,14 @@ def main() -> None:
|
|
|
179
179
|
"--demo-storage",
|
|
180
180
|
help="Enable in-process blob storage for externalized payloads (no S3 required)",
|
|
181
181
|
),
|
|
182
|
+
max_externalized_response_bytes: int | None = typer.Option( # noqa: B008
|
|
183
|
+
None,
|
|
184
|
+
"--max-externalized-response-bytes",
|
|
185
|
+
help=(
|
|
186
|
+
"Cap on a single externalized response. Hard on every method type, "
|
|
187
|
+
"with no continuation escape. Default: no cap."
|
|
188
|
+
),
|
|
189
|
+
),
|
|
182
190
|
port_file: str | None = typer.Option(
|
|
183
191
|
None,
|
|
184
192
|
"--port-file",
|
|
@@ -379,6 +387,7 @@ def main() -> None:
|
|
|
379
387
|
upload_url_provider=upload_url_provider,
|
|
380
388
|
max_upload_bytes=max_upload_bytes if upload_url_provider is not None else None,
|
|
381
389
|
max_request_bytes=max_request_bytes,
|
|
390
|
+
max_externalized_response_bytes=max_externalized_response_bytes,
|
|
382
391
|
authenticate=authenticate,
|
|
383
392
|
oauth_resource_metadata=oauth_metadata,
|
|
384
393
|
enable_landing_page=False,
|