vgi-python 0.48.2__py3-none-any.whl → 0.49.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.
@@ -15,10 +15,11 @@ own ``__dict__``, not an inherited attribute — so the redeclaration on this
15
15
  subclass's body is what takes effect.
16
16
 
17
17
  A normal client (Python ``Client`` or the C++ extension) declares
18
- ``protocol_version = "1.0.0"`` and sends it on every request. Because this
19
- worker enforces ``"99.0.0"``, the major versions differ and the dispatch
20
- boundary raises ``ProtocolVersionError`` with a directional "upgrade the
21
- client" message that round-trips back to the caller.
18
+ ``protocol_version = "3.1.0"`` and sends it on every request. Because this
19
+ worker defaults to ``"99.0.0"``, the major/minor versions differ and the
20
+ dispatch boundary raises ``ProtocolVersionError`` with a directional upgrade
21
+ message that round-trips back to the caller. Tests may override the worker to
22
+ ``"3.0.0"`` to exercise the real previous-minor rollout boundary.
22
23
 
23
24
  Otherwise this is a drop-in replacement for ``vgi-fixture-worker``: it
24
25
  inherits every function and the catalog from :class:`ExampleWorker`, so any
@@ -30,15 +31,17 @@ Registered as the ``vgi-fixture-bad-protocol-worker`` entry point.
30
31
 
31
32
  from __future__ import annotations
32
33
 
34
+ import os
33
35
  from typing import ClassVar
34
36
 
35
37
  from vgi._test_fixtures.worker import ExampleWorker
36
38
  from vgi.protocol import VgiProtocol
37
39
 
38
- # A major-version bump guarantees a mismatch against any real client's
39
- # "1.0.0" (vgi-rpc compares major+minor exactly, ignoring patch). Declared on
40
+ # A major-version bump guarantees a mismatch against any real client. Tests can
41
+ # override this with a previous minor (for example 3.0.0 versus 3.1.0) to prove
42
+ # that the production rolling-upgrade boundary is enforced too. Declared on
40
43
  # this class body so ``vars(BadProtocol)["protocol_version"]`` resolves to it.
41
- BAD_PROTOCOL_VERSION = "99.0.0"
44
+ BAD_PROTOCOL_VERSION = os.environ.get("VGI_BAD_PROTOCOL_VERSION", "99.0.0")
42
45
 
43
46
 
44
47
  class BadProtocol(VgiProtocol):
@@ -37,7 +37,18 @@ from vgi.arguments import Arg
37
37
  from vgi.cache_control import CacheControl
38
38
  from vgi.filter_v2 import replaying_accepted_state
39
39
  from vgi.metadata import FunctionExample, PartitionKind
40
- from vgi.protocol import PlanResponse, ScanSplit, VgiOutputCollector
40
+ from vgi.protocol import (
41
+ FieldPath,
42
+ KeyGrouping,
43
+ OrderByDirection,
44
+ OrderByNullOrder,
45
+ PlanResponse,
46
+ RangeKey,
47
+ RangePartitioning,
48
+ ScanSplit,
49
+ TransformSpec,
50
+ VgiOutputCollector,
51
+ )
41
52
  from vgi.schema_utils import partition_field, schema
42
53
 
43
54
  if TYPE_CHECKING:
@@ -858,6 +869,14 @@ class SplitPartitionArgs:
858
869
  """Arguments for the partitioned split fixture."""
859
870
 
860
871
  rows_per_country: Annotated[int, Arg("rows_per_country", default=5, doc="Rows in each partition", ge=0)]
872
+ key_mode: Annotated[
873
+ str,
874
+ Arg(
875
+ "key_mode",
876
+ default="valid",
877
+ doc="Test-only Protocol 3.1 key shape: valid, missing, two_rows, or wrong_type",
878
+ ),
879
+ ]
861
880
 
862
881
 
863
882
  @bind_fixed_schema
@@ -889,17 +908,50 @@ class SplitPartitionedFunction(TableFunctionGenerator[SplitPartitionArgs, PartSp
889
908
 
890
909
  @classmethod
891
910
  def on_plan(cls, params: BindParams[SplitPartitionArgs], request: Any) -> PlanResponse:
892
- """One split per country, each naming its own partition."""
911
+ """One split per country, carrying its exact protocol-3.1 key."""
912
+ def key_value(country: str) -> pa.RecordBatch | None:
913
+ if params.args.key_mode == "missing":
914
+ return None
915
+ if params.args.key_mode == "two_rows":
916
+ return pa.RecordBatch.from_pydict({"country": [country, country]})
917
+ if params.args.key_mode == "wrong_type":
918
+ return pa.RecordBatch.from_pydict({"country": [1]})
919
+ if params.args.key_mode != "valid":
920
+ msg = f"unknown split_partitioned key_mode: {params.args.key_mode}"
921
+ raise ValueError(msg)
922
+ return pa.RecordBatch.from_pydict({"country": [country]})
923
+
924
+ identity = TransformSpec(
925
+ id="vgi.identity/1",
926
+ inputs=[FieldPath(components=["country"]).serialize_to_bytes()],
927
+ ).serialize_to_bytes()
928
+ key_grouping = KeyGrouping(keys=[identity]).serialize_to_bytes()
929
+ range_key = RangeKey(
930
+ transform=identity,
931
+ direction=OrderByDirection.ASC,
932
+ null_order=OrderByNullOrder.NULLS_LAST,
933
+ ).serialize_to_bytes()
934
+ range_partitioning = RangePartitioning(
935
+ keys=[range_key],
936
+ # Equality belongs to the range on the right, so these boundaries
937
+ # isolate BR, DE, JP and US into ranges 0 through 3 respectively.
938
+ split_points=pa.RecordBatch.from_pydict({"country": ["DE", "JP", "US"]}),
939
+ ).serialize_to_bytes()
940
+ range_index = {"BR": 0, "DE": 1, "JP": 2, "US": 3}
893
941
  return PlanResponse(
894
942
  splits=[
895
943
  ScanSplit(
896
944
  payload=country.encode("ascii"),
897
945
  estimated_rows=params.args.rows_per_country,
898
946
  rows_exact=True,
947
+ key_value=key_value(country),
948
+ range_index=range_index[country],
899
949
  )
900
950
  for country in _PART_COUNTRIES
901
951
  ],
902
952
  estimated_total_splits=len(_PART_COUNTRIES),
953
+ key_grouping=key_grouping,
954
+ range_partitioning=range_partitioning,
903
955
  )
904
956
 
905
957
  @classmethod
@@ -940,12 +992,18 @@ class SplitPartitionedFunction(TableFunctionGenerator[SplitPartitionArgs, PartSp
940
992
  # assertion unchanged — the file could not detect the exact failure
941
993
  # it exists to detect.
942
994
  base = _PART_COUNTRIES.index(country) * 100
943
- out.emit(
944
- pa.RecordBatch.from_pydict(
945
- {"country": [country] * rows, "sales": [base + i + 1 for i in range(rows)]},
946
- schema=_PART_ROW,
947
- )
995
+ batch = pa.RecordBatch.from_pydict(
996
+ {"country": [country] * rows, "sales": [base + i + 1 for i in range(rows)]},
997
+ schema=_PART_ROW,
948
998
  )
999
+ # Bypass the legacy per-batch vgi_partition_values metadata. The
1000
+ # DuckDB adapter must source this split's SINGLE_VALUE partition
1001
+ # tuple from ScanSplit.key_value; if it falls back, the existing
1002
+ # C++ missing-metadata check makes this integration test fail.
1003
+ inner = out
1004
+ while hasattr(inner, "_inner"):
1005
+ inner = inner._inner
1006
+ inner.emit(batch)
949
1007
  return
950
1008
 
951
1009
 
vgi/protocol.py CHANGED
@@ -101,6 +101,8 @@ from vgi.typed_values import TypedValueBatch
101
101
 
102
102
  __all__ = [
103
103
  "BindRequest",
104
+ "OrderByDirection",
105
+ "OrderByNullOrder",
104
106
  "TableFunctionProjectionRequest",
105
107
  "TableFunctionProjectionResponse",
106
108
  "ClientCapabilities",
@@ -511,20 +513,12 @@ class TableFunctionStatisticsRequest(ArrowSerializableDataclass):
511
513
 
512
514
  @dataclass(frozen=True, slots=True, kw_only=True)
513
515
  class PartitionTransform(ArrowSerializableDataclass):
514
- """How a partition column is derived, for engines that do storage-partitioned joins.
515
-
516
- Spark's ``KeyGroupedPartitioning`` needs the transform *expression*, not just the
517
- partition values: ``country=US`` does not say whether partitions are
518
- ``identity(country)`` or ``bucket(16, user_id)``. This is Iceberg's vocabulary,
519
- which ``Expressions.bucket(n, col)`` maps onto directly.
520
-
521
- Attributes:
522
- column: Name of the partition column in the function's output schema.
523
- transform: Transform kind — one of ``identity``, ``bucket``, ``truncate``,
524
- ``year``, ``month``, ``day``, ``hour``.
525
- param: Bucket count for ``bucket`` / width for ``truncate``; ``None`` for
526
- the rest.
516
+ """Deprecated protocol-2 partition-transform description.
527
517
 
518
+ Protocol 3.1 producers should use :class:`TransformSpec` inside
519
+ :class:`KeyGrouping` or :class:`RangePartitioning`. This record remains
520
+ available so existing producers can continue to populate the deprecated
521
+ :attr:`PlanResponse.partitioning` field while they migrate.
528
522
  """
529
523
 
530
524
  column: str
@@ -532,6 +526,122 @@ class PartitionTransform(ArrowSerializableDataclass):
532
526
  param: int | None = None
533
527
 
534
528
 
529
+ @dataclass(frozen=True, slots=True, kw_only=True)
530
+ class FieldPath(ArrowSerializableDataclass):
531
+ """One raw, component-preserving field reference used by a transform."""
532
+
533
+ components: list[str]
534
+
535
+ def __post_init__(self) -> None:
536
+ if not self.components or any(not component for component in self.components):
537
+ raise ValueError("field path must contain only non-empty components")
538
+
539
+
540
+ @dataclass(frozen=True, slots=True, kw_only=True)
541
+ class TransformSpec(ArrowSerializableDataclass):
542
+ """A versioned, registry-defined partition transform.
543
+
544
+ ``id`` identifies normative semantics (for example ``vgi.identity/1`` or
545
+ ``iceberg.bucket/1``). ``inputs`` contains serialized :class:`FieldPath`
546
+ records. ``parameters`` is either absent or a one-row Arrow batch, which
547
+ permits any number of named, typed parameters without admitting arbitrary
548
+ recursive expressions.
549
+ """
550
+
551
+ id: str
552
+ inputs: Annotated[list[bytes], ArrowType(pa.list_(pa.binary()))] = field(default_factory=list)
553
+ parameters: Annotated[pa.RecordBatch | None, ArrowType(pa.binary())] = None
554
+
555
+ def __post_init__(self) -> None:
556
+ if not self.id or "/" not in self.id:
557
+ raise ValueError("transform id must be a non-empty, versioned name")
558
+ if not self.inputs:
559
+ raise ValueError("transform must reference at least one input field")
560
+ for value in self.inputs:
561
+ FieldPath.deserialize_from_bytes(value)
562
+ if self.parameters is not None and self.parameters.num_rows != 1:
563
+ raise ValueError("transform parameters must contain exactly one row")
564
+
565
+
566
+ @dataclass(frozen=True, slots=True, kw_only=True)
567
+ class KeyGrouping(ArrowSerializableDataclass):
568
+ """Transforms whose exact result tuple is constant within every split."""
569
+
570
+ keys: Annotated[list[bytes], ArrowType(pa.list_(pa.binary()))]
571
+
572
+ def __post_init__(self) -> None:
573
+ if not self.keys:
574
+ raise ValueError("key grouping must contain at least one transform")
575
+ for value in self.keys:
576
+ TransformSpec.deserialize_from_bytes(value)
577
+
578
+
579
+ @dataclass(frozen=True, slots=True, kw_only=True)
580
+ class RangeKey(ArrowSerializableDataclass):
581
+ """One transform and comparator component of a range key."""
582
+
583
+ transform: Annotated[bytes, ArrowType(pa.binary())]
584
+ direction: OrderByDirection
585
+ null_order: OrderByNullOrder
586
+
587
+ def __post_init__(self) -> None:
588
+ TransformSpec.deserialize_from_bytes(self.transform)
589
+ OrderByDirection(self.direction)
590
+ OrderByNullOrder(self.null_order)
591
+
592
+
593
+ @dataclass(frozen=True, slots=True, kw_only=True)
594
+ class RangePartitioning(ArrowSerializableDataclass):
595
+ """Exact half-open range distribution shared by all splits in a plan.
596
+
597
+ ``N`` split-point rows define ``N + 1`` ranges. Values equal to point ``i``
598
+ belong to range ``i + 1``. Each split names its range with
599
+ :attr:`ScanSplit.range_index`.
600
+ """
601
+
602
+ keys: Annotated[list[bytes], ArrowType(pa.list_(pa.binary()))]
603
+ split_points: Annotated[pa.RecordBatch, ArrowType(pa.binary())]
604
+
605
+ def __post_init__(self) -> None:
606
+ if not self.keys:
607
+ raise ValueError("range partitioning must contain at least one key")
608
+ decoded_keys = [RangeKey.deserialize_from_bytes(value) for value in self.keys]
609
+ if self.split_points.num_columns != len(self.keys):
610
+ raise ValueError("range split-point width must match the range key width")
611
+ for row in range(1, self.split_points.num_rows):
612
+ if _compare_range_rows(self.split_points, row - 1, row, decoded_keys) >= 0:
613
+ raise ValueError("range split points must be strictly ordered by the declared comparator")
614
+
615
+
616
+ def _compare_range_rows(
617
+ batch: pa.RecordBatch,
618
+ left_row: int,
619
+ right_row: int,
620
+ keys: list[RangeKey],
621
+ ) -> int:
622
+ """Compare two split-point rows using the protocol's lexicographic order."""
623
+ for column, key in zip(batch.columns, keys, strict=True):
624
+ left = column[left_row]
625
+ right = column[right_row]
626
+ if not left.is_valid or not right.is_valid:
627
+ if not left.is_valid and not right.is_valid:
628
+ continue
629
+ left_first = key.null_order == OrderByNullOrder.NULLS_FIRST
630
+ return (-1 if left_first else 1) if not left.is_valid else (1 if left_first else -1)
631
+ try:
632
+ if pc.equal(left, right).as_py() is True:
633
+ continue
634
+ less = pc.less(left, right).as_py() is True
635
+ greater = pc.greater(left, right).as_py() is True
636
+ except (pa.ArrowInvalid, pa.ArrowNotImplementedError, TypeError) as error:
637
+ raise ValueError("range split points contain values that cannot be ordered") from error
638
+ if not less and not greater:
639
+ raise ValueError("range split points contain unordered values")
640
+ result = -1 if less else 1
641
+ return -result if key.direction == OrderByDirection.DESC else result
642
+ return 0
643
+
644
+
535
645
  @dataclass(frozen=True, slots=True, kw_only=True)
536
646
  class SortField(ArrowSerializableDataclass):
537
647
  """Ordering guaranteed *within* each split.
@@ -578,10 +688,15 @@ class ScanSplit(ArrowSerializableDataclass):
578
688
  estimated_bytes: Byte estimate. Load-bearing for engines that bin-pack
579
689
  (DataFusion weight, Trino SplitWeight); ``None`` degrades them to
580
690
  round-robin by count. DuckDB claims greedily and does not need it.
581
- partition_bounds: 2-row (min, max) Arrow batch in the existing
582
- ``vgi_partition_values`` encoding, one column per partition column.
583
- For SINGLE_VALUE_PARTITIONS min == max, so row 0 is the exact key —
584
- but only report partitioning when the split really is single-valued.
691
+ key_value: One-row Arrow batch aligned with ``PlanResponse.key_grouping``.
692
+ Every row emitted by this split evaluates to this exact transformed
693
+ tuple under null-safe equality.
694
+ range_index: Zero-based range owned by this split when
695
+ ``PlanResponse.range_partitioning`` is present.
696
+ column_bounds: 2-row (min, max) Arrow batch used only for containment and
697
+ pruning. It is not an exact grouping key or a range assignment.
698
+ partition_bounds: Deprecated compatibility spelling for older protocol-2
699
+ producers. New protocol-3 producers use ``column_bounds``.
585
700
  column_statistics: Per-column statistics blob (same encoding as
586
701
  ``table_function_statistics``).
587
702
  location_ids: Indices into ``PlanResponse.locations`` naming hosts where
@@ -603,6 +718,9 @@ class ScanSplit(ArrowSerializableDataclass):
603
718
  estimated_rows: int | None = None
604
719
  rows_exact: bool = False
605
720
  estimated_bytes: int | None = None
721
+ key_value: Annotated[pa.RecordBatch | None, ArrowType(pa.binary())] = None
722
+ range_index: int | None = None
723
+ column_bounds: Annotated[pa.RecordBatch | None, ArrowType(pa.binary())] = None
606
724
  partition_bounds: Annotated[pa.RecordBatch | None, ArrowType(pa.binary())] = None
607
725
  column_statistics: Annotated[bytes | None, ArrowType(pa.binary())] = None
608
726
  location_ids: list[int] | None = None
@@ -720,9 +838,12 @@ class PlanResponse(ArrowSerializableDataclass):
720
838
  ``transaction`` (reuses the CACHE_SCOPE_* vocabulary). Transaction-scoped
721
839
  plans are not cacheable and are not redeemable after commit or rollback.
722
840
  locations: Hoisted host list; splits index into it via ``location_ids``.
723
- partitioning: Serialized [`PartitionTransform`][] records describing how
724
- partition columns are derived. Report none unless every split really is
725
- single-valued.
841
+ key_grouping: Serialized :class:`KeyGrouping` when each split has one
842
+ exact transformed key tuple.
843
+ range_partitioning: Serialized :class:`RangePartitioning` containing the
844
+ exact ordered split points and comparator.
845
+ partitioning: Deprecated protocol-2 partition-transform list. Protocol-3
846
+ producers use ``key_grouping`` and/or ``range_partitioning``.
726
847
  sort_order: Serialized [`SortField`][] records describing ordering *within*
727
848
  each split.
728
849
  cache_max_age_seconds: How long this plan may be reused. Ignored for
@@ -757,6 +878,8 @@ class PlanResponse(ArrowSerializableDataclass):
757
878
  scope: str = CACHE_SCOPE_CATALOG
758
879
 
759
880
  locations: list[str] | None = None
881
+ key_grouping: Annotated[bytes | None, ArrowType(pa.binary())] = None
882
+ range_partitioning: Annotated[bytes | None, ArrowType(pa.binary())] = None
760
883
  partitioning: Annotated[list[bytes] | None, ArrowType(pa.list_(pa.binary()))] = None
761
884
  sort_order: Annotated[list[bytes] | None, ArrowType(pa.list_(pa.binary()))] = None
762
885
 
@@ -3483,7 +3606,7 @@ class VgiProtocol(Protocol):
3483
3606
  # ``VgiProtocol``, Java and C# ``VgiService``, Go the framework default
3484
3607
  # ``Service``, TypeScript ``vgi`` -- so no client could address them all.
3485
3608
  protocol_name: ClassVar[str] = "vgi.v3"
3486
- protocol_version: ClassVar[str] = "3.0.0"
3609
+ protocol_version: ClassVar[str] = "3.1.0"
3487
3610
 
3488
3611
  def bind(self, request: BindRequest) -> BindResponse:
3489
3612
  """Resolve output schema and validate arguments."""
vgi/protocol_version.txt CHANGED
@@ -1 +1 @@
1
- 3.0.0
1
+ 3.1.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: vgi-python
3
- Version: 0.48.2
3
+ Version: 0.49.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
@@ -27,8 +27,8 @@ vgi/meta_worker.py,sha256=QctITX8rP2qv_3pTCqIgqhoC8zQ26U82LC7fov8Huog,38641
27
27
  vgi/metadata.py,sha256=nk0tE5w0mlOA_5sVSCNJTWC09bcZkjXYnvEziGguYc8,77829
28
28
  vgi/otel.py,sha256=JlUDnk2c0UXSo_j1PJdS67Y--t55sXPg054ADuvHANc,15452
29
29
  vgi/profiling.py,sha256=rK7vij3oHhXC3JHI2BsMySsXh7_hrXi0aXpCW8iFSIc,3671
30
- vgi/protocol.py,sha256=JXL5C25DeoxLOmJE3h8dRZ_qEdDpsyK1m6Ux3e9HFkc,188869
31
- vgi/protocol_version.txt,sha256=KYW-iyjTrehY6Nj7S8IvVlsb9gIN_5gtzhQfdyG5mZw,6
30
+ vgi/protocol.py,sha256=uBqWXxPM85er7xm8Owi_41mG5YdcL3jV1zJOylWawX8,194395
31
+ vgi/protocol_version.txt,sha256=svRNO24p-LG3PqRzXwBq_8TRmOH9nH1Q5zYVmx72NsY,6
32
32
  vgi/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
33
33
  vgi/query_execution.py,sha256=zsaRAKDsaoq4wvavHzeokBICAo3CCid86WDJVY04zxc,21353
34
34
  vgi/query_parameters.py,sha256=nnMH_SnF_fAh4y0mI87aAQdl3G4JqP2NibEcFX5pijI,7926
@@ -52,7 +52,7 @@ vgi/_test_fixtures/__init__.py,sha256=xQq-QQLk8USQ6TZHsPiPcZldNNUdcJ2gToXMPGzScL
52
52
  vgi/_test_fixtures/attach_options.py,sha256=mfzHW44G1hw5gazuAMwVHgp3VTRCXBifhWszMs7Tndc,12636
53
53
  vgi/_test_fixtures/attach_ticket_vectors.json,sha256=Ny0U9Cyn-_2G-ZYlC3ZpeG6bCux_bhtJZXGanti47fw,143027
54
54
  vgi/_test_fixtures/bad_enum.py,sha256=b8l-afmXgGR6iqgIUiVeqHu0C0F8Of8ahEmdQGtqd1g,2916
55
- vgi/_test_fixtures/bad_protocol.py,sha256=l2wNT0cZHlTHvbI9yzzli__MIrDoWacYcXEtu2x8Xho,2992
55
+ vgi/_test_fixtures/bad_protocol.py,sha256=nTlxgMc921CLZHKPhyieBvk5Pd-w_2j40arrW4YSy-Y,3228
56
56
  vgi/_test_fixtures/cancellable.py,sha256=hetTRLyT4kkazPNcxAdfzj0pYzEmegMksamJbR0w3Ho,12151
57
57
  vgi/_test_fixtures/catalog.py,sha256=Klz1BwU6iD9qa9k0Z0yZH-r1B95o-qCyx0jLvbSxzyM,31089
58
58
  vgi/_test_fixtures/catalog_contents.py,sha256=FTX-UtBBIGxFvhSYe4ZLryL-yM6ULK7dmkk0aAdEkbw,12094
@@ -127,7 +127,7 @@ vgi/_test_fixtures/table/same_name_cached.py,sha256=JMXSXTcqyO7XwWqIizgddlTvLYuU
127
127
  vgi/_test_fixtures/table/same_name_schemas.py,sha256=yRti9HXO3-Y9ahiLIHoaZ31LvWjtPSbjxmPJyoQ_0mQ,4930
128
128
  vgi/_test_fixtures/table/sequence.py,sha256=0kOckCwRu19RMKgFrUcnnnDR8e0utjX_E2kD3w6NnRU,26352
129
129
  vgi/_test_fixtures/table/settings.py,sha256=C4J5OKY3vgNbB09QAiQlzwhI3Tc5wtewHHLFhO5xvo4,16689
130
- vgi/_test_fixtures/table/splits.py,sha256=qkN_zs87qo3Rbd5P-YKNpr6XCxsRwgmwAzjruuyjAGM,48971
130
+ vgi/_test_fixtures/table/splits.py,sha256=08GrBXQu-J-S3BDcNt7J75bHquykXmaTrRO9VR5iXU0,51414
131
131
  vgi/_test_fixtures/table/transaction_storage.py,sha256=cGSaM25cE6brwbUJ0-mchocX0UfwOb7uscCATKLYeME,6086
132
132
  vgi/_test_fixtures/table/tt_pushdown.py,sha256=jMELJ2HkG5-yIuYqKRYaPSouJxs3N-tiWRPthoPZ9ko,7420
133
133
  vgi/_test_fixtures/table/typed_probe.py,sha256=JjgLeHuhkXAVnCcezgqR7ayXMp3tTTixegftCwjKOSY,5122
@@ -206,8 +206,8 @@ vgi/transactor/_duckdb_compat.py,sha256=sXVZ9JLKAQyGR1BjWczSwdQEavtr-TcZPoVZZnTr
206
206
  vgi/transactor/client.py,sha256=7DTeMksogsw6ANjQjGOPpKYrV76rg4_kGjktMJf54jg,4486
207
207
  vgi/transactor/protocol.py,sha256=koHi8ZH_QvZRBjFI_m_o1SpbcilBv_wAjmaGq0k61BY,5201
208
208
  vgi/transactor/server.py,sha256=fJi72oaS8PiOI3rFW0MdetGBFEzFBkLdlDAMmbB50Uw,32663
209
- vgi_python-0.48.2.dist-info/METADATA,sha256=K6lKTMEkR5uT6dD5S0fx_Z6Un6hGWEy9zsZl738Gq8s,26066
210
- vgi_python-0.48.2.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
211
- vgi_python-0.48.2.dist-info/entry_points.txt,sha256=3Kz1vgodw3pOL_xjtSyDB55-ZRy-U2X-X_Bdr582x0Q,165
212
- vgi_python-0.48.2.dist-info/licenses/LICENSE,sha256=pbJb4zZasP6n5ifEV81wFu017TarjydaYVmGbHcehtY,6103
213
- vgi_python-0.48.2.dist-info/RECORD,,
209
+ vgi_python-0.49.0.dist-info/METADATA,sha256=PxwkTnK120eSbyppa7ziC3rQ8j6I_-W5gle-PwJMiKA,26066
210
+ vgi_python-0.49.0.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
211
+ vgi_python-0.49.0.dist-info/entry_points.txt,sha256=3Kz1vgodw3pOL_xjtSyDB55-ZRy-U2X-X_Bdr582x0Q,165
212
+ vgi_python-0.49.0.dist-info/licenses/LICENSE,sha256=pbJb4zZasP6n5ifEV81wFu017TarjydaYVmGbHcehtY,6103
213
+ vgi_python-0.49.0.dist-info/RECORD,,