Skip to content

Commit 376f49c

Browse files
rustyconoverclaude
andcommitted
Move is_default from SchemaInfo to default_schema on CatalogAttachResult
Replace the is_default boolean field on SchemaInfo with a default_schema string field on CatalogAttachResult. This makes the default schema a property of the catalog attachment rather than each schema. Changes: - CatalogAttachResult: Add default_schema field - SchemaInfo: Remove is_default field - Update all catalog implementations and tests - Update documentation Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 1d9dac0 commit 376f49c

27 files changed

Lines changed: 317 additions & 158 deletions

README.md

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
```python
2121
# my_worker.py
22+
from typing import Annotated
2223
from vgi import ScalarFunction, Arg, Worker
2324
import pyarrow as pa
2425
import pyarrow.compute as pc
@@ -29,7 +30,7 @@ class Greeting(ScalarFunction):
2930
class Meta:
3031
output_type = pa.string()
3132

32-
col_name = Arg[str](0, doc="Column containing names")
33+
col_name: Annotated[str, Arg(0, doc="Column containing names")]
3334

3435
def compute(self, batch: pa.RecordBatch) -> pa.Array:
3536
names = batch.column(self.col_name)
@@ -106,6 +107,7 @@ A worker is a Python script that defines one or more functions:
106107
```python
107108
#!/usr/bin/env python
108109
# my_worker.py
110+
from typing import Annotated
109111
import pyarrow as pa
110112
import pyarrow.compute as pc
111113
from vgi import ScalarFunction, Arg, Worker
@@ -117,7 +119,7 @@ class UpperCase(ScalarFunction):
117119
class Meta:
118120
output_type = pa.string()
119121

120-
col_name = Arg[str](0, doc="Column to uppercase")
122+
col_name: Annotated[str, Arg(0, doc="Column to uppercase")]
121123

122124
def compute(self, batch: pa.RecordBatch) -> pa.Array:
123125
return pc.utf8_upper(batch.column(self.col_name))
@@ -155,11 +157,11 @@ Your function is now available in DuckDB. Ship the Python script to your team, a
155157

156158
## Going Further: Type-Safe Arguments
157159

158-
For production use, you'll want type validation. Use `Arg[AnyArrow]` with `type_bound` to ensure columns have the correct type:
160+
For production use, you'll want type validation. Use `AnyArrowValue` with `type_bound` to ensure columns have the correct type:
159161

160162
```python
161-
from vgi import ScalarFunction, Arg, Worker
162-
from vgi.arguments import AnyArrow
163+
from typing import Annotated
164+
from vgi import ScalarFunction, Arg, AnyArrowValue, Worker
163165
import pyarrow as pa
164166
import pyarrow.compute as pc
165167

@@ -170,8 +172,8 @@ class AddColumns(ScalarFunction):
170172
class Meta:
171173
output_type = pa.int64()
172174

173-
left = Arg[AnyArrow](0, type_bound=pa.types.is_integer, doc="First column")
174-
right = Arg[AnyArrow](1, type_bound=pa.types.is_integer, doc="Second column")
175+
left: Annotated[AnyArrowValue, Arg(0, type_bound=pa.types.is_integer, doc="First column")]
176+
right: Annotated[AnyArrowValue, Arg(1, type_bound=pa.types.is_integer, doc="Second column")]
175177

176178
def compute(self, batch: pa.RecordBatch) -> pa.Array:
177179
return pc.add(
@@ -188,8 +190,8 @@ SELECT add_columns(price, tax) as total FROM orders;
188190
-- Error: Column 'name' has type string, expected integer
189191
```
190192

191-
Key differences from `Arg[str]`:
192-
- `Arg[AnyArrow]` validates the column's Arrow type at bind time
193+
Key differences from `Annotated[str, Arg(...)]`:
194+
- `AnyArrowValue` validates the column's Arrow type at bind time
193195
- `type_bound` specifies which types are allowed
194196
- Access the column name via `.value` (e.g., `self.left.value`)
195197

@@ -214,7 +216,7 @@ class Double(ScalarFunction):
214216
class Meta:
215217
output_type = pa.int64()
216218

217-
col_name = Arg[str](0)
219+
col_name: Annotated[str, Arg(0)]
218220

219221
def compute(self, batch: pa.RecordBatch) -> pa.Array:
220222
return pc.multiply(batch.column(self.col_name), 2)
@@ -229,7 +231,7 @@ class Sequence(TableFunctionGenerator):
229231
class Meta:
230232
max_workers = 1
231233

232-
count = Arg[int](0)
234+
count: Annotated[int, Arg(0)]
233235

234236
@property
235237
def output_schema(self) -> pa.Schema:
@@ -257,7 +259,7 @@ class Sum(TableInOutFunction):
257259
class Meta:
258260
max_workers = 1 # Aggregations need single worker
259261

260-
col_name = Arg[str](0)
262+
col_name: Annotated[str, Arg(0)]
261263

262264
def bind(self) -> None:
263265
self.total = 0

docs/argument-serialization.md

Lines changed: 33 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,10 @@ Positional arguments have no special metadata. Their position index is
3636
determined by their order in the schema.
3737

3838
```python
39-
# Arg[int](0) becomes:
39+
# count: Annotated[int, Arg(0)] becomes:
4040
pa.field("count", pa.int64())
4141

42-
# Arg[str](1) becomes:
42+
# name: Annotated[str, Arg(1)] becomes:
4343
pa.field("name", pa.utf8())
4444
```
4545

@@ -49,35 +49,35 @@ Named arguments have `vgi_arg=named` metadata. The field name is the
4949
argument key used in SQL.
5050

5151
```python
52-
# Arg[str]("format") becomes:
52+
# format: Annotated[str, Arg("format")] becomes:
5353
pa.field("format", pa.utf8(), metadata={b"vgi_arg": b"named"})
5454
```
5555

5656
## Special Types
5757

5858
### Table Input
5959

60-
Table input arguments (`Arg[TableInput]`) receive streaming RecordBatches
61-
rather than scalar values.
60+
Table input arguments (`Annotated[TableInput, Arg(...)]`) receive streaming
61+
RecordBatches rather than scalar values.
6262

6363
- **Arrow type**: `pa.null()`
6464
- **Metadata**: `{b"vgi_type": b"table"}`
6565

6666
```python
67-
# Arg[TableInput](1) becomes:
67+
# data: Annotated[TableInput, Arg(1)] becomes:
6868
pa.field("data", pa.null(), metadata={b"vgi_type": b"table"})
6969
```
7070

7171
### Any Type
7272

73-
Any-type arguments (`Arg[AnyArrow]`) accept any valid Arrow scalar type
74-
at runtime.
73+
Any-type arguments (`Annotated[AnyArrowValue, Arg(...)]`) accept any valid
74+
Arrow scalar type at runtime.
7575

7676
- **Arrow type**: `pa.null()`
7777
- **Metadata**: `{b"vgi_type": b"any"}`
7878

7979
```python
80-
# Arg[AnyArrow](0) becomes:
80+
# value: Annotated[AnyArrowValue, Arg(0)] becomes:
8181
pa.field("value", pa.null(), metadata={b"vgi_type": b"any"})
8282
```
8383

@@ -90,7 +90,7 @@ arguments from their position onwards.
9090
- **Metadata**: `{b"vgi_varargs": b"true"}`
9191

9292
```python
93-
# Arg[str](0, varargs=True) becomes:
93+
# columns: Annotated[tuple[str, ...], Arg(0, varargs=True)] becomes:
9494
pa.field("columns", pa.utf8(), metadata={b"vgi_varargs": b"true"})
9595
```
9696

@@ -100,7 +100,7 @@ Fields can have multiple metadata keys. For example, a named argument
100100
that accepts any type:
101101

102102
```python
103-
# Arg[AnyArrow]("threshold") becomes:
103+
# threshold: Annotated[AnyArrowValue, Arg("threshold")] becomes:
104104
pa.field("threshold", pa.null(), metadata={
105105
b"vgi_arg": b"named",
106106
b"vgi_type": b"any",
@@ -112,10 +112,12 @@ pa.field("threshold", pa.null(), metadata={
112112
### Example 1: Simple Function
113113

114114
```python
115+
from typing import Annotated
116+
115117
class MyFunction(TableInOutFunction):
116-
count = Arg[int](0) # Positional 0
117-
name = Arg[str](1) # Positional 1
118-
verbose = Arg[bool]("verbose") # Named
118+
count: Annotated[int, Arg(0)] # Positional 0
119+
name: Annotated[str, Arg(1)] # Positional 1
120+
verbose: Annotated[bool, Arg("verbose")] # Named
119121

120122
# Serializes to:
121123
schema = pa.schema([
@@ -128,9 +130,12 @@ schema = pa.schema([
128130
### Example 2: Function with Table Input
129131

130132
```python
133+
from typing import Annotated
134+
from vgi.arguments import TableInput
135+
131136
class TransformFunction(TableInOutFunction):
132-
multiplier = Arg[float](0)
133-
data: TableInput = Arg[TableInput](1)
137+
multiplier: Annotated[float, Arg(0)]
138+
data: Annotated[TableInput, Arg(1)]
134139

135140
# Serializes to:
136141
schema = pa.schema([
@@ -142,8 +147,10 @@ schema = pa.schema([
142147
### Example 3: Function with Varargs
143148

144149
```python
150+
from typing import Annotated
151+
145152
class SumColumnsFunction(TableInOutFunction):
146-
columns = Arg[str](0, varargs=True)
153+
columns: Annotated[tuple[str, ...], Arg(0, varargs=True)]
147154

148155
# Serializes to:
149156
schema = pa.schema([
@@ -154,12 +161,16 @@ schema = pa.schema([
154161
### Example 4: Complex Function
155162

156163
```python
164+
from typing import Annotated
165+
from vgi import AnyArrowValue
166+
from vgi.arguments import TableInput
167+
157168
class ComplexFunction(TableInOutFunction):
158-
count = Arg[int](0)
159-
data: TableInput = Arg[TableInput](1)
160-
extra = Arg[float](2, varargs=True)
161-
format = Arg[str]("format")
162-
threshold: AnyArrow = Arg[AnyArrow]("threshold")
169+
count: Annotated[int, Arg(0)]
170+
data: Annotated[TableInput, Arg(1)]
171+
extra: Annotated[tuple[float, ...], Arg(2, varargs=True)]
172+
format: Annotated[str, Arg("format")]
173+
threshold: Annotated[AnyArrowValue, Arg("threshold")]
163174

164175
# Serializes to:
165176
schema = pa.schema([

docs/catalog-interface.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ Returned by `catalog_attach()` with attachment metadata:
131131
| `catalog_version_frozen` | `bool` | Whether metadata will change |
132132
| `catalog_version` | `int` | Current version (increments on changes) |
133133
| `attach_id_required` | `bool` | Whether attach_id must be persisted |
134+
| `default_schema` | `str` | Name of the default schema (usually "main") |
134135

135136
### SchemaInfo
136137

@@ -140,7 +141,6 @@ Information about a schema in a catalog:
140141
|-------|------|-------------|
141142
| `attach_id` | `AttachId` | Parent attachment |
142143
| `name` | `str` | Schema name |
143-
| `is_default` | `bool` | Whether this is the default schema |
144144
| `comment` | `str \| None` | Optional description |
145145
| `tags` | `dict[str, str]` | Key-value metadata |
146146

@@ -307,7 +307,7 @@ class MyReadOnlyCatalog(ReadOnlyCatalogInterface):
307307

308308
def schema_get(self, *, attach_id, transaction_id, name) -> SchemaInfo | None:
309309
if name == "main":
310-
return SchemaInfo(attach_id=attach_id, name="main", is_default=True, comment=None, tags={})
310+
return SchemaInfo(attach_id=attach_id, name="main", comment=None, tags={})
311311
return None
312312

313313
# table_get, view_get return None by default
@@ -643,7 +643,6 @@ class SimpleCatalog(CatalogInterface):
643643
return SchemaInfo(
644644
attach_id=attach_id,
645645
name="main",
646-
is_default=True,
647646
comment="Default schema",
648647
tags={},
649648
)

docs/generator-api.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ including yielding log messages during processing. For most use cases, prefer th
1111
### Basic Template
1212

1313
```python
14+
from typing import Annotated
1415
import pyarrow as pa
1516
import pyarrow.compute as pc
1617
from vgi import ScalarFunctionGenerator, Output, Arg
@@ -19,7 +20,7 @@ from vgi.log import Level, Message
1920
class MyScalarFunction(ScalarFunctionGenerator):
2021
"""Transform each row to a single output value."""
2122

22-
col_name = Arg[str](0, doc="Name of the column to transform")
23+
col_name: Annotated[str, Arg(0, doc="Name of the column to transform")]
2324

2425
@property
2526
def output_schema(self) -> pa.Schema:
@@ -77,6 +78,7 @@ Use `TableFunctionGenerator` when you need to generate data without receiving in
7778
### Basic Template
7879

7980
```python
81+
from typing import Annotated
8082
import pyarrow as pa
8183
from vgi import TableFunctionGenerator, Output, Arg
8284
from vgi.table_function import TableCardinality
@@ -88,7 +90,7 @@ class MyTableFunction(TableFunctionGenerator):
8890
name = "my_table_function"
8991
max_workers = 1 # Or None for parallel
9092

91-
count = Arg[int](0, doc="Number of rows to generate")
93+
count: Annotated[int, Arg(0, doc="Number of rows to generate")]
9294
BATCH_SIZE = 1000
9395

9496
@property

docs/metadata.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ Functions can define a nested `Meta` class to provide introspection metadata. No
77
Metadata works with all function types: `ScalarFunction`, `TableFunctionGenerator`, and `TableInOutFunction`.
88

99
```python
10+
from typing import Annotated
1011
from vgi import TableInOutFunction, Arg
1112

1213
class SumColumnsFunction(TableInOutFunction):
@@ -18,13 +19,14 @@ class SumColumnsFunction(TableInOutFunction):
1819
categories = ["aggregation", "numeric"]
1920
max_workers = 1 # Single-threaded (used by max_processes property)
2021

21-
column_name = Arg[str]("column", default=None, doc="Column to sum (optional)")
22+
column_name: Annotated[str | None, Arg("column", default=None, doc="Column to sum")]
2223

2324
def transform(self, batch):
2425
...
2526
```
2627

2728
```python
29+
from typing import Annotated
2830
from vgi import ScalarFunction, Arg
2931
from vgi.arguments import AnyArrow
3032
import pyarrow as pa
@@ -38,7 +40,7 @@ class DoubleValues(ScalarFunction):
3840
output_type = AnyArrow # Dynamic type - depends on input
3941
categories = ["numeric", "transform"]
4042

41-
col_name = Arg[str](0, doc="Name of the column to double")
43+
col_name: Annotated[str, Arg(0, doc="Name of the column to double")]
4244

4345
@property
4446
def output_type(self) -> pa.DataType:

tests/catalog/test_catalog_interface.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,6 @@ def schema_get(
8888
return SchemaInfo(
8989
attach_id=attach_id,
9090
name="main",
91-
is_default=True,
9291
comment=None,
9392
tags={},
9493
)
@@ -142,7 +141,6 @@ def test_schemas_returns_main(self) -> None:
142141

143142
assert len(schemas) == 1
144143
assert schemas[0].name == "main"
145-
assert schemas[0].is_default is True
146144
assert schemas[0].comment is None
147145
assert schemas[0].tags == {}
148146

tests/catalog/test_integration.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@ def test_default_main_schema_exists(self) -> None:
5353

5454
assert len(schemas) == 1
5555
assert schemas[0].name == "main"
56-
assert schemas[0].is_default is True
5756

5857
def test_schema_get_main(self) -> None:
5958
"""Can get the main schema."""

0 commit comments

Comments
 (0)