vgi-python 0.26.0__tar.gz → 0.27.0__tar.gz

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