vgi-python 0.29.4__tar.gz → 0.29.6__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 (441) hide show
  1. {vgi_python-0.29.4 → vgi_python-0.29.6}/PKG-INFO +1 -1
  2. {vgi_python-0.29.4 → vgi_python-0.29.6}/pyproject.toml +1 -1
  3. vgi_python-0.29.6/tests/table_in_out/test_blended_exchange.py +282 -0
  4. {vgi_python-0.29.4 → vgi_python-0.29.6}/uv.lock +1 -1
  5. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/client/client.py +130 -15
  6. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/protocol.py +75 -0
  7. {vgi_python-0.29.4 → vgi_python-0.29.6}/.gitattributes +0 -0
  8. {vgi_python-0.29.4 → vgi_python-0.29.6}/.github/dependabot.yml +0 -0
  9. {vgi_python-0.29.4 → vgi_python-0.29.6}/.github/styles/config/vocabularies/VGI/accept.txt +0 -0
  10. {vgi_python-0.29.4 → vgi_python-0.29.6}/.github/workflows/ci.yml +0 -0
  11. {vgi_python-0.29.4 → vgi_python-0.29.6}/.github/workflows/docs.yml +0 -0
  12. {vgi_python-0.29.4 → vgi_python-0.29.6}/.github/workflows/integration.yml +0 -0
  13. {vgi_python-0.29.4 → vgi_python-0.29.6}/.github/workflows/release.yml +0 -0
  14. {vgi_python-0.29.4 → vgi_python-0.29.6}/.gitignore +0 -0
  15. {vgi_python-0.29.4 → vgi_python-0.29.6}/.python-version +0 -0
  16. {vgi_python-0.29.4 → vgi_python-0.29.6}/.vale.ini +0 -0
  17. {vgi_python-0.29.4 → vgi_python-0.29.6}/CLAUDE.md +0 -0
  18. {vgi_python-0.29.4 → vgi_python-0.29.6}/DOCS_ACCEPTANCE_CRITERIA.md +0 -0
  19. {vgi_python-0.29.4 → vgi_python-0.29.6}/DOCS_REVIEW_RUBRIC.md +0 -0
  20. {vgi_python-0.29.4 → vgi_python-0.29.6}/DOCS_USABILITY_TEST.md +0 -0
  21. {vgi_python-0.29.4 → vgi_python-0.29.6}/LICENSE +0 -0
  22. {vgi_python-0.29.4 → vgi_python-0.29.6}/README.md +0 -0
  23. {vgi_python-0.29.4 → vgi_python-0.29.6}/RELEASE-RUNBOOK.md +0 -0
  24. {vgi_python-0.29.4 → vgi_python-0.29.6}/SECURITY.md +0 -0
  25. {vgi_python-0.29.4 → vgi_python-0.29.6}/ci/README.md +0 -0
  26. {vgi_python-0.29.4 → vgi_python-0.29.6}/ci/preprocess-require.awk +0 -0
  27. {vgi_python-0.29.4 → vgi_python-0.29.6}/ci/run-integration.sh +0 -0
  28. {vgi_python-0.29.4 → vgi_python-0.29.6}/dist-vgi/.gitignore +0 -0
  29. {vgi_python-0.29.4 → vgi_python-0.29.6}/docs/aggregate-functions.md +0 -0
  30. {vgi_python-0.29.4 → vgi_python-0.29.6}/docs/api/arguments.md +0 -0
  31. {vgi_python-0.29.4 → vgi_python-0.29.6}/docs/api/auth.md +0 -0
  32. {vgi_python-0.29.4 → vgi_python-0.29.6}/docs/api/catalogs.md +0 -0
  33. {vgi_python-0.29.4 → vgi_python-0.29.6}/docs/api/client.md +0 -0
  34. {vgi_python-0.29.4 → vgi_python-0.29.6}/docs/api/exceptions.md +0 -0
  35. {vgi_python-0.29.4 → vgi_python-0.29.6}/docs/api/filters.md +0 -0
  36. {vgi_python-0.29.4 → vgi_python-0.29.6}/docs/api/functions.md +0 -0
  37. {vgi_python-0.29.4 → vgi_python-0.29.6}/docs/api/http.md +0 -0
  38. {vgi_python-0.29.4 → vgi_python-0.29.6}/docs/api/index.md +0 -0
  39. {vgi_python-0.29.4 → vgi_python-0.29.6}/docs/api/metadata.md +0 -0
  40. {vgi_python-0.29.4 → vgi_python-0.29.6}/docs/api/observability.md +0 -0
  41. {vgi_python-0.29.4 → vgi_python-0.29.6}/docs/api/storage.md +0 -0
  42. {vgi_python-0.29.4 → vgi_python-0.29.6}/docs/api/transactor.md +0 -0
  43. {vgi_python-0.29.4 → vgi_python-0.29.6}/docs/api/worker.md +0 -0
  44. {vgi_python-0.29.4 → vgi_python-0.29.6}/docs/argument-serialization.md +0 -0
  45. {vgi_python-0.29.4 → vgi_python-0.29.6}/docs/assets/apple-touch-icon.png +0 -0
  46. {vgi_python-0.29.4 → vgi_python-0.29.6}/docs/assets/favicon-16x16.png +0 -0
  47. {vgi_python-0.29.4 → vgi_python-0.29.6}/docs/assets/favicon-32x32.png +0 -0
  48. {vgi_python-0.29.4 → vgi_python-0.29.6}/docs/assets/favicon.ico +0 -0
  49. {vgi_python-0.29.4 → vgi_python-0.29.6}/docs/assets/kinds/aggregate.svg +0 -0
  50. {vgi_python-0.29.4 → vgi_python-0.29.6}/docs/assets/kinds/buffering.svg +0 -0
  51. {vgi_python-0.29.4 → vgi_python-0.29.6}/docs/assets/kinds/scalar.svg +0 -0
  52. {vgi_python-0.29.4 → vgi_python-0.29.6}/docs/assets/kinds/table-in-out.svg +0 -0
  53. {vgi_python-0.29.4 → vgi_python-0.29.6}/docs/assets/kinds/table.svg +0 -0
  54. {vgi_python-0.29.4 → vgi_python-0.29.6}/docs/assets/logo.png +0 -0
  55. {vgi_python-0.29.4 → vgi_python-0.29.6}/docs/assets/social-card.png +0 -0
  56. {vgi_python-0.29.4 → vgi_python-0.29.6}/docs/authentication.md +0 -0
  57. {vgi_python-0.29.4 → vgi_python-0.29.6}/docs/catalog-interface.md +0 -0
  58. {vgi_python-0.29.4 → vgi_python-0.29.6}/docs/cli.md +0 -0
  59. {vgi_python-0.29.4 → vgi_python-0.29.6}/docs/column-statistics.md +0 -0
  60. {vgi_python-0.29.4 → vgi_python-0.29.6}/docs/concepts/index.md +0 -0
  61. {vgi_python-0.29.4 → vgi_python-0.29.6}/docs/contributing-docs.md +0 -0
  62. {vgi_python-0.29.4 → vgi_python-0.29.6}/docs/filter-pushdown.md +0 -0
  63. {vgi_python-0.29.4 → vgi_python-0.29.6}/docs/generator-api.md +0 -0
  64. {vgi_python-0.29.4 → vgi_python-0.29.6}/docs/global-functions.md +0 -0
  65. {vgi_python-0.29.4 → vgi_python-0.29.6}/docs/how-to/catalogs.md +0 -0
  66. {vgi_python-0.29.4 → vgi_python-0.29.6}/docs/how-to/function-patterns.md +0 -0
  67. {vgi_python-0.29.4 → vgi_python-0.29.6}/docs/how-to/http-auth.md +0 -0
  68. {vgi_python-0.29.4 → vgi_python-0.29.6}/docs/how-to/index.md +0 -0
  69. {vgi_python-0.29.4 → vgi_python-0.29.6}/docs/how-to/pushdown-and-statistics.md +0 -0
  70. {vgi_python-0.29.4 → vgi_python-0.29.6}/docs/how-to/state-storage.md +0 -0
  71. {vgi_python-0.29.4 → vgi_python-0.29.6}/docs/index.md +0 -0
  72. {vgi_python-0.29.4 → vgi_python-0.29.6}/docs/lifecycle.md +0 -0
  73. {vgi_python-0.29.4 → vgi_python-0.29.6}/docs/metadata.md +0 -0
  74. {vgi_python-0.29.4 → vgi_python-0.29.6}/docs/overrides/main.html +0 -0
  75. {vgi_python-0.29.4 → vgi_python-0.29.6}/docs/robots.txt +0 -0
  76. {vgi_python-0.29.4 → vgi_python-0.29.6}/docs/shared-storage.md +0 -0
  77. {vgi_python-0.29.4 → vgi_python-0.29.6}/docs/stylesheets/extra.css +0 -0
  78. {vgi_python-0.29.4 → vgi_python-0.29.6}/docs/tutorial/index.md +0 -0
  79. {vgi_python-0.29.4 → vgi_python-0.29.6}/docs/tutorial/scalar.md +0 -0
  80. {vgi_python-0.29.4 → vgi_python-0.29.6}/docs/tutorial/table.md +0 -0
  81. {vgi_python-0.29.4 → vgi_python-0.29.6}/docs/vgi-logo.png +0 -0
  82. {vgi_python-0.29.4 → vgi_python-0.29.6}/examples/batch_index_worker.py +0 -0
  83. {vgi_python-0.29.4 → vgi_python-0.29.6}/examples/cache_worker.py +0 -0
  84. {vgi_python-0.29.4 → vgi_python-0.29.6}/examples/calc_scalar_worker.py +0 -0
  85. {vgi_python-0.29.4 → vgi_python-0.29.6}/examples/calc_worker.py +0 -0
  86. {vgi_python-0.29.4 → vgi_python-0.29.6}/examples/copy_format_worker.py +0 -0
  87. {vgi_python-0.29.4 → vgi_python-0.29.6}/examples/filter_worker.py +0 -0
  88. {vgi_python-0.29.4 → vgi_python-0.29.6}/examples/greeting_scalar_worker.py +0 -0
  89. {vgi_python-0.29.4 → vgi_python-0.29.6}/examples/row_count_worker.py +0 -0
  90. {vgi_python-0.29.4 → vgi_python-0.29.6}/examples/series_streaming_worker.py +0 -0
  91. {vgi_python-0.29.4 → vgi_python-0.29.6}/examples/sum_worker.py +0 -0
  92. {vgi_python-0.29.4 → vgi_python-0.29.6}/mkdocs.yml +0 -0
  93. {vgi_python-0.29.4 → vgi_python-0.29.6}/packages/vgi-fixtures/LICENSE +0 -0
  94. {vgi_python-0.29.4 → vgi_python-0.29.6}/packages/vgi-fixtures/README.md +0 -0
  95. {vgi_python-0.29.4 → vgi_python-0.29.6}/packages/vgi-fixtures/docker/Dockerfile +0 -0
  96. {vgi_python-0.29.4 → vgi_python-0.29.6}/packages/vgi-fixtures/docker/docker-entrypoint.sh +0 -0
  97. {vgi_python-0.29.4 → vgi_python-0.29.6}/packages/vgi-fixtures/pyproject.toml +0 -0
  98. {vgi_python-0.29.4 → vgi_python-0.29.6}/scripts/measure_startup.py +0 -0
  99. {vgi_python-0.29.4 → vgi_python-0.29.6}/scripts/run_all_tests.sh +0 -0
  100. {vgi_python-0.29.4 → vgi_python-0.29.6}/test-data/generate.sh +0 -0
  101. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/__init__.py +0 -0
  102. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/_http_fixtures.py +0 -0
  103. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/catalog/__init__.py +0 -0
  104. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/catalog/test_attach_option.py +0 -0
  105. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/catalog/test_catalog_interface.py +0 -0
  106. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/catalog/test_client_catalog.py +0 -0
  107. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/catalog/test_column_statistics.py +0 -0
  108. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/catalog/test_declarative.py +0 -0
  109. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/catalog/test_example_worker_catalog.py +0 -0
  110. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/catalog/test_integration.py +0 -0
  111. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/catalog/test_required_filters.py +0 -0
  112. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/catalog/test_scan_branches.py +0 -0
  113. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/catalog/test_serialization.py +0 -0
  114. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/catalog/test_setting.py +0 -0
  115. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/catalog/test_storage.py +0 -0
  116. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/catalog/test_time_travel.py +0 -0
  117. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/catalog/test_writable_table.py +0 -0
  118. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/client/__init__.py +0 -0
  119. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/client/test_aggregate_client.py +0 -0
  120. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/client/test_batch_metadata_callback.py +0 -0
  121. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/client/test_broken_pipe.py +0 -0
  122. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/client/test_cli.py +0 -0
  123. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/client/test_cli_catalog_functions.py +0 -0
  124. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/client/test_copy_client.py +0 -0
  125. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/client/test_scan_branches_client.py +0 -0
  126. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/client/test_split_client.py +0 -0
  127. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/client/test_stop_force.py +0 -0
  128. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/client/test_table_column_statistics.py +0 -0
  129. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/client/test_time_travel_client.py +0 -0
  130. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/client/test_worker_debug.py +0 -0
  131. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/conformance/__init__.py +0 -0
  132. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/conformance/_stub.py +0 -0
  133. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/conformance/conftest.py +0 -0
  134. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/conformance/test_accumulate.py +0 -0
  135. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/conformance/test_aggregate.py +0 -0
  136. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/conformance/test_attach.py +0 -0
  137. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/conformance/test_bearer_auth.py +0 -0
  138. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/conformance/test_const_param_enforcement.py +0 -0
  139. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/conformance/test_container.py +0 -0
  140. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/conformance/test_copy_from.py +0 -0
  141. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/conformance/test_copy_to.py +0 -0
  142. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/conformance/test_directory_parity.py +0 -0
  143. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/conformance/test_function_inventory.py +0 -0
  144. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/conformance/test_github.py +0 -0
  145. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/conformance/test_http_client.py +0 -0
  146. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/conformance/test_http_external_location.py +0 -0
  147. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/conformance/test_http_upload_url.py +0 -0
  148. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/conformance/test_macro.py +0 -0
  149. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/conformance/test_overload.py +0 -0
  150. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/conformance/test_protocol_inventory.py +0 -0
  151. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/conformance/test_protocol_version.py +0 -0
  152. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/conformance/test_resumable_scan.py +0 -0
  153. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/conformance/test_scalar.py +0 -0
  154. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/conformance/test_scalar_attach_opaque_data.py +0 -0
  155. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/conformance/test_secret.py +0 -0
  156. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/conformance/test_settings.py +0 -0
  157. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/conformance/test_splits.py +0 -0
  158. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/conformance/test_table.py +0 -0
  159. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/conformance/test_table_in_out.py +0 -0
  160. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/conformance/test_view.py +0 -0
  161. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/conformance/test_writable.py +0 -0
  162. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/conftest.py +0 -0
  163. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/data/split_tokens/anchor_len_overrun.bin +0 -0
  164. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/data/split_tokens/bad_fingerprint.bin +0 -0
  165. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/data/split_tokens/bad_flags_unsealed_but_key_present.bin +0 -0
  166. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/data/split_tokens/bad_version.bin +0 -0
  167. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/data/split_tokens/generate.py +0 -0
  168. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/data/split_tokens/manifest.json +0 -0
  169. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/data/split_tokens/reserved_flag_bit.bin +0 -0
  170. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/data/split_tokens/stale_anchor.bin +0 -0
  171. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/data/split_tokens/truncated.bin +0 -0
  172. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/data/split_tokens/valid_sealed.bin +0 -0
  173. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/data/split_tokens/valid_unsealed.bin +0 -0
  174. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/scalar/__init__.py +0 -0
  175. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/scalar/test_bernoulli_function.py +0 -0
  176. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/scalar/test_binary_packet_function.py +0 -0
  177. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/scalar/test_client.py +0 -0
  178. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/scalar/test_conditional_message_function.py +0 -0
  179. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/scalar/test_hash_seed_function.py +0 -0
  180. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/scalar/test_multiply_by_setting_function.py +0 -0
  181. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/scalar/test_multiply_function.py +0 -0
  182. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/scalar/test_random_bytes_function.py +0 -0
  183. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/scalar/test_return_secret_value_function.py +0 -0
  184. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/table/__init__.py +0 -0
  185. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/table/generator/__init__.py +0 -0
  186. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/table/generator/test_constant_columns_function.py +0 -0
  187. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/table/generator/test_double_sequence_function.py +0 -0
  188. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/table/generator/test_exception_function.py +0 -0
  189. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/table/generator/test_filter_echo_function.py +0 -0
  190. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/table/generator/test_logging_function.py +0 -0
  191. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/table/generator/test_nested_sequence_function.py +0 -0
  192. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/table/generator/test_partitioned_function.py +0 -0
  193. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/table/generator/test_projected_data_function.py +0 -0
  194. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/table/generator/test_sequence_function.py +0 -0
  195. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/table/generator/test_settings_function.py +0 -0
  196. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/table/generator/test_struct_settings_function.py +0 -0
  197. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/table/generator/test_ten_thousand_function.py +0 -0
  198. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/table/test_wide_columns.py +0 -0
  199. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/table_in_out/__init__.py +0 -0
  200. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/table_in_out/generator/__init__.py +0 -0
  201. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/table_in_out/generator/test_buffer_input_function.py +0 -0
  202. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/table_in_out/generator/test_echo_function.py +0 -0
  203. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/table_in_out/generator/test_exception_functions.py +0 -0
  204. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/table_in_out/generator/test_filter_by_setting_function.py +0 -0
  205. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/table_in_out/generator/test_repeat_inputs_function.py +0 -0
  206. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/table_in_out/generator/test_substream_partial_sum_function.py +0 -0
  207. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/table_in_out/generator/test_sum_all_columns_function.py +0 -0
  208. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/table_in_out/test_blended_metadata.py +0 -0
  209. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/table_in_out/test_client.py +0 -0
  210. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/test_access_log_audit.py +0 -0
  211. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/test_aggregate_function.py +0 -0
  212. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/test_argument_spec.py +0 -0
  213. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/test_auth.py +0 -0
  214. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/test_bind_exceptions.py +0 -0
  215. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/test_bind_request_at_clause.py +0 -0
  216. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/test_bound_storage_conformance.py +0 -0
  217. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/test_cache_control.py +0 -0
  218. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/test_call_state_attach.py +0 -0
  219. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/test_catalog_auth_binding.py +0 -0
  220. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/test_copy_from_function.py +0 -0
  221. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/test_copy_to_function.py +0 -0
  222. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/test_docstrings.py +0 -0
  223. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/test_documentation_examples.py +0 -0
  224. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/test_example_function_arg_types.py +0 -0
  225. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/test_examples_workers.py +0 -0
  226. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/test_exception_handling.py +0 -0
  227. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/test_exceptions.py +0 -0
  228. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/test_filter_pushdown.py +0 -0
  229. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/test_filter_pushdown_extension.py +0 -0
  230. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/test_fixture_catalog_parity.py +0 -0
  231. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/test_function_storage.py +0 -0
  232. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/test_function_storage_azure_sql.py +0 -0
  233. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/test_function_storage_cf_do.py +0 -0
  234. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/test_function_storage_cf_do_integration.py +0 -0
  235. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/test_function_storage_conformance.py +0 -0
  236. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/test_generated_cpp_constants.py +0 -0
  237. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/test_generated_cpp_protocol_version.py +0 -0
  238. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/test_generated_cpp_request_builders.py +0 -0
  239. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/test_generated_cpp_schemas.py +0 -0
  240. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/test_generated_cpp_secret.py +0 -0
  241. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/test_generated_go_schemas.py +0 -0
  242. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/test_generated_java_schemas.py +0 -0
  243. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/test_generated_protocol_version.py +0 -0
  244. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/test_generated_rust_request_builders.py +0 -0
  245. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/test_generated_rust_schemas.py +0 -0
  246. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/test_generated_schemas_cross_lang.py +0 -0
  247. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/test_generated_ts_client.py +0 -0
  248. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/test_generated_ts_schemas.py +0 -0
  249. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/test_http_demo_storage.py +0 -0
  250. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/test_http_s3_offload_input.py +0 -0
  251. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/test_http_s3_offload_output.py +0 -0
  252. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/test_launch_transport.py +0 -0
  253. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/test_metadata.py +0 -0
  254. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/test_mypy_consolidated.py +0 -0
  255. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/test_nest_tensor.py +0 -0
  256. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/test_otel.py +0 -0
  257. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/test_projection_enforcement.py +0 -0
  258. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/test_projection_repro.py +0 -0
  259. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/test_protocol_classes.py +0 -0
  260. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/test_resolved_secrets.py +0 -0
  261. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/test_schema_scoped_functions.py +0 -0
  262. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/test_schema_utils.py +0 -0
  263. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/test_serve.py +0 -0
  264. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/test_setting_secret_annotations.py +0 -0
  265. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/test_signing_key.py +0 -0
  266. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/test_split_token.py +0 -0
  267. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/test_table_buffering_function.py +0 -0
  268. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/test_table_function_dynamic_to_string.py +0 -0
  269. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/test_tcp_transport.py +0 -0
  270. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/test_type_bounds.py +0 -0
  271. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/test_union_argument.py +0 -0
  272. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/test_wire_record_schema_parity.py +0 -0
  273. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/test_worker.py +0 -0
  274. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/test_worker_cli.py +0 -0
  275. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/test_worker_page.py +0 -0
  276. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/transactor/__init__.py +0 -0
  277. {vgi_python-0.29.4 → vgi_python-0.29.6}/tests/transactor/test_transactor.py +0 -0
  278. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/__init__.py +0 -0
  279. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_duckdb.py +0 -0
  280. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_storage_profile.py +0 -0
  281. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/__init__.py +0 -0
  282. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/accumulate/__init__.py +0 -0
  283. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/accumulate/worker.py +0 -0
  284. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/aggregate/__init__.py +0 -0
  285. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/aggregate/_common.py +0 -0
  286. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/aggregate/basic.py +0 -0
  287. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/aggregate/dynamic.py +0 -0
  288. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/aggregate/generic.py +0 -0
  289. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/aggregate/listagg.py +0 -0
  290. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/aggregate/percentile.py +0 -0
  291. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/aggregate/same_name.py +0 -0
  292. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/aggregate/secret_typed.py +0 -0
  293. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/aggregate/streaming.py +0 -0
  294. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/aggregate/varargs.py +0 -0
  295. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/aggregate/window.py +0 -0
  296. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/attach_options.py +0 -0
  297. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/bad_enum.py +0 -0
  298. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/bad_protocol.py +0 -0
  299. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/cancellable.py +0 -0
  300. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/catalog.py +0 -0
  301. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/companion.py +0 -0
  302. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/copy_from.py +0 -0
  303. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/copy_to.py +0 -0
  304. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/global_functions.py +0 -0
  305. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/http_server.py +0 -0
  306. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/narrow_bind/__init__.py +0 -0
  307. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/narrow_bind/worker.py +0 -0
  308. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/nest_tensor.py +0 -0
  309. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/orchard_catalog.py +0 -0
  310. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/projection_repro/__init__.py +0 -0
  311. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/projection_repro/worker.py +0 -0
  312. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/scalar/__init__.py +0 -0
  313. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/scalar/_common.py +0 -0
  314. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/scalar/arithmetic.py +0 -0
  315. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/scalar/bench_ladder.py +0 -0
  316. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/scalar/binary.py +0 -0
  317. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/scalar/formatting.py +0 -0
  318. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/scalar/geo.py +0 -0
  319. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/scalar/null_handling.py +0 -0
  320. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/scalar/random_demo.py +0 -0
  321. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/scalar/same_name.py +0 -0
  322. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/scalar/settings_secrets.py +0 -0
  323. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/scalar/type_info.py +0 -0
  324. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/schema_reconcile/__init__.py +0 -0
  325. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/schema_reconcile/worker.py +0 -0
  326. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/simple_writable.py +0 -0
  327. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/table/__init__.py +0 -0
  328. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/table/_common.py +0 -0
  329. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/table/batch_index.py +0 -0
  330. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/table/batch_index_broken.py +0 -0
  331. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/table/cache.py +0 -0
  332. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/table/catalog_scans.py +0 -0
  333. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/table/filters.py +0 -0
  334. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/table/late_materialization.py +0 -0
  335. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/table/make_series.py +0 -0
  336. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/table/misc.py +0 -0
  337. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/table/order_modes.py +0 -0
  338. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/table/pairs.py +0 -0
  339. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/table/partition_columns.py +0 -0
  340. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/table/partition_columns_broken.py +0 -0
  341. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/table/profiling_example.py +0 -0
  342. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/table/required_filters.py +0 -0
  343. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/table/same_name_cached.py +0 -0
  344. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/table/sequence.py +0 -0
  345. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/table/settings.py +0 -0
  346. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/table/splits.py +0 -0
  347. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/table/transaction_storage.py +0 -0
  348. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/table/tt_pushdown.py +0 -0
  349. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/table/typed_probe.py +0 -0
  350. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/table/versioned.py +0 -0
  351. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/table/wide.py +0 -0
  352. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/table_in_out.py +0 -0
  353. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/table_in_out_same_name.py +0 -0
  354. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/twin_catalogs.py +0 -0
  355. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/versioned.py +0 -0
  356. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/versioned_tables.py +0 -0
  357. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/worker.py +0 -0
  358. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/writable/__init__.py +0 -0
  359. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/writable/generic.py +0 -0
  360. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/writable/table.py +0 -0
  361. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/_test_fixtures/writable/worker.py +0 -0
  362. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/aggregate_function.py +0 -0
  363. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/argument_spec.py +0 -0
  364. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/arguments.py +0 -0
  365. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/attach_header.py +0 -0
  366. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/auth.py +0 -0
  367. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/cache_control.py +0 -0
  368. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/catalog/__init__.py +0 -0
  369. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/catalog/_descriptor_spec.py +0 -0
  370. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/catalog/attach_option.py +0 -0
  371. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/catalog/catalog_interface.py +0 -0
  372. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/catalog/descriptors.py +0 -0
  373. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/catalog/duckdb_statistics.py +0 -0
  374. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/catalog/secret_type.py +0 -0
  375. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/catalog/setting.py +0 -0
  376. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/catalog/storage.py +0 -0
  377. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/client/__init__.py +0 -0
  378. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/client/aggregate.py +0 -0
  379. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/client/catalog_mixin.py +0 -0
  380. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/client/cli.py +0 -0
  381. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/client/cli_catalog.py +0 -0
  382. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/client/cli_schema.py +0 -0
  383. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/client/cli_table.py +0 -0
  384. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/client/cli_transaction.py +0 -0
  385. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/client/cli_utils.py +0 -0
  386. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/client/cli_view.py +0 -0
  387. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/client/errors.py +0 -0
  388. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/codegen/__init__.py +0 -0
  389. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/codegen/_common.py +0 -0
  390. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/codegen/cpp_constants.py +0 -0
  391. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/codegen/cpp_protocol_version.py +0 -0
  392. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/codegen/cpp_request_builders.py +0 -0
  393. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/codegen/cpp_schemas.py +0 -0
  394. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/codegen/cpp_secret_protocol_version.py +0 -0
  395. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/codegen/cpp_secret_request_builders.py +0 -0
  396. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/codegen/cpp_secret_schemas.py +0 -0
  397. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/codegen/go_schemas.py +0 -0
  398. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/codegen/java_schemas.py +0 -0
  399. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/codegen/protocol_version.py +0 -0
  400. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/codegen/rust_request_builders.py +0 -0
  401. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/codegen/rust_schemas.py +0 -0
  402. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/codegen/ts_client.py +0 -0
  403. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/codegen/ts_schemas.py +0 -0
  404. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/copy_from_function.py +0 -0
  405. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/copy_to_function.py +0 -0
  406. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/exceptions.py +0 -0
  407. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/function.py +0 -0
  408. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/function_storage.py +0 -0
  409. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/function_storage_azure_sql.py +0 -0
  410. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/function_storage_cf_do.py +0 -0
  411. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/http/__init__.py +0 -0
  412. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/http/demo_storage.py +0 -0
  413. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/http/landing.html +0 -0
  414. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/http/landing_page.py +0 -0
  415. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/http/vgi-client.js +0 -0
  416. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/http/worker_page.py +0 -0
  417. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/invocation.py +0 -0
  418. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/logging_config.py +0 -0
  419. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/meta_worker.py +0 -0
  420. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/metadata.py +0 -0
  421. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/otel.py +0 -0
  422. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/profiling.py +0 -0
  423. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/protocol_version.txt +0 -0
  424. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/py.typed +0 -0
  425. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/scalar_function.py +0 -0
  426. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/schema_utils.py +0 -0
  427. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/secret_protocol.py +0 -0
  428. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/secret_service.py +0 -0
  429. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/serve.py +0 -0
  430. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/split_token.py +0 -0
  431. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/table_buffering_function.py +0 -0
  432. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/table_filter_pushdown.py +0 -0
  433. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/table_function.py +0 -0
  434. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/table_in_out_function.py +0 -0
  435. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/transactor/__init__.py +0 -0
  436. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/transactor/_duckdb_compat.py +0 -0
  437. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/transactor/client.py +0 -0
  438. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/transactor/protocol.py +0 -0
  439. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/transactor/server.py +0 -0
  440. {vgi_python-0.29.4 → vgi_python-0.29.6}/vgi/worker.py +0 -0
  441. {vgi_python-0.29.4 → vgi_python-0.29.6}/wrangler.jsonc +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: vgi-python
3
- Version: 0.29.4
3
+ Version: 0.29.6
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "vgi-python"
3
- version = "0.29.4"
3
+ version = "0.29.6"
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,282 @@
1
+ # Copyright 2025, 2026 Query Farm LLC - https://query.farm
2
+
3
+ """RPC-level exchange coverage for blended (`RowTransformFunction`) table functions.
4
+
5
+ `test_blended_metadata.py` covers `resolve_metadata()` in isolation — no worker
6
+ subprocess. This file is the first exchange-level coverage for the blended
7
+ fixtures registered in `vgi/_test_fixtures/table_in_out.py`: it drives them
8
+ through `Client.table_in_out_function(...)`'s new `parent_row_callback`, which
9
+ decodes `vgi_rpc.parent_row#b64` provenance for a 1->N / 1->0 emit (see
10
+ `vgi.protocol._decode_parent_rows`).
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from typing import Any
16
+
17
+ import pyarrow as pa
18
+ import pytest
19
+
20
+ from tests.conftest import make_schema
21
+ from vgi.arguments import Arguments
22
+ from vgi.client import Client
23
+ from vgi.client.client import ClientError
24
+
25
+
26
+ class TestBlendedExplodeProvenance:
27
+ """`blended_explode`: 1->N fan-out with per-output-row parent_row provenance."""
28
+
29
+ def test_parent_row_pairing_is_correct(self, fixture_worker: str) -> None:
30
+ """Each output row's parent index must point at the input row that produced it.
31
+
32
+ Asserting only the final row *set* would pass even for a naive
33
+ "assume identity" implementation whenever fan-out counts differ
34
+ across rows -- so this reconstructs the exact expected
35
+ (parent_row, emitted_value) pairs in emission order and compares.
36
+ """
37
+ input_schema = make_schema([pa.field("n", pa.int64())])
38
+ counts = [0, 1, 3, 2]
39
+ batch = pa.RecordBatch.from_pydict({"n": counts}, schema=input_schema)
40
+
41
+ parent_rows_seen: list[int] = []
42
+
43
+ with Client(fixture_worker) as client:
44
+ results = list(
45
+ client.table_in_out_function(
46
+ function_name="blended_explode",
47
+ schema_name="main",
48
+ input=iter([batch]),
49
+ parent_row_callback=parent_rows_seen.extend,
50
+ )
51
+ )
52
+
53
+ assert len(results) == 1
54
+ out = results[0]
55
+ i_values = out.column("i").to_pylist()
56
+
57
+ assert len(i_values) == sum(counts)
58
+ assert len(parent_rows_seen) == len(i_values)
59
+
60
+ expected_pairs = [(row_idx, k) for row_idx, n in enumerate(counts) for k in range(n)]
61
+ actual_pairs = list(zip(parent_rows_seen, i_values, strict=True))
62
+ assert actual_pairs == expected_pairs
63
+
64
+ def test_all_rows_filtered_to_empty_result(self, fixture_worker: str) -> None:
65
+ """n=0 for every input row -> a correctly-schemed, zero-row output."""
66
+ input_schema = make_schema([pa.field("n", pa.int64())])
67
+ batch = pa.RecordBatch.from_pydict({"n": [0, 0, 0]}, schema=input_schema)
68
+
69
+ parent_rows_seen: list[list[int]] = []
70
+
71
+ with Client(fixture_worker) as client:
72
+ results = list(
73
+ client.table_in_out_function(
74
+ function_name="blended_explode",
75
+ schema_name="main",
76
+ input=iter([batch]),
77
+ parent_row_callback=parent_rows_seen.append,
78
+ )
79
+ )
80
+
81
+ assert len(results) == 1
82
+ out = results[0]
83
+ assert out.num_rows == 0
84
+ assert out.schema.names == ["i"]
85
+ assert parent_rows_seen == [[]]
86
+
87
+ def test_identity_map_when_row_counts_match(self, fixture_worker: str) -> None:
88
+ """n=1 for every row -> a 1:1 emit; the identity map is a valid decode.
89
+
90
+ `blended_explode` never encodes `vgi_rpc.parent_row` for a 1:1 emit
91
+ (the C++ reference assumes identity for that case too), so this
92
+ exercises `_decode_parent_rows`'s absent-metadata branch, not just
93
+ the explicit-array branch the other tests exercise.
94
+ """
95
+ input_schema = make_schema([pa.field("n", pa.int64())])
96
+ batch = pa.RecordBatch.from_pydict({"n": [1, 1, 1]}, schema=input_schema)
97
+
98
+ parent_rows_seen: list[int] = []
99
+
100
+ with Client(fixture_worker) as client:
101
+ results = list(
102
+ client.table_in_out_function(
103
+ function_name="blended_explode",
104
+ schema_name="main",
105
+ input=iter([batch]),
106
+ parent_row_callback=parent_rows_seen.extend,
107
+ )
108
+ )
109
+
110
+ assert len(results) == 1
111
+ assert results[0].column("i").to_pylist() == [0, 0, 0]
112
+ assert parent_rows_seen == [0, 1, 2]
113
+
114
+
115
+ class TestHostileProvenance:
116
+ """`hostile_provenance`: adversarial worker payloads must raise, never corrupt."""
117
+
118
+ @pytest.mark.parametrize("mode", ["range", "length", "base64"])
119
+ def test_malformed_provenance_raises_client_error(
120
+ self,
121
+ client_transport: Any,
122
+ mode: str,
123
+ ) -> None:
124
+ """Each hostile mode must raise ClientError on both subprocess and HTTP.
125
+
126
+ Symmetry matters here: an asymmetry between transports would mean one
127
+ parse path validates and the other doesn't -- a security bug, not
128
+ just an inconsistency.
129
+ """
130
+ input_schema = make_schema([pa.field("x", pa.int64())])
131
+ batch = pa.RecordBatch.from_pydict({"x": [1, 2, 3]}, schema=input_schema)
132
+
133
+ with client_transport() as client, pytest.raises(ClientError):
134
+ list(
135
+ client.table_in_out_function(
136
+ function_name="hostile_provenance",
137
+ schema_name="main",
138
+ input=iter([batch]),
139
+ arguments=Arguments(named={"mode": pa.scalar(mode)}),
140
+ parent_row_callback=lambda _rows: None,
141
+ )
142
+ )
143
+
144
+ def test_no_callback_no_decode_no_raise(self, fixture_worker: str) -> None:
145
+ """Without `parent_row_callback`, malformed provenance is never even decoded.
146
+
147
+ `decode_parent_rows` is derived from `parent_row_callback is not None`
148
+ -- this is the backward-compat guarantee that an ordinary (non-blended)
149
+ table-in-out caller, which never passes the callback, is completely
150
+ unaffected by this feature existing.
151
+ """
152
+ input_schema = make_schema([pa.field("x", pa.int64())])
153
+ batch = pa.RecordBatch.from_pydict({"x": [1, 2, 3]}, schema=input_schema)
154
+
155
+ with Client(fixture_worker) as client:
156
+ results = list(
157
+ client.table_in_out_function(
158
+ function_name="hostile_provenance",
159
+ schema_name="main",
160
+ input=iter([batch]),
161
+ arguments=Arguments(named={"mode": pa.scalar("range")}),
162
+ )
163
+ )
164
+
165
+ assert len(results) == 1
166
+ assert results[0].column("hv").to_pylist() == [1, 2, 3]
167
+
168
+
169
+ class TestBlendedArityAndVarargs:
170
+ """Broader coverage once the core provenance mechanism is proven."""
171
+
172
+ def test_geo_encode_two_arg_overload(self, fixture_worker: str) -> None:
173
+ """`geo_encode(lat, lon)` -- the 2-arg overload, identity 1:1 map."""
174
+ input_schema = make_schema([pa.field("latitude", pa.float64()), pa.field("longitude", pa.float64())])
175
+ batch = pa.RecordBatch.from_pydict(
176
+ {"latitude": [52.0, 48.5], "longitude": [13.0, 2.3]},
177
+ schema=input_schema,
178
+ )
179
+
180
+ parent_rows_seen: list[int] = []
181
+
182
+ with Client(fixture_worker) as client:
183
+ results = list(
184
+ client.table_in_out_function(
185
+ function_name="geo_encode",
186
+ schema_name="main",
187
+ input=iter([batch]),
188
+ arguments=Arguments(named={"precision": pa.scalar(1)}),
189
+ parent_row_callback=parent_rows_seen.extend,
190
+ )
191
+ )
192
+
193
+ assert len(results) == 1
194
+ assert results[0].column("geohash").to_pylist() == ["52.0:13.0", "48.5:2.3"]
195
+ assert parent_rows_seen == [0, 1]
196
+
197
+ def test_geo_encode_three_arg_overload(self, fixture_worker: str) -> None:
198
+ """`geo_encode(lat, lon, alt)` -- same Meta.name, resolved by input arity."""
199
+ input_schema = make_schema(
200
+ [
201
+ pa.field("latitude", pa.float64()),
202
+ pa.field("longitude", pa.float64()),
203
+ pa.field("altitude", pa.float64()),
204
+ ]
205
+ )
206
+ batch = pa.RecordBatch.from_pydict(
207
+ {"latitude": [52.0], "longitude": [13.0], "altitude": [100.0]},
208
+ schema=input_schema,
209
+ )
210
+
211
+ with Client(fixture_worker) as client:
212
+ results = list(
213
+ client.table_in_out_function(
214
+ function_name="geo_encode",
215
+ schema_name="main",
216
+ input=iter([batch]),
217
+ arguments=Arguments(named={"precision": pa.scalar(0)}),
218
+ )
219
+ )
220
+
221
+ assert len(results) == 1
222
+ assert results[0].column("geohash").to_pylist() == ["52.0:13.0:100.0"]
223
+
224
+ def test_row_sum_varargs(self, fixture_worker: str) -> None:
225
+ """`row_sum(a, b, c)` -- varargs positional input, positionally-named columns."""
226
+ input_schema = make_schema([pa.field("col0", pa.float64()), pa.field("col1", pa.float64())])
227
+ batch = pa.RecordBatch.from_pydict({"col0": [1.0, -2.0], "col1": [2.0, -3.0]}, schema=input_schema)
228
+
229
+ with Client(fixture_worker) as client:
230
+ results = list(
231
+ client.table_in_out_function(
232
+ function_name="row_sum",
233
+ schema_name="main",
234
+ input=iter([batch]),
235
+ arguments=Arguments(named={"absolute": pa.scalar(True)}),
236
+ )
237
+ )
238
+
239
+ assert len(results) == 1
240
+ assert results[0].column("row_sum").to_pylist() == [3.0, 5.0]
241
+
242
+
243
+ class TestHasFinalizeSkip:
244
+ """`has_finalize=False` skips the FINALIZE `init()` entirely for a no-finalize function.
245
+
246
+ Real interop bug found live against a non-Python worker SDK: the client
247
+ unconditionally sent a FINALIZE-phase init() for every table-in-out call,
248
+ which the Python fixture worker happens to no-op gracefully but a
249
+ TypeScript-SDK worker rejects outright ("a blended row-transform function
250
+ has no FINALIZE phase"). `has_finalize=False` avoids sending that init()
251
+ at all rather than depending on every SDK tolerating an unexpected one.
252
+ Output must be identical either way for a function that has no finalize
253
+ stage (there is nothing to skip yielding) -- this is the equivalence this
254
+ suite can prove locally; the cross-SDK rejection itself is TypeScript-only.
255
+ """
256
+
257
+ def test_output_identical_with_and_without_has_finalize(self, client_transport: Any) -> None:
258
+ """Same output whether the FINALIZE init() is sent or skipped, both transports."""
259
+ input_schema = make_schema([pa.field("n", pa.int64())])
260
+ batch = pa.RecordBatch.from_pydict({"n": [0, 1, 3, 2]}, schema=input_schema)
261
+
262
+ with client_transport() as client:
263
+ with_finalize = list(
264
+ client.table_in_out_function(
265
+ function_name="blended_explode",
266
+ schema_name="main",
267
+ input=iter([batch]),
268
+ )
269
+ )
270
+ with client_transport() as client:
271
+ without_finalize = list(
272
+ client.table_in_out_function(
273
+ function_name="blended_explode",
274
+ schema_name="main",
275
+ input=iter([batch]),
276
+ has_finalize=False,
277
+ )
278
+ )
279
+
280
+ assert len(with_finalize) == 1
281
+ assert len(without_finalize) == 1
282
+ assert with_finalize[0].column("i").to_pylist() == without_finalize[0].column("i").to_pylist()
@@ -2569,7 +2569,7 @@ requires-dist = [
2569
2569
 
2570
2570
  [[package]]
2571
2571
  name = "vgi-python"
2572
- version = "0.29.4"
2572
+ version = "0.29.6"
2573
2573
  source = { editable = "." }
2574
2574
  dependencies = [
2575
2575
  { name = "click" },
@@ -118,6 +118,7 @@ from vgi.protocol import (
118
118
  TableBufferingProcessRequest,
119
119
  TableFunctionPlanRequest,
120
120
  VgiProtocol,
121
+ _decode_parent_rows,
121
122
  )
122
123
  from vgi.table_function import TableInOutFunctionInitPhase
123
124
 
@@ -296,6 +297,33 @@ class Client(CatalogClientMixin, AggregateClientMixin):
296
297
  return batches[0]
297
298
  return combined[0]
298
299
 
300
+ @staticmethod
301
+ def _combine_parent_rows(parent_rows_batches: list[list[int]]) -> list[int]:
302
+ """Concatenate per-output-batch parent-row lists in emission order.
303
+
304
+ Mirrors `_combine_batches` for blended row-transform provenance: when
305
+ a table-in-out call produces multiple output batches for one input
306
+ batch (a worker replying `HAVE_MORE_OUTPUT` more than once), each
307
+ sub-batch's decoded parent-row list already indexes into that same
308
+ shipped input batch (see `_decode_parent_rows`'s contract), so
309
+ concatenating the lists in the order the sub-batches were emitted is
310
+ sound — no index renumbering is needed, because `_combine_batches`
311
+ also concatenates the sub-batches themselves in that same order.
312
+
313
+ Args:
314
+ parent_rows_batches: One decoded parent-row list per output
315
+ sub-batch, in emission order.
316
+
317
+ Returns:
318
+ The concatenated parent-row list, aligned row-for-row with what
319
+ `_combine_batches` produces from the corresponding output batches.
320
+
321
+ """
322
+ combined: list[int] = []
323
+ for batch_rows in parent_rows_batches:
324
+ combined.extend(batch_rows)
325
+ return combined
326
+
299
327
  @staticmethod
300
328
  def _on_worker_log(msg: Message) -> None:
301
329
  """Forward log messages from vgi_rpc to the worker logger."""
@@ -1618,7 +1646,9 @@ class Client(CatalogClientMixin, AggregateClientMixin):
1618
1646
  worker: WorkerConnection,
1619
1647
  input_batch: pa.RecordBatch,
1620
1648
  batch_index: int,
1621
- ) -> list[pa.RecordBatch]:
1649
+ *,
1650
+ decode_parent_rows: bool = False,
1651
+ ) -> tuple[list[pa.RecordBatch], list[list[int]] | None]:
1622
1652
  """Send a batch to a worker and collect all output batches.
1623
1653
 
1624
1654
  Sends the input batch via stream.exchange(), then checks the vgi.status
@@ -1629,19 +1659,32 @@ class Client(CatalogClientMixin, AggregateClientMixin):
1629
1659
  worker: The worker connection to use. Must have stream initialized.
1630
1660
  input_batch: The input `RecordBatch` to send to the worker.
1631
1661
  batch_index: Index of this batch in the input sequence (for logging).
1662
+ decode_parent_rows: When True, additionally decode each output
1663
+ batch's `vgi_rpc.parent_row#b64` metadata (see
1664
+ `vgi.protocol._decode_parent_rows`) — used by blended
1665
+ row-transform table functions, where a worker's output row
1666
+ count need not match its input row count. False for every
1667
+ other table-in-out / scalar caller, which has no provenance
1668
+ concept and must not have the row-count-mismatch check inside
1669
+ `_decode_parent_rows` start firing for it.
1632
1670
 
1633
1671
  Returns:
1634
- List of output `RecordBatch`es produced by processing this input batch.
1672
+ A tuple of (output batches, per-batch decoded parent-row lists).
1673
+ The second element is `None` when `decode_parent_rows` is False —
1674
+ keeps the common path from building a list it never uses.
1635
1675
 
1636
1676
  Raises:
1637
- [`ClientError`][]: If worker.stream is None, or if the worker returns
1638
- an unexpected status, or if the RPC call fails.
1677
+ [`ClientError`][]: If worker.stream is None, if the worker returns
1678
+ an unexpected status, if the RPC call fails, or (when
1679
+ `decode_parent_rows` is True) if `vgi_rpc.parent_row#b64` is
1680
+ malformed or absent-with-mismatched-row-counts.
1639
1681
 
1640
1682
  """
1641
1683
  if worker.stream is None:
1642
1684
  raise ClientError(f"Worker {worker.worker_index} stream not initialized")
1643
1685
 
1644
1686
  output_batches: list[pa.RecordBatch] = []
1687
+ parent_rows_batches: list[list[int]] | None = [] if decode_parent_rows else None
1645
1688
 
1646
1689
  while True:
1647
1690
  _logger.debug(
@@ -1665,6 +1708,18 @@ class Client(CatalogClientMixin, AggregateClientMixin):
1665
1708
 
1666
1709
  output_batches.append(output.batch)
1667
1710
 
1711
+ if parent_rows_batches is not None:
1712
+ try:
1713
+ parent_rows_batches.append(
1714
+ _decode_parent_rows(
1715
+ output.custom_metadata,
1716
+ output_rows=output.batch.num_rows,
1717
+ input_rows=input_batch.num_rows,
1718
+ )
1719
+ )
1720
+ except RuntimeError as e:
1721
+ raise ClientError(str(e)) from e
1722
+
1668
1723
  # Check vgi.status for table-in-out status
1669
1724
  status = None
1670
1725
  if output.custom_metadata:
@@ -1678,22 +1733,25 @@ class Client(CatalogClientMixin, AggregateClientMixin):
1678
1733
  else:
1679
1734
  raise ClientError(f"Unexpected status from worker {worker.worker_index}: {status!r}")
1680
1735
 
1681
- return output_batches
1736
+ return output_batches, parent_rows_batches
1682
1737
 
1683
1738
  def _worker_thread_loop(
1684
1739
  self,
1685
1740
  worker: WorkerConnection,
1686
1741
  input_queue: Queue[tuple[int, pa.RecordBatch] | None],
1687
- output_queue: Queue[tuple[int, list[pa.RecordBatch]] | BaseException],
1742
+ output_queue: Queue[tuple[int, list[pa.RecordBatch], list[list[int]] | None] | BaseException],
1743
+ *,
1744
+ decode_parent_rows: bool = False,
1688
1745
  ) -> None:
1689
1746
  """Thread function that processes batches for a single worker.
1690
1747
 
1691
1748
  Runs in a dedicated thread, pulling (batch_index, batch) tuples from
1692
1749
  the input queue, processing them via _process_batch_on_worker, and
1693
- pushing (batch_index, output_batches) tuples to the output queue.
1750
+ pushing (batch_index, output_batches, parent_rows_batches) tuples to
1751
+ the output queue.
1694
1752
 
1695
1753
  When None is received from input_queue, signals thread completion by
1696
- pushing (-1, []) to output_queue and exits.
1754
+ pushing (-1, [], None) to output_queue and exits.
1697
1755
 
1698
1756
  If an exception occurs during processing, it is caught, logged, and
1699
1757
  pushed to output_queue as the exception object itself.
@@ -1703,6 +1761,8 @@ class Client(CatalogClientMixin, AggregateClientMixin):
1703
1761
  input_queue: Thread-safe queue providing (batch_index, `RecordBatch`)
1704
1762
  tuples for processing. A None value signals end of input.
1705
1763
  output_queue: Thread-safe queue for results.
1764
+ decode_parent_rows: Forwarded to `_process_batch_on_worker` — see
1765
+ its docstring.
1706
1766
 
1707
1767
  """
1708
1768
  try:
@@ -1710,12 +1770,14 @@ class Client(CatalogClientMixin, AggregateClientMixin):
1710
1770
  item = input_queue.get()
1711
1771
  if item is None:
1712
1772
  # End of input - signal thread completion
1713
- output_queue.put((-1, []))
1773
+ output_queue.put((-1, [], None))
1714
1774
  break
1715
1775
 
1716
1776
  batch_index, input_batch = item
1717
- outputs = self._process_batch_on_worker(worker, input_batch, batch_index)
1718
- output_queue.put((batch_index, outputs))
1777
+ outputs, parent_rows_batches = self._process_batch_on_worker(
1778
+ worker, input_batch, batch_index, decode_parent_rows=decode_parent_rows
1779
+ )
1780
+ output_queue.put((batch_index, outputs, parent_rows_batches))
1719
1781
  except Exception as e:
1720
1782
  _logger.exception("worker_thread_error worker_index=%s", worker.worker_index)
1721
1783
  output_queue.put(e)
@@ -1726,6 +1788,8 @@ class Client(CatalogClientMixin, AggregateClientMixin):
1726
1788
  all_workers: list[WorkerConnection],
1727
1789
  first_batch: pa.RecordBatch,
1728
1790
  remaining_input: Iterator[pa.RecordBatch],
1791
+ decode_parent_rows: bool = False,
1792
+ parent_row_callback: Callable[[list[int]], None] | None = None,
1729
1793
  ) -> Generator[pa.RecordBatch]:
1730
1794
  """Distribute input batches round-robin across workers and collect output.
1731
1795
 
@@ -1738,6 +1802,15 @@ class Client(CatalogClientMixin, AggregateClientMixin):
1738
1802
  first_batch: The first input batch, already consumed from the
1739
1803
  iterator by the calling method.
1740
1804
  remaining_input: Iterator for remaining input batches.
1805
+ decode_parent_rows: See `_process_batch_on_worker`. Forwarded to
1806
+ every worker thread; False (the default) preserves today's
1807
+ behavior exactly for every existing caller.
1808
+ parent_row_callback: Optional callback invoked once per yielded
1809
+ output batch, immediately before the yield, with that batch's
1810
+ combined parent-row list (see `_combine_parent_rows`) — the
1811
+ same "callback then yield" contract `table_function`'s
1812
+ `batch_metadata_callback` uses. Only meaningful when
1813
+ `decode_parent_rows` is True; ignored otherwise.
1741
1814
 
1742
1815
  Yields:
1743
1816
  Output `RecordBatch`es from processing. When multiple batches are
@@ -1754,7 +1827,7 @@ class Client(CatalogClientMixin, AggregateClientMixin):
1754
1827
 
1755
1828
  # Create queues for each worker
1756
1829
  input_queues: list[Queue[tuple[int, pa.RecordBatch] | None]] = [Queue() for _ in range(num_workers)]
1757
- output_queue: Queue[tuple[int, list[pa.RecordBatch]] | BaseException] = Queue()
1830
+ output_queue: Queue[tuple[int, list[pa.RecordBatch], list[list[int]] | None] | BaseException] = Queue()
1758
1831
 
1759
1832
  # Start worker threads
1760
1833
  threads: list[threading.Thread] = []
@@ -1762,6 +1835,7 @@ class Client(CatalogClientMixin, AggregateClientMixin):
1762
1835
  thread = threading.Thread(
1763
1836
  target=self._worker_thread_loop,
1764
1837
  args=(worker, input_queues[i], output_queue),
1838
+ kwargs={"decode_parent_rows": decode_parent_rows},
1765
1839
  daemon=True,
1766
1840
  )
1767
1841
  thread.start()
@@ -1804,12 +1878,14 @@ class Client(CatalogClientMixin, AggregateClientMixin):
1804
1878
  raise ClientError.from_rpc_error(result) from result
1805
1879
  raise ClientError(f"Worker thread failed: {result}") from result
1806
1880
 
1807
- batch_idx, output_batches = result
1881
+ batch_idx, output_batches, parent_rows_batches = result
1808
1882
  outputs_received += 1
1809
1883
 
1810
1884
  # Combine output batches if needed
1811
1885
  combined = self._combine_batches(output_batches)
1812
1886
  if combined is not None:
1887
+ if decode_parent_rows and parent_row_callback is not None and parent_rows_batches is not None:
1888
+ parent_row_callback(self._combine_parent_rows(parent_rows_batches))
1813
1889
  yield combined
1814
1890
 
1815
1891
  _logger.debug(
@@ -1838,6 +1914,8 @@ class Client(CatalogClientMixin, AggregateClientMixin):
1838
1914
  pushdown_filters: bytes | None = None,
1839
1915
  settings: dict[str, Any] | None = None,
1840
1916
  transaction_opaque_data: bytes | None = None,
1917
+ parent_row_callback: Callable[[list[int]], None] | None = None,
1918
+ has_finalize: bool = True,
1841
1919
  ) -> Generator[pa.RecordBatch]:
1842
1920
  """Invoke a table-in-out function on the worker and stream results.
1843
1921
 
@@ -1865,6 +1943,34 @@ class Client(CatalogClientMixin, AggregateClientMixin):
1865
1943
  settings: Optional dictionary of settings/pragmas to
1866
1944
  pass to the function.
1867
1945
  transaction_opaque_data: Optional unique identifier for the DuckDB transaction.
1946
+ parent_row_callback: Optional callback invoked once per yielded
1947
+ output batch (before finalize), immediately before the yield,
1948
+ with that batch's decoded `vgi_rpc.parent_row` provenance —
1949
+ `parent_rows[i]` is the 0-based index into the input batch
1950
+ that produced output row `i`. Passing this switches on
1951
+ provenance decoding: a batch with no `vgi_rpc.parent_row`
1952
+ metadata is only accepted when its row count matches the
1953
+ input batch's (raising `ClientError` otherwise), since a
1954
+ worker changing row count without provenance is a worker bug
1955
+ for a function that opted into this contract. Intended for
1956
+ blended row-transform functions (`RowTransformFunction`,
1957
+ `FunctionInfo.input_from_args`); leave unset for ordinary
1958
+ table-in-out functions, which have no provenance concept and
1959
+ may legitimately change row count.
1960
+ has_finalize: Whether this function declares a FINALIZE stage
1961
+ (`FunctionInfo.has_finalize`). Defaults to `True` — every
1962
+ caller before this parameter existed got a FINALIZE-phase
1963
+ `init()` unconditionally, so this preserves that exactly.
1964
+ Pass `False` for a function known to have no finalize (every
1965
+ blended `RowTransformFunction` — `has_finalize` is always
1966
+ false for those, enforced at `resolve_metadata()`) to skip
1967
+ the FINALIZE `init()` entirely, not just send one expecting
1968
+ an empty reply. Confirmed load-bearing, not cosmetic: some
1969
+ worker SDKs (e.g. the TypeScript one) actively reject an
1970
+ unexpected FINALIZE `init()` for a function that never
1971
+ advertised `has_finalize`, rather than silently no-op'ing
1972
+ it — the C++ DuckDB extension avoids this the same way, by
1973
+ conditionally registering `in_out_function_final` at all.
1868
1974
 
1869
1975
  Yields:
1870
1976
  Output `RecordBatch`es from the function. In single-worker mode, output
@@ -1915,6 +2021,8 @@ class Client(CatalogClientMixin, AggregateClientMixin):
1915
2021
  all_workers=all_workers,
1916
2022
  first_batch=first_batch,
1917
2023
  remaining_input=input,
2024
+ decode_parent_rows=parent_row_callback is not None,
2025
+ parent_row_callback=parent_row_callback,
1918
2026
  )
1919
2027
 
1920
2028
  # Close all input streams
@@ -1926,7 +2034,14 @@ class Client(CatalogClientMixin, AggregateClientMixin):
1926
2034
  # Close secondary workers
1927
2035
  self._close_secondary_workers()
1928
2036
 
1929
- # Finalize on primary worker
2037
+ # Finalize on primary worker — skipped entirely (not just
2038
+ # sent expecting an empty reply) when the function has no
2039
+ # finalize stage. See has_finalize's docstring: some worker
2040
+ # SDKs reject an unadvertised FINALIZE init() outright.
2041
+ if not has_finalize:
2042
+ _logger.debug("skipping_finalize_no_finalize_stage")
2043
+ return
2044
+
1930
2045
  _logger.debug("finalizing_primary_worker")
1931
2046
  yield from self._finalize_primary_worker(
1932
2047
  bind_request,
@@ -2299,7 +2414,7 @@ class Client(CatalogClientMixin, AggregateClientMixin):
2299
2414
  cursor=cursor,
2300
2415
  )
2301
2416
  try:
2302
- response: PlanResponse = self._primary.proxy.table_function_plan(request=plan_request) # type: ignore[assignment]
2417
+ response: PlanResponse = self._primary.proxy.table_function_plan(request=plan_request)
2303
2418
  except RpcError as e:
2304
2419
  raise ClientError.from_rpc_error(e) from e
2305
2420