Skip to content

Latest commit

 

History

14 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

gqlalchemy

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:

  1. a database schema (SQLAlchemy metadata / helpers),
  2. a GraphQL schema (Graphene types and inputs), and
  3. 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.


Philosophy

  • 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 Field wrapper + Model mixin 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.

Highlights

  • Generate GraphQL ObjectType and Input definitions automatically from SQLAlchemy models.
  • Generate Query and Mutation fields (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, and delete at 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 AsyncEngine sessions or your own session provider.

Quick example (model-first)

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 username is writable only on create, email is writable on create & update, and is_admin is read-only;
  • generate a SQLAlchemy table definition (so you may create tables or generate migration scripts from the same metadata);
  • expose user queries and mutations (user, userList, createUser, updateUser, deleteUser) where each resolver respects the per-field permissions.

Generating artifacts (overview)

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 a graphene.Schema that includes Query and Mutation entries 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.


Permissions & access control

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=False will be omitted from Create inputs and the create resolver will ignore values supplied for it.
  • A field with readable=False will not appear in returned object types or will be redacted by resolvers.
  • update/delete controls apply similarly in Update resolvers and deletion policies.

This approach centralizes business-level access control in model declarations and reduces the risk of accidentally exposing or mutating fields.


Where to look in the code

  • gqlalchemy/types.py — typed field wrappers and defaults.
  • gqlalchemy/field.py — the Field object and permission flags.
  • gqlalchemy/model.py — the Model mixin 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.

Contributing & next steps

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.


FastAPI integration

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_async accepts context_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.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages