Skip to content

Commit 5ccdbf0

Browse files
authored
Merge pull request #84 from CaliLuke/feat/enhanced-function-queries
feat: Enhanced FunctionQuery with complete TypeQL query generation
2 parents 0b4cb9f + c9762e9 commit 5ccdbf0

19 files changed

Lines changed: 1805 additions & 105 deletions

File tree

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
repos:
2+
- repo: https://github.com/astral-sh/ruff-pre-commit
3+
rev: v0.8.4
4+
hooks:
5+
- id: ruff
6+
args: [--fix]
7+
- id: ruff-format
8+
9+
- repo: local
10+
hooks:
11+
- id: ty
12+
name: ty type check
13+
entry: bash -c 'cd packages/python && uvx ty check .'
14+
language: system
15+
pass_filenames: false
16+
always_run: true
17+
types: [python]
18+
19+
- id: pyright
20+
name: pyright type check
21+
entry: bash -c 'cd packages/python && uv run pyright tests/'
22+
language: system
23+
pass_filenames: false
24+
always_run: true
25+
types: [python]

packages/python/CLAUDE.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ This project requires **Python 3.13+** (see .python-version)
1818
# Install dependencies
1919
uv sync --extra dev
2020

21+
# Install pre-commit hooks (required for development)
22+
pre-commit install
23+
2124
# Run tests
2225
uv run pytest # Unit tests (fast, no deps)
2326
./test-integration.sh # Integration tests (with Docker)
@@ -26,6 +29,12 @@ Podman users: integration tests work with Podman too—set `CONTAINER_TOOL=podma
2629

2730
# Run examples
2831
uv run python examples/basic/crud_01_define.py
32+
33+
# Code quality (run before committing)
34+
uv run ruff check --fix . # Lint and auto-fix
35+
uv run ruff format . # Format code
36+
uvx ty check . # Type check library
37+
uv run pyright tests/ # Type check tests
2938
```
3039

3140
## Project Structure

packages/python/docs/api/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ TypeBridge provides a Pythonic interface to TypeDB that aligns with TypeDB's typ
1919

2020
- **[CRUD Operations](crud.md)** - Create, read, update, delete with type-safe managers
2121
- **[Queries](queries.md)** - Query expressions, filtering, aggregations, and pagination
22+
- **[Functions](functions.md)** - TypeDB schema-defined functions and FunctionQuery
2223
- **[Schema Management](schema.md)** - Schema operations, conflict detection, and migrations
2324

2425
### Code Generation
@@ -189,6 +190,7 @@ entity person,
189190
- [Cardinality Documentation](cardinality.md)
190191
- [CRUD Operations Documentation](crud.md)
191192
- [Queries Documentation](queries.md)
193+
- [Functions Documentation](functions.md)
192194
- [Schema Management Documentation](schema.md)
193195
- [Generator Documentation](generator.md)
194196
- [Validation Documentation](validation.md)
Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
# TypeDB Functions
2+
3+
TypeDB 3.x supports schema-defined functions using the `fun` keyword. TypeBridge provides the `FunctionQuery` class to generate TypeQL queries for calling these functions from Python.
4+
5+
## Overview
6+
7+
Functions in TypeDB are defined in the schema and executed as part of queries. TypeBridge's code generator can parse function definitions and generate Python wrapper functions that return `FunctionQuery` objects.
8+
9+
## Function Patterns
10+
11+
TypeDB functions support several patterns:
12+
13+
| Pattern | Schema Example | TypeQL Syntax |
14+
|---------|---------------|---------------|
15+
| Single value | `fun count() -> integer` | `let $x = count();` |
16+
| Stream | `fun list-ids() -> { id }` | `let $x in list-ids();` |
17+
| Parameterized | `fun get($id: string) -> entity` | `let $e = get("abc");` |
18+
| Composite | `fun divide($a: int, $b: int) -> int, int` | `let ($q, $r) = divide(10, 3);` |
19+
20+
## Using FunctionQuery
21+
22+
### Basic Usage
23+
24+
```python
25+
from type_bridge.expressions import FunctionQuery, ReturnType
26+
27+
# Define a function query
28+
fn = FunctionQuery(
29+
name="count-artifacts",
30+
return_type=ReturnType(["integer"]),
31+
)
32+
33+
# Generate the TypeQL query
34+
query = fn.to_query()
35+
# Output:
36+
# match let $integer = count-artifacts();
37+
# fetch { "integer": $integer };
38+
39+
# Execute against database
40+
with db.transaction() as tx:
41+
results = tx.execute(query)
42+
count = results[0]["integer"]
43+
```
44+
45+
### Stream Functions
46+
47+
Functions that return multiple rows use `{ }` syntax in TypeDB:
48+
49+
```python
50+
# Schema: fun list-user-ids() -> { id }:
51+
fn = FunctionQuery(
52+
name="list-user-ids",
53+
return_type=ReturnType(["id"], is_stream=True),
54+
)
55+
56+
query = fn.to_query(limit=10)
57+
# Output:
58+
# match let $id in list-user-ids();
59+
# limit 10;
60+
# fetch { "id": $id };
61+
```
62+
63+
### Parameterized Functions
64+
65+
Pass arguments to functions:
66+
67+
```python
68+
# Schema: fun get-neighbors($target_id: string) -> { neighbor }:
69+
fn = FunctionQuery(
70+
name="get-neighbors",
71+
args=[("$target_id", "art-001")],
72+
return_type=ReturnType(["neighbor"], is_stream=True),
73+
)
74+
75+
query = fn.to_query()
76+
# Output:
77+
# match let $neighbor in get-neighbors("art-001");
78+
# fetch { "neighbor": $neighbor };
79+
```
80+
81+
### Query Modifiers
82+
83+
Add pagination, sorting, and other modifiers:
84+
85+
```python
86+
fn = FunctionQuery(
87+
name="list-scores",
88+
return_type=ReturnType(["id", "score"], is_stream=True),
89+
)
90+
91+
query = fn.to_query(
92+
limit=10,
93+
offset=20,
94+
sort_var="score",
95+
sort_order="desc",
96+
)
97+
# Output:
98+
# match let ($id, $score) in list-scores();
99+
# sort $score desc;
100+
# offset 20;
101+
# limit 10;
102+
# fetch { "id": $id, "score": $score };
103+
```
104+
105+
## FunctionQuery API
106+
107+
### Constructor
108+
109+
```python
110+
FunctionQuery(
111+
name: str, # TypeDB function name
112+
return_type: ReturnType, # Return type description
113+
args: list[tuple[str, Any]] = [], # Function arguments
114+
docstring: str | None = None, # Optional documentation
115+
)
116+
```
117+
118+
### ReturnType
119+
120+
```python
121+
ReturnType(
122+
types: list[str], # List of return type names
123+
is_stream: bool = False, # True if returns multiple rows
124+
is_optional: list[bool] = [], # Optional flags per type
125+
)
126+
```
127+
128+
### Methods
129+
130+
| Method | Description |
131+
|--------|-------------|
132+
| `to_call()` | Generate function call expression: `func-name(args)` |
133+
| `to_match_let(result_vars)` | Generate match let clause |
134+
| `to_fetch(result_vars, fetch_keys)` | Generate fetch clause |
135+
| `to_query(limit, offset, sort_var, sort_order)` | Generate complete query |
136+
| `to_reduce_query()` | Generate reduce query (non-stream only) |
137+
| `with_args(**kwargs)` | Create copy with bound arguments |
138+
139+
### Properties
140+
141+
| Property | Description |
142+
|----------|-------------|
143+
| `return_type.is_stream` | True if function returns multiple rows |
144+
| `return_type.is_composite` | True if function returns tuples |
145+
| `return_type.is_single_value` | True if single non-stream value |
146+
147+
## Code Generation
148+
149+
When using the TypeBridge generator with a schema containing functions, Python wrapper functions are automatically generated:
150+
151+
### Schema (schema.tql)
152+
153+
```typeql
154+
define
155+
attribute artifact-id, value string;
156+
157+
entity artifact,
158+
owns artifact-id @key;
159+
160+
fun count-artifacts() -> integer:
161+
match $a isa artifact;
162+
return count($a);
163+
164+
fun list-artifact-ids() -> { artifact-id }:
165+
match $a isa artifact, has artifact-id $id;
166+
return { $id };
167+
168+
fun get-artifact-by-id($id: string) -> artifact:
169+
match $a isa artifact, has artifact-id $id;
170+
return first $a;
171+
```
172+
173+
### Generated Code (functions.py)
174+
175+
```python
176+
from typing import Iterator
177+
from type_bridge.expressions import FunctionQuery, ReturnType
178+
179+
180+
def count_artifacts() -> FunctionQuery[int]:
181+
"""Call TypeDB function `count-artifacts`.
182+
183+
Returns: integer
184+
"""
185+
return FunctionQuery(
186+
name="count-artifacts",
187+
args=[],
188+
return_type=ReturnType(["integer"], is_stream=False),
189+
)
190+
191+
192+
def list_artifact_ids() -> FunctionQuery[Iterator[str]]:
193+
"""Call TypeDB function `list-artifact-ids`.
194+
195+
Returns: stream of artifact-id
196+
"""
197+
return FunctionQuery(
198+
name="list-artifact-ids",
199+
args=[],
200+
return_type=ReturnType(["artifact-id"], is_stream=True),
201+
)
202+
203+
204+
def get_artifact_by_id(id: str | str) -> FunctionQuery[str]:
205+
"""Call TypeDB function `get-artifact-by-id`.
206+
207+
Returns: artifact
208+
"""
209+
return FunctionQuery(
210+
name="get-artifact-by-id",
211+
args=[("$id", id)],
212+
return_type=ReturnType(["artifact"], is_stream=False),
213+
)
214+
```
215+
216+
### Using Generated Functions
217+
218+
```python
219+
from myschema.functions import count_artifacts, list_artifact_ids
220+
221+
# Simple count
222+
fn = count_artifacts()
223+
query = fn.to_query()
224+
with db.transaction() as tx:
225+
results = tx.execute(query)
226+
total = results[0]["integer"]
227+
228+
# Stream with pagination
229+
fn = list_artifact_ids()
230+
query = fn.to_query(limit=100)
231+
with db.transaction() as tx:
232+
results = tx.execute(query)
233+
ids = [r["artifact_id"] for r in results]
234+
```
235+
236+
## Limitations
237+
238+
### Composite Stream Functions
239+
240+
TypeDB 3.x does not support destructuring tuples directly from stream functions. The syntax `let ($a, $b) in stream_func()` is not valid.
241+
242+
For functions returning streams of tuples, you may need to use a different approach or restructure the function.
243+
244+
### Runtime Execution
245+
246+
Functions are executed by TypeDB, not by TypeBridge. The `FunctionQuery` class only generates the TypeQL query string - actual execution happens when you run the query against the database.
247+
248+
## Best Practices
249+
250+
1. **Use the generator** - Let TypeBridge generate function wrappers from your schema
251+
2. **Add pagination** - Use `limit` and `offset` for stream functions
252+
3. **Handle errors** - Wrap execution in try/except for TypeDB errors
253+
4. **Test against real DB** - Function syntax depends on TypeDB version
254+
255+
## See Also
256+
257+
- [Generator Documentation](generator.md) - Code generation from schemas
258+
- [Queries Documentation](queries.md) - Query expressions and filtering
259+
- [CRUD Operations](crud.md) - Working with entities and relations

packages/python/pyproject.toml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,22 @@ dev = [
5454
python = ".venv"
5555
extra-paths = ["."]
5656

57+
# Tests and examples use metaclass patterns that ty can't fully understand
58+
[[tool.ty.overrides]]
59+
include = ["tests/**", "examples/**"]
60+
61+
[tool.ty.overrides.rules]
62+
# Metaclass-generated __init__ kwargs
63+
unknown-argument = "ignore"
64+
# Dynamic attributes from hasattr checks
65+
unresolved-attribute = "ignore"
66+
# Dynamic method calls after hasattr
67+
call-non-callable = "ignore"
68+
# Type coercion in tests
69+
invalid-argument-type = "ignore"
70+
invalid-assignment = "ignore"
71+
unsupported-operator = "ignore"
72+
5773
[tool.mypy]
5874
python_version = "3.13"
5975
ignore_missing_imports = false

0 commit comments

Comments
 (0)