A demo library that bridges SQLAlchemy models and GraphQL — like Graphene, but cooler.
gqlalchemy is a compact, opinionated toolkit that lets you describe your domain with Python (SQLAlchemy + small Field wrappers) and automatically derive three things from the same model declaration:
- a database schema (SQLAlchemy metadata / helpers),
- a GraphQL schema (Graphene types and inputs), and
- fully-working CRUD endpoints (resolvers and adapters for web frameworks).
This repository is a demonstration of that idea — a lightweight, dependency-minimal codebase that shows how model-first development can generate DB schemas, GQL schemas and REST/GraphQL CRUD endpoints with fine-grained, per-field permissions.
- Single source of truth. Define your model once in Python and use it to generate the DB layout, GraphQL types/inputs and endpoint plumbing.
- Developer ergonomics. Minimal boilerplate: small
Fieldwrapper +Modelmixin exposes metadata used by the generators. - Security by design. Field-level access control (create/read/update/delete) configurable in the model definition so generated endpoints enforce business-level permissions out of the box.
- Composable outputs. You can use the generated GraphQL schema with your GraphQL server, export SDL for documentation, or plug the CRUD resolvers into any ASGI/WSGI framework.
- Generate GraphQL
ObjectTypeandInputdefinitions automatically from SQLAlchemy models. - Generate
QueryandMutationfields (single fetch, list, create, update, delete) per model. - Helpers to produce database schema from model metadata (useful as a demo / bootstrap for migrations).
- Field-level permission metadata: configure
create,read,update, anddeleteat the field-level. - Built-in filter inputs and common operators for list queries (
eq,ne,lt,gt,in,contains, and boolean combinators). - Async-friendly resolvers and a small SQLAlchemy backend adapter — can be used with SQLAlchemy
AsyncEnginesessions or your own session provider.
Below is a minimal example that shows how models are declared. This example does not create any database connections or run a program — it only demonstrates the model-first metadata that gqlalchemy uses to generate other artifacts.
from sqlalchemy.orm import DeclarativeBase
from gqlalchemy.model import Model
from gqlalchemy.types import Integer, String, Boolean
class Base(DeclarativeBase):
pass
class User(Base, Model):
__tablename__ = "user"
id = Integer(primary_key=True, required=True)
# Field flags control which operations the field participates in
username = String(required=True, creatable=True, updatable=False, permissions={
"create": True, "read": True, "update": False, "delete": False
})
email = String(required=True, creatable=True, updatable=True, permissions={
"create": True, "read": True, "update": True, "delete": False
})
is_admin = Boolean(required=True, creatable=False, updatable=False, permissions={
"create": False, "read": True, "update": False, "delete": False
})From the User model above the library can:
- generate GraphQL types and inputs where
usernameis writable only on create,emailis writable on create & update, andis_adminis read-only; - generate a SQLAlchemy table definition (so you may create tables or generate migration scripts from the same metadata);
- expose
userqueries and mutations (user,userList,createUser,updateUser,deleteUser) where each resolver respects the per-field permissions.
The library exposes helpers to assemble the pieces you need. Typical actions in an application are:
-
GraphQL schema: call
gqlalchemy.schema.generate_schema(Base, Model)to build agraphene.Schemathat includesQueryandMutationentries for every model. -
DB bootstrap (demo helper): you can either use SQLAlchemy native metadata (
Base.metadata.create_all(engine)) or the included convenience helpers to emit a database schema from model metadata — suitable for demos and quick start environments. -
CRUD endpoints: use the generated resolvers directly or use the small adapter helpers to register GraphQL+HTTP endpoints in your web framework of choice. Generated resolvers always consult the field-level permission metadata before writing or returning fields.
Note: In production you will typically wire the generated GraphQL schema into your server framework and use a migration tool (e.g. Alembic) for DB migrations. The repository aims to be a clear demo of the generator approach, with practical, re-usable primitives.
Each Field supports permission metadata controlling whether that field is included for create/read/update/delete operations. The generated GraphQL Input types and resolvers are generated accordingly:
- A field with
creatable=Falsewill be omitted fromCreateinputs and thecreateresolver will ignore values supplied for it. - A field with
readable=Falsewill not appear in returned object types or will be redacted by resolvers. update/deletecontrols apply similarly inUpdateresolvers and deletion policies.
This approach centralizes business-level access control in model declarations and reduces the risk of accidentally exposing or mutating fields.
gqlalchemy/types.py— typed field wrappers and defaults.gqlalchemy/field.py— theFieldobject and permission flags.gqlalchemy/model.py— theModelmixin and resolver metadata.gqlalchemy/filters.py— filter input generation and SQLAlchemy expression compilation.gqlalchemy/schema.py— GraphQL schema generation.gqlalchemy/backends/— small adapters that demonstrate how to connect resolvers to SQLAlchemy sessions.
This repo is a demonstration: if you want a production-ready package we can:
- add explicit middleware examples for authentication & authorization,
- provide integration examples with Apollo/Graphene servers and common Python web frameworks,
- generate ready-to-run example projects and Dockerized demos,
- extend the permissions model (roles, field-level policies, multi-tenant scoping).
If you want any of the above, tell me which direction you prefer and I will update the README or produce example code / a patch.
You can generate a graphene.Schema with gqlalchemy.schema.generate_schema(Base, Model) and expose it via a FastAPI app. The function returns a graphene.Schema instance which can be used with a GraphQL ASGI adapter (or executed directly with schema.execute_async). The example below shows two ways to wire the generated schema into FastAPI: using a GraphQL ASGI adapter (preferred) and a minimal HTTP endpoint that calls schema.execute_async directly.
from fastapi import FastAPI, Request
from gqlalchemy.schema import generate_schema
# assume `Base` and `Model` are already defined and your models imported
schema = generate_schema(Base, Model) # -> graphene.Schema
app = FastAPI()
# Option A: use a GraphQL ASGI adapter (preferred)
from starlette.graphql import GraphQLApp # or another adapter provided by your GraphQL server package
app.add_route("/graphql", GraphQLApp(schema=schema))
# Option B: minimal HTTP endpoint that accepts GraphQL requests and uses schema.execute_async
@app.post("/graphql")
async def graphql_post(request: Request):
payload = await request.json()
result = await schema.execute_async(
payload.get("query"),
variable_values=payload.get("variables"),
context_value={"request": request},
)
# `result.to_dict()` provides `data` and `errors` in a GraphQL-compatible shape
return result.to_dict()Notes & recommendations:
- Prefer using a fully-featured GraphQL server adapter when possible (GraphQL Playground, GraphiQL, subscriptions support, batching, persisted queries). These adapters provide tooling and production-ready features that a minimal HTTP wrapper does not.
schema.execute_asyncacceptscontext_value(use it to pass request/user/session), and returns an execution result that can be converted into a GraphQL response structure (result.to_dict()).- In production combine the generated schema with authentication/authorization middleware so per-field permissions defined in model metadata are enforced with your app's user/session context.