Skip to content
Query.Farm
Talk with Us

vgi.client

Module overview

VGI client package for communicating with VGI workers.

This package provides:

  • Client: A class for programmatic interaction with VGI workers, including both function invocation and catalog operations
  • ClientError: Exception raised by Client function operations
  • CatalogClientMixin: Mixin class providing catalog operations
  • AggregateClientMixin: Mixin class providing aggregate invocation
  • AggregateSession / AggregateStreamingSession: Handles on a bound aggregate
  • OutputWriter: Helper for writing output in various formats
  • main: CLI entry point

Usage (API):

from vgi.client import Client, ClientError
from vgi.arguments import Arguments
with Client("./my_worker.py") as client:
for batch in client.table_in_out_function(
function_name="echo",
arguments=Arguments(positional=[], named={}),
input=input_batches,
):
process(batch)

Usage (Catalog API):

from vgi.client import Client
client = Client("./my_worker")
result = client.catalog_attach(
name="my_catalog", options={}, data_version_spec=None, implementation_version=None
)

Usage (CLI):

vgi-client --input data.parquet --function echo
vgi-client --input data.parquet --function sum_all_columns
source

Description

Mixin adding aggregate-function invocation to the VGI Client.

Every aggregate RPC is unary and runs on the client’s primary worker connection, so start() (or the context-manager protocol) must have run first — same requirement as scalar_function / table_function.

Methods

source
aggregate_session(
*,
function_name: str,
schema_name: str,
input_schema: pa.Schema | None = None,
arguments: Arguments | None = None,
settings: dict[str, Any] | None = None,
secrets: dict[str, Any] | None = None,
) -> Iterator[AggregateSession]

Bind an aggregate and yield a session over its raw RPCs.

The session is destroyed on exit, including on exception, so worker-side group state never outlives the with block.

Parameters

function_name
Name of the aggregate to bind.
schema_name
Catalog schema that declares the function. A name is unique only within a schema, so this is what identifies the implementation.
input_schema
Schema of the aggregate’s value columns, in declaration order — without the __vgi_group_id column, which the driver prepends per call. None for a nullary aggregate.
arguments
Constant arguments (ConstParam values) for the aggregate. Defaults to empty.
settings
Optional DuckDB-style settings visible to the function.
secrets
Optional pre-resolved secret values. Two-phase secret resolution is not available on the aggregate path — the worker rejects a bind that requests scoped secrets.

Raises

ClientError
If the client is not started or the bind fails.
source
aggregate_bind(
*,
function_name: str,
schema_name: str,
input_schema: pa.Schema | None = None,
arguments: Arguments | None = None,
settings: dict[str, Any] | None = None,
secrets: dict[str, Any] | None = None,
) -> AggregateSession

Bind an aggregate function without taking responsibility for teardown.

Prefer :meth:aggregate_session, which destroys the session for you. This entry point exists for callers that must own the lifetime explicitly (e.g. a proxy that hands the execution_id to another process); they must call :meth:AggregateSession.destroy themselves.

Parameters

function_name
Name of the aggregate to bind.
schema_name
Catalog schema that declares the function.
input_schema
Schema of the aggregate’s value columns, in declaration order, without the __vgi_group_id column. None for a nullary aggregate.
arguments
Constant arguments for the aggregate. Defaults to empty.
settings
Optional DuckDB-style settings visible to the function.
secrets
Optional pre-resolved secret values.

Returns

An AggregateSession carrying the worker’s execution_id and resolved output schema.

Raises

ClientError
If the client is not started or the bind fails.
source
aggregate_function(
*,
function_name: str,
schema_name: str,
input: Iterable[pa.RecordBatch] = (),
group_by: Sequence[str] = (),
arguments: Arguments | None = None,
settings: dict[str, Any] | None = None,
secrets: dict[str, Any] | None = None,
input_schema: pa.Schema | None = None,
finalize_chunk_size: int = DEFAULT_FINALIZE_CHUNK_SIZE,
) -> pa.RecordBatch

Run an aggregate over input and return one row per group.

This is the SELECT <group_by>, agg(...) FROM t GROUP BY <group_by> shape, driven client-side: group ids are allocated per distinct group_by key in first-seen order (DuckDB’s hash-aggregate order), every batch is pumped through aggregate_update, and the groups are finalized in chunks of finalize_chunk_size.

Every input column that is not named in group_by is passed to the aggregate as a value column, in the order it appears in the batch — so order your input columns to match the aggregate’s declared parameters.

Parameters

function_name
Name of the aggregate to invoke.
schema_name
Catalog schema that declares the function.
input
Input batches. All must share one schema. May be empty.
group_by
Column names to group on. Empty (the default) means a global aggregate, which returns exactly one row even for empty input — matching SQL’s SELECT agg(x) FROM empty_table.
arguments
Constant arguments for the aggregate. Defaults to empty.
settings
Optional settings visible to the function.
secrets
Optional pre-resolved secret values.
input_schema
Value-column schema to bind with when input yields no batches. Ignored once a first batch is seen. Supply it when binding a varargs aggregate over empty input, which otherwise fails its bind-time arity check.
finalize_chunk_size
Group ids per aggregate_finalize call.

Returns

A RecordBatch of the group_by columns followed by the aggregate’s output columns, one row per group, in group-id order.

Raises

ValueError
If a group_by column is missing from the input, or the input batches disagree on schema.
ClientError
If the client is not started or an RPC fails.
source
aggregate_streaming(
*,
function_name: str,
schema_name: str,
input_schema: pa.Schema,
partition_key_count: int,
order_key_count: int = 0,
arguments: Arguments | None = None,
settings: dict[str, Any] | None = None,
secrets: dict[str, Any] | None = None,
output_schema: pa.Schema | None = None,
) -> Iterator[AggregateStreamingSession]

Open a streaming-partitioned aggregate session and yield it.

The session is closed on exit, including on exception.

Parameters

function_name
Name of the aggregate to open.
schema_name
Catalog schema that declares the function.
input_schema
Schema of every chunk. Column order is fixed by the protocol: partition_key_count partition-key columns first, then order_key_count order-key columns, then the aggregate’s value columns.
partition_key_count
How many leading columns are partition keys.
order_key_count
How many columns after the partition keys are order keys. Informational to the worker.
arguments
Constant arguments for the aggregate. Defaults to empty.
settings
Optional settings visible to the function.
secrets
Optional pre-resolved secret values.
output_schema
The aggregate’s output schema. Resolved with a throwaway aggregate_bind over the value columns when omitted, which is what the DuckDB extension does.

Raises

ValueError
If the key counts exceed input_schema’s width.
ClientError
If the client is not started or an RPC fails.
source

Description

A bound aggregate execution — the raw RPC surface, one method per call.

Obtained from :meth:AggregateClientMixin.aggregate_session. Group ids are caller-allocated int64s; the worker keys its per-group state on them and never invents one. Reuse the same id across update calls to accumulate into one group, exactly as DuckDB reuses the id stamped on a state pointer.

Attributes

bytes

Worker-minted identifier for this aggregate execution. Scopes every piece of worker-side state, including window partitions.

pa.Schema

Schema the aggregate’s finalize produces (typically a single result column).

Methods

source
update(
*,
group_ids: Sequence[int] | pa.Array[Any],
batch: pa.RecordBatch | None = None,
) -> None

Accumulate one chunk of rows into per-group state.

Parameters

group_ids
One group id per row, parallel to batch’s rows.
batch
The aggregate’s value columns for those rows, in declaration order. None for a nullary aggregate (vgi_count()), where the row count is carried by group_ids alone.

Raises

ValueError
If batch and group_ids disagree on row count.
ClientError
If the RPC fails.
source
combine(
*,
source_group_ids: Sequence[int] | pa.Array[Any],
target_group_ids: Sequence[int] | pa.Array[Any],
) -> None

Merge each source group’s state into the paired target group.

Mirrors DuckDB’s combine step, where thread-local hash tables are merged into the global one.

Parameters

source_group_ids
Groups whose state is merged from. Left as-is.
target_group_ids
Groups merged into, parallel to source_group_ids and the same length.

Raises

ValueError
If the two sequences differ in length.
ClientError
If the RPC fails.
source
finalize(group_ids: Sequence[int] | pa.Array[Any]) -> pa.RecordBatch

Produce the result row for each of group_ids, in that order.

A group id that was never updated finalizes to whatever the function returns for an absent state — NULL for SUM/AVG, 0 for COUNT.

Parameters

group_ids
The groups to produce results for, in output order.

Returns

A RecordBatch with :attr:output_schema and one row per group id.

Raises

ClientError
If the RPC fails.
source
destroy() -> None

Release every piece of worker state for this execution (best-effort).

Called for you when :meth:AggregateClientMixin.aggregate_session exits. Like the C++ destructor it never raises: a failure here means leaked worker rows, not a wrong answer, and the worker reclaims them on execution timeout anyway.

source
window_init(
*,
partition_id: int,
partition: pa.RecordBatch,
filter_mask: Sequence[bool] | pa.BooleanArray | None = None,
frame_stats: tuple[tuple[int, int], tuple[int, int]] | None = None,
all_valid: Sequence[bool] | None = None,
) -> None

Ship one window partition to the worker so it can be queried by frame.

Parameters

partition_id
Caller-allocated id for this partition, scoped to the session’s execution_id.
partition
Every input column, every row of the partition, in window order.
filter_mask
Per-row mask from a FILTER (WHERE …) clause. None (the default) means no filter.
frame_stats
DuckDB’s per-partition frame bounds, as ((begin_delta, end_delta), (begin_delta, end_delta)). None sends zeros, which is what a caller with no frame statistics should do.
all_valid
One flag per input column, True when the column has no nulls. None means “assume all valid”.

Raises

ClientError
If the RPC fails.
source
window(
*,
partition_id: int,
rid: int,
frames: Frames,
) -> pa.RecordBatch

Compute the aggregate for one output row of a window partition.

Parameters

partition_id
The partition previously shipped by :meth:window_init.
rid
Row index within the partition of the output row being computed.
frames
The row’s subframes as (begin, end) half-open offsets into the partition. One entry normally; two or three for EXCLUDE TIES / EXCLUDE GROUP.

Returns

A one-row RecordBatch with :attr:output_schema.

Raises

ClientError
If the RPC fails.
source
window_batch(
*,
partition_id: int,
row_idx: int,
frames: Sequence[Frames],
) -> pa.RecordBatch

Compute len(frames) consecutive window output rows in one RPC.

Parameters

partition_id
The partition previously shipped by :meth:window_init.
row_idx
Partition-relative index of the first output row.
frames
One subframe list per output row, in row order.

Returns

A RecordBatch with :attr:output_schema and len(frames) rows.

Raises

ClientError
If the RPC fails.
source
window_destroy(partition_id: int) -> None

Evict one window partition from worker storage (best-effort).

source

Description

An open streaming-partitioned aggregate session.

Obtained from :meth:AggregateClientMixin.aggregate_streaming. Each :meth:chunk returns one output row per input row — the aggregate’s value at that row’s position within its partition.

Attributes

bytes

Worker-minted identifier for this streaming session.

pa.Schema

Schema of every batch :meth:chunk returns.

Methods

source
chunk(batch: pa.RecordBatch) -> pa.RecordBatch

Process one input chunk and return its per-row output.

Parameters

batch
One chunk, matching the input_schema agreed at open time: partition-key columns first, then order-key columns, then the aggregate’s value columns.

Returns

A RecordBatch with :attr:output_schema and one row per input row.

Raises

ClientError
If the RPC fails.
source
close() -> None

End the session and free its worker-side state (best-effort).

source

Description

Mixin that adds catalog operations to a VGI Client.

Catalog methods spawn ephemeral connections under the hood — for subprocess transport a pooled subprocess worker; for HTTP transport a short-lived http_connect session reusing the Client’s shared httpx2.Client (bearer token, headers); for TCP transport a short-lived tcp_connect session. Browsing catalogs over HTTP is the canonical non-DuckDB use case this mixin supports.

Other attributes expected from Client: _transport (subprocess, http, tcp, or launch), _base_url (HTTP base URL), _tcp_host / _tcp_port (TCP endpoint), _launch_argv / _launch_idle_timeout / _launch_state_dir / _launch_socket_path (launcher worker identity + lifecycle), and _get_or_create_httpx_client() (shared HTTP client factory).

Attributes

str | Sequence[str]

Worker command used for subprocess transport — a string to be split, or an argv sequence taken as-is.

Methods

source
catalogs() -> list[CatalogInfo]

Get list of catalog discovery records from the worker.

Returns

List of CatalogInfo records carrying per-catalog name, implementation_version, and data_version_spec.
source
catalog_attach(
*,
name: str,
options: dict[str, Any] | None = None,
data_version_spec: str | None,
implementation_version: str | None,
) -> CatalogAttachResult

Attach to a catalog.

Parameters

name
The catalog name to attach to.
options
Optional dictionary of catalog-specific options.
data_version_spec
Semver constraint for the catalog’s data version (None = unconstrained — worker picks).
implementation_version
Semver constraint for the worker’s implementation version (None = unconstrained).

Returns

CatalogAttachResult with attach_opaque_data, catalog capabilities, and the resolved concrete versions the worker picked.
source
catalog_detach(*, attach_opaque_data: AttachOpaqueData) -> None

Detach from a catalog.

Parameters

attach_opaque_data
The attachment ID from catalog_attach.
source
catalog_create(
*,
name: str,
on_conflict: OnConflict = OnConflict.ERROR,
options: dict[str, Any] | None = None,
) -> None

Create a new catalog.

Parameters

name
The name for the new catalog.
on_conflict
Behavior if catalog already exists.
options
Optional dictionary of catalog-specific options.
source
catalog_drop(*, name: str) -> None

Drop a catalog.

Parameters

name
The name of the catalog to drop.
source
catalog_version(
*,
attach_opaque_data: AttachOpaqueData,
transaction_opaque_data: TransactionOpaqueData | None = None,
) -> int

Get the current catalog version.

Parameters

attach_opaque_data
The attachment ID from catalog_attach.
transaction_opaque_data
Optional transaction ID for transactional reads.

Returns

The current catalog version number, or 0 if empty.
source
catalog_transaction_begin(
*,
attach_opaque_data: AttachOpaqueData,
) -> TransactionOpaqueData | None

Begin a new transaction.

Parameters

attach_opaque_data
The attachment ID from catalog_attach.

Returns

TransactionOpaqueData for the new transaction, or None if transactions are not supported by this catalog.
source
catalog_transaction_commit(
*,
attach_opaque_data: AttachOpaqueData,
transaction_opaque_data: TransactionOpaqueData,
) -> None

Commit a transaction.

Parameters

attach_opaque_data
The attachment ID from catalog_attach.
transaction_opaque_data
The transaction ID to commit.
source
catalog_transaction_rollback(
*,
attach_opaque_data: AttachOpaqueData,
transaction_opaque_data: TransactionOpaqueData,
) -> None

Rollback a transaction.

Parameters

attach_opaque_data
The attachment ID from catalog_attach.
transaction_opaque_data
The transaction ID to rollback.
source
schemas(
*,
attach_opaque_data: AttachOpaqueData,
transaction_opaque_data: TransactionOpaqueData | None = None,
) -> list[SchemaInfo]

List schemas in the catalog.

Parameters

attach_opaque_data
The attachment ID from catalog_attach.
transaction_opaque_data
Optional transaction ID for transactional reads.

Returns

List of SchemaInfo for each schema in the catalog.
source
schema_get(
*,
attach_opaque_data: AttachOpaqueData,
transaction_opaque_data: TransactionOpaqueData | None = None,
name: str,
) -> SchemaInfo | None

Get information about a schema.

Parameters

attach_opaque_data
The attachment ID from catalog_attach.
transaction_opaque_data
Optional transaction ID for transactional reads.
name
The schema name.

Returns

SchemaInfo for the schema, or None if not found.
source
schema_create(
*,
attach_opaque_data: AttachOpaqueData,
transaction_opaque_data: TransactionOpaqueData | None = None,
name: str,
comment: str | None = None,
tags: dict[str, str] | None = None,
) -> None

Create a new schema.

Parameters

attach_opaque_data
The attachment ID from catalog_attach.
transaction_opaque_data
Optional transaction ID.
name
The name for the new schema.
comment
Optional description of the schema.
tags
Optional key-value tags for the schema.
source
schema_drop(
*,
attach_opaque_data: AttachOpaqueData,
transaction_opaque_data: TransactionOpaqueData | None = None,
name: str,
ignore_not_found: bool = False,
cascade: bool = False,
) -> None

Drop a schema.

Parameters

attach_opaque_data
The attachment ID from catalog_attach.
transaction_opaque_data
Optional transaction ID.
name
The name of the schema to drop.
ignore_not_found
If True, don’t error if schema doesn’t exist.
cascade
If True, drop all contained tables and views.
source
schema_contents(
*,
attach_opaque_data: AttachOpaqueData,
transaction_opaque_data: TransactionOpaqueData | None = None,
name: str,
type: SchemaObjectType,
) -> Sequence[TableInfo | ViewInfo | FunctionInfo | MacroInfo | IndexInfo]

List contents of a schema (tables, views, functions, macros, indexes).

Parameters

attach_opaque_data
The attachment ID from catalog_attach.
transaction_opaque_data
Optional transaction ID for transactional reads.
name
The schema name.
type
The type of objects to return. Must be a SchemaObjectType enum:
SchemaObjectType.TABLE: Return only tables
SchemaObjectType.VIEW: Return only views
SchemaObjectType.SCALAR_FUNCTION: Return only scalar functions
SchemaObjectType.TABLE_FUNCTION: Return only table functions
SchemaObjectType.SCALAR_MACRO: Return only scalar macros
SchemaObjectType.TABLE_MACRO: Return only table macros
SchemaObjectType.INDEX: Return only indexes

Returns

List of TableInfo, ViewInfo, FunctionInfo, MacroInfo, or IndexInfo depending on the type.
source
copy_formats(
*,
attach_opaque_data: AttachOpaqueData,
transaction_opaque_data: TransactionOpaqueData | None = None,
) -> Sequence[CopyFromFormatInfo]

List the custom COPY formats this catalog advertises.

Catalog-level rather than schema-scoped, matching the C++ extension, which registers one DuckDB CopyFunction per entry at ATTACH. Each entry’s handler is the worker function name to pass to Client.copy_from / Client.copy_to, and direction says which of the two it serves ("from", "to", or "both").

Parameters

attach_opaque_data
The attachment ID from catalog_attach.
transaction_opaque_data
Optional transaction ID for transactional reads.

Returns

List of CopyFromFormatInfo. Empty for a catalog with no custom formats, and also for a worker that predates the RPC — the method is additive, so MethodNotImplementedError is read as “advertises none”, exactly as the extension reads it during ATTACH.
source
table_get(
*,
attach_opaque_data: AttachOpaqueData,
transaction_opaque_data: TransactionOpaqueData | None = None,
schema_name: str,
name: str,
at_unit: str | None = None,
at_value: str | None = None,
) -> TableInfo | None

Get information about a table.

Parameters

attach_opaque_data
The attachment ID from catalog_attach.
transaction_opaque_data
Optional transaction ID for transactional reads.
schema_name
The schema containing the table.
name
The table name.
at_unit
Optional time travel unit (e.g. ‘timestamp’, ‘version’) — the schema at a past point may differ from the live one. None for the live schema.
at_value
Optional time travel value, paired with at_unit.

Returns

TableInfo for the table, or None if not found.
source
table_column_statistics(
*,
attach_opaque_data: AttachOpaqueData,
transaction_opaque_data: TransactionOpaqueData | None = None,
schema_name: str,
name: str,
) -> list[ColumnStatistics]

Fetch a table’s column statistics, decoded.

Workers may inline statistics on TableInfo (see TableInfo.column_statistics) or serve them lazily through this per-table call, which is what TableInfo.supports_column_statistics advertises. Prefer the inlined copy when present and fall back to this.

Parameters

attach_opaque_data
The attachment ID from catalog_attach.
transaction_opaque_data
Optional transaction ID for transactional reads.
schema_name
The schema containing the table.
name
The table name.

Returns

Per-column statistics, or an empty list when the worker has none for this table.
source
table_create(
*,
attach_opaque_data: AttachOpaqueData,
transaction_opaque_data: TransactionOpaqueData | None = None,
schema_name: str,
name: str,
columns: SerializedSchema,
on_conflict: OnConflict = OnConflict.ERROR,
not_null_constraints: list[int] | None = None,
unique_constraints: list[list[int]] | None = None,
check_constraints: list[str] | None = None,
) -> None

Create a new table.

Parameters

attach_opaque_data
The attachment ID from catalog_attach.
transaction_opaque_data
Optional transaction ID.
schema_name
The schema to create the table in.
name
The name for the new table.
columns
Serialized PyArrow schema for the table columns.
on_conflict
Behavior if table already exists.
not_null_constraints
Column indices that must not be null.
unique_constraints
Lists of column indices for unique constraints.
check_constraints
SQL expressions for check constraints.
source
table_drop(
*,
attach_opaque_data: AttachOpaqueData,
transaction_opaque_data: TransactionOpaqueData | None = None,
schema_name: str,
name: str,
ignore_not_found: bool = False,
cascade: bool = False,
) -> None

Drop a table.

Parameters

attach_opaque_data
The attachment ID from catalog_attach.
transaction_opaque_data
Optional transaction ID.
schema_name
The schema containing the table.
name
The name of the table to drop.
ignore_not_found
If True, don’t error if table doesn’t exist.
cascade
If True, also drop dependent objects.
source
table_scan_function_get(
*,
attach_opaque_data: AttachOpaqueData,
transaction_opaque_data: TransactionOpaqueData | None = None,
schema_name: str,
name: str,
at_unit: str | None = None,
at_value: str | None = None,
) -> ScanFunctionResult

Get the scan function for a table.

Returns a ScanFunctionResult that tells the VGI DuckDB extension which DuckDB function to call to obtain the table data.

Parameters

attach_opaque_data
The attachment ID from catalog_attach.
transaction_opaque_data
Optional transaction ID for transactional reads.
schema_name
The schema containing the table.
name
The table name.
at_unit
Optional time travel unit (e.g., ‘timestamp’, ‘version’).
at_value
Optional time travel value.

Returns

ScanFunctionResult with function_name, arguments, and extensions.

Raises

CatalogClientError
If table_scan_function_get returned no result.
source
table_scan_branches_get(
*,
attach_opaque_data: AttachOpaqueData,
transaction_opaque_data: TransactionOpaqueData | None = None,
schema_name: str,
name: str,
at_unit: str | None = None,
at_value: str | None = None,
) -> ScanBranchesResult

Get the list of scan branches for a (possibly multi-source) table.

Multi-branch tables compose a logical scan from N physical sources (canonical case: Kafka hot tier + Iceberg cold tier). The VGI DuckDB extension’s optimizer rewrites the placeholder scan into LogicalSetOperation(UNION_ALL, ...), one arm per branch — a caller of this method is expected to do the equivalent (e.g. pl.concat over one scan per branch in vgi-polars).

Falls back to :meth:table_scan_function_get, wrapped as a single-branch ScanBranchesResult, when the worker raises MethodNotImplementedError for the additive catalog_table_scan_ branches_get RPC — mirroring exactly how the C++ extension reads that same fallback, so a worker written before this RPC existed still works, just as a one-branch table.

Parameters

attach_opaque_data
The attachment ID from catalog_attach.
transaction_opaque_data
Optional transaction ID for transactional reads.
schema_name
The schema containing the table.
name
The table name.
at_unit
Optional time travel unit (e.g., ‘timestamp’, ‘version’). The C++ extension refuses AT(…) on tables with more than one branch at bind time — a worker returning multiple branches should expect this to always be None.
at_value
Optional time travel value.

Returns

ScanBranchesResult with one or more ScanBranch entries plus the union of required DuckDB extensions across all branches.
source
table_comment_set(
*,
attach_opaque_data: AttachOpaqueData,
transaction_opaque_data: TransactionOpaqueData | None = None,
schema_name: str,
name: str,
comment: str | None,
ignore_not_found: bool = False,
) -> None

Set or clear the comment on a table.

Parameters

attach_opaque_data
The attachment ID from catalog_attach.
transaction_opaque_data
Optional transaction ID.
schema_name
The schema containing the table.
name
The table name.
comment
The new comment, or None to clear.
ignore_not_found
If True, don’t error if table doesn’t exist.
source
table_rename(
*,
attach_opaque_data: AttachOpaqueData,
transaction_opaque_data: TransactionOpaqueData | None = None,
schema_name: str,
name: str,
new_name: str,
ignore_not_found: bool = False,
) -> None

Rename a table.

Parameters

attach_opaque_data
The attachment ID from catalog_attach.
transaction_opaque_data
Optional transaction ID.
schema_name
The schema containing the table.
name
The current name of the table.
new_name
The new name for the table.
ignore_not_found
If True, don’t error if table doesn’t exist.
source
table_column_add(
*,
attach_opaque_data: AttachOpaqueData,
transaction_opaque_data: TransactionOpaqueData | None = None,
schema_name: str,
name: str,
column_definition: SerializedSchema,
ignore_not_found: bool = False,
if_column_not_exists: bool = False,
) -> None

Add a new column to a table.

Parameters

attach_opaque_data
The attachment ID from catalog_attach.
transaction_opaque_data
Optional transaction ID.
schema_name
The schema containing the table.
name
The table name.
column_definition
Serialized schema with single field for the new column.
ignore_not_found
If True, don’t error if table doesn’t exist.
if_column_not_exists
If True, don’t error if column already exists.
source
table_column_drop(
*,
attach_opaque_data: AttachOpaqueData,
transaction_opaque_data: TransactionOpaqueData | None = None,
schema_name: str,
name: str,
column_name: str,
ignore_not_found: bool = False,
if_column_exists: bool = False,
cascade: bool = False,
) -> None

Drop a column from a table.

Parameters

attach_opaque_data
The attachment ID from catalog_attach.
transaction_opaque_data
Optional transaction ID.
schema_name
The schema containing the table.
name
The table name.
column_name
The name of the column to drop.
ignore_not_found
If True, don’t error if table doesn’t exist.
if_column_exists
If True, don’t error if column doesn’t exist.
cascade
If True, drop dependent constraints.
source
table_column_rename(
*,
attach_opaque_data: AttachOpaqueData,
transaction_opaque_data: TransactionOpaqueData | None = None,
schema_name: str,
name: str,
column_name: str,
new_column_name: str,
ignore_not_found: bool = False,
) -> None

Rename a column.

Parameters

attach_opaque_data
The attachment ID from catalog_attach.
transaction_opaque_data
Optional transaction ID.
schema_name
The schema containing the table.
name
The table name.
column_name
The current name of the column.
new_column_name
The new name for the column.
ignore_not_found
If True, don’t error if table doesn’t exist.
source
table_column_default_set(
*,
attach_opaque_data: AttachOpaqueData,
transaction_opaque_data: TransactionOpaqueData | None = None,
schema_name: str,
name: str,
column_name: str,
expression: SqlExpression,
ignore_not_found: bool = False,
) -> None

Set the default value expression for a column.

Parameters

attach_opaque_data
The attachment ID from catalog_attach.
transaction_opaque_data
Optional transaction ID.
schema_name
The schema containing the table.
name
The table name.
column_name
The column to set the default for.
expression
The SQL expression for the default value.
ignore_not_found
If True, don’t error if table doesn’t exist.
source
table_column_default_drop(
*,
attach_opaque_data: AttachOpaqueData,
transaction_opaque_data: TransactionOpaqueData | None = None,
schema_name: str,
name: str,
column_name: str,
ignore_not_found: bool = False,
) -> None

Remove the default value from a column.

Parameters

attach_opaque_data
The attachment ID from catalog_attach.
transaction_opaque_data
Optional transaction ID.
schema_name
The schema containing the table.
name
The table name.
column_name
The column to remove the default from.
ignore_not_found
If True, don’t error if table doesn’t exist.
source
table_column_type_change(
*,
attach_opaque_data: AttachOpaqueData,
transaction_opaque_data: TransactionOpaqueData | None = None,
schema_name: str,
name: str,
column_definition: SerializedSchema,
expression: SqlExpression | None = None,
ignore_not_found: bool = False,
) -> None

Change the type of a column.

Parameters

attach_opaque_data
The attachment ID from catalog_attach.
transaction_opaque_data
Optional transaction ID.
schema_name
The schema containing the table.
name
The table name.
column_definition
Serialized schema with single field defining the new type. Column name is taken from the schema field name.
expression
Optional SQL expression to convert existing values.
ignore_not_found
If True, don’t error if table doesn’t exist.
source
table_not_null_drop(
*,
attach_opaque_data: AttachOpaqueData,
transaction_opaque_data: TransactionOpaqueData | None = None,
schema_name: str,
name: str,
column_name: str,
ignore_not_found: bool = False,
) -> None

Remove NOT NULL constraint from a column.

Parameters

attach_opaque_data
The attachment ID from catalog_attach.
transaction_opaque_data
Optional transaction ID.
schema_name
The schema containing the table.
name
The table name.
column_name
The column to remove NOT NULL from.
ignore_not_found
If True, don’t error if table doesn’t exist.
source
table_not_null_set(
*,
attach_opaque_data: AttachOpaqueData,
transaction_opaque_data: TransactionOpaqueData | None = None,
schema_name: str,
name: str,
column_name: str,
ignore_not_found: bool = False,
) -> None

Add NOT NULL constraint to a column.

Parameters

attach_opaque_data
The attachment ID from catalog_attach.
transaction_opaque_data
Optional transaction ID.
schema_name
The schema containing the table.
name
The table name.
column_name
The column to add NOT NULL to.
ignore_not_found
If True, don’t error if table doesn’t exist.
source
view_get(
*,
attach_opaque_data: AttachOpaqueData,
transaction_opaque_data: TransactionOpaqueData | None = None,
schema_name: str,
name: str,
) -> ViewInfo | None

Get information about a view.

Parameters

attach_opaque_data
The attachment ID from catalog_attach.
transaction_opaque_data
Optional transaction ID for transactional reads.
schema_name
The schema containing the view.
name
The view name.

Returns

ViewInfo for the view, or None if not found.
source
view_create(
*,
attach_opaque_data: AttachOpaqueData,
transaction_opaque_data: TransactionOpaqueData | None = None,
schema_name: str,
name: str,
definition: str,
on_conflict: OnConflict = OnConflict.ERROR,
) -> None

Create a new view.

Parameters

attach_opaque_data
The attachment ID from catalog_attach.
transaction_opaque_data
Optional transaction ID.
schema_name
The schema to create the view in.
name
The name for the new view.
definition
The SQL SELECT statement defining the view.
on_conflict
Behavior if view already exists.
source
view_drop(
*,
attach_opaque_data: AttachOpaqueData,
transaction_opaque_data: TransactionOpaqueData | None = None,
schema_name: str,
name: str,
ignore_not_found: bool = False,
cascade: bool = False,
) -> None

Drop a view.

Parameters

attach_opaque_data
The attachment ID from catalog_attach.
transaction_opaque_data
Optional transaction ID.
schema_name
The schema containing the view.
name
The name of the view to drop.
ignore_not_found
If True, don’t error if view doesn’t exist.
cascade
If True, also drop dependent objects.
source
view_rename(
*,
attach_opaque_data: AttachOpaqueData,
transaction_opaque_data: TransactionOpaqueData | None = None,
schema_name: str,
name: str,
new_name: str,
ignore_not_found: bool = False,
) -> None

Rename a view.

Parameters

attach_opaque_data
The attachment ID from catalog_attach.
transaction_opaque_data
Optional transaction ID.
schema_name
The schema containing the view.
name
The current name of the view.
new_name
The new name for the view.
ignore_not_found
If True, don’t error if view doesn’t exist.
source
view_comment_set(
*,
attach_opaque_data: AttachOpaqueData,
transaction_opaque_data: TransactionOpaqueData | None = None,
schema_name: str,
name: str,
comment: str | None,
ignore_not_found: bool = False,
) -> None

Set or clear the comment on a view.

Parameters

attach_opaque_data
The attachment ID from catalog_attach.
transaction_opaque_data
Optional transaction ID.
schema_name
The schema containing the view.
name
The view name.
comment
The new comment, or None to clear.
ignore_not_found
If True, don’t error if view doesn’t exist.
source
macro_get(
*,
attach_opaque_data: AttachOpaqueData,
transaction_opaque_data: TransactionOpaqueData | None = None,
schema_name: str,
name: str,
) -> MacroInfo | None

Get information about a macro.

Parameters

attach_opaque_data
The attachment ID from catalog_attach.
transaction_opaque_data
Optional transaction ID for transactional reads.
schema_name
The schema containing the macro.
name
The macro name.

Returns

MacroInfo for the macro, or None if not found.
source
macro_create(
*,
attach_opaque_data: AttachOpaqueData,
transaction_opaque_data: TransactionOpaqueData | None = None,
schema_name: str,
name: str,
macro_type: MacroType,
parameters: list[str],
definition: str,
on_conflict: OnConflict = OnConflict.ERROR,
parameter_default_values: pa.RecordBatch | None = None,
arguments_schema: pa.Schema | None = None,
) -> None

Create a new macro.

Parameters

attach_opaque_data
The attachment ID from catalog_attach.
transaction_opaque_data
Optional transaction ID.
schema_name
The schema to create the macro in.
name
The name for the new macro.
macro_type
Whether this is a scalar or table macro.
parameters
Ordered list of parameter names.
definition
SQL expression (scalar) or query (table).
on_conflict
Behavior if macro already exists.
parameter_default_values
One-row RecordBatch with typed defaults.
arguments_schema
Optional Arrow schema (one nullable field per parameter, in parameters order) carrying per-parameter descriptions via the vgi_doc field metadata key. Build with vgi.argument_spec.macro_arguments_schema.
source
macro_drop(
*,
attach_opaque_data: AttachOpaqueData,
transaction_opaque_data: TransactionOpaqueData | None = None,
schema_name: str,
name: str,
ignore_not_found: bool = False,
) -> None

Drop a macro.

Parameters

attach_opaque_data
The attachment ID from catalog_attach.
transaction_opaque_data
Optional transaction ID.
schema_name
The schema containing the macro.
name
The name of the macro to drop.
ignore_not_found
If True, don’t error if macro doesn’t exist.
source

Bases: CatalogClientMixin, AggregateClientMixin

Description

Canonical VGI client — HTTP is the path other-language ports mirror.

Two transports:

  • HTTP (Client.from_http(base_url, bearer_token=...)). The canonical non-DuckDB path. Uses vgi_rpc.http.http_connect under the hood; transparently resolves pointer batches returned by workers that externalize large outputs (demo storage, S3). Transparently externalizes large input batches when the server advertises upload-URL support.
  • Subprocess (Client(server_path)). Python-only convenience for local workers. Uses shell subprocesses + a WorkerPool for reuse. Ports don’t need to mirror this.

Catalog operations (catalogs(), schema_contents(), etc.) are provided by CatalogClientMixin and don’t require start(). They open a short-lived connection per call (HTTP) or borrow a pooled subprocess worker.

Aggregate invocation (aggregate_function, aggregate_session, aggregate_streaming) comes from AggregateClientMixin. Custom COPY formats reuse the table-function and buffered drivers with a COPY context attached — see copy_from / copy_to, and copy_formats() for discovery.

Function invocation (scalar_function, table_function, table_in_out_function, aggregate_function) requires start() — typically via the context-manager protocol:

with Client.from_http("http://host:port", bearer_token="...") as c:
for batch in c.table_function(function_name="sequence", ...):
...

Attributes

float

Seconds to wait for a worker thread to join during shutdown.

float

Seconds to wait for a worker process to exit during shutdown before killing it. A hang guard — teardown never blocks past this, and never raises TimeoutExpired at the caller.

float

Seconds to wait for a worker’s stderr to finish draining before an error message is built from it.

str | Sequence[str]

Subprocess-only. The VGI worker command. A string is split with shlex.split; pass a sequence to give the argv exactly, which is what you want for arguments carrying spaces or quotes ([sys.executable, "-c", script]). No shell is involved either way, so shell syntax — pipes, redirection, VAR=value prefixes, ~ expansion — is not interpreted.

Subprocess-only. If True, worker stderr is passed through to the parent process’s stderr in real-time.

bool

Whether this client’s transport can drive :meth:table_scan_resumable.

True only for HTTP, whose producer streams round-trip state in continuation tokens. The pipe/subprocess transport holds a live stream with no serializable resume point.

Methods

source
from_http(
base_url: str,
*,
bearer_token: str | None = None,
oauth: bool = False,
oauth_refresh_token: str | None = None,
oauth_flow: Literal[‘auto’, ‘device_code’, ‘pkce’] = ‘auto’,
oauth_timeout_seconds: float = 120.0,
oauth_prompt: Literal[‘none’, ‘login’, ‘select_account’, ‘consent’] = ‘none’,
httpx_client: Any | None = None,
external_location: Any | None = None,
worker_limit: int | None = None,
attach_opaque_data: bytes | None = None,
) -> Client

Create a Client bound to a remote HTTP VGI worker.

Canonical entry point for non-DuckDB callers (e.g. a TypeScript port browsing catalog contents). Subprocess-specific kwargs are not accepted; pool/stderr semantics do not apply. See Client.__init__ for what oauth/oauth_refresh_token/oauth_flow do.

source
from_tcp(
host: str,
port: int,
*,
external_location: Any | None = None,
worker_limit: int | None = None,
attach_opaque_data: bytes | None = None,
) -> Client

Create a Client bound to a running TCP VGI worker.

Connects via vgi_rpc.rpc.tcp_connect (raw Arrow-IPC framing). The framing carries no authentication or encryption — only connect to trusted endpoints on loopback or a trusted network; use Client.from_http(...) for untrusted networks. Spin up a matching worker with vgi-fixture-worker --tcp [HOST:]PORT.

source
from_launch(
worker_argv: Sequence[str],
*,
idle_timeout: float = 300.0,
state_dir: str | None = None,
socket_path: str | None = None,
external_location: Any | None = None,
worker_limit: int | None = None,
attach_opaque_data: bytes | None = None,
) -> Client

Create a Client bound to a launcher-managed warm worker.

Spawns (or reuses) a worker process serving over an AF_UNIX socket via vgi_rpc.launcher — every client across the machine pointing at the same worker_argv shares one warm worker, coordinated by a per-command-hash flock, and the worker self-terminates after idle_timeout idle seconds with zero connected clients. This is the Python client-side counterpart to the VGI DuckDB extension’s launch:<argv> LOCATION scheme. Requires the vgi-python[launch] extra.

Parameters

worker_argv
The worker command and arguments, as an argv sequence — not a shell string, and not split with shlex.split the way server_path is for transport=“subprocess”.
idle_timeout
Shared worker self-shutdown after this many idle seconds.
state_dir
Override the launcher’s default per-user state directory (lockfiles + sockets).
socket_path
Explicit socket path, skipping the hash-derived default — every caller passing the same explicit path shares that worker regardless of worker_argv differences.
external_location
Optional ExternalLocationConfig — see the constructor’s docstring.
worker_limit
Maximum number of parallel worker connections.
attach_opaque_data
Optional unique identifier for the DuckDB database attachment.

Returns

A Client bound to the launcher-managed worker.
source
get_worker_stderr() -> str

Return all captured stderr from the worker processes.

Returns stderr output from the primary worker and all additional workers spawned for parallel processing. The output is accumulated in a shared buffer throughout the client’s lifetime.

This method is thread-safe and can be called while processing is ongoing, though the buffer may not yet contain all output until the workers have completed.

Note

This method only returns data when passthrough_stderr=False was set in the constructor. When passthrough_stderr=True, stderr goes directly to the parent process’s stderr and this method returns an empty string.

Returns

All captured stderr output as a UTF-8 decoded string. Invalid UTF-8 sequences are replaced with the Unicode replacement character.
source
oauth_identity() -> Any | None

Return the signed-in OAuth identity’s parsed id_token claims.

Returns None when this client isn’t using OAuth, or hasn’t completed a login yet (the identity is only known once a real exchange has happened — calling this before the first request never triggers one). See vgi_rpc.http.OAuthIdentity for the fields (sub/email/name/issuer/claims).

source
start() -> None

Start the primary worker subprocess.

Spawns the worker process using the server_path configured in init, sets up RPC transport, and creates a typed VgiProtocol proxy for method calls.

After this method returns, the client is ready to invoke functions via table_in_out_function(), table_function(), or scalar_function(). When using the context manager protocol (with statement), this method is called automatically.

The stderr buffer is cleared when start() is called, so any stderr from previous runs is discarded.

Raises

ClientError
If the client is already started (call stop() first), or if stdout/stderr pipes fail to be created.
source
stop(*, force: bool = False) -> int

Stop all worker subprocesses and clean up resources.

Terminates all workers in the following order:

  1. Stops all additional workers (spawned for parallel processing)
  2. Stops the primary worker
  3. Waits for all stderr drain threads to complete (with timeout)
  4. Resets all internal state

After this method returns, the client can be started again with start(). When using the context manager protocol (with statement), this method is called automatically on exit.

Cancelling an in-flight call

A graceful stop waits for a worker that is blocked inside a handler, so it cannot be used to abandon a scan that has overrun its budget. Pass force=True to SIGKILL direct subprocess workers first, which unblocks any thread waiting on their output immediately. Note this only applies to direct subprocess workers: a pooled worker is returned to its pool rather than owned by this client, so construct the client with pool=None when you need to be able to cancel it. HTTP and TCP workers are already prompt to close.

Parameters

force
Kill direct subprocess workers instead of shutting them down gracefully — see “Cancelling an in-flight call” above.

Returns

The exit code of the primary worker process. Returns 0 for normal termination, non-zero values indicate errors (a forced stop reports the signal exit code, -9 on POSIX). Exit codes from additional workers are logged but not returned.

Raises

ClientError
If the client was not started (call start() first).
source
server_capabilities() -> Any

Return the HTTP server’s advertised capabilities.

Only valid for HTTP-mode clients. The returned HttpServerCapabilities carries max_request_bytes, upload_url_support, and max_upload_bytes — the fields the client consults before deciding to externalize large input batches via upload URLs (see Phase 4 of the whimsical-mccarthy plan).

source
table_in_out_function(
*,
function_name: str,
schema_name: str,
input: Iterator[pa.RecordBatch],
arguments: Arguments | None = None,
bind_result_callback: Callable[[BindResponse], None] | None = None,
projection_ids: list[int] | None = None,
pushdown_filters: bytes | None = None,
join_keys: list[pa.RecordBatch] | None = None,
settings: dict[str, Any] | None = None,
transaction_opaque_data: bytes | None = None,
parent_row_callback: Callable[[list[int]], None] | None = None,
has_finalize: bool = True,
) -> Generator[pa.RecordBatch]

Invoke a table-in-out function on the worker and stream results.

For parallel processing (max_workers > 1), input batches are distributed round-robin across workers using dedicated threads. Output order may not match input order in parallel mode. Only the primary worker receives the FINALIZE phase and produces final aggregated output.

Parameters

function_name
Name of the function to invoke. Must exist in the worker’s registry.
schema_name
Name of the catalog schema that declares the function. Required — a worker may register one name in several schemas, so the (schema, name) pair is what identifies the implementation.
input
Iterator yielding input RecordBatches. Must yield at least one batch. The first batch’s schema is used to initialize the IPC stream. Raises ClientError if the iterator is empty.
arguments
Optional Arguments container with positional and named arguments to pass to the function. Defaults to empty Arguments().
bind_result_callback
Optional callback invoked with the BindResponse before processing begins.
projection_ids
Optional list of column indices for column projection.
pushdown_filters
Optional byte string containing filter predicates to push down to the function.
join_keys
Optional serialized join-key batches for semi-join pushdown — one single-column RecordBatch per join-key column, matched worker-side by column name. Same mechanism as :meth:table_function’s join_keys.
settings
Optional dictionary of settings/pragmas to pass to the function.
transaction_opaque_data
Optional unique identifier for the DuckDB transaction.
parent_row_callback
Optional callback invoked once per yielded output batch (before finalize), immediately before the yield, with that batch’s decoded vgi_rpc.parent_row provenance — parent_rows[i] is the 0-based index into the input batch that produced output row i. Passing this switches on provenance decoding: a batch with no vgi_rpc.parent_row metadata is only accepted when its row count matches the input batch’s (raising ClientError otherwise), since a worker changing row count without provenance is a worker bug for a function that opted into this contract. Intended for blended row-transform functions (RowTransformFunction, FunctionInfo.input_from_args); leave unset for ordinary table-in-out functions, which have no provenance concept and may legitimately change row count.
has_finalize
Whether this function declares a FINALIZE stage (FunctionInfo.has_finalize). Defaults to True — every caller before this parameter existed got a FINALIZE-phase init() unconditionally, so this preserves that exactly. Pass False for a function known to have no finalize (every blended RowTransformFunctionhas_finalize is always false for those, enforced at resolve_metadata()) to skip the FINALIZE init() entirely, not just send one expecting an empty reply. Confirmed load-bearing, not cosmetic: some worker SDKs (e.g. the TypeScript one) actively reject an unexpected FINALIZE init() for a function that never advertised has_finalize, rather than silently no-op’ing it — the C++ DuckDB extension avoids this the same way, by conditionally registering in_out_function_final at all.

Raises

ClientError
If the client is not started, input iterator is empty, input iterator yields non-RecordBatch objects, communication with the worker fails, or the worker returns an unexpected status or exception.
source
table_buffering_function(
*,
function_name: str,
schema_name: str,
input: Iterator[pa.RecordBatch],
arguments: Arguments | None = None,
bind_result_callback: Callable[[BindResponse], None] | None = None,
projection_ids: list[int] | None = None,
pushdown_filters: bytes | None = None,
settings: dict[str, Any] | None = None,
transaction_opaque_data: bytes | None = None,
copy_to: CopyToContext | None = None,
input_schema: pa.Schema | None = None,
) -> Generator[pa.RecordBatch]

Invoke a TableBufferingFunction (Sink+Source) and stream results.

This mirrors the C++ PhysicalVgiTableBufferingFunction operator rather than the streaming INPUT/FINALIZE path used by :meth:table_in_out_function. The sequence is:

  1. bindinit(phase=TABLE_BUFFERING) on the primary worker. The sink init persists init metadata to cold storage so any pool worker can serve subsequent process/combine RPCs; its stream carries no data, so it is closed immediately after the header.
  2. table_buffering_process (unary) per input batch — the worker sinks the batch and returns an opaque state_id.
  3. table_buffering_combine (unary) once at end-of-input — the worker hands all state_ids to user combine() and returns opaque finalize_state_ids (the source-side partition keys).
  4. init(phase=TABLE_BUFFERING_FINALIZE, finalize_state_id=...) per finalize key — a producer stream driving user finalize() per tick. Output batches are yielded in finalize-key order.
  5. table_buffering_destructor (unary, best-effort) for cleanup.

Unlike :meth:table_in_out_function this driver runs entirely on the primary worker connection (process/combine are unary RPCs); the worker buffers all input regardless, so the aggregate result is identical to the distributed C++ path.

Parameters

function_name
Name of the TableBufferingFunction to invoke.
schema_name
Name of the catalog schema that declares the function. Required — a worker may register one name in several schemas, so the (schema, name) pair is what identifies the implementation.
input
Iterator yielding input RecordBatches. May be empty — buffering aggregations still produce a result for zero rows.
arguments
Optional Arguments container. Defaults to empty.
bind_result_callback
Optional callback invoked with the BindResponse before processing begins.
projection_ids
Optional column indices for projection.
pushdown_filters
Optional serialized filter predicates.
settings
Optional settings/pragmas to pass to the function.
transaction_opaque_data
Optional DuckDB transaction identifier.
copy_to
Optional CopyToContext marking this sink as a COPY … TO write. A CopyToFunction returns no finalize keys, so the generator yields nothing. Prefer :meth:copy_to, which builds the context and drains for you.
input_schema
Schema to bind with instead of the first input batch’s. Needed when input may be empty and the function still depends on the source schema (a CopyToFunction writing a header row for an empty COPY).

Raises

ClientError
If the client is not started or any RPC fails.
source
bind(
*,
function_name: str,
schema_name: str,
arguments: Arguments | None = None,
function_type: FunctionType = FunctionType.TABLE,
settings: dict[str, Any] | None = None,
transaction_opaque_data: bytes | None = None,
) -> BindResponse

Resolve a function’s bind response without running init()/process().

Runs only the bind() RPC — no init(), no worker execution, no data produced. This is the schema-discovery primitive table_function()/ table_in_out_function()/scalar_function() lack a standalone version of: their own bind_result_callback only fires as a side effect of a generator’s first next(), which has already started init() and real execution by the time it runs. Prefer the catalog RPCs (Client.table_get, Client.schema_contents(type=TABLE_FUNCTION)) when a catalog attach is available — those are equally zero-execution and additionally expose pushdown-capability flags this method does not. Use this method for a bare (non-catalog) function name, where no attach exists to ask instead.

Parameters

function_name
Name of the function to bind. Must exist in the worker’s registry.
schema_name
Name of the catalog schema that declares the function. Required — a worker may register one name in several schemas, so the (schema, name) pair is what identifies the implementation.
arguments
Optional Arguments container with positional and named arguments to pass to the function. Defaults to empty Arguments().
function_type
Which kind of function to bind (FunctionType.TABLE, .SCALAR, or .TABLE_IN_OUT). Defaults to TABLE, the common case for schema discovery.
settings
Optional dictionary of settings/pragmas — some functions’ output schema depends on setting values (see Meta.required_settings).
transaction_opaque_data
Optional unique identifier for the DuckDB transaction.

Returns

BindResponse with output_schema and any opaque bind data — no batches, no worker state beyond the bind itself.

Raises

ClientError
If the client is not started, communication with the worker fails, or the worker returns an exception.
source
table_function_plan(
*,
function_name: str,
schema_name: str,
arguments: Arguments | None = None,
projection_ids: list[int] | None = None,
pushdown_filters: bytes | None = None,
join_keys: list[pa.RecordBatch] | None = None,
settings: dict[str, Any] | None = None,
transaction_opaque_data: bytes | None = None,
target_split_bytes: int | None = None,
min_splits: int | None = None,
max_splits_per_response: int | None = None,
cursor: bytes | None = None,
) -> PlanResponse

Plan a table-function scan into named, independently redeemable splits.

Runs bind() then on_plan() and returns the resulting PlanResponse, whose splits are each individually redeemable by :meth:table_function via its split_tokens argument — from this process or, since a split names work rather than describing it (“these three files at version 47”, not “rows 0-999 of whatever this returns now”), any other. Workers that don’t opt in via supports_splits (FunctionInfo.supports_splits) inherit a framework default (commonly one split for the whole scan) — check that flag first if the caller cares whether real parallelism/checkpointing is available versus a single degenerate split.

A response’s next_cursors is normally empty or one entry; more than one means the plan is paginated across parallel, disjoint enumeration branches — the caller is responsible for that disjointness (VGI itself does not verify it; see the “Split disjointness is a worker contract” note in the VGI extension’s own docs). For a single sequential caller, following next_cursors one at a time (via cursor=) and concatenating each response’s splits is always correct, whether the plan doled out one cursor or several.

Parameters

function_name
Name of the table function to plan.
schema_name
Name of the catalog schema that declares the function.
arguments
Optional Arguments container. Defaults to empty Arguments().
projection_ids
Optional list of column indices for projection — threaded into the plan so split sizing can account for it.
pushdown_filters
Optional byte string of filter predicates, same wire format as :meth:table_function’s.
join_keys
Optional serialized join-key batches for semi-join pushdown, threaded into split sizing/pruning the same way pushdown_filters is — same wire mechanism as :meth:table_function’s join_keys.
settings
Optional dictionary of settings/pragmas.
transaction_opaque_data
Optional transaction identifier.
target_split_bytes
Requested split size — the primary sizing lever; the client can’t see per-split cost and will treat returned splits as interchangeable units.
min_splits
Parallelism floor — ask for at least this many splits even for a small table, so a caller with idle readers has enough units to hand them.
max_splits_per_response
Pagination cap on this one response (distinct from min_splits, which is a sizing hint, not a pagination control).
cursor
Resume point from a previous response’s next_cursors, or None to start a fresh plan.

Returns

PlanResponse with splits (each an individually redeemable ScanSplit, carrying the token to pass back into table_function(split_tokens=…)) and next_cursors for pagination.

Raises

ClientError
If the client is not started, communication with the worker fails, or the worker returns an exception.
source
table_function(
*,
function_name: str,
schema_name: str,
arguments: Arguments | None = None,
bind_result_callback: Callable[[BindResponse], None] | None = None,
projection_ids: list[int] | None = None,
pushdown_filters: bytes | None = None,
join_keys: list[pa.RecordBatch] | None = None,
settings: dict[str, Any] | None = None,
transaction_opaque_data: bytes | None = None,
copy_from: CopyFromContext | None = None,
split_tokens: list[bytes] | None = None,
split_execution_id: bytes | None = None,
split_init_opaque_data: bytes | None = None,
batch_metadata_callback: Callable[[pa.KeyValueMetadata | None], None] | None = None,
at_unit: str | None = None,
at_value: str | None = None,
) -> Generator[pa.RecordBatch]

Invoke a table function (source function) and stream output batches.

Table functions generate output batches without receiving input data. They are useful for data sources, generators, or functions that produce results based solely on their arguments.

For parallel processing (max_workers > 1), output is read from all workers concurrently using threads. Output order is non-deterministic. This is unrelated to (and mutually exclusive in effect with) redeeming splits — see split_tokens below.

Parameters

function_name
Name of the function to invoke. Must exist in the worker’s registry and be a table function (not table-in-out).
schema_name
Name of the catalog schema that declares the function. Required — a worker may register one name in several schemas, so the (schema, name) pair is what identifies the implementation.
arguments
Optional Arguments container with positional and named arguments to pass to the function. Defaults to empty Arguments().
bind_result_callback
Optional callback invoked with the BindResponse before processing begins.
projection_ids
Optional list of column indices for column projection.
pushdown_filters
Optional byte string containing filter predicates to push down to the function.
join_keys
Optional serialized join-key batches for semi-join pushdown — one single-column RecordBatch per join-key column, matched worker-side by column name (see PushdownFilters.get_join_keys_column). Same wire mechanism DuckDB’s own join pushdown into VGI already exercises; Client simply had no public way to set it before this.
settings
Optional dictionary of settings/pragmas to pass to the function.
transaction_opaque_data
Optional unique identifier for the DuckDB transaction.
copy_from
Optional CopyFromContext marking this scan as a COPY … FROM read. Prefer :meth:copy_from, which builds the context for you.
split_tokens
Redeem these specific split tokens (from a prior :meth:table_function_plan call) instead of an ordinary whole-scan init. Forces single-worker mode for this call — the server’s advertised max_workers describes fan-out for reading the whole table, not for one already-named unit of work. To read multiple splits in parallel, drive them through multiple Client/thread instances yourself; to read them sequentially (still sound and replayable, just without concurrency), call this once per split token in a loop.
split_execution_id
When redeeming a split, the originating PlanResponse.execution_id — echoed on this init so a worker whose splits share cross-process state via BoundStorage can find it. None for an ordinary whole-scan init.
split_init_opaque_data
When redeeming a split, the originating PlanResponse.init_opaque_data, echoed the same way.
batch_metadata_callback
Optional callback invoked once per yielded batch, before it’s yielded, with that batch’s custom_metadata (None if it carried none) — e.g. a worker’s vgi.cache.* cacheability advertisement (vgi/cache_control.py), which rides AnnotatedBatch.custom_metadata and is otherwise unreachable through this generator’s plain pa.RecordBatch yields. Invoked serially from the generator’s own consumption loop (even in parallel mode, where multiple worker threads feed one shared queue) — never concurrently with itself.
at_unit
Optional time travel unit (e.g. ‘timestamp’, ‘version’) — scan the table as of a past point rather than live. None for a live scan. Threaded straight into BindRequest.at_unit (the wire protocol has always carried this field; a worker that doesn’t support time travel on this function rejects it at bind, the same as any other unsupported bind option).
at_value
Optional time travel value, paired with at_unit.

Raises

ClientError
If the client is not started, communication with the worker fails, or the worker returns an exception.
source
table_scan_resumable(
*,
function_name: str,
schema_name: str,
arguments: Arguments | None = None,
projection_ids: list[int] | None = None,
pushdown_filters: bytes | None = None,
settings: dict[str, Any] | None = None,
transaction_opaque_data: bytes | None = None,
resume_token: bytes | None = None,
) -> ResumableTableScan

Open (or resume) a resumable table-function scan.

Resumable variant of :meth:table_function: the returned :class:ResumableTableScan yields (batch, token) one batch at a time, surfacing the worker’s continuation token so a stateless caller can persist it and resume on another process/node.

When resume_token is given, the scan continues from that token (the bind/init is still issued — the upstream’s first turn is produced and discarded — so the same function_name/projection/filters must be supplied). When None, a fresh scan starts.

Parameters

function_name
Name of the table function to scan.
schema_name
Name of the catalog schema that declares the function. Required — a worker may register one name in several schemas, so the (schema, name) pair is what identifies the implementation.
arguments
Positional/named arguments for the function’s bind.
projection_ids
Optional column indices to project (projection pushdown). None selects all columns.
pushdown_filters
Optional serialized filter-pushdown payload.
settings
Optional DuckDB settings to apply for the scan.
transaction_opaque_data
Optional catalog transaction handle.
resume_token
Continuation token from a prior batch to resume from; must be paired with the same function_name/projection/ filters. None starts a fresh scan.

Returns

A ResumableTableScan yielding (batch, token) pairs.

Raises

ResumeUnsupported
If the transport is not HTTP.
ClientError
If the client is not started or the worker errors.
source
table_scan_continue(
*,
resume_token: bytes,
output_schema: pa.Schema | None = None,
) -> ResumableTableScan

Resume a producer table scan from a continuation token WITHOUT re-binding.

The cheap counterpart to table_scan_resumable(resume_token=...): a continuation token is a signed, self-describing snapshot of the worker’s producer state, so the server recovers state + schemas + function identity from the token alone. This skips the bind/init round-trip (and the discarded first turn) that table_scan_resumable pays — the right primitive for a stateless relay that holds a per-batch token and resumes on any node every batch.

The client must be started and connected to a worker that honours the token (the token is verified against the caller’s auth identity, and routed by the same init stream method that minted it). HTTP transport only.

Parameters

resume_token
A token previously returned by ResumableTableScan.next().
output_schema
Unused on the producer-continuation path (each response carries its own schema); accepted for symmetry with table_scan_resumable.

Returns

A ResumableTableScan positioned AFTER the token; next() continues the stream, yielding (batch, token) per call.

Raises

ResumeUnsupported
If the transport is not HTTP.
ClientError
If the client is not started.
source
copy_from(
*,
function_name: str,
schema_name: str,
format: str,
file_path: str,
expected_schema: pa.Schema,
arguments: Arguments | None = None,
bind_result_callback: Callable[[BindResponse], None] | None = None,
projection_ids: list[int] | None = None,
pushdown_filters: bytes | None = None,
settings: dict[str, Any] | None = None,
transaction_opaque_data: bytes | None = None,
) -> Generator[pa.RecordBatch]

Read a custom COPY ... FROM format and stream the parsed rows.

A CopyFromFunction is an ordinary producer-mode table function that additionally receives a CopyFromContext, so this is :meth:table_function with that context attached — the same shape the C++ extension’s copy_from_bind produces.

Discover the (format, handler) pairs a catalog advertises with client.copy_formats(attach_opaque_data=...); handler is the function_name to pass here.

Parameters

function_name
The reader function (a CopyFromFunction) — the handler field of the advertised format.
schema_name
Catalog schema that declares the function.
format
The SQL FORMAT identifier the read is running under.
file_path
Source path from the COPY … FROM ‘path’ statement.
expected_schema
Schema of the COPY target’s columns, in target order. The reader must emit batches matching it exactly — DuckDB inserts no cast between the scan and the INSERT.
arguments
The COPY options, as named Arguments. Defaults to empty, which is valid only when every option has a default.
bind_result_callback
Optional callback invoked with the BindResponse before reading begins.
projection_ids
Optional column indices for projection.
pushdown_filters
Optional serialized filter predicates.
settings
Optional settings/pragmas to pass to the function.
transaction_opaque_data
Optional DuckDB transaction identifier.

Raises

ClientError
If the client is not started or an RPC fails.
source
copy_to(
*,
function_name: str,
schema_name: str,
format: str,
file_path: str,
input: Iterator[pa.RecordBatch],
input_schema: pa.Schema | None = None,
arguments: Arguments | None = None,
bind_result_callback: Callable[[BindResponse], None] | None = None,
settings: dict[str, Any] | None = None,
transaction_opaque_data: bytes | None = None,
) -> None

Write input to a custom COPY ... TO format and close it.

A CopyToFunction is a buffered Sink+Combine function with no Source phase, so this is :meth:table_buffering_function with a CopyToContext attached: every batch is sunk via table_buffering_process and the terminal write happens once inside table_buffering_combine. Returns when the destination is closed.

Unlike the C++ path this drives a single worker connection, so ordered writers (Meta.sink_order_dependent) see source order for free.

Parameters

function_name
The writer function (a CopyToFunction) — the handler field of the advertised format.
schema_name
Catalog schema that declares the function.
format
The SQL FORMAT identifier the write is running under.
file_path
Destination path from the COPY … TO ‘path’ statement.
input
Iterator of source batches. May be empty — the writer’s close() still runs and must produce an empty destination.
input_schema
Source schema to bind with. Required when input may be empty and the writer needs the source column names.
arguments
The COPY options, as named Arguments. Defaults to empty, which is valid only when every option has a default.
bind_result_callback
Optional callback invoked with the BindResponse before writing begins.
settings
Optional settings/pragmas to pass to the function.
transaction_opaque_data
Optional DuckDB transaction identifier.

Raises

ClientError
If the client is not started or an RPC fails.
source
scalar_function(
*,
function_name: str,
schema_name: str,
input: Iterator[pa.RecordBatch],
arguments: Arguments | None = None,
bind_result_callback: Callable[[BindResponse], None] | None = None,
settings: dict[str, Any] | None = None,
secrets: dict[str, Any] | None = None,
transaction_opaque_data: bytes | None = None,
) -> Generator[pa.RecordBatch]

Invoke a scalar function on the worker and stream results.

Scalar functions transform input batches to single-column output with 1:1 row mapping. Processing ends when input is exhausted.

For parallel processing (max_workers > 1), input batches are distributed round-robin across workers using dedicated threads. Output order may not match input order in parallel mode.

Parameters

function_name
Name of the function to invoke. Must exist in the worker’s registry.
schema_name
Name of the catalog schema that declares the function. Required — a worker may register one name in several schemas, so the (schema, name) pair is what identifies the implementation.
input
Iterator yielding input RecordBatches. Must yield at least one batch. The first batch’s schema is used to initialize the IPC stream. Raises ClientError if the iterator is empty.
arguments
Optional Arguments container with positional and named arguments to pass to the function. Defaults to empty Arguments().
bind_result_callback
Optional callback invoked with the BindResponse before processing begins.
settings
Optional dictionary of settings/pragmas to pass to the function.
secrets
Optional dictionary of secret name to value pairs. Values can be simple scalars or dicts (for struct-typed secrets).
transaction_opaque_data
Optional unique identifier for the DuckDB transaction.

Raises

ClientError
If the client is not started, input iterator is empty, input iterator yields non-RecordBatch objects, communication with the worker fails, or the worker returns an unexpected status or exception.
Inherited members (43)
source

Bases: Exception

Description

Error raised by Client operations.

The first line of str(ClientError) is the remote exception as the worker raised it ({error_type}: {error_message}), so that whatever a user typed into their raise ValueError(...) shows up at the top of their traceback instead of being buried under VGI framing. Remote traceback and worker-stderr excerpts, when present, follow after an empty line.

Methods

source
from_rpc_error(e: RpcError) -> ClientError

Create a ClientError from an RpcError, including remote traceback.

Lead with the user’s exception (error_type: error_message) so the most actionable line is first. The Remote traceback section trails and is only included when the worker produced one.

source
main() -> None

CLI entry point for vgi-client.

source

Description

Handles writing output batches in various formats.

Supported formats

  • json: JSON Lines format (one JSON object per row)
  • csv: CSV with header
  • parquet: Apache Parquet columnar format
  • arrow-ipc: Apache Arrow IPC streaming format (useful for debugging)

The arrow-ipc format writes batches in the standard Arrow IPC streaming format, which can be read by any Arrow implementation. This is useful for:

- Debugging VGI protocol issues
- Inspecting raw output data with tools like pyarrow or arrow CLI
- Piping data to other Arrow-aware tools

Attributes

Path to output file, “-” for stdout, or None for logging.

Output format (“parquet”, “csv”, “json”, or “arrow-ipc”).

Optional schema for the output data.

Methods

source
write_batch(batch: pa.RecordBatch) -> None

Write a batch to the output destination in the configured format.

source
close() -> None

Close the underlying writer if one exists.

source

Description

A resumable, one-batch-at-a-time handle on an upstream table-function scan.

Unlike :meth:Client.table_function (a live generator that hides the server’s continuation token), each :meth:next returns (batch, token) where token is the worker’s serialized producer state AFTER batch. A stateless client (e.g. a load-balanced proxy) can persist token, drop the connection, and resume on another node via Client.table_scan_resumable(resume_token=token, ...).

Single-worker: reads the primary stream only (parallel max_workers>1 reads are unordered and not resumable from a single token).

Methods

source
next() -> tuple[pa.RecordBatch | None, bytes | None]

Return (batch, resume_token); (None, None) at end-of-stream.

resume_token resumes the scan AFTER batch on any node.

source
close() -> None

Release the underlying stream (no-op over HTTP — stateless).

source

Bases: ClientError

Description

Raised when a resumable scan is requested on a non-resumable transport.

Only the HTTP transport round-trips producer state in continuation tokens, so only HTTP clients can drive :meth:Client.table_scan_resumable. On the pipe/subprocess transport the stream is a live connection with no serializable resume point; the caller must keep the live stream in-process instead.

Inherited members (1)
  • from_rpc_error method · from ClientError — Create a ClientError from an RpcError, including remote traceback.