Skip to main content

High performance type-driven Python GraphQL library backed by Rust

Project description

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

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

grommet-0.1.0.tar.gz (146.3 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

grommet-0.1.0-cp314-cp314-manylinux_2_28_x86_64.whl (1.1 MB view details)

Uploaded CPython 3.14manylinux: glibc 2.28+ x86-64

grommet-0.1.0-cp313-cp313-manylinux_2_28_x86_64.whl (1.1 MB view details)

Uploaded CPython 3.13manylinux: glibc 2.28+ x86-64

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

Hashes for grommet-0.1.0.tar.gz
Algorithm Hash digest
SHA256 f04b3e0a71d6c7399d0100e79b0c6cd295d238d003054bb6b49a9347682a9569
MD5 41fa5156c9bd7f27f406541e0a6bd2d2
BLAKE2b-256 86d30eb398790e7f1a61b7705b7a53f3418de908cfbf1244c48fa138f47d8c5d

See more details on using hashes here.

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

Hashes for grommet-0.1.0-cp314-cp314-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 addee0f07f879608e68f1b57406d4a06067eb801c14f45837b0d297abe573013
MD5 16ba99951eb6193e78f95041f92c0e35
BLAKE2b-256 aca0ea3ce1fa764956ef167ab5047fc4c13ddae26103f5856406e2efe929f9a3

See more details on using hashes here.

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

Hashes for grommet-0.1.0-cp313-cp313-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 dbc81915e0c69da0cf463daf302c0792a58038203a9ab28097be4a19d5664627
MD5 b3d38596ae0730db3d4eb06dfb50b866
BLAKE2b-256 63bc3f226fd2e0501728a0787173b1c53825d95de184af6a2240fa388bb937df

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page