High performance type-driven Python GraphQL library backed by Rust
Project description
grommet
High performance async Python GraphQL server library inspired by Strawberry and backed by async-graphql.
This is an experiment in a nearly 100% AI-written project. I provide guidelines and design guidance through review of the generated code and curated revision plans, but AI does the heavy lifting. Features are developed as my token and usage counts reset.
The goal is to utilize AI to prove the concept, but do so while also laying solid technical foundations for future human-driven development and maintenance; my personal belief is that the latter is always necessary.
Quick Start
Installation
pip install grommet
# or
uv add grommet
Examples
Define your GraphQL types as decorated dataclasses, build a schema, and execute queries:
@grommet.type
@dataclass
class Query:
greeting: str = "Hello world!"
schema = grommet.Schema(query=Query)
result = await schema.execute("{ greeting }")
print(result.data) # {'greeting': 'Hello world!'}
Add descriptions to types and fields for better SDL:
@grommet.type(description="All queries")
@dataclass
class Query:
greeting: Annotated[str, grommet.Field(description="A simple greeting") = "Hello world!"
sdl = grommet.Schema(query=Query).sdl
print(sdl)
# """
# All queries
# """
# query Query {
# "A simple greeting"
# greeting: String!
# }
Root types (Query, Mutation, Subscription) cannot have fields without defaults. Use grommet.field to define
fields using resolvers to dynamically return values, possibly with required and optional arguments:
@grommet.type
@dataclass
class Query:
@grommet.field(description="A simple greeting")
async def greeting(self, name: str, title: str | None = None) -> str:
return f"Hello {name}!" if not title else f"Hello, {title} {name}."
schema = grommet.Schema(query=Query)
result = await schema.execute('{ greeting(name: "Gromit") }')
print(result.data) # {'greeting': 'Hello Gromit!'}
result = await schema.execute('{ greeting(name: "Gromit", title: "Mr.") }')
print(result.data) # {'greeting': 'Hello Mr. Gromit.'}
Limit what fields are exposed to the schema via grommet.Hidden, ClassVar, or the standard _private_var syntax:
@grommet.type
@dataclass
class User:
_foo: int
bar: ClassVar[int]
hidden: Annotated[int, grommet.Hidden]
name: str
def _message(self) -> str:
return f"Hello {self.name}" + ("!" * self._foo * self.bar * self.hidden)
@grommet.field
async def greeting(self) -> str:
return self._message()
@grommet.type
@dataclass
class Query:
@grommet.field
async def user(self, name: str) -> User:
return User(_foo=2, bar=2, hidden=2, name=name)
schema = grommet.Schema(query=Query)
result = await schema.execute('{ user(name: "Gromit") { greeting } }')
print(result.data) # {'user': {'greeting': 'Hello Gromit!!!!!!'}}
Add mutations by defining a separate mutation root type, passing variables:
@grommet.input(description="User input.")
@dataclass
class AddUserInput:
name: Annotated[str, grommet.Field(description="The name of the user.")]
title: Annotated[
str | None, grommet.Field(description="The title of the user, if any.")
]
@grommet.type
@dataclass
class User:
name: str
title: str | None
@grommet.field
async def greeting(self) -> str:
return (
f"Hello {self.name}!"
if not self.title
else f"Hello, {self.title} {self.name}."
)
@grommet.type
@dataclass
class Mutation:
@grommet.field
async def add_user(self, input: AddUserInput) -> User:
return User(name=input.name, title=input.title)
schema = grommet.Schema(query=Query, mutation=Mutation)
mutation = """
mutation ($name: String!, $title: String) {
add_user(input: { name: $name, title: $title }) { greeting }
}
"""
result = await schema.execute(mutation, variables={"name": "Gromit"})
print(result.data) # {'add_user': {'greeting': 'Hello Gromit!'}}
result = await schema.execute(mutation, variables={"name": "Gromit", "title": "Mr."})
print(result.data) # {'add_user': {'greeting': 'Hello Mr. Gromit.'}}
Stream real-time data with subscriptions:
from collections.abc import AsyncIterator
@grommet.type
@dataclass
class Subscription:
@grommet.subscription
async def counter(self, limit: int) -> AsyncIterator[int]:
for i in range(limit):
yield i
schema = grommet.Schema(query=Query, subscription=Subscription)
stream = await schema.execute("subscription { counter(limit: 3) }")
async for result in stream:
print(result.data)
# {'counter': 0}
# {'counter': 1}
# {'counter': 2}
Store and access arbitrary information using the operation state:
@grommet.type
@dataclass
class Query:
@grommet.field
async def greeting(
self, context: Annotated[dict[str, str], grommet.Context]
) -> str:
return f"Hello request {context['request_id']}!"
schema = grommet.Schema(query=Query)
result = await schema.execute("{ greeting }", context={"request_id": "123"})
print(result.data) # {'greeting': 'Hello request 123!'}
Define unions, optionally providing a name or description:
@grommet.type
@dataclass
class A:
a: int
@grommet.type
@dataclass
class B:
b: int
type NamedAB = Annotated[A | B, grommet.Union(name="NamedAB", description="A or B")]
@grommet.type
@dataclass
class Query:
@grommet.field
async def named(self, type: str) -> NamedAB:
return A(a=1) if type == "A" else B(b=2)
@grommet.field
async def unnamed(self, type: str) -> A | B:
return A(a=1) if type == "A" else B(b=2)
schema = grommet.Schema(query=Query)
print("union NamedAB" in schema.sdl) # True
## if a name is not explicitly set, grommet will concatenate all the member names
print("union AB" in schema.sdl) # True
result = await schema.execute('{ named(type: "A") { ... on A { a } ... on B { b } } }')
print(result.data) # {'named': {'a': 1}}
result = await schema.execute(
'{ unnamed(type: "B") { ... on A { a } ... on B { b } } }'
)
print(result.data) # {'unnamed': {'b': 2}}
Simplify unions through common interfaces:
@grommet.interface(description="A letter")
@dataclass
class Letter:
letter: str
@grommet.type
@dataclass
class A(Letter):
pass
@grommet.type
@dataclass
class B(Letter):
some_subfield: list[int]
@grommet.type
@dataclass
class Query:
@grommet.field
async def common(self, type: str) -> Letter:
return A(letter="A") if type == "A" else B(letter="B", some_subfield=[42])
schema = grommet.Schema(query=Query)
print(schema.sdl)
# """
# A letter
# """
# interface Letter {
# letter: String!
# }
#
# type A implements Letter {
# letter: String!
# }
#
# type B implements Letter {
# letter: String!
# some_subfield: [Int!]!
# }
#
# type Query {
# common(type: String!): Letter!
# }
Development
The public APIs for this project are defined by me (a human). Everything else is AI-written following AGENTS.md and plan guidelines. Implementation iterations take the form of plan documents in ai_plans/.
This project is configured for uv + maturin.
Install prek for quality control:
prek install
prek run -a
Run unit tests with:
maturin develop --uv
uv run pytest
uv run cargo test # you need to be in the venv!
Run benchmarks with:
maturin develop --uv -r
uv run python benchmarks/bench_large.py
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file grommet-0.1.0.tar.gz.
File metadata
- Download URL: grommet-0.1.0.tar.gz
- Upload date:
- Size: 146.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.13 {"installer":{"name":"uv","version":"0.9.13"},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"12","id":"bookworm","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f04b3e0a71d6c7399d0100e79b0c6cd295d238d003054bb6b49a9347682a9569
|
|
| MD5 |
41fa5156c9bd7f27f406541e0a6bd2d2
|
|
| BLAKE2b-256 |
86d30eb398790e7f1a61b7705b7a53f3418de908cfbf1244c48fa138f47d8c5d
|
File details
Details for the file grommet-0.1.0-cp314-cp314-manylinux_2_28_x86_64.whl.
File metadata
- Download URL: grommet-0.1.0-cp314-cp314-manylinux_2_28_x86_64.whl
- Upload date:
- Size: 1.1 MB
- Tags: CPython 3.14, manylinux: glibc 2.28+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.13 {"installer":{"name":"uv","version":"0.9.13"},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"12","id":"bookworm","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
addee0f07f879608e68f1b57406d4a06067eb801c14f45837b0d297abe573013
|
|
| MD5 |
16ba99951eb6193e78f95041f92c0e35
|
|
| BLAKE2b-256 |
aca0ea3ce1fa764956ef167ab5047fc4c13ddae26103f5856406e2efe929f9a3
|
File details
Details for the file grommet-0.1.0-cp313-cp313-manylinux_2_28_x86_64.whl.
File metadata
- Download URL: grommet-0.1.0-cp313-cp313-manylinux_2_28_x86_64.whl
- Upload date:
- Size: 1.1 MB
- Tags: CPython 3.13, manylinux: glibc 2.28+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.13 {"installer":{"name":"uv","version":"0.9.13"},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"12","id":"bookworm","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
dbc81915e0c69da0cf463daf302c0792a58038203a9ab28097be4a19d5664627
|
|
| MD5 |
b3d38596ae0730db3d4eb06dfb50b866
|
|
| BLAKE2b-256 |
63bc3f226fd2e0501728a0787173b1c53825d95de184af6a2240fa388bb937df
|