vgi-python 0.33.0__py3-none-any.whl → 0.34.0__py3-none-any.whl

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.
@@ -51,7 +51,7 @@ from vgi_rpc import ArrowSerializableDataclass, ArrowType
51
51
  from vgi_rpc.log import Level
52
52
  from vgi_rpc.rpc import OutputCollector
53
53
 
54
- from vgi.arguments import Arg, Setting, TableInput
54
+ from vgi.arguments import AnyArrow, Arg, Setting, TableInput
55
55
  from vgi.cache_control import CacheControl
56
56
  from vgi.invocation import BindResponse
57
57
  from vgi.metadata import FunctionExample
@@ -94,6 +94,8 @@ __all__ = [
94
94
  "GeoEncode3Function",
95
95
  "RowSumFunction",
96
96
  "BlendedDropFunction",
97
+ "BlendedAnyFunction",
98
+ "BlendedAnyVarargsFunction",
97
99
  "SubstreamPartialSumFunction",
98
100
  "MultiBatchFinishFunction",
99
101
  "SumAllColumnsFunction",
@@ -1880,6 +1882,87 @@ class BlendedExplodeFunction(RowTransformFunction[_ExplodeArgs]):
1880
1882
  cast("VgiOutputCollector", out).emit(out_batch, parent_rows=parent_rows)
1881
1883
 
1882
1884
 
1885
+ @dataclass(slots=True, frozen=True, kw_only=True)
1886
+ class _AnyArgs:
1887
+ """One positional ANY-typed input column."""
1888
+
1889
+ value: Annotated[AnyArrow, Arg(0, doc="Input column of any type (echoed back unchanged)")]
1890
+
1891
+
1892
+ class BlendedAnyFunction(RowTransformFunction[_AnyArgs]):
1893
+ """Blended 1->1 echo of a single ANY-typed input column.
1894
+
1895
+ Proves a blended positional arg may be declared ANY: the declared type names
1896
+ no concrete Arrow type, so the client must build the worker's input schema
1897
+ from the type DuckDB actually resolved for the call (a STRUCT built per row,
1898
+ a whole-row struct, a LIST, a plain VARCHAR ...) rather than from the
1899
+ declaration. The output column ``value`` is bound to that resolved input
1900
+ type at bind time and the column is passed through untouched, so a test
1901
+ asserts on DuckDB's own ``typeof(value)`` and on the round-tripped values —
1902
+ nothing here depends on how an SDK happens to render a type name.
1903
+ """
1904
+
1905
+ class Meta:
1906
+ name = "blended_any"
1907
+ description = "Blended 1->1 echo of one ANY-typed input column (output typed from the input)"
1908
+ categories = ["blended", "test"]
1909
+
1910
+ @classmethod
1911
+ def on_bind(cls, params: BindParams[_AnyArgs]) -> BindResponse:
1912
+ input_schema = params.bind_call.input_schema
1913
+ assert input_schema is not None
1914
+ return BindResponse(output_schema=pa.schema([pa.field("value", input_schema.field(0).type)]))
1915
+
1916
+ @classmethod
1917
+ def process(
1918
+ cls,
1919
+ params: ProcessParams[_AnyArgs],
1920
+ state: None,
1921
+ batch: pa.RecordBatch,
1922
+ out: OutputCollector,
1923
+ ) -> None:
1924
+ out.emit(pa.record_batch([batch.column(0)], schema=params.output_schema))
1925
+
1926
+
1927
+ @dataclass(slots=True, frozen=True, kw_only=True)
1928
+ class _AnyVarargsArgs:
1929
+ """N positional ANY-typed input columns, each free to be a different type."""
1930
+
1931
+ values: Annotated[list[AnyArrow], Arg(0, varargs=True, doc="Input columns of any types (echoed back)")]
1932
+
1933
+
1934
+ class BlendedAnyVarargsFunction(RowTransformFunction[_AnyVarargsArgs]):
1935
+ """Blended 1->1 echo of N ANY-typed VARARGS input columns.
1936
+
1937
+ The varargs counterpart of ``blended_any``: every runtime column may resolve
1938
+ to a different concrete type, so the client must take each column's type
1939
+ from the call rather than from the (ANY) vararg element type. Output columns
1940
+ are ``col0..colN-1`` — the names the client generates for varargs blended
1941
+ input — each bound to its own resolved input type.
1942
+ """
1943
+
1944
+ class Meta:
1945
+ name = "blended_any_varargs"
1946
+ description = "Blended 1->1 echo of N ANY-typed varargs input columns (col0..colN-1)"
1947
+ categories = ["blended", "test"]
1948
+
1949
+ @classmethod
1950
+ def on_bind(cls, params: BindParams[_AnyVarargsArgs]) -> BindResponse:
1951
+ input_schema = params.bind_call.input_schema
1952
+ assert input_schema is not None
1953
+ return BindResponse(output_schema=pa.schema([pa.field(f"col{i}", f.type) for i, f in enumerate(input_schema)]))
1954
+
1955
+ @classmethod
1956
+ def process(
1957
+ cls,
1958
+ params: ProcessParams[_AnyVarargsArgs],
1959
+ state: None,
1960
+ batch: pa.RecordBatch,
1961
+ out: OutputCollector,
1962
+ ) -> None:
1963
+ out.emit(pa.record_batch(cls.input_columns(batch), schema=params.output_schema))
1964
+
1965
+
1883
1966
  class CachedExplodeFunction(RowTransformFunction[_ExplodeArgs]):
1884
1967
  """Cacheable blended 1->N fan-out advertising vgi.cache.per_value.
1885
1968
 
@@ -267,6 +267,8 @@ from vgi._test_fixtures.table.tt_pushdown import (
267
267
  )
268
268
  from vgi._test_fixtures.table_in_out import (
269
269
  BatchIndexBufferInputFunction,
270
+ BlendedAnyFunction,
271
+ BlendedAnyVarargsFunction,
270
272
  BlendedDropFunction,
271
273
  BlendedExplodeFunction,
272
274
  BufferEmitWideFunction,
@@ -432,6 +434,8 @@ _EXAMPLE_CATALOG = Catalog(
432
434
  GeoEncode3Function,
433
435
  RowSumFunction,
434
436
  BlendedDropFunction,
437
+ BlendedAnyFunction,
438
+ BlendedAnyVarargsFunction,
435
439
  BlendedExplodeFunction,
436
440
  CachedExplodeFunction,
437
441
  ProjectableBlendedFunction,
vgi/argument_spec.py CHANGED
@@ -19,7 +19,8 @@ import warnings
19
19
  from collections.abc import Sequence
20
20
  from dataclasses import dataclass
21
21
  from functools import cache
22
- from typing import Annotated, Any, get_args, get_origin, get_type_hints
22
+ from types import UnionType
23
+ from typing import Annotated, Any, Union, get_args, get_origin, get_type_hints
23
24
 
24
25
  import pyarrow as pa
25
26
 
@@ -395,14 +396,22 @@ def parameter_default_values_from_specs(
395
396
  arrays: list[pa.Array[Any]] = []
396
397
  names: list[str] = []
397
398
  for spec in default_specs:
398
- if pa.types.is_null(spec.arrow_type):
399
- raise ValueError(
400
- f"Parameter {spec.name!r} has a default but no concrete Arrow type; "
401
- "typed defaults require an explicit parameter type"
402
- )
403
399
  value = spec.default_value
404
400
  if isinstance(value, pa.Scalar):
405
401
  value = value.as_py()
402
+ # An ANY-typed parameter (AnyArrow) has no concrete Arrow type, so a
403
+ # VALUE default cannot be represented. `None` still can: a null-typed
404
+ # array holds nulls, and it is column PRESENCE, not the value, that
405
+ # marks a parameter as having a default. Allowing it lets a required
406
+ # ANY parameter be declared `default=None` so that omitting it reaches
407
+ # the function's own on_bind() validation — which can name the argument
408
+ # and show its shape — instead of raising a bare KeyError during
409
+ # argument parsing, before any user code runs.
410
+ if pa.types.is_null(spec.arrow_type) and value is not None:
411
+ raise ValueError(
412
+ f"Parameter {spec.name!r} has a non-null default but no concrete Arrow type; "
413
+ "a typed default requires an explicit parameter type (None is allowed)"
414
+ )
406
415
  try:
407
416
  arrays.append(pa.array([value], type=spec.arrow_type))
408
417
  except (pa.ArrowException, TypeError, ValueError) as exc:
@@ -538,6 +547,30 @@ def _format_range(
538
547
  return f"{low}, {high}"
539
548
 
540
549
 
550
+ def _unwrap_optional(annotation: Any) -> Any:
551
+ """``T | None`` -> ``T``; anything else unchanged.
552
+
553
+ A special marker type (``AnyArrow``, ``TableInput``) is recognised by
554
+ identity, so an Optional wrapper would hide it — an ``AnyArrow | None``
555
+ parameter would silently become a NULL-typed one, and DuckDB then cannot
556
+ cast anything into it. Unwrapping first lets a marker be declared nullable,
557
+ which is how a REQUIRED ANY argument opts into reaching its function's own
558
+ ``on_bind()`` validation (with ``default=None``) instead of being rejected
559
+ during parsing. Mirrors what ``_accepts_none`` already looks for.
560
+
561
+ Args:
562
+ annotation: The declared type of an ``Annotated[T, Arg(...)]`` field.
563
+
564
+ Returns:
565
+ The single non-``None`` member of an optional union, else ``annotation``.
566
+ """
567
+ origin = get_origin(annotation)
568
+ if origin is not Union and origin is not UnionType:
569
+ return annotation
570
+ members = [a for a in get_args(annotation) if a is not type(None)]
571
+ return members[0] if len(members) == 1 else annotation
572
+
573
+
541
574
  def _constraint_kwargs(arg: Arg[Any]) -> dict[str, Any]:
542
575
  """Extract discovery-facing constraint metadata from an [`Arg`][] descriptor.
543
576
 
@@ -744,6 +777,7 @@ def _extract_argument_specs(
744
777
  if type_args:
745
778
  infer_type = type_args[0]
746
779
 
780
+ infer_type = _unwrap_optional(infer_type)
747
781
  is_table_input = infer_type is TableInput
748
782
  is_any_type = infer_type is AnyArrow or infer_type is AnyArrowValue
749
783
 
@@ -790,8 +824,8 @@ def _extract_argument_specs(
790
824
  # Check for special types (AnyArrow, TableInput)
791
825
  # Priority: Arg subscript type (Arg[AnyArrow]) > class type hint
792
826
  # Also check _returns_any_arrow_value for Annotated[AnyArrowValue, ...]
793
- hint = hints.get(attr_name)
794
- type_param = getattr(arg_legacy, "_type_param", None)
827
+ hint = _unwrap_optional(hints.get(attr_name))
828
+ type_param = _unwrap_optional(getattr(arg_legacy, "_type_param", None))
795
829
  is_table_input = type_param is TableInput or hint is TableInput
796
830
  is_any_type = (
797
831
  type_param is AnyArrow
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: vgi-python
3
- Version: 0.33.0
3
+ Version: 0.34.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
@@ -2,7 +2,7 @@ vgi/__init__.py,sha256=8uLMNQcNzWjp8VzYtj2u98_Wp8WpB8c_dKlMv67TH0g,5384
2
2
  vgi/_duckdb.py,sha256=YB5D7N3Bwg_xP6X8a5QlumtlAovSej1A1Go5XlNGVko,2162
3
3
  vgi/_storage_profile.py,sha256=VkTsXojuE0tHEzurmteQSAiL1vI3CZSgYkL6D_h8GvE,5061
4
4
  vgi/aggregate_function.py,sha256=v03wBSy2Rrl9F2pMYvu8GnGrTCQrAVm01HXIFNdjnm4,26886
5
- vgi/argument_spec.py,sha256=_aO3r6LrRrM7ziOQ27i_ouaofXD_2XMHiwhihNVfj0o,32374
5
+ vgi/argument_spec.py,sha256=SADH97VlFu3asI8CWZ_I7XTuSxwqFciFgdupdAf-tkY,34231
6
6
  vgi/arguments.py,sha256=ncnC1CS7KN-2rDjSw9vdf1uHqV5ozebet_eZtcq_ujw,72300
7
7
  vgi/attach_header.py,sha256=sxpzCOVa5fl_-kQSCZgeUxS5I9Kuv8VCUjAOF5s1pr0,3782
8
8
  vgi/auth.py,sha256=TlD5bhdyXnFuAoCnoHMU9ffjewVEnpwO5auIvp9GsCA,2486
@@ -52,12 +52,12 @@ vgi/_test_fixtures/http_server.py,sha256=AviomIEClCEuA9WPgQOzrMtQ484N04rQupK8dWO
52
52
  vgi/_test_fixtures/nest_tensor.py,sha256=UjjW9UwipYG3ehQpZOm4hdzsafMyvVc0ZKJCrqiZTPU,24471
53
53
  vgi/_test_fixtures/orchard_catalog.py,sha256=8Uv-mKTIgl-OYU9JLgG23owlHGmgniyF2chPoKXGtuI,1643
54
54
  vgi/_test_fixtures/simple_writable.py,sha256=OmyENVnsgJmYH0_5sNWMt_Xgj9JoPaOu7uI5_ROZDKg,32020
55
- vgi/_test_fixtures/table_in_out.py,sha256=Ug-n9wvqT5StT7Sm0OQ1MAmOCQr2jzdcpfneiQCpmJM,89813
55
+ vgi/_test_fixtures/table_in_out.py,sha256=sWTSXwH-BUaSNOQsDmDqlFZxZ5GGtD-_xmysD2g7aV8,93148
56
56
  vgi/_test_fixtures/table_in_out_same_name.py,sha256=jdsQNdKn64T54W_TcLB-rhydI69Y0Uzr5n6FU7v9rIU,8649
57
57
  vgi/_test_fixtures/twin_catalogs.py,sha256=UFsFuZ_nFpe62HatGnOMru36vk35Xt3Ta1Hkr1aWN9w,4450
58
58
  vgi/_test_fixtures/versioned.py,sha256=gnxNejc4cNGPKl_DxJk1SU8Ielj-rwmQYGkiShXzxK4,5786
59
59
  vgi/_test_fixtures/versioned_tables.py,sha256=_JHPe6LaiO0x2We28Ion8hh6pSATmHZYvAczB4uwBsA,22272
60
- vgi/_test_fixtures/worker.py,sha256=5ocHOoa4mksNCTA39LOdvnhDhusam9hVpEb2MQOFSjU,96306
60
+ vgi/_test_fixtures/worker.py,sha256=tAdCcL9x3LwgtY0OG56bapmBCwGZJ-M7paQY9hXjgB8,96440
61
61
  vgi/_test_fixtures/accumulate/__init__.py,sha256=4hYT8jqRoVHSjV9TB7v0Z1CMJtdLuPaDWSz4J2fvMDs,868
62
62
  vgi/_test_fixtures/accumulate/worker.py,sha256=4a2VaCoOIXPEoJpkNume7FoBlQzs9kRl8b-_ZJMHvnQ,30165
63
63
  vgi/_test_fixtures/aggregate/__init__.py,sha256=2GCp80FNrsWFOA2SV3lCffzLjoN5AKwrf57t2YRg8O0,2393
@@ -152,8 +152,8 @@ vgi/transactor/_duckdb_compat.py,sha256=sXVZ9JLKAQyGR1BjWczSwdQEavtr-TcZPoVZZnTr
152
152
  vgi/transactor/client.py,sha256=7DTeMksogsw6ANjQjGOPpKYrV76rg4_kGjktMJf54jg,4486
153
153
  vgi/transactor/protocol.py,sha256=v59IvrKnuvwOXvn_HEcBAbCzDVcx0akgKN0R1mChXg0,5034
154
154
  vgi/transactor/server.py,sha256=nzsZQxJZdgappdYX8okrFIrjFtUbdM_Hhskl1fZ2nDY,32553
155
- vgi_python-0.33.0.dist-info/METADATA,sha256=RsTar3agbo8sxMrJEFtAFx4Qc-nICjnLZdUMLK83JcA,25617
156
- vgi_python-0.33.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
157
- vgi_python-0.33.0.dist-info/entry_points.txt,sha256=3Kz1vgodw3pOL_xjtSyDB55-ZRy-U2X-X_Bdr582x0Q,165
158
- vgi_python-0.33.0.dist-info/licenses/LICENSE,sha256=pbJb4zZasP6n5ifEV81wFu017TarjydaYVmGbHcehtY,6103
159
- vgi_python-0.33.0.dist-info/RECORD,,
155
+ vgi_python-0.34.0.dist-info/METADATA,sha256=4-WKG1yNlJmW1IRUWKY1kssQ_QLYhOrSlZqQs_7dz28,25617
156
+ vgi_python-0.34.0.dist-info/WHEEL,sha256=THafob7ofN-NsuMN7Mg4qZyHaQI7KkD-QlcQatYhXPo,87
157
+ vgi_python-0.34.0.dist-info/entry_points.txt,sha256=3Kz1vgodw3pOL_xjtSyDB55-ZRy-U2X-X_Bdr582x0Q,165
158
+ vgi_python-0.34.0.dist-info/licenses/LICENSE,sha256=pbJb4zZasP6n5ifEV81wFu017TarjydaYVmGbHcehtY,6103
159
+ vgi_python-0.34.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.32.0
2
+ Generator: hatchling 1.32.3
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any