Skip to main content

grommet

Made with AI Licensed under BSD-3-Clause-Clear

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

Metadata

Release files for grommet 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for grommet 0.1.0
File Size Uploaded Provenance Actions
grommet-0.1.0.tar.gz 146.3 kB Without provenance Details for grommet-0.1.0.tar.gz

Built distributions (wheels)

Table of built distributions (wheels) for grommet 0.1.0
File Interpreter ABI Platform Actions
grommet-0.1.0-cp314-cp314-manylinux_2_28_x86_64.whl CPython 3.14 CPython 3.14 Linux glibc 2.28+ x86-64 Details for grommet-0.1.0-cp314-cp314-manylinux_2_28_x86_64.whl
grommet-0.1.0-cp313-cp313-manylinux_2_28_x86_64.whl CPython 3.13 CPython 3.13 Linux glibc 2.28+ x86-64 Details for grommet-0.1.0-cp313-cp313-manylinux_2_28_x86_64.whl

Total release size: 2.4 MB

Release files / grommet-0.1.0.tar.gz

Download URL grommet-0.1.0.tar.gz
Size 146.3 kB
Tags Source
SHA-256 checksum
How to use checksums
f04b3e0a71d6c7399d0100e79b0c6cd295d238d003054bb6b49a9347682a9569
BLAKE2b-256 checksum
How to use checksums
86d30eb398790e7f1a61b7705b7a53f3418de908cfbf1244c48fa138f47d8c5d
Upload date
Uploaded using Trusted Publishing?
What is 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}

Release files / grommet-0.1.0-cp314-cp314-manylinux_2_28_x86_64.whl

Download URL grommet-0.1.0-cp314-cp314-manylinux_2_28_x86_64.whl
Size 1.1 MB
Tags CPython 3.14 Linux glibc 2.28+ x86-64
SHA-256 checksum
How to use checksums
addee0f07f879608e68f1b57406d4a06067eb801c14f45837b0d297abe573013
BLAKE2b-256 checksum
How to use checksums
aca0ea3ce1fa764956ef167ab5047fc4c13ddae26103f5856406e2efe929f9a3
Upload date
Uploaded using Trusted Publishing?
What is 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}

Release files / grommet-0.1.0-cp313-cp313-manylinux_2_28_x86_64.whl

Download URL grommet-0.1.0-cp313-cp313-manylinux_2_28_x86_64.whl
Size 1.1 MB
Tags CPython 3.13 Linux glibc 2.28+ x86-64
SHA-256 checksum
How to use checksums
dbc81915e0c69da0cf463daf302c0792a58038203a9ab28097be4a19d5664627
BLAKE2b-256 checksum
How to use checksums
63bc3f226fd2e0501728a0787173b1c53825d95de184af6a2240fa388bb937df
Upload date
Uploaded using Trusted Publishing?
What is 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}

Security for grommet 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.0 This release

3 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page