vgi-python 0.43.1__py3-none-any.whl → 0.45.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.
- vgi/_test_fixtures/aggregate/dynamic.py +3 -1
- vgi/_test_fixtures/http_server.py +1 -1
- vgi/_test_fixtures/identity_fixture.py +1 -1
- vgi/_test_fixtures/scalar/_common.py +2 -2
- vgi/arguments.py +9 -4
- vgi/attach_ticket.py +16 -7
- vgi/auth.py +1 -1
- vgi/catalog/attach_option.py +3 -0
- vgi/catalog/catalog_interface.py +37 -37
- vgi/client/aggregate.py +4 -6
- vgi/exceptions.py +62 -1
- vgi/filter_v2.py +4 -4
- vgi/function.py +14 -0
- vgi/meta_worker.py +13 -12
- vgi/rpc_server.py +6 -6
- vgi/scalar_function.py +12 -22
- vgi/serve.py +1 -1
- vgi/table_function.py +166 -164
- vgi/worker.py +22 -15
- {vgi_python-0.43.1.dist-info → vgi_python-0.45.0.dist-info}/METADATA +2 -2
- {vgi_python-0.43.1.dist-info → vgi_python-0.45.0.dist-info}/RECORD +24 -24
- {vgi_python-0.43.1.dist-info → vgi_python-0.45.0.dist-info}/WHEEL +0 -0
- {vgi_python-0.43.1.dist-info → vgi_python-0.45.0.dist-info}/entry_points.txt +0 -0
- {vgi_python-0.43.1.dist-info → vgi_python-0.45.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -232,7 +232,9 @@ class _DynamicAggregateBase(AggregateFunction[DynamicState]):
|
|
|
232
232
|
t = t.filter(null_mask)
|
|
233
233
|
slices.append(t)
|
|
234
234
|
if not slices:
|
|
235
|
-
|
|
235
|
+
# pa.nulls(0, ...), not pa.array([], ...): the latter has no
|
|
236
|
+
# builder for union or extension types.
|
|
237
|
+
return pa.table({c: pa.nulls(0, type=cols[i].type) for i, c in enumerate(col_names)})
|
|
236
238
|
return pa.concat_tables(slices)
|
|
237
239
|
|
|
238
240
|
@staticmethod
|
|
@@ -193,7 +193,7 @@ def main() -> None:
|
|
|
193
193
|
"--identity",
|
|
194
194
|
envvar="VGI_FIXTURE_IDENTITY",
|
|
195
195
|
help=(
|
|
196
|
-
"Host vgi_rpc.Identity.
|
|
196
|
+
"Host vgi_rpc.Identity.v2 with the vgi-rpc conformance fixture policy and its "
|
|
197
197
|
"spoofable X-Conformance-Principal header authentication. Test-only; for "
|
|
198
198
|
"vgi-rpc-test-hosted --identity."
|
|
199
199
|
),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Copyright 2025, 2026 Query Farm LLC - https://query.farm
|
|
2
2
|
|
|
3
|
-
"""Opt the HTTP fixture into ``vgi_rpc.Identity.
|
|
3
|
+
"""Opt the HTTP fixture into ``vgi_rpc.Identity.v2`` with the conformance policy.
|
|
4
4
|
|
|
5
5
|
Enabled by ``vgi-fixture-http --identity`` (or ``VGI_FIXTURE_IDENTITY=1``) so
|
|
6
6
|
the cross-SDK hosted-protocols group can run its identity cases with
|
|
@@ -6,7 +6,7 @@ from __future__ import annotations
|
|
|
6
6
|
|
|
7
7
|
import pyarrow as pa
|
|
8
8
|
|
|
9
|
-
from vgi.exceptions import
|
|
9
|
+
from vgi.exceptions import ArgumentTypeError
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
def _is_addable_type(dtype: pa.DataType) -> bool:
|
|
@@ -66,4 +66,4 @@ def _promote_for_addition(dtype: pa.DataType) -> pa.DataType:
|
|
|
66
66
|
# accepts that values >= 5e37 will overflow at compute time.
|
|
67
67
|
new_precision = min(dtype.precision + 1, 38)
|
|
68
68
|
return pa.decimal128(new_precision, dtype.scale)
|
|
69
|
-
raise
|
|
69
|
+
raise ArgumentTypeError(f"Unsupported numeric type for addition: {dtype}")
|
vgi/arguments.py
CHANGED
|
@@ -19,10 +19,11 @@ import typing
|
|
|
19
19
|
import warnings
|
|
20
20
|
from collections.abc import Callable, Mapping, Sequence
|
|
21
21
|
from dataclasses import dataclass
|
|
22
|
-
from typing import TYPE_CHECKING, Any, Final, TypeVar, overload
|
|
22
|
+
from typing import TYPE_CHECKING, Any, ClassVar, Final, TypeVar, overload
|
|
23
23
|
|
|
24
24
|
import pyarrow as pa
|
|
25
25
|
from vgi_rpc import ArrowSerializableDataclass
|
|
26
|
+
from vgi_rpc.errors import Code
|
|
26
27
|
|
|
27
28
|
if TYPE_CHECKING:
|
|
28
29
|
from pyarrow import Scalar
|
|
@@ -638,6 +639,7 @@ class ArgumentValidationError(ValueError):
|
|
|
638
639
|
valid_range: Human-readable description of valid values.
|
|
639
640
|
default: Default value (if any) that could be used instead.
|
|
640
641
|
choices: Valid choices, if the argument is constrained to a set.
|
|
642
|
+
error_code: ``INVALID_ARGUMENT`` -- the caller's input is wrong, not the worker.
|
|
641
643
|
|
|
642
644
|
"""
|
|
643
645
|
|
|
@@ -649,6 +651,7 @@ class ArgumentValidationError(ValueError):
|
|
|
649
651
|
valid_range: str | None
|
|
650
652
|
default: Any
|
|
651
653
|
choices: Sequence[Any] | None
|
|
654
|
+
error_code: ClassVar[Code] = Code.INVALID_ARGUMENT
|
|
652
655
|
|
|
653
656
|
def __init__(
|
|
654
657
|
self,
|
|
@@ -1429,10 +1432,12 @@ class Arg[ArgT]:
|
|
|
1429
1432
|
field_type: The Arrow type of the column to validate.
|
|
1430
1433
|
|
|
1431
1434
|
Raises:
|
|
1432
|
-
[`
|
|
1435
|
+
[`ArgumentTypeError`][vgi.exceptions.ArgumentTypeError]: If the type bound is
|
|
1436
|
+
not satisfied (a [`SchemaValidationError`][vgi.exceptions.SchemaValidationError]
|
|
1437
|
+
coded ``INVALID_ARGUMENT``).
|
|
1433
1438
|
|
|
1434
1439
|
"""
|
|
1435
|
-
from vgi.exceptions import
|
|
1440
|
+
from vgi.exceptions import ArgumentTypeError
|
|
1436
1441
|
|
|
1437
1442
|
if self.type_bound is None:
|
|
1438
1443
|
return
|
|
@@ -1446,7 +1451,7 @@ class Arg[ArgT]:
|
|
|
1446
1451
|
# OR logic: at least one predicate must pass
|
|
1447
1452
|
if not any(predicate(field_type) for predicate in predicates):
|
|
1448
1453
|
predicate_names = [getattr(p, "__name__", str(p)) for p in predicates]
|
|
1449
|
-
raise
|
|
1454
|
+
raise ArgumentTypeError(
|
|
1450
1455
|
self.format_error(f"column type {field_type} does not match any of: {', '.join(predicate_names)}")
|
|
1451
1456
|
)
|
|
1452
1457
|
|
vgi/attach_ticket.py
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
r"""Attach tickets: a user's ATTACH, sealed so a runner can replay it later as that user.
|
|
4
4
|
|
|
5
5
|
A ticket is the *what* half of an unattended session; a sealed grant
|
|
6
|
-
(``vgi_rpc.Identity.
|
|
6
|
+
(``vgi_rpc.Identity.v2`` ``issue_grant``) is the *who*. While the user is
|
|
7
7
|
attached and logged in, the client asks the worker to seal the options it
|
|
8
8
|
attached with -- secret ones included -- into a ticket only this worker can
|
|
9
9
|
open. Later any runner holding the user's grant attaches with the single option
|
|
@@ -43,7 +43,6 @@ from __future__ import annotations
|
|
|
43
43
|
|
|
44
44
|
import base64
|
|
45
45
|
import dataclasses
|
|
46
|
-
import math
|
|
47
46
|
import os
|
|
48
47
|
import re
|
|
49
48
|
import secrets
|
|
@@ -51,6 +50,7 @@ import struct
|
|
|
51
50
|
import sys
|
|
52
51
|
import time
|
|
53
52
|
from collections.abc import Mapping, Sequence
|
|
53
|
+
from datetime import UTC, datetime
|
|
54
54
|
from typing import TYPE_CHECKING, Annotated, Any, ClassVar, Protocol
|
|
55
55
|
|
|
56
56
|
import pyarrow as pa
|
|
@@ -553,13 +553,20 @@ class AttachTicket(ArrowSerializableDataclass):
|
|
|
553
553
|
|
|
554
554
|
Attributes:
|
|
555
555
|
ticket: ``vgia1.`` text. Not a credential, but never logged.
|
|
556
|
-
expires_at:
|
|
557
|
-
|
|
556
|
+
expires_at: The UTC instant after which the worker refuses it, carried
|
|
557
|
+
as ``timestamp[us, tz="UTC"]``; ``None`` (null on the wire) when
|
|
558
|
+
the worker sets no maximum lifetime. Protocol ``1.0.0`` carried a
|
|
559
|
+
float64 of unix seconds with ``+inf`` for "no expiry".
|
|
558
560
|
|
|
559
561
|
"""
|
|
560
562
|
|
|
561
563
|
ticket: str
|
|
562
|
-
expires_at:
|
|
564
|
+
expires_at: Annotated[datetime | None, ArrowType(pa.timestamp("us", tz="UTC"))]
|
|
565
|
+
|
|
566
|
+
def __post_init__(self) -> None:
|
|
567
|
+
"""Refuse a naive ``expires_at``, which has no defined instant."""
|
|
568
|
+
if self.expires_at is not None and (self.expires_at.tzinfo is None or self.expires_at.utcoffset() is None):
|
|
569
|
+
raise ValueError("AttachTicket.expires_at must be timezone-aware (UTC) or None")
|
|
563
570
|
|
|
564
571
|
|
|
565
572
|
class AttachTickets(Protocol):
|
|
@@ -571,7 +578,7 @@ class AttachTickets(Protocol):
|
|
|
571
578
|
"""
|
|
572
579
|
|
|
573
580
|
protocol_name: ClassVar[str] = ATTACH_TICKETS_PROTOCOL_NAME
|
|
574
|
-
protocol_version: ClassVar[str] = "
|
|
581
|
+
protocol_version: ClassVar[str] = "2.0.0"
|
|
575
582
|
|
|
576
583
|
def seal_attach(self, request: SealAttachRequest) -> AttachTicket:
|
|
577
584
|
"""Seal the caller's attach of ``request.catalog_name`` into a ticket.
|
|
@@ -730,4 +737,6 @@ class AttachTicketsImpl:
|
|
|
730
737
|
)
|
|
731
738
|
except ValueError as exc:
|
|
732
739
|
raise _invalid_request("seal_attach request is invalid", [("request", str(exc))]) from exc
|
|
733
|
-
return AttachTicket(
|
|
740
|
+
return AttachTicket(
|
|
741
|
+
ticket=token, expires_at=None if expires_at == 0 else datetime.fromtimestamp(expires_at, tz=UTC)
|
|
742
|
+
)
|
vgi/auth.py
CHANGED
|
@@ -24,7 +24,7 @@ from typing import TYPE_CHECKING, Any
|
|
|
24
24
|
|
|
25
25
|
from vgi_rpc.rpc import AuthContext, CallContext
|
|
26
26
|
|
|
27
|
-
# ``vgi_rpc.Identity.
|
|
27
|
+
# ``vgi_rpc.Identity.v2`` lives at the RPC layer as of vgi-rpc 0.46.0 -- it was
|
|
28
28
|
# an HTTP JSON route (``POST {prefix}/__introspect_token__``) through 0.45.x,
|
|
29
29
|
# which meant it existed on exactly one transport. So this is no longer gated
|
|
30
30
|
# on the ``http`` extra. Still not re-exported by ``vgi_rpc.rpc`` itself, so
|
vgi/catalog/attach_option.py
CHANGED
|
@@ -23,6 +23,7 @@ from dataclasses import dataclass
|
|
|
23
23
|
from typing import Any, ClassVar
|
|
24
24
|
|
|
25
25
|
import pyarrow as pa
|
|
26
|
+
from vgi_rpc.errors import Code
|
|
26
27
|
|
|
27
28
|
from vgi.catalog._descriptor_spec import _DescriptorBase, _extract_specs, _SpecBase
|
|
28
29
|
|
|
@@ -156,10 +157,12 @@ class MissingAttachOptionsError(ValueError):
|
|
|
156
157
|
|
|
157
158
|
Attributes:
|
|
158
159
|
missing: Names of the required options that were not supplied.
|
|
160
|
+
error_code: ``INVALID_ARGUMENT``.
|
|
159
161
|
|
|
160
162
|
"""
|
|
161
163
|
|
|
162
164
|
missing: list[str]
|
|
165
|
+
error_code: ClassVar[Code] = Code.INVALID_ARGUMENT
|
|
163
166
|
|
|
164
167
|
def __init__(self, catalog_name: str, missing: list[str]) -> None:
|
|
165
168
|
"""Build the error from the catalog name and the missing option names."""
|
vgi/catalog/catalog_interface.py
CHANGED
|
@@ -38,7 +38,7 @@ from vgi_rpc import ArrowSerializableDataclass, ArrowType
|
|
|
38
38
|
from vgi_rpc.utils import deserialize_record_batch, serialize_record_batch_bytes
|
|
39
39
|
|
|
40
40
|
from vgi.arguments import Arguments, SecretLookupEntry
|
|
41
|
-
from vgi.exceptions import CatalogReadOnlyError
|
|
41
|
+
from vgi.exceptions import CatalogReadOnlyError, InvalidArgumentError, NotFoundError, UnsupportedOperationError
|
|
42
42
|
from vgi.metadata import (
|
|
43
43
|
ArgumentMonotonicity,
|
|
44
44
|
DistinctDependence,
|
|
@@ -100,7 +100,7 @@ __all__ = [
|
|
|
100
100
|
def _validate_at_params(at_unit: str | None, at_value: str | None) -> None:
|
|
101
101
|
"""Validate that at_unit and at_value are both provided or both absent."""
|
|
102
102
|
if bool(at_unit) != bool(at_value):
|
|
103
|
-
raise
|
|
103
|
+
raise InvalidArgumentError("at_unit and at_value must both be provided or both be None")
|
|
104
104
|
|
|
105
105
|
|
|
106
106
|
@dataclass(frozen=True)
|
|
@@ -1810,12 +1810,12 @@ class CatalogInterface(ABC):
|
|
|
1810
1810
|
If on_conflict is ERROR and the catalog already exists, raise an error.
|
|
1811
1811
|
|
|
1812
1812
|
"""
|
|
1813
|
-
raise
|
|
1813
|
+
raise UnsupportedOperationError("Catalog create not implemented.")
|
|
1814
1814
|
|
|
1815
1815
|
# Drop a catalog
|
|
1816
1816
|
def catalog_drop(self, *, name: str) -> None:
|
|
1817
1817
|
"""Drop the catalog with the given name."""
|
|
1818
|
-
raise
|
|
1818
|
+
raise UnsupportedOperationError("Catalog drop not implemented.")
|
|
1819
1819
|
|
|
1820
1820
|
# Transactions are initiated and driven by DuckDB it is rare for CatalogInterface
|
|
1821
1821
|
# implementors to implement them, but I want to support them.
|
|
@@ -1830,7 +1830,7 @@ class CatalogInterface(ABC):
|
|
|
1830
1830
|
|
|
1831
1831
|
If the implementation does not support transactions, it can return None.
|
|
1832
1832
|
"""
|
|
1833
|
-
raise
|
|
1833
|
+
raise UnsupportedOperationError("Catalog transactions not implemented.")
|
|
1834
1834
|
|
|
1835
1835
|
def catalog_transaction_commit(
|
|
1836
1836
|
self, *, attach_opaque_data: AttachOpaqueData, transaction_opaque_data: TransactionOpaqueData
|
|
@@ -1839,7 +1839,7 @@ class CatalogInterface(ABC):
|
|
|
1839
1839
|
|
|
1840
1840
|
If the transaction cannot be committed, an exception should be raised.
|
|
1841
1841
|
"""
|
|
1842
|
-
raise
|
|
1842
|
+
raise UnsupportedOperationError("Catalog transactions not implemented.")
|
|
1843
1843
|
|
|
1844
1844
|
def catalog_transaction_rollback(
|
|
1845
1845
|
self, *, attach_opaque_data: AttachOpaqueData, transaction_opaque_data: TransactionOpaqueData
|
|
@@ -1848,7 +1848,7 @@ class CatalogInterface(ABC):
|
|
|
1848
1848
|
|
|
1849
1849
|
If the transaction cannot be rolled back, an exception should be raised.
|
|
1850
1850
|
"""
|
|
1851
|
-
raise
|
|
1851
|
+
raise UnsupportedOperationError("Catalog transactions not implemented.")
|
|
1852
1852
|
|
|
1853
1853
|
@abstractmethod
|
|
1854
1854
|
def catalog_attach(
|
|
@@ -1936,7 +1936,7 @@ class CatalogInterface(ABC):
|
|
|
1936
1936
|
tags: dict[str, str],
|
|
1937
1937
|
) -> None:
|
|
1938
1938
|
"""Create a new schema with the given path, comment, and tags."""
|
|
1939
|
-
raise
|
|
1939
|
+
raise UnsupportedOperationError("Schema create not implemented.")
|
|
1940
1940
|
|
|
1941
1941
|
def schema_drop(
|
|
1942
1942
|
self,
|
|
@@ -1948,7 +1948,7 @@ class CatalogInterface(ABC):
|
|
|
1948
1948
|
cascade: bool,
|
|
1949
1949
|
) -> None:
|
|
1950
1950
|
"""Drop the schema with the given path."""
|
|
1951
|
-
raise
|
|
1951
|
+
raise UnsupportedOperationError("Schema drop not implemented.")
|
|
1952
1952
|
|
|
1953
1953
|
@overload
|
|
1954
1954
|
def schema_contents(
|
|
@@ -2034,7 +2034,7 @@ class CatalogInterface(ABC):
|
|
|
2034
2034
|
depending on the type parameter.
|
|
2035
2035
|
|
|
2036
2036
|
"""
|
|
2037
|
-
raise
|
|
2037
|
+
raise UnsupportedOperationError("Schema contents not implemented.")
|
|
2038
2038
|
|
|
2039
2039
|
def catalog_contents(
|
|
2040
2040
|
self,
|
|
@@ -2159,7 +2159,7 @@ class CatalogInterface(ABC):
|
|
|
2159
2159
|
|
|
2160
2160
|
Comments and tags are not supported on table creation.
|
|
2161
2161
|
"""
|
|
2162
|
-
raise
|
|
2162
|
+
raise UnsupportedOperationError("Table create not implemented.")
|
|
2163
2163
|
|
|
2164
2164
|
def table_drop(
|
|
2165
2165
|
self,
|
|
@@ -2172,7 +2172,7 @@ class CatalogInterface(ABC):
|
|
|
2172
2172
|
cascade: bool = False,
|
|
2173
2173
|
) -> None:
|
|
2174
2174
|
"""Drop the table with the given name."""
|
|
2175
|
-
raise
|
|
2175
|
+
raise UnsupportedOperationError("Table drop not implemented.")
|
|
2176
2176
|
|
|
2177
2177
|
def table_comment_set(
|
|
2178
2178
|
self,
|
|
@@ -2185,7 +2185,7 @@ class CatalogInterface(ABC):
|
|
|
2185
2185
|
ignore_not_found: bool,
|
|
2186
2186
|
) -> None:
|
|
2187
2187
|
"""Set the comment for the table with the given name."""
|
|
2188
|
-
raise
|
|
2188
|
+
raise UnsupportedOperationError("Table comment set not implemented.")
|
|
2189
2189
|
|
|
2190
2190
|
def table_column_comment_set(
|
|
2191
2191
|
self,
|
|
@@ -2199,7 +2199,7 @@ class CatalogInterface(ABC):
|
|
|
2199
2199
|
ignore_not_found: bool,
|
|
2200
2200
|
) -> None:
|
|
2201
2201
|
"""Set the comment for a column in the table."""
|
|
2202
|
-
raise
|
|
2202
|
+
raise UnsupportedOperationError("Table column comment set not implemented.")
|
|
2203
2203
|
|
|
2204
2204
|
def table_rename(
|
|
2205
2205
|
self,
|
|
@@ -2212,7 +2212,7 @@ class CatalogInterface(ABC):
|
|
|
2212
2212
|
ignore_not_found: bool,
|
|
2213
2213
|
) -> None:
|
|
2214
2214
|
"""Rename the table with the given name to the new name."""
|
|
2215
|
-
raise
|
|
2215
|
+
raise UnsupportedOperationError("Table rename not implemented.")
|
|
2216
2216
|
|
|
2217
2217
|
def table_column_add(
|
|
2218
2218
|
self,
|
|
@@ -2228,7 +2228,7 @@ class CatalogInterface(ABC):
|
|
|
2228
2228
|
if_column_not_exists: bool,
|
|
2229
2229
|
) -> None:
|
|
2230
2230
|
"""Add a column to the table with the given name."""
|
|
2231
|
-
raise
|
|
2231
|
+
raise UnsupportedOperationError("Table column add not implemented.")
|
|
2232
2232
|
|
|
2233
2233
|
def table_column_drop(
|
|
2234
2234
|
self,
|
|
@@ -2243,7 +2243,7 @@ class CatalogInterface(ABC):
|
|
|
2243
2243
|
cascade: bool,
|
|
2244
2244
|
) -> None:
|
|
2245
2245
|
"""Drop the column from the table with the given name."""
|
|
2246
|
-
raise
|
|
2246
|
+
raise UnsupportedOperationError("Table column drop not implemented.")
|
|
2247
2247
|
|
|
2248
2248
|
def table_column_rename(
|
|
2249
2249
|
self,
|
|
@@ -2257,7 +2257,7 @@ class CatalogInterface(ABC):
|
|
|
2257
2257
|
ignore_not_found: bool,
|
|
2258
2258
|
) -> None:
|
|
2259
2259
|
"""Rename the column in the table with the given name."""
|
|
2260
|
-
raise
|
|
2260
|
+
raise UnsupportedOperationError("Table column rename not implemented.")
|
|
2261
2261
|
|
|
2262
2262
|
def table_column_default_set(
|
|
2263
2263
|
self,
|
|
@@ -2271,7 +2271,7 @@ class CatalogInterface(ABC):
|
|
|
2271
2271
|
ignore_not_found: bool,
|
|
2272
2272
|
) -> None:
|
|
2273
2273
|
"""Set the default expression for the column."""
|
|
2274
|
-
raise
|
|
2274
|
+
raise UnsupportedOperationError("Table column default set not implemented.")
|
|
2275
2275
|
|
|
2276
2276
|
def table_column_default_drop(
|
|
2277
2277
|
self,
|
|
@@ -2284,7 +2284,7 @@ class CatalogInterface(ABC):
|
|
|
2284
2284
|
ignore_not_found: bool,
|
|
2285
2285
|
) -> None:
|
|
2286
2286
|
"""Drop the default expression for the column."""
|
|
2287
|
-
raise
|
|
2287
|
+
raise UnsupportedOperationError("Table column default drop not implemented.")
|
|
2288
2288
|
|
|
2289
2289
|
def table_column_type_change(
|
|
2290
2290
|
self,
|
|
@@ -2303,7 +2303,7 @@ class CatalogInterface(ABC):
|
|
|
2303
2303
|
|
|
2304
2304
|
The name of the column to change is taken from the field in the provided schema.
|
|
2305
2305
|
"""
|
|
2306
|
-
raise
|
|
2306
|
+
raise UnsupportedOperationError("Table column type change not implemented.")
|
|
2307
2307
|
|
|
2308
2308
|
def table_not_null_drop(
|
|
2309
2309
|
self,
|
|
@@ -2316,7 +2316,7 @@ class CatalogInterface(ABC):
|
|
|
2316
2316
|
ignore_not_found: bool,
|
|
2317
2317
|
) -> None:
|
|
2318
2318
|
"""Drop the NOT NULL constraint from the column."""
|
|
2319
|
-
raise
|
|
2319
|
+
raise UnsupportedOperationError("Table NOT NULL drop not implemented.")
|
|
2320
2320
|
|
|
2321
2321
|
def table_not_null_set(
|
|
2322
2322
|
self,
|
|
@@ -2329,7 +2329,7 @@ class CatalogInterface(ABC):
|
|
|
2329
2329
|
ignore_not_found: bool,
|
|
2330
2330
|
) -> None:
|
|
2331
2331
|
"""Set the NOT NULL constraint on the column."""
|
|
2332
|
-
raise
|
|
2332
|
+
raise UnsupportedOperationError("Table NOT NULL set not implemented.")
|
|
2333
2333
|
|
|
2334
2334
|
def table_scan_function_get(
|
|
2335
2335
|
self,
|
|
@@ -2347,7 +2347,7 @@ class CatalogInterface(ABC):
|
|
|
2347
2347
|
Returns information about the VGI table function to call when scanning
|
|
2348
2348
|
this table. The at_unit and at_value support time travel queries.
|
|
2349
2349
|
"""
|
|
2350
|
-
raise
|
|
2350
|
+
raise UnsupportedOperationError("Table scan function get not implemented.")
|
|
2351
2351
|
|
|
2352
2352
|
def table_scan_branches_get(
|
|
2353
2353
|
self,
|
|
@@ -2466,7 +2466,7 @@ class CatalogInterface(ABC):
|
|
|
2466
2466
|
For single-branch tables this is ``None`` (or unset for legacy
|
|
2467
2467
|
overrides).
|
|
2468
2468
|
"""
|
|
2469
|
-
raise
|
|
2469
|
+
raise UnsupportedOperationError("Table insert not supported.")
|
|
2470
2470
|
|
|
2471
2471
|
def table_update_function_get(
|
|
2472
2472
|
self,
|
|
@@ -2482,7 +2482,7 @@ class CatalogInterface(ABC):
|
|
|
2482
2482
|
to call for updating rows in this table. Input batches will include a
|
|
2483
2483
|
rowid column plus the columns being updated.
|
|
2484
2484
|
"""
|
|
2485
|
-
raise
|
|
2485
|
+
raise UnsupportedOperationError("Table update not supported.")
|
|
2486
2486
|
|
|
2487
2487
|
def table_delete_function_get(
|
|
2488
2488
|
self,
|
|
@@ -2498,7 +2498,7 @@ class CatalogInterface(ABC):
|
|
|
2498
2498
|
to call for deleting rows from this table. Input batches will contain
|
|
2499
2499
|
a rowid column identifying the rows to delete.
|
|
2500
2500
|
"""
|
|
2501
|
-
raise
|
|
2501
|
+
raise UnsupportedOperationError("Table delete not supported.")
|
|
2502
2502
|
|
|
2503
2503
|
def view_create(
|
|
2504
2504
|
self,
|
|
@@ -2511,7 +2511,7 @@ class CatalogInterface(ABC):
|
|
|
2511
2511
|
on_conflict: OnConflict,
|
|
2512
2512
|
) -> None:
|
|
2513
2513
|
"""Create a new view with the given definition."""
|
|
2514
|
-
raise
|
|
2514
|
+
raise UnsupportedOperationError("View create not implemented.")
|
|
2515
2515
|
|
|
2516
2516
|
def view_drop(
|
|
2517
2517
|
self,
|
|
@@ -2524,7 +2524,7 @@ class CatalogInterface(ABC):
|
|
|
2524
2524
|
cascade: bool = False,
|
|
2525
2525
|
) -> None:
|
|
2526
2526
|
"""Drop the view with the given name."""
|
|
2527
|
-
raise
|
|
2527
|
+
raise UnsupportedOperationError("View drop not implemented.")
|
|
2528
2528
|
|
|
2529
2529
|
def view_rename(
|
|
2530
2530
|
self,
|
|
@@ -2537,7 +2537,7 @@ class CatalogInterface(ABC):
|
|
|
2537
2537
|
ignore_not_found: bool,
|
|
2538
2538
|
) -> None:
|
|
2539
2539
|
"""Rename the view to the new name."""
|
|
2540
|
-
raise
|
|
2540
|
+
raise UnsupportedOperationError("View rename not implemented.")
|
|
2541
2541
|
|
|
2542
2542
|
@abstractmethod
|
|
2543
2543
|
def view_get(
|
|
@@ -2564,7 +2564,7 @@ class CatalogInterface(ABC):
|
|
|
2564
2564
|
ignore_not_found: bool,
|
|
2565
2565
|
) -> None:
|
|
2566
2566
|
"""Set the comment for the view with the given name."""
|
|
2567
|
-
raise
|
|
2567
|
+
raise UnsupportedOperationError("View comment set not implemented.")
|
|
2568
2568
|
|
|
2569
2569
|
# ---- Macros ----
|
|
2570
2570
|
|
|
@@ -2613,7 +2613,7 @@ class CatalogInterface(ABC):
|
|
|
2613
2613
|
descriptions via the ``vgi_doc`` field metadata key. ``None`` when
|
|
2614
2614
|
no per-parameter docs are supplied.
|
|
2615
2615
|
"""
|
|
2616
|
-
raise
|
|
2616
|
+
raise UnsupportedOperationError("Macro create not implemented.")
|
|
2617
2617
|
|
|
2618
2618
|
def macro_drop(
|
|
2619
2619
|
self,
|
|
@@ -2625,7 +2625,7 @@ class CatalogInterface(ABC):
|
|
|
2625
2625
|
ignore_not_found: bool,
|
|
2626
2626
|
) -> None:
|
|
2627
2627
|
"""Drop the macro with the given name."""
|
|
2628
|
-
raise
|
|
2628
|
+
raise UnsupportedOperationError("Macro drop not implemented.")
|
|
2629
2629
|
|
|
2630
2630
|
# ---- Indexes ----
|
|
2631
2631
|
|
|
@@ -2659,7 +2659,7 @@ class CatalogInterface(ABC):
|
|
|
2659
2659
|
options: dict[str, str] | None = None,
|
|
2660
2660
|
) -> None:
|
|
2661
2661
|
"""Create a new index on the specified table."""
|
|
2662
|
-
raise
|
|
2662
|
+
raise UnsupportedOperationError("Index create not implemented.")
|
|
2663
2663
|
|
|
2664
2664
|
def index_drop(
|
|
2665
2665
|
self,
|
|
@@ -2672,7 +2672,7 @@ class CatalogInterface(ABC):
|
|
|
2672
2672
|
cascade: bool = False,
|
|
2673
2673
|
) -> None:
|
|
2674
2674
|
"""Drop the index with the given name."""
|
|
2675
|
-
raise
|
|
2675
|
+
raise UnsupportedOperationError("Index drop not implemented.")
|
|
2676
2676
|
|
|
2677
2677
|
def copy_from_formats(
|
|
2678
2678
|
self,
|
|
@@ -3015,7 +3015,7 @@ class ReadOnlyCatalogInterface(CatalogInterface):
|
|
|
3015
3015
|
del data_version_spec, implementation_version, ctx
|
|
3016
3016
|
effective_name = self._effective_catalog_name
|
|
3017
3017
|
if name != effective_name:
|
|
3018
|
-
raise
|
|
3018
|
+
raise NotFoundError(f"Unknown catalog: {name!r}. Available: {effective_name}")
|
|
3019
3019
|
validate_required_attach_options(effective_name, self.attach_option_specs, options)
|
|
3020
3020
|
|
|
3021
3021
|
# Serialize settings and secret types for the attach result
|
|
@@ -3276,7 +3276,7 @@ class ReadOnlyCatalogInterface(CatalogInterface):
|
|
|
3276
3276
|
|
|
3277
3277
|
table = self._table_registry.get((schema_path_key(schema_path), name.lower()))
|
|
3278
3278
|
if table is None:
|
|
3279
|
-
raise
|
|
3279
|
+
raise NotFoundError(f"Table {schema_path_display(schema_path)}::{name!r} not found in catalog.")
|
|
3280
3280
|
|
|
3281
3281
|
write_func = getattr(table, attr_name, None)
|
|
3282
3282
|
if write_func is None:
|
vgi/client/aggregate.py
CHANGED
|
@@ -50,6 +50,7 @@ from typing import Any
|
|
|
50
50
|
|
|
51
51
|
import pyarrow as pa
|
|
52
52
|
from vgi_rpc.rpc import RpcError
|
|
53
|
+
from vgi_rpc.utils import empty_batch
|
|
53
54
|
|
|
54
55
|
from vgi.aggregate_function import GROUP_COLUMN_NAME
|
|
55
56
|
from vgi.arguments import Arguments
|
|
@@ -783,12 +784,9 @@ class AggregateClientMixin:
|
|
|
783
784
|
batch = next(batches, None)
|
|
784
785
|
|
|
785
786
|
if not group_keys:
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
schema=session.output_schema,
|
|
790
|
-
)
|
|
791
|
-
]
|
|
787
|
+
# empty_batch, not pa.array([], type=...): that has no builder
|
|
788
|
+
# for union or extension types.
|
|
789
|
+
results = [empty_batch(session.output_schema)]
|
|
792
790
|
else:
|
|
793
791
|
results = [
|
|
794
792
|
session.finalize(range(start, min(start + finalize_chunk_size, len(group_keys))))
|
vgi/exceptions.py
CHANGED
|
@@ -14,15 +14,21 @@ from __future__ import annotations
|
|
|
14
14
|
|
|
15
15
|
from typing import TYPE_CHECKING, ClassVar
|
|
16
16
|
|
|
17
|
+
from vgi_rpc.errors import Code
|
|
18
|
+
|
|
17
19
|
if TYPE_CHECKING:
|
|
18
20
|
import pyarrow as pa
|
|
19
21
|
|
|
20
22
|
__all__ = [
|
|
23
|
+
"ArgumentTypeError",
|
|
21
24
|
"BindStateNotFoundError",
|
|
22
25
|
"CatalogReadOnlyError",
|
|
23
26
|
"ExecutionIdentifierError",
|
|
27
|
+
"InvalidArgumentError",
|
|
28
|
+
"NotFoundError",
|
|
24
29
|
"OpaqueDataNotRecognizedError",
|
|
25
30
|
"SchemaValidationError",
|
|
31
|
+
"UnsupportedOperationError",
|
|
26
32
|
]
|
|
27
33
|
|
|
28
34
|
|
|
@@ -55,8 +61,14 @@ class CatalogReadOnlyError(Exception):
|
|
|
55
61
|
- table_get(), view_get() - get table/view info
|
|
56
62
|
- table_scan_function_get() - get scan function for tables
|
|
57
63
|
|
|
64
|
+
Also raised when a write targets a table that has no write function.
|
|
65
|
+
``FAILED_PRECONDITION``: the request is well-formed, the catalog is just
|
|
66
|
+
not in a state that permits it.
|
|
67
|
+
|
|
58
68
|
"""
|
|
59
69
|
|
|
70
|
+
error_code: ClassVar[Code] = Code.FAILED_PRECONDITION
|
|
71
|
+
|
|
60
72
|
|
|
61
73
|
class OpaqueDataNotRecognizedError(ValueError):
|
|
62
74
|
"""An opaque value (``attach_opaque_data`` / ``transaction_opaque_data``) failed to open.
|
|
@@ -72,7 +84,7 @@ class OpaqueDataNotRecognizedError(ValueError):
|
|
|
72
84
|
working.
|
|
73
85
|
"""
|
|
74
86
|
|
|
75
|
-
error_code: ClassVar[
|
|
87
|
+
error_code: ClassVar[Code] = Code.INVALID_ARGUMENT
|
|
76
88
|
error_kind: ClassVar[str] = "opaque_data_not_recognized"
|
|
77
89
|
|
|
78
90
|
def __init__(self, field: str) -> None:
|
|
@@ -230,3 +242,52 @@ class SchemaValidationError(Exception):
|
|
|
230
242
|
lines.append(f" {field.name}: {field.type}{nullable}")
|
|
231
243
|
|
|
232
244
|
return "\n".join(lines)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
class ArgumentTypeError(SchemaValidationError):
|
|
248
|
+
"""An argument or input column has a type the function does not accept.
|
|
249
|
+
|
|
250
|
+
Raised when a value fails an ``Arg[AnyArrow]`` ``type_bound`` -- e.g.
|
|
251
|
+
``double('abc')`` against a numeric-only bound. Unlike its base class,
|
|
252
|
+
which reports a worker's *own* output violating its declared schema (a
|
|
253
|
+
bug, sent as ``UNKNOWN``), this is the caller's input being wrong, so it is
|
|
254
|
+
``INVALID_ARGUMENT``. Subclasses [`SchemaValidationError`][] so existing
|
|
255
|
+
handlers keep working.
|
|
256
|
+
"""
|
|
257
|
+
|
|
258
|
+
error_code: ClassVar[Code] = Code.INVALID_ARGUMENT
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
class InvalidArgumentError(ValueError):
|
|
262
|
+
"""A request's arguments cannot be accepted (``INVALID_ARGUMENT``).
|
|
263
|
+
|
|
264
|
+
For argument problems that are not a single argument's constraint (those
|
|
265
|
+
raise [`ArgumentValidationError`][vgi.arguments.ArgumentValidationError]):
|
|
266
|
+
no overload matching the supplied argument types, an ambiguous call, or a
|
|
267
|
+
call shape the function does not take. Subclasses ``ValueError`` so
|
|
268
|
+
existing handlers keep working.
|
|
269
|
+
"""
|
|
270
|
+
|
|
271
|
+
error_code: ClassVar[Code] = Code.INVALID_ARGUMENT
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
class NotFoundError(ValueError):
|
|
275
|
+
"""A named function, table, schema, or catalog does not exist (``NOT_FOUND``).
|
|
276
|
+
|
|
277
|
+
Subclasses ``ValueError`` so existing handlers keep working.
|
|
278
|
+
"""
|
|
279
|
+
|
|
280
|
+
error_code: ClassVar[Code] = Code.NOT_FOUND
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
class UnsupportedOperationError(NotImplementedError):
|
|
284
|
+
"""An operation the SDK or catalog explicitly does not support (``UNIMPLEMENTED``).
|
|
285
|
+
|
|
286
|
+
Raised by the [`CatalogInterface`][vgi.catalog.CatalogInterface] defaults
|
|
287
|
+
for DDL/DML a catalog has not implemented, and by framework paths that are
|
|
288
|
+
not supported yet. Subclasses ``NotImplementedError`` so existing handlers
|
|
289
|
+
-- including the ones that treat an unimplemented optional hook as
|
|
290
|
+
"absent" -- keep working.
|
|
291
|
+
"""
|
|
292
|
+
|
|
293
|
+
error_code: ClassVar[Code] = Code.UNIMPLEMENTED
|
vgi/filter_v2.py
CHANGED
|
@@ -16,6 +16,7 @@ from typing import Any
|
|
|
16
16
|
from typing import Literal as TypingLiteral
|
|
17
17
|
|
|
18
18
|
import pyarrow as pa
|
|
19
|
+
from vgi_rpc.utils import empty_batch
|
|
19
20
|
|
|
20
21
|
FILTER_ENCODING = "vgi.filters.v2"
|
|
21
22
|
FILTER_VERSION = "2"
|
|
@@ -765,10 +766,9 @@ class _Parser:
|
|
|
765
766
|
and not isinstance(expression, RuntimeFilter)
|
|
766
767
|
and not _REPLAYING_ACCEPTED_STATE.get()
|
|
767
768
|
):
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
)
|
|
769
|
+
# empty_batch, not pa.array([], type=...): that has no builder
|
|
770
|
+
# for union or extension types.
|
|
771
|
+
empty = empty_batch(self.output_schema)
|
|
772
772
|
try:
|
|
773
773
|
_evaluate_expression(expression, empty, self.context)
|
|
774
774
|
except Exception as exc:
|