vgi-python 0.8.7__tar.gz → 0.8.9__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.8.7 → vgi_python-0.8.9}/PKG-INFO +1 -2
- {vgi_python-0.8.7 → vgi_python-0.8.9}/pyproject.toml +10 -4
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/conformance/test_protocol_inventory.py +9 -0
- vgi_python-0.8.9/tests/test_copy_from_function.py +147 -0
- vgi_python-0.8.9/tests/test_copy_to_function.py +120 -0
- vgi_python-0.8.9/tests/test_docstrings.py +65 -0
- vgi_python-0.8.9/tests/test_resolved_secrets.py +61 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/uv.lock +1 -25
- vgi_python-0.8.9/vgi/_test_fixtures/copy_from.py +99 -0
- vgi_python-0.8.9/vgi/_test_fixtures/copy_to.py +160 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/_test_fixtures/table/__init__.py +4 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/_test_fixtures/table/pairs.py +107 -1
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/_test_fixtures/table/settings.py +57 -2
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/_test_fixtures/worker.py +11 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/catalog/__init__.py +2 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/catalog/catalog_interface.py +131 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/codegen/_common.py +2 -0
- vgi_python-0.8.9/vgi/copy_from_function.py +157 -0
- vgi_python-0.8.9/vgi/copy_to_function.py +179 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/meta_worker.py +1 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/protocol.py +75 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/scalar_function.py +22 -4
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/table_function.py +93 -12
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/worker.py +24 -9
- vgi_python-0.8.7/tests/test_docstrings.py +0 -60
- {vgi_python-0.8.7 → vgi_python-0.8.9}/.gitattributes +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/.github/dependabot.yml +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/.github/styles/config/vocabularies/VGI/accept.txt +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/.github/workflows/ci.yml +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/.github/workflows/docs.yml +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/.github/workflows/integration.yml +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/.github/workflows/release.yml +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/.gitignore +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/.python-version +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/.vale.ini +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/CLAUDE.md +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/DOCS_ACCEPTANCE_CRITERIA.md +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/DOCS_REVIEW_RUBRIC.md +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/DOCS_USABILITY_TEST.md +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/LICENSE +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/README.md +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/SECURITY.md +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/ci/README.md +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/ci/preprocess-require.awk +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/ci/run-integration.sh +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/dist-vgi/.gitignore +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/docs/aggregate-functions.md +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/docs/api/arguments.md +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/docs/api/auth.md +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/docs/api/catalogs.md +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/docs/api/client.md +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/docs/api/exceptions.md +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/docs/api/filters.md +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/docs/api/functions.md +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/docs/api/http.md +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/docs/api/index.md +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/docs/api/metadata.md +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/docs/api/observability.md +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/docs/api/storage.md +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/docs/api/transactor.md +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/docs/api/worker.md +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/docs/argument-serialization.md +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/docs/assets/apple-touch-icon.png +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/docs/assets/favicon-16x16.png +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/docs/assets/favicon-32x32.png +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/docs/assets/favicon.ico +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/docs/assets/kinds/aggregate.svg +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/docs/assets/kinds/buffering.svg +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/docs/assets/kinds/scalar.svg +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/docs/assets/kinds/table-in-out.svg +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/docs/assets/kinds/table.svg +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/docs/assets/logo.png +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/docs/assets/social-card.png +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/docs/authentication.md +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/docs/catalog-interface.md +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/docs/cli.md +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/docs/column-statistics.md +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/docs/concepts/index.md +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/docs/contributing-docs.md +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/docs/filter-pushdown.md +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/docs/generator-api.md +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/docs/how-to/catalogs.md +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/docs/how-to/function-patterns.md +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/docs/how-to/http-auth.md +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/docs/how-to/index.md +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/docs/how-to/pushdown-and-statistics.md +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/docs/how-to/state-storage.md +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/docs/index.md +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/docs/lifecycle.md +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/docs/metadata.md +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/docs/overrides/main.html +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/docs/robots.txt +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/docs/shared-storage.md +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/docs/stylesheets/extra.css +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/docs/tutorial/index.md +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/docs/tutorial/scalar.md +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/docs/tutorial/table.md +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/docs/vgi-logo.png +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/examples/calc_scalar_worker.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/examples/calc_worker.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/examples/filter_worker.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/examples/greeting_scalar_worker.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/examples/row_count_worker.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/examples/series_streaming_worker.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/examples/sum_worker.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/mkdocs.yml +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/packages/vgi-fixtures/LICENSE +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/packages/vgi-fixtures/README.md +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/packages/vgi-fixtures/pyproject.toml +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/scripts/measure_startup.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/scripts/run_all_tests.sh +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/test-data/generate.sh +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/__init__.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/_http_fixtures.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/catalog/__init__.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/catalog/test_catalog_interface.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/catalog/test_client_catalog.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/catalog/test_column_statistics.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/catalog/test_declarative.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/catalog/test_example_worker_catalog.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/catalog/test_integration.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/catalog/test_required_field_filter_paths.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/catalog/test_scan_branches.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/catalog/test_serialization.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/catalog/test_setting.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/catalog/test_storage.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/catalog/test_time_travel.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/catalog/test_writable_table.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/client/__init__.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/client/test_broken_pipe.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/client/test_cli.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/client/test_cli_catalog_functions.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/client/test_worker_debug.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/conformance/__init__.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/conformance/_stub.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/conformance/conftest.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/conformance/test_accumulate.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/conformance/test_aggregate.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/conformance/test_attach.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/conformance/test_bearer_auth.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/conformance/test_directory_parity.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/conformance/test_function_inventory.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/conformance/test_http_client.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/conformance/test_http_external_location.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/conformance/test_http_upload_url.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/conformance/test_macro.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/conformance/test_overload.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/conformance/test_protocol_version.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/conformance/test_resumable_scan.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/conformance/test_scalar.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/conformance/test_scalar_attach_opaque_data.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/conformance/test_secret.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/conformance/test_settings.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/conformance/test_table.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/conformance/test_table_in_out.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/conformance/test_view.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/conformance/test_writable.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/conftest.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/scalar/__init__.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/scalar/test_bernoulli_function.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/scalar/test_binary_packet_function.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/scalar/test_client.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/scalar/test_conditional_message_function.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/scalar/test_hash_seed_function.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/scalar/test_multiply_by_setting_function.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/scalar/test_multiply_function.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/scalar/test_random_bytes_function.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/scalar/test_return_secret_value_function.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/table/__init__.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/table/generator/__init__.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/table/generator/test_constant_columns_function.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/table/generator/test_double_sequence_function.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/table/generator/test_exception_function.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/table/generator/test_filter_echo_function.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/table/generator/test_logging_function.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/table/generator/test_nested_sequence_function.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/table/generator/test_partitioned_function.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/table/generator/test_projected_data_function.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/table/generator/test_sequence_function.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/table/generator/test_settings_function.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/table/generator/test_struct_settings_function.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/table/generator/test_ten_thousand_function.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/table_in_out/__init__.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/table_in_out/generator/__init__.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/table_in_out/generator/test_buffer_input_function.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/table_in_out/generator/test_echo_function.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/table_in_out/generator/test_exception_functions.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/table_in_out/generator/test_filter_by_setting_function.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/table_in_out/generator/test_repeat_inputs_function.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/table_in_out/generator/test_sum_all_columns_function.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/table_in_out/test_client.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/test_access_log_audit.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/test_aggregate_function.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/test_argument_spec.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/test_auth.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/test_bind_exceptions.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/test_bind_request_at_clause.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/test_bound_storage_conformance.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/test_catalog_auth_binding.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/test_documentation_examples.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/test_example_function_arg_types.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/test_examples_workers.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/test_exception_handling.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/test_exceptions.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/test_filter_pushdown.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/test_filter_pushdown_extension.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/test_function_storage.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/test_function_storage_azure_sql.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/test_function_storage_cf_do.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/test_function_storage_cf_do_integration.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/test_function_storage_conformance.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/test_generated_cpp_constants.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/test_generated_cpp_protocol_version.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/test_generated_cpp_request_builders.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/test_generated_cpp_schemas.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/test_generated_cpp_secret.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/test_generated_go_schemas.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/test_generated_protocol_version.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/test_generated_schemas_cross_lang.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/test_generated_ts_client.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/test_generated_ts_schemas.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/test_http_demo_storage.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/test_http_s3_offload_input.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/test_http_s3_offload_output.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/test_metadata.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/test_mypy_consolidated.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/test_nest_tensor.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/test_otel.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/test_projection_enforcement.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/test_projection_repro.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/test_protocol_classes.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/test_schema_utils.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/test_serve.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/test_setting_secret_annotations.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/test_table_buffering_function.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/test_table_function_dynamic_to_string.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/test_tcp_transport.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/test_type_bounds.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/test_union_argument.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/test_worker.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/test_worker_cli.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/test_worker_page.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/transactor/__init__.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/tests/transactor/test_transactor.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/__init__.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/_duckdb.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/_storage_profile.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/_test_fixtures/__init__.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/_test_fixtures/accumulate/__init__.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/_test_fixtures/accumulate/worker.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/_test_fixtures/aggregate/__init__.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/_test_fixtures/aggregate/_common.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/_test_fixtures/aggregate/basic.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/_test_fixtures/aggregate/dynamic.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/_test_fixtures/aggregate/generic.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/_test_fixtures/aggregate/listagg.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/_test_fixtures/aggregate/percentile.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/_test_fixtures/aggregate/streaming.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/_test_fixtures/aggregate/varargs.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/_test_fixtures/aggregate/window.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/_test_fixtures/attach_options.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/_test_fixtures/bad_enum.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/_test_fixtures/bad_protocol.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/_test_fixtures/cancellable.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/_test_fixtures/catalog.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/_test_fixtures/http_server.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/_test_fixtures/narrow_bind/__init__.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/_test_fixtures/narrow_bind/worker.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/_test_fixtures/nest_tensor.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/_test_fixtures/orchard_catalog.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/_test_fixtures/projection_repro/__init__.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/_test_fixtures/projection_repro/worker.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/_test_fixtures/scalar/__init__.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/_test_fixtures/scalar/_common.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/_test_fixtures/scalar/arithmetic.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/_test_fixtures/scalar/binary.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/_test_fixtures/scalar/formatting.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/_test_fixtures/scalar/geo.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/_test_fixtures/scalar/null_handling.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/_test_fixtures/scalar/random_demo.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/_test_fixtures/scalar/settings_secrets.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/_test_fixtures/scalar/type_info.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/_test_fixtures/schema_reconcile/__init__.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/_test_fixtures/schema_reconcile/worker.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/_test_fixtures/simple_writable.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/_test_fixtures/table/_common.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/_test_fixtures/table/batch_index.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/_test_fixtures/table/batch_index_broken.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/_test_fixtures/table/catalog_scans.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/_test_fixtures/table/filters.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/_test_fixtures/table/late_materialization.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/_test_fixtures/table/make_series.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/_test_fixtures/table/misc.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/_test_fixtures/table/order_modes.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/_test_fixtures/table/partition_columns.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/_test_fixtures/table/partition_columns_broken.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/_test_fixtures/table/profiling_example.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/_test_fixtures/table/required_filters.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/_test_fixtures/table/sequence.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/_test_fixtures/table/transaction_storage.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/_test_fixtures/table/tt_pushdown.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/_test_fixtures/table/typed_probe.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/_test_fixtures/table/versioned.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/_test_fixtures/table_in_out.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/_test_fixtures/versioned.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/_test_fixtures/versioned_tables.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/_test_fixtures/writable/__init__.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/_test_fixtures/writable/generic.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/_test_fixtures/writable/table.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/_test_fixtures/writable/worker.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/aggregate_function.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/argument_spec.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/arguments.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/auth.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/catalog/_descriptor_spec.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/catalog/attach_option.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/catalog/descriptors.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/catalog/duckdb_statistics.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/catalog/secret_type.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/catalog/setting.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/catalog/storage.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/client/__init__.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/client/catalog_mixin.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/client/cli.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/client/cli_catalog.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/client/cli_schema.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/client/cli_table.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/client/cli_transaction.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/client/cli_utils.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/client/cli_view.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/client/client.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/codegen/__init__.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/codegen/cpp_constants.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/codegen/cpp_protocol_version.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/codegen/cpp_request_builders.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/codegen/cpp_schemas.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/codegen/cpp_secret_protocol_version.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/codegen/cpp_secret_request_builders.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/codegen/cpp_secret_schemas.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/codegen/go_schemas.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/codegen/protocol_version.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/codegen/ts_client.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/codegen/ts_schemas.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/exceptions.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/function.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/function_storage.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/function_storage_azure_sql.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/function_storage_cf_do.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/http/__init__.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/http/demo_storage.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/http/worker_page.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/invocation.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/logging_config.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/metadata.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/otel.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/protocol_version.txt +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/py.typed +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/schema_utils.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/secret_protocol.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/secret_service.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/serve.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/table_buffering_function.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/table_filter_pushdown.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/table_in_out_function.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/transactor/__init__.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/transactor/_duckdb_compat.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/transactor/client.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/transactor/protocol.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/vgi/transactor/server.py +0 -0
- {vgi_python-0.8.7 → vgi_python-0.8.9}/wrangler.jsonc +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: vgi-python
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.9
|
|
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
|
|
@@ -172,7 +172,6 @@ Requires-Dist: duckdb>=1.5.3; extra == 'dev'
|
|
|
172
172
|
Requires-Dist: mypy; extra == 'dev'
|
|
173
173
|
Requires-Dist: numpy>=2.4.1; extra == 'dev'
|
|
174
174
|
Requires-Dist: pyarrow-stubs; extra == 'dev'
|
|
175
|
-
Requires-Dist: pydoclint; extra == 'dev'
|
|
176
175
|
Requires-Dist: pymssql>=2.3.0; extra == 'dev'
|
|
177
176
|
Requires-Dist: pytest; extra == 'dev'
|
|
178
177
|
Requires-Dist: pytest-cov; extra == 'dev'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "vgi-python"
|
|
3
|
-
version = "0.8.
|
|
3
|
+
version = "0.8.9"
|
|
4
4
|
description = "Vector Gateway Interface - Connect DuckDB to external programs via Apache Arrow"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
keywords = [
|
|
@@ -77,7 +77,11 @@ dev = [
|
|
|
77
77
|
"duckdb>=1.5.3",
|
|
78
78
|
"mypy",
|
|
79
79
|
"pyarrow-stubs",
|
|
80
|
-
|
|
80
|
+
# pydoclint is intentionally NOT a project dependency: it pulls
|
|
81
|
+
# docstring-parser-fork, which clobbers the canonical docstring-parser
|
|
82
|
+
# (a vgi-rpc dependency) in the shared `docstring_parser` namespace,
|
|
83
|
+
# non-deterministically breaking imports. tests/test_docstrings.py runs it
|
|
84
|
+
# in an isolated env via `uvx pydoclint` instead.
|
|
81
85
|
"pytest",
|
|
82
86
|
"pytest-cov",
|
|
83
87
|
"pytest-examples",
|
|
@@ -142,7 +146,9 @@ quote-style = "double"
|
|
|
142
146
|
# Docstring consistency gate (complements ruff's `D` rules, which only check
|
|
143
147
|
# docstring *shape*, not whether documented args/attributes match the code).
|
|
144
148
|
# Enforced families: DOC001 (parse), DOC101/102/103 (arg<->signature), DOC4xx
|
|
145
|
-
# (yields), DOC60x (dataclass attributes).
|
|
149
|
+
# (yields), DOC60x (dataclass attributes). pydoclint is NOT a project dependency
|
|
150
|
+
# (it pulls docstring-parser-fork, which clobbers vgi-rpc's docstring-parser);
|
|
151
|
+
# run it isolated: `uvx --python $(which python) pydoclint --config pyproject.toml vgi/`.
|
|
146
152
|
style = "google"
|
|
147
153
|
# We keep types in signatures, not docstrings (Google style) — so don't ask for
|
|
148
154
|
# them in docstrings, and don't try to match return/yield *types* against prose.
|
|
@@ -162,7 +168,7 @@ allow_init_docstring = true
|
|
|
162
168
|
# No baseline: vgi/ is fully clean under this config, so the gate enforces
|
|
163
169
|
# every in-scope violation with no grandfathered exceptions. If a future change
|
|
164
170
|
# needs to defer cleanup, freeze the current state and re-add `baseline`:
|
|
165
|
-
#
|
|
171
|
+
# uvx pydoclint --config pyproject.toml --generate-baseline=True --baseline=.pydoclint-baseline vgi/
|
|
166
172
|
|
|
167
173
|
[tool.mypy]
|
|
168
174
|
python_version = "3.13"
|
|
@@ -180,6 +180,15 @@ _RPC_ALLOWLIST: dict[str, tuple[str, ...] | NotExposed] = {
|
|
|
180
180
|
"catalog_schema_contents_views": ("schema_contents",),
|
|
181
181
|
"catalog_schema_contents_functions": ("schema_contents",),
|
|
182
182
|
"catalog_schema_contents_macros": ("schema_contents",),
|
|
183
|
+
"catalog_copy_from_formats": NotExposed(
|
|
184
|
+
reason=(
|
|
185
|
+
"COPY ... FROM format discovery. Consumed today by the C++ extension, "
|
|
186
|
+
"which calls this at ATTACH to register a DuckDB CopyFunction per "
|
|
187
|
+
"advertised format; covered by C++ integration/copy_from/*.test. Client "
|
|
188
|
+
"wrappers for the other VGI languages are planned to follow the "
|
|
189
|
+
"Python/C++ implementation."
|
|
190
|
+
)
|
|
191
|
+
),
|
|
183
192
|
"catalog_schema_contents_indexes": NotExposed(
|
|
184
193
|
reason=(
|
|
185
194
|
"DuckDB-only metadata path. Indexes are catalog-planner territory; "
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# Copyright 2025, 2026 Query Farm LLC - https://query.farm
|
|
2
|
+
|
|
3
|
+
"""Worker-side unit tests for CopyFromFunction and the example_lines format."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import tempfile
|
|
8
|
+
|
|
9
|
+
import pyarrow as pa
|
|
10
|
+
import pytest
|
|
11
|
+
|
|
12
|
+
from vgi._test_fixtures.copy_from import ExampleLinesCopyFromArgs, ExampleLinesCopyFromFunction
|
|
13
|
+
from vgi._test_fixtures.worker import ExampleCatalog
|
|
14
|
+
from vgi.arguments import Arguments
|
|
15
|
+
from vgi.invocation import FunctionType
|
|
16
|
+
from vgi.protocol import BindRequest, CopyFromContext
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class _CollectOut:
|
|
20
|
+
"""Minimal OutputCollector stand-in for read()."""
|
|
21
|
+
|
|
22
|
+
def __init__(self) -> None:
|
|
23
|
+
self.batches: list[pa.RecordBatch] = []
|
|
24
|
+
|
|
25
|
+
def emit(self, batch: pa.RecordBatch, **_kwargs: object) -> None:
|
|
26
|
+
self.batches.append(batch)
|
|
27
|
+
|
|
28
|
+
def finish(self) -> None: # pragma: no cover - read() never calls finish itself
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _write(text: str) -> str:
|
|
33
|
+
"""Write ``text`` to a throwaway file and return its path."""
|
|
34
|
+
with tempfile.NamedTemporaryFile("w", suffix=".txt", delete=False) as fp:
|
|
35
|
+
fp.write(text)
|
|
36
|
+
return fp.name
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
EXPECTED = pa.schema([("a", pa.int64()), ("b", pa.string())])
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_on_bind_binds_to_expected_schema() -> None:
|
|
43
|
+
"""on_bind binds output to the COPY target schema."""
|
|
44
|
+
cf = CopyFromContext(format="example_lines", file_path="/x", expected_schema=EXPECTED)
|
|
45
|
+
br = BindRequest(
|
|
46
|
+
function_name="example_lines_copy_reader",
|
|
47
|
+
arguments=Arguments(named={"null_string": pa.scalar("NA")}),
|
|
48
|
+
function_type=FunctionType.TABLE,
|
|
49
|
+
copy_from=cf,
|
|
50
|
+
)
|
|
51
|
+
resp = ExampleLinesCopyFromFunction.bind(br)
|
|
52
|
+
assert resp.output_schema.equals(EXPECTED)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def test_on_bind_without_copy_from_context_raises() -> None:
|
|
56
|
+
"""on_bind rejects a non-COPY invocation."""
|
|
57
|
+
br = BindRequest(
|
|
58
|
+
function_name="example_lines_copy_reader",
|
|
59
|
+
arguments=Arguments(named={"null_string": pa.scalar("NA")}),
|
|
60
|
+
function_type=FunctionType.TABLE,
|
|
61
|
+
)
|
|
62
|
+
with pytest.raises(ValueError, match="COPY FROM format reader"):
|
|
63
|
+
ExampleLinesCopyFromFunction.bind(br)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def test_read_parses_and_coerces_with_null_string() -> None:
|
|
67
|
+
"""read() parses, null-maps, and casts to the target schema."""
|
|
68
|
+
path = _write("1,foo\n2,NA\n3,baz\n")
|
|
69
|
+
out = _CollectOut()
|
|
70
|
+
ExampleLinesCopyFromFunction.read(
|
|
71
|
+
path=path,
|
|
72
|
+
options=ExampleLinesCopyFromArgs(null_string="NA"),
|
|
73
|
+
expected_schema=EXPECTED,
|
|
74
|
+
params=None,
|
|
75
|
+
out=out,
|
|
76
|
+
)
|
|
77
|
+
table = pa.Table.from_batches(out.batches)
|
|
78
|
+
assert table.schema.equals(EXPECTED)
|
|
79
|
+
assert table.to_pydict() == {"a": [1, 2, 3], "b": ["foo", None, "baz"]}
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def test_read_custom_delimiter_and_skip_rows() -> None:
|
|
83
|
+
"""read() honors delimiter and skip_rows options."""
|
|
84
|
+
path = _write("# header\n1|a\n2|b\n")
|
|
85
|
+
out = _CollectOut()
|
|
86
|
+
ExampleLinesCopyFromFunction.read(
|
|
87
|
+
path=path,
|
|
88
|
+
options=ExampleLinesCopyFromArgs(null_string="NA", delimiter="|", skip_rows=1),
|
|
89
|
+
expected_schema=EXPECTED,
|
|
90
|
+
params=None,
|
|
91
|
+
out=out,
|
|
92
|
+
)
|
|
93
|
+
assert pa.Table.from_batches(out.batches).to_pydict() == {"a": [1, 2], "b": ["a", "b"]}
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def test_read_on_error_fail_vs_skip() -> None:
|
|
97
|
+
"""on_error 'fail' raises; 'skip' drops the bad row."""
|
|
98
|
+
path = _write("1,a\nBADROW\n3,c\n")
|
|
99
|
+
with pytest.raises(ValueError, match="example_lines: row has"):
|
|
100
|
+
ExampleLinesCopyFromFunction.read(
|
|
101
|
+
path=path,
|
|
102
|
+
options=ExampleLinesCopyFromArgs(null_string="NA"), # on_error defaults to "fail"
|
|
103
|
+
expected_schema=EXPECTED,
|
|
104
|
+
params=None,
|
|
105
|
+
out=_CollectOut(),
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
out = _CollectOut()
|
|
109
|
+
ExampleLinesCopyFromFunction.read(
|
|
110
|
+
path=path,
|
|
111
|
+
options=ExampleLinesCopyFromArgs(null_string="NA", on_error="skip"),
|
|
112
|
+
expected_schema=EXPECTED,
|
|
113
|
+
params=None,
|
|
114
|
+
out=out,
|
|
115
|
+
)
|
|
116
|
+
assert pa.Table.from_batches(out.batches).num_rows == 2
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def test_catalog_advertises_copy_format() -> None:
|
|
120
|
+
"""The example catalog advertises the example_lines format."""
|
|
121
|
+
formats = ExampleCatalog().copy_from_formats(attach_opaque_data=b"", transaction_opaque_data=None)
|
|
122
|
+
by_name = {f.format_name: f for f in formats}
|
|
123
|
+
assert "example_lines" in by_name
|
|
124
|
+
fmt = by_name["example_lines"]
|
|
125
|
+
assert fmt.handler == "example_lines_copy_reader"
|
|
126
|
+
assert fmt.direction == "from"
|
|
127
|
+
assert fmt.comment == "Toy delimited-text reader for tests"
|
|
128
|
+
assert fmt.tags.get("category") == "copy_from"
|
|
129
|
+
opt_schema = pa.ipc.read_schema(pa.py_buffer(fmt.options))
|
|
130
|
+
assert set(opt_schema.names) == {"delimiter", "null_string", "skip_rows", "on_error"}
|
|
131
|
+
assert opt_schema.field("null_string").metadata[b"vgi_doc"] == b"Token parsed as SQL NULL"
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def test_bind_request_copy_from_wire_roundtrip() -> None:
|
|
135
|
+
"""copy_from survives a BindRequest wire round-trip."""
|
|
136
|
+
cf = CopyFromContext(format="example_lines", file_path="/p", expected_schema=EXPECTED)
|
|
137
|
+
br = BindRequest(
|
|
138
|
+
function_name="h",
|
|
139
|
+
arguments=Arguments(named={"null_string": pa.scalar("NA")}),
|
|
140
|
+
function_type=FunctionType.TABLE,
|
|
141
|
+
copy_from=cf,
|
|
142
|
+
)
|
|
143
|
+
restored = BindRequest.deserialize_from_bytes(br.serialize_to_bytes())
|
|
144
|
+
assert restored.copy_from is not None
|
|
145
|
+
assert restored.copy_from.format == "example_lines"
|
|
146
|
+
assert restored.copy_from.file_path == "/p"
|
|
147
|
+
assert restored.copy_from.expected_schema.equals(EXPECTED)
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# Copyright 2025, 2026 Query Farm LLC - https://query.farm
|
|
2
|
+
|
|
3
|
+
"""Worker-side unit tests for CopyToFunction and the example_lines_out format."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import tempfile
|
|
8
|
+
import types
|
|
9
|
+
|
|
10
|
+
import pyarrow as pa
|
|
11
|
+
|
|
12
|
+
from vgi._test_fixtures.copy_to import ExampleLinesCopyToArgs, ExampleLinesCopyToFunction
|
|
13
|
+
from vgi._test_fixtures.worker import ExampleCatalog
|
|
14
|
+
|
|
15
|
+
SCHEMA = pa.schema([("a", pa.int64()), ("b", pa.string())])
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class _Store:
|
|
19
|
+
"""Minimal in-memory BoundStorage stub (append + ordered log scan)."""
|
|
20
|
+
|
|
21
|
+
def __init__(self) -> None:
|
|
22
|
+
self.log: list[tuple[int, bytes]] = []
|
|
23
|
+
|
|
24
|
+
def state_append(self, ns: bytes, key: bytes, val: bytes) -> None:
|
|
25
|
+
self.log.append((len(self.log), val))
|
|
26
|
+
|
|
27
|
+
def state_log_scan(self, ns: bytes, key: bytes, after_id: int = -1, limit: int | None = None) -> list:
|
|
28
|
+
rows = [(i, v) for (i, v) in self.log if i > after_id]
|
|
29
|
+
return rows if limit is None else rows[:limit]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _tmp_path() -> str:
|
|
33
|
+
with tempfile.NamedTemporaryFile("w", suffix=".txt", delete=False) as fh:
|
|
34
|
+
return fh.name
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _read(path: str) -> str:
|
|
38
|
+
with open(path, encoding="utf-8") as fh:
|
|
39
|
+
return fh.read()
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _params(store: _Store) -> types.SimpleNamespace:
|
|
43
|
+
bind_call = types.SimpleNamespace(input_schema=SCHEMA)
|
|
44
|
+
init_call = types.SimpleNamespace(bind_call=bind_call)
|
|
45
|
+
return types.SimpleNamespace(storage=store, init_call=init_call, execution_id=b"x", args=None)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def test_write_then_close_round_trips_with_null_string() -> None:
|
|
49
|
+
"""write() buffers shards; close() concatenates them to a delimited file."""
|
|
50
|
+
store = _Store()
|
|
51
|
+
params = _params(store)
|
|
52
|
+
opts = ExampleLinesCopyToArgs(null_string="NA")
|
|
53
|
+
out_name = _tmp_path()
|
|
54
|
+
|
|
55
|
+
ExampleLinesCopyToFunction.write(
|
|
56
|
+
batch=pa.record_batch({"a": [1, 2], "b": ["foo", None]}, schema=SCHEMA),
|
|
57
|
+
options=opts,
|
|
58
|
+
file_path=out_name,
|
|
59
|
+
params=params,
|
|
60
|
+
)
|
|
61
|
+
ExampleLinesCopyToFunction.write(
|
|
62
|
+
batch=pa.record_batch({"a": [3], "b": ["baz"]}, schema=SCHEMA),
|
|
63
|
+
options=opts,
|
|
64
|
+
file_path=out_name,
|
|
65
|
+
params=params,
|
|
66
|
+
)
|
|
67
|
+
n = ExampleLinesCopyToFunction.close(options=opts, file_path=out_name, params=params)
|
|
68
|
+
assert n == 3
|
|
69
|
+
assert _read(out_name) == "1,foo\n2,NA\n3,baz\n"
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def test_close_honors_delimiter_and_header() -> None:
|
|
73
|
+
"""Non-default delimiter + header row are applied."""
|
|
74
|
+
store = _Store()
|
|
75
|
+
params = _params(store)
|
|
76
|
+
opts = ExampleLinesCopyToArgs(null_string="NA", delimiter="|", header=True)
|
|
77
|
+
out_name = _tmp_path()
|
|
78
|
+
ExampleLinesCopyToFunction.write(
|
|
79
|
+
batch=pa.record_batch({"a": [1], "b": ["x"]}, schema=SCHEMA),
|
|
80
|
+
options=opts,
|
|
81
|
+
file_path=out_name,
|
|
82
|
+
params=params,
|
|
83
|
+
)
|
|
84
|
+
n = ExampleLinesCopyToFunction.close(options=opts, file_path=out_name, params=params)
|
|
85
|
+
assert n == 1
|
|
86
|
+
assert _read(out_name) == "a|b\n1|x\n"
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def test_close_empty_input_with_header_writes_header_only() -> None:
|
|
90
|
+
"""An empty COPY with header=true still emits the header row (0 data rows)."""
|
|
91
|
+
store = _Store()
|
|
92
|
+
params = _params(store)
|
|
93
|
+
out_name = _tmp_path()
|
|
94
|
+
n = ExampleLinesCopyToFunction.close(
|
|
95
|
+
options=ExampleLinesCopyToArgs(null_string="NA", header=True),
|
|
96
|
+
file_path=out_name,
|
|
97
|
+
params=params,
|
|
98
|
+
)
|
|
99
|
+
assert n == 0
|
|
100
|
+
assert _read(out_name) == "a,b\n"
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def test_catalog_advertises_copy_to_format() -> None:
|
|
104
|
+
"""The example catalog advertises example_lines_out with direction='to'."""
|
|
105
|
+
formats = ExampleCatalog().copy_from_formats(attach_opaque_data=b"", transaction_opaque_data=None)
|
|
106
|
+
by = {(f.direction, f.format_name): f for f in formats}
|
|
107
|
+
assert ("to", "example_lines_out") in by
|
|
108
|
+
fmt = by[("to", "example_lines_out")]
|
|
109
|
+
assert fmt.handler == "example_lines_writer"
|
|
110
|
+
assert fmt.comment == "Toy delimited-text writer for tests"
|
|
111
|
+
assert fmt.tags.get("category") == "copy_to"
|
|
112
|
+
opt_schema = pa.ipc.read_schema(pa.py_buffer(fmt.options))
|
|
113
|
+
assert set(opt_schema.names) == {
|
|
114
|
+
"delimiter",
|
|
115
|
+
"null_string",
|
|
116
|
+
"header",
|
|
117
|
+
"header_repeat",
|
|
118
|
+
"on_exists",
|
|
119
|
+
"fail_on_value",
|
|
120
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Copyright 2025, 2026 Query Farm LLC - https://query.farm
|
|
2
|
+
|
|
3
|
+
"""Docstring-consistency gate.
|
|
4
|
+
|
|
5
|
+
Runs pydoclint over the ``vgi/`` package as part of the test suite. pydoclint
|
|
6
|
+
complements ruff's ``D`` rules: ruff checks docstring *shape*, while pydoclint
|
|
7
|
+
verifies that documented arguments, return values, yields, and dataclass
|
|
8
|
+
attributes actually match the code. Configuration lives in ``[tool.pydoclint]``
|
|
9
|
+
in ``pyproject.toml`` — this test invokes the same CLI, so there is no
|
|
10
|
+
duplicated rule set.
|
|
11
|
+
|
|
12
|
+
pydoclint runs through ``uvx`` (an isolated, ephemeral environment) rather than
|
|
13
|
+
as a project dependency. It requires ``docstring-parser-fork`` while ``vgi-rpc``
|
|
14
|
+
(a runtime dependency) requires the upstream ``docstring-parser``; both own the
|
|
15
|
+
``docstring_parser`` import namespace, so installing pydoclint into the project
|
|
16
|
+
env clobbers it non-deterministically and breaks unrelated imports (e.g.
|
|
17
|
+
``conftest`` collection). Running via uvx keeps the fork out of the project tree.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
import re
|
|
21
|
+
import shutil
|
|
22
|
+
import subprocess
|
|
23
|
+
import sys
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
|
|
26
|
+
import pytest
|
|
27
|
+
|
|
28
|
+
_REPO_ROOT = Path(__file__).resolve().parents[1]
|
|
29
|
+
|
|
30
|
+
# A real violation line looks like `` 42: DOC101: ...``. If pydoclint exits
|
|
31
|
+
# non-zero without emitting any such code, it failed to *run* rather than finding
|
|
32
|
+
# violations.
|
|
33
|
+
_VIOLATION_RE = re.compile(r"\bDOC\d{3}\b")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_pydoclint_clean() -> None:
|
|
37
|
+
"""``vgi/`` must pass the pydoclint docstring gate (config in pyproject.toml)."""
|
|
38
|
+
uvx = shutil.which("uvx") or shutil.which("uv")
|
|
39
|
+
if uvx is None: # pragma: no cover - uv is always present in dev/CI
|
|
40
|
+
pytest.skip("uv/uvx is not available to run pydoclint")
|
|
41
|
+
|
|
42
|
+
# `uvx pydoclint ...` == `uv tool run pydoclint ...`; both run pydoclint in an
|
|
43
|
+
# isolated env so docstring-parser-fork never enters the project tree.
|
|
44
|
+
# Pin the ephemeral env to *this* interpreter (``--python sys.executable``):
|
|
45
|
+
# pydoclint parses with its own runtime's AST, so it must run on a Python new
|
|
46
|
+
# enough for the repo's syntax (PEP 695 generics need 3.12+); uvx's default
|
|
47
|
+
# interpreter may be older and would report spurious DOC002 syntax errors.
|
|
48
|
+
base = [uvx] if Path(uvx).name == "uvx" else [uvx, "tool", "run"]
|
|
49
|
+
cmd = [*base, "--python", sys.executable, "pydoclint", "--config", "pyproject.toml", "vgi/"]
|
|
50
|
+
result = subprocess.run(cmd, cwd=_REPO_ROOT, capture_output=True, text=True)
|
|
51
|
+
output = result.stdout + result.stderr
|
|
52
|
+
|
|
53
|
+
if result.returncode == 0:
|
|
54
|
+
return
|
|
55
|
+
|
|
56
|
+
if not _VIOLATION_RE.search(output): # pragma: no cover - env-dependent (e.g. offline uvx)
|
|
57
|
+
pytest.skip(f"pydoclint could not run via uvx:\n{output}")
|
|
58
|
+
|
|
59
|
+
pytest.fail(
|
|
60
|
+
"pydoclint found docstring violations:\n\n"
|
|
61
|
+
f"{output}\n"
|
|
62
|
+
"Fix the docstrings, or run `uvx pydoclint --config pyproject.toml "
|
|
63
|
+
"--generate-baseline=True --baseline=.pydoclint-baseline vgi/` "
|
|
64
|
+
"to defer them (see [tool.pydoclint] in pyproject.toml)."
|
|
65
|
+
)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""Unit tests for ResolvedSecrets type- and scope-aware selection."""
|
|
2
|
+
|
|
3
|
+
from vgi.table_function import ResolvedSecrets
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def _secrets() -> ResolvedSecrets:
|
|
7
|
+
# Values are plain strings here; ResolvedSecrets also accepts pyarrow Scalars
|
|
8
|
+
# (it calls .as_py() when present).
|
|
9
|
+
return ResolvedSecrets(
|
|
10
|
+
{
|
|
11
|
+
"my_s3": {"type": "s3", "key_id": "AAA", "scope": "s3://bucket-a"},
|
|
12
|
+
"my_s3_b": {
|
|
13
|
+
"type": "s3",
|
|
14
|
+
"key_id": "BBB",
|
|
15
|
+
"scope": "s3://bucket-b\ns3://bucket-b2",
|
|
16
|
+
},
|
|
17
|
+
"my_gcs": {"type": "gcs", "key_id": "G"},
|
|
18
|
+
}
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_type_aware() -> None:
|
|
23
|
+
"""Type-aware accessors find/identify secrets by type."""
|
|
24
|
+
s = _secrets()
|
|
25
|
+
assert s.secret_type("my_s3") == "s3"
|
|
26
|
+
assert s.secret_type("my_gcs") == "gcs"
|
|
27
|
+
assert len(s.of_type("s3")) == 2
|
|
28
|
+
assert len(s.of_type("gcs")) == 1
|
|
29
|
+
assert s.of_type("azure") == []
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_for_scope_of_type_per_bucket() -> None:
|
|
33
|
+
"""Per-bucket scope selection picks the right s3 secret."""
|
|
34
|
+
s = _secrets()
|
|
35
|
+
assert s.for_scope_of_type("s3://bucket-a/x.dat", "s3")["key_id"] == "AAA"
|
|
36
|
+
assert s.for_scope_of_type("s3://bucket-b2/y.dat", "s3")["key_id"] == "BBB"
|
|
37
|
+
assert s.field_for("s3://bucket-a/x.dat", "key_id") == "AAA"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_longest_prefix_and_fallback() -> None:
|
|
41
|
+
"""Longest scope prefix wins; unscoped is the fallback."""
|
|
42
|
+
s = ResolvedSecrets(
|
|
43
|
+
{
|
|
44
|
+
"broad": {"type": "s3", "key_id": "broad", "scope": "s3://bucket"},
|
|
45
|
+
"narrow": {"type": "s3", "key_id": "narrow", "scope": "s3://bucket/data"},
|
|
46
|
+
}
|
|
47
|
+
)
|
|
48
|
+
assert s.for_scope("s3://bucket/data/x.dat")["key_id"] == "narrow"
|
|
49
|
+
assert s.for_scope("s3://bucket/other/x.dat")["key_id"] == "broad"
|
|
50
|
+
|
|
51
|
+
unscoped = ResolvedSecrets({"only": {"type": "s3", "key_id": "only"}})
|
|
52
|
+
assert unscoped.for_scope("s3://any/x")["key_id"] == "only"
|
|
53
|
+
|
|
54
|
+
assert s.for_scope("s3://nope/x") is None
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def test_dict_access_still_works() -> None:
|
|
58
|
+
"""ResolvedSecrets keeps plain dict access."""
|
|
59
|
+
s = _secrets()
|
|
60
|
+
assert s["my_s3"]["key_id"] == "AAA"
|
|
61
|
+
assert s.get("missing") is None
|
|
@@ -516,15 +516,6 @@ wheels = [
|
|
|
516
516
|
{ url = "https://files.pythonhosted.org/packages/a7/5f/ed01f9a3cdffbd5a008556fc7b2a08ddb1cc6ace7effa7340604b1d16699/docstring_parser-0.18.0-py3-none-any.whl", hash = "sha256:b3fcbed555c47d8479be0796ef7e19c2670d428d72e96da63f3a40122860374b", size = 22484, upload-time = "2026-04-14T04:09:18.638Z" },
|
|
517
517
|
]
|
|
518
518
|
|
|
519
|
-
[[package]]
|
|
520
|
-
name = "docstring-parser-fork"
|
|
521
|
-
version = "0.0.14"
|
|
522
|
-
source = { registry = "https://pypi.org/simple" }
|
|
523
|
-
sdist = { url = "https://files.pythonhosted.org/packages/66/bf/27f9cab2f0cd1d17a4420572088bbc19f36d726fbcf165edf226a8926dbc/docstring_parser_fork-0.0.14.tar.gz", hash = "sha256:a2743a63d8d36c09650594f7b4ab5b2758fee8629dcf794d1b221b23179baa5c", size = 34551, upload-time = "2025-09-07T17:27:38.272Z" }
|
|
524
|
-
wheels = [
|
|
525
|
-
{ url = "https://files.pythonhosted.org/packages/bf/50/98b146aea0f1cd7531d25f12bea69fa9ce8d1662124f93fb30dc4511b65e/docstring_parser_fork-0.0.14-py3-none-any.whl", hash = "sha256:4c544f234ef2cc2749a3df32b70c437d77888b1099143a1ad5454452c574b9af", size = 43063, upload-time = "2025-09-07T17:27:37.012Z" },
|
|
526
|
-
]
|
|
527
|
-
|
|
528
519
|
[[package]]
|
|
529
520
|
name = "duckdb"
|
|
530
521
|
version = "1.5.4"
|
|
@@ -1657,19 +1648,6 @@ wheels = [
|
|
|
1657
1648
|
{ url = "https://files.pythonhosted.org/packages/f6/d2/42dd53d0a85c27606f316d3aa5d2869c4e8470a5ed6dec30e4a1abe19192/pydantic_core-2.46.4-cp314-cp314t-win_arm64.whl", hash = "sha256:4fcbe087dbc2068af7eda3aa87634eba216dbda64d1ae73c8684b621d33f6596", size = 2017325, upload-time = "2026-05-06T13:40:52.723Z" },
|
|
1658
1649
|
]
|
|
1659
1650
|
|
|
1660
|
-
[[package]]
|
|
1661
|
-
name = "pydoclint"
|
|
1662
|
-
version = "0.8.7"
|
|
1663
|
-
source = { registry = "https://pypi.org/simple" }
|
|
1664
|
-
dependencies = [
|
|
1665
|
-
{ name = "click" },
|
|
1666
|
-
{ name = "docstring-parser-fork" },
|
|
1667
|
-
]
|
|
1668
|
-
sdist = { url = "https://files.pythonhosted.org/packages/bf/2e/e4df6b0cbb281cb6cf355f5d90b8c66939c7f3cd799c075fab23e0983dfb/pydoclint-0.8.7.tar.gz", hash = "sha256:1ce5055dfdaf2bc3c999d3ffd5494556b903b7090bb5fadac90819107f90629b", size = 199793, upload-time = "2026-06-22T01:24:20.117Z" }
|
|
1669
|
-
wheels = [
|
|
1670
|
-
{ url = "https://files.pythonhosted.org/packages/e9/d4/15fe5c3e66bbf3f60eb9c6ef7c2dca3931302f9acf317e65d70e7d8b338a/pydoclint-0.8.7-py3-none-any.whl", hash = "sha256:40f6b80e59129fe753f6a2f9edd76e4259c970eedc1ce3663adc43f54af439d3", size = 82963, upload-time = "2026-06-22T01:24:19.102Z" },
|
|
1671
|
-
]
|
|
1672
|
-
|
|
1673
1651
|
[[package]]
|
|
1674
1652
|
name = "pygments"
|
|
1675
1653
|
version = "2.20.0"
|
|
@@ -2250,7 +2228,7 @@ requires-dist = [
|
|
|
2250
2228
|
|
|
2251
2229
|
[[package]]
|
|
2252
2230
|
name = "vgi-python"
|
|
2253
|
-
version = "0.8.
|
|
2231
|
+
version = "0.8.9"
|
|
2254
2232
|
source = { editable = "." }
|
|
2255
2233
|
dependencies = [
|
|
2256
2234
|
{ name = "click" },
|
|
@@ -2272,7 +2250,6 @@ dev = [
|
|
|
2272
2250
|
{ name = "mypy" },
|
|
2273
2251
|
{ name = "numpy" },
|
|
2274
2252
|
{ name = "pyarrow-stubs" },
|
|
2275
|
-
{ name = "pydoclint" },
|
|
2276
2253
|
{ name = "pymssql" },
|
|
2277
2254
|
{ name = "pytest" },
|
|
2278
2255
|
{ name = "pytest-cov" },
|
|
@@ -2346,7 +2323,6 @@ requires-dist = [
|
|
|
2346
2323
|
{ name = "platformdirs" },
|
|
2347
2324
|
{ name = "pyarrow" },
|
|
2348
2325
|
{ name = "pyarrow-stubs", marker = "extra == 'dev'" },
|
|
2349
|
-
{ name = "pydoclint", marker = "extra == 'dev'" },
|
|
2350
2326
|
{ name = "pymssql", marker = "extra == 'azure'", specifier = ">=2.3.0" },
|
|
2351
2327
|
{ name = "pytest", marker = "extra == 'dev'" },
|
|
2352
2328
|
{ name = "pytest-cov", marker = "extra == 'dev'" },
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# Copyright 2025, 2026 Query Farm LLC - https://query.farm
|
|
2
|
+
|
|
3
|
+
"""Fixture ``COPY ... FROM`` format reader for VGI integration tests.
|
|
4
|
+
|
|
5
|
+
``ExampleLinesCopyFromFunction`` registers the SQL format ``example_lines`` — a
|
|
6
|
+
toy delimited-text reader. It exercises the full COPY-FROM path plus the option
|
|
7
|
+
machinery: a defaulted option (``delimiter``), an ``INTEGER`` option with a range
|
|
8
|
+
constraint (``skip_rows``), a required option (``null_string``), and an
|
|
9
|
+
enum/``choices`` option (``on_error``).
|
|
10
|
+
|
|
11
|
+
Usage::
|
|
12
|
+
|
|
13
|
+
CREATE TABLE t (a INTEGER, b VARCHAR);
|
|
14
|
+
COPY t FROM '/path/data.txt' (FORMAT example_lines, null_string 'NA');
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from dataclasses import dataclass
|
|
20
|
+
from typing import TYPE_CHECKING, Annotated, ClassVar
|
|
21
|
+
|
|
22
|
+
import pyarrow as pa
|
|
23
|
+
|
|
24
|
+
from vgi.arguments import Arg
|
|
25
|
+
from vgi.copy_from_function import CopyFromFunction
|
|
26
|
+
|
|
27
|
+
if TYPE_CHECKING:
|
|
28
|
+
from vgi_rpc.rpc import OutputCollector
|
|
29
|
+
|
|
30
|
+
from vgi.table_function import ProcessParams
|
|
31
|
+
|
|
32
|
+
__all__ = ["ExampleLinesCopyFromFunction"]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass(slots=True, frozen=True, kw_only=True)
|
|
36
|
+
class ExampleLinesCopyFromArgs:
|
|
37
|
+
"""Options for the ``example_lines`` COPY format."""
|
|
38
|
+
|
|
39
|
+
null_string: Annotated[str, Arg("null_string", doc="Token parsed as SQL NULL")]
|
|
40
|
+
delimiter: Annotated[str, Arg("delimiter", default=",", doc="Field separator")] = ","
|
|
41
|
+
skip_rows: Annotated[int, Arg("skip_rows", default=0, ge=0, doc="Leading lines to skip before data")] = 0
|
|
42
|
+
on_error: Annotated[
|
|
43
|
+
str,
|
|
44
|
+
Arg(
|
|
45
|
+
"on_error",
|
|
46
|
+
default="fail",
|
|
47
|
+
choices=["fail", "skip"],
|
|
48
|
+
doc="Behavior on a row whose column count does not match the target",
|
|
49
|
+
),
|
|
50
|
+
] = "fail"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class ExampleLinesCopyFromFunction(CopyFromFunction[ExampleLinesCopyFromArgs]):
|
|
54
|
+
"""Toy delimited-text ``COPY ... FROM`` reader (test fixture)."""
|
|
55
|
+
|
|
56
|
+
COPY_FROM_FORMAT: ClassVar[str] = "example_lines"
|
|
57
|
+
COPY_FROM_COMMENT: ClassVar[str | None] = "Toy delimited-text reader for tests"
|
|
58
|
+
|
|
59
|
+
class Meta:
|
|
60
|
+
name = "example_lines_copy_reader"
|
|
61
|
+
description = "Read a delimited text file into the COPY target table"
|
|
62
|
+
categories = ["copy", "test"]
|
|
63
|
+
tags = {"category": "copy_from", "stability": "test"}
|
|
64
|
+
|
|
65
|
+
@classmethod
|
|
66
|
+
def read(
|
|
67
|
+
cls,
|
|
68
|
+
*,
|
|
69
|
+
path: str,
|
|
70
|
+
options: ExampleLinesCopyFromArgs,
|
|
71
|
+
expected_schema: pa.Schema,
|
|
72
|
+
params: ProcessParams[ExampleLinesCopyFromArgs],
|
|
73
|
+
out: OutputCollector,
|
|
74
|
+
) -> None:
|
|
75
|
+
"""Parse ``path`` line-by-line and emit one batch matching ``expected_schema``."""
|
|
76
|
+
with open(path, encoding="utf-8") as fh:
|
|
77
|
+
lines = fh.read().splitlines()
|
|
78
|
+
lines = lines[options.skip_rows :]
|
|
79
|
+
|
|
80
|
+
ncols = len(expected_schema)
|
|
81
|
+
rows: list[list[str]] = []
|
|
82
|
+
for line in lines:
|
|
83
|
+
if line == "":
|
|
84
|
+
continue
|
|
85
|
+
cells = line.split(options.delimiter)
|
|
86
|
+
if len(cells) != ncols:
|
|
87
|
+
if options.on_error == "skip":
|
|
88
|
+
continue
|
|
89
|
+
raise ValueError(f"example_lines: row has {len(cells)} fields, expected {ncols}: {line!r}")
|
|
90
|
+
rows.append(cells)
|
|
91
|
+
|
|
92
|
+
# Column-major string arrays, NULL where the cell equals null_string,
|
|
93
|
+
# then cast each column to the target type (DuckDB inserts no cast).
|
|
94
|
+
columns = list(zip(*rows, strict=True)) if rows else [() for _ in range(ncols)]
|
|
95
|
+
arrays = []
|
|
96
|
+
for idx, field in enumerate(expected_schema):
|
|
97
|
+
raw = [None if v == options.null_string else v for v in columns[idx]]
|
|
98
|
+
arrays.append(pa.array(raw, type=pa.string()).cast(field.type))
|
|
99
|
+
out.emit(pa.RecordBatch.from_arrays(arrays, schema=expected_schema))
|