Skip to main content

GraphQL SDL generation and query optimization for SQLModel

Project description

SQLModel GraphQL

pypi PyPI Downloads Python Versions

The Simplest Way to GraphQL -- Enable Your SQLModel Application

No schema files. No resolvers. No boilerplate. Just decorators and Python. Your SQLModel classes become GraphQL APIs instantly.

Plus: Built-in MCP Server — Expose your data to AI assistants (Claude, GPT, etc.) with zero extra code.

Features

  • Automatic SDL Generation: Generate GraphQL schema from SQLModel classes
  • @query/@mutation Decorators: Mark methods as GraphQL operations
  • Query Optimization: Parse GraphQL queries to generate optimized SQLAlchemy queries
  • N+1 Prevention: Automatic selectinload and load_only generation
  • MCP Integration: Expose GraphQL as MCP tools for AI assistants

Installation

pip install sqlmodel-graphql

Or with uv:

uv add sqlmodel-graphql

Quick Start

1. Define Your Models

from typing import Optional
from sqlmodel import SQLModel, Field, Relationship, select
from sqlmodel_graphql import query, mutation, QueryMeta

class User(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    name: str
    email: str
    posts: list["Post"] = Relationship(back_populates="author")

    @query(name='users')
    async def get_all(cls, limit: int = 10, query_meta: Optional[QueryMeta] = None) -> list['User']:
        """Get all users with optional query optimization."""
        from demo.database import async_session

        async with async_session() as session:
            stmt = select(cls).limit(limit)
            if query_meta:
                # Apply optimization: only load requested fields and relationships
                stmt = stmt.options(*query_meta.to_options(cls))
            result = await session.exec(stmt)
            return list(result.all())

    @query(name='user')
    async def get_by_id(cls, id: int, query_meta: Optional[QueryMeta] = None) -> Optional['User']:
        return await fetch_user(id, query_meta)

    @mutation(name='createUser')
    async def create(cls, name: str, email: str) -> 'User':
        return await create_user(name, email)

class Post(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    title: str
    content: str = ""
    author_id: int = Field(foreign_key="user.id")
    author: User = Relationship(back_populates="posts")

2. Generate GraphQL SDL

from sqlmodel_graphql import SDLGenerator

generator = SDLGenerator([User, Post])
sdl = generator.generate()
print(sdl)

Output:

type User {
  id: Int
  name: String!
  email: String!
  posts: [Post!]!
}

type Post {
  id: Int
  title: String!
  content: String!
  author_id: Int!
  author: User!
}

type Query {
  users(limit: Int): [User!]!
  user(id: Int!): User
}

type Mutation {
  createUser(name: String!, email: String!): User!
}

3. Execute Queries with GraphQLHandler

from sqlmodel_graphql import GraphQLHandler

handler = GraphQLHandler(entities=[User, Post])

# Execute a GraphQL query
result = await handler.execute("""
{
  users(limit: 5) {
    id
    name
    posts {
      title
      comments {
        content
        author {
          name
        }
      }
    }
  }
}
""")

# Result includes nested relationships automatically:
# {
#   "data": {
#     "users": [
#       {
#         "id": 1,
#         "name": "Alice",
#         "posts": [
#           {
#             "title": "Hello World",
#             "comments": [
#               {"content": "Great post!", "author": {"name": "Bob"}}
#             ]
#           }
#         ]
#       }
#     ]
#   }
# }

MCP Integration

Turn your SQLModel entities into AI-ready tools with a single function call.

from sqlmodel_graphql.mcp import create_mcp_server
from myapp.models import BaseEntity

# Create MCP server from your base class
# All SQLModel subclasses with @query/@mutation decorators are auto-discovered
mcp = create_mcp_server(
    base=BaseEntity,
    name="My Blog API"
)

# Run for AI assistants (Claude Desktop, etc.)
mcp.run()  # stdio mode (default)
# mcp.run(transport="streamable-http")  # HTTP mode

Available MCP Tools

The server exposes three tools for AI:

  1. get_schema - Discover available queries, mutations, and types
  2. graphql_query - Execute dynamic GraphQL queries with dot-notation field paths
  3. graphql_mutation - Execute GraphQL mutations

Example: AI Query Flow

AI: What data is available?
    → get_schema() → Returns queries, mutations, types

AI: Get users with their posts
    → graphql_query(
        operation_name="users",
        arguments={"limit": 10},
        fields=["id", "name", "posts.title", "posts.content"]
      )

AI: Create a new user
    → graphql_mutation(
        operation_name="create_user",
        arguments={"name": "Alice", "email": "alice@example.com"},
        fields=["id", "name"]
      )

Why MCP?

Traditional GraphQL requires AI to:

  • Know the exact GraphQL syntax
  • Understand the full schema structure

With MCP, AI can:

  • Discover schema dynamically via get_schema
  • Query with simple field paths (no GraphQL syntax needed)
  • Focus on business logic, not query construction

Installation

# Core library
pip install sqlmodel-graphql

# MCP support (optional)
pip install mcp

Running MCP Server

# demo/mcp_server.py
uv run python --with mcp demo/mcp_server.py           # stdio mode
uv run python --with mcp demo/mcp_server.py --http    # HTTP mode

How It Works

GraphQL Query                        QueryMeta
─────────────                        ─────────
{ users {                            QueryMeta(
  id                                   fields=[FieldSelection('id'), FieldSelection('name')],
  name                                 relationships={
  posts {                                'posts': RelationshipSelection(
    title                                  fields=[FieldSelection('title')]
  }                                     }
}                                     }
)
       ↓
  query_meta.to_options(User)
       ↓
  select(User).options(
    load_only(User.id, User.name),
    selectinload(User.posts).options(load_only(Post.title))
  )

Query Optimization Flow

  1. GraphQLHandler receives the query
  2. QueryParser parses the selection set into QueryMeta
  3. QueryMeta is injected into your @query method as query_meta parameter
  4. query_meta.to_options(entity) generates SQLAlchemy options:
    • load_only() for requested scalar fields
    • selectinload() for requested relationships
  5. Database query only fetches what's needed, preventing N+1 problems

API Reference

@query(name=None, description=None)

Mark a method as a GraphQL query.

@query(name='users', description='Get all users')
async def get_all(cls, limit: int = 10, query_meta: Optional[QueryMeta] = None) -> list['User']:
    ...

@mutation(name=None, description=None)

Mark a method as a GraphQL mutation.

@mutation(name='createUser')
async def create(cls, name: str, email: str) -> 'User':
    ...

SDLGenerator(entities)

Generate GraphQL SDL from SQLModel classes.

generator = SDLGenerator([User, Post])
sdl = generator.generate()

GraphQLHandler(entities=None, base=None)

Execute GraphQL queries against SQLModel entities with auto-discovery support.

# Auto-discover from SQLModel (default)
handler = GraphQLHandler()

# Use custom base class
handler = GraphQLHandler(base=MyBase)

# Explicit entities
handler = GraphQLHandler(entities=[User, Post])

result = await handler.execute("{ users { id name } }")

Auto-Discovery Features:

  • Automatically finds all SQLModel subclasses with @query/@mutation decorators
  • Includes all related entities through Relationship fields
  • Supports custom base classes for better organization
  • Recursive discovery of nested relationships

QueryParser()

Parse GraphQL queries to QueryMeta.

parser = QueryParser()
metas = parser.parse("{ users { id name } }")
# metas['users'] -> QueryMeta(fields=[...], relationships={...})

QueryMeta

Metadata extracted from GraphQL selection set.

@dataclass
class QueryMeta:
    fields: list[FieldSelection]
    relationships: dict[str, RelationshipSelection]

    def to_options(self, entity: type[SQLModel]) -> list[Any]:
        """Convert to SQLAlchemy options for query optimization."""

License

MIT License

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

sqlmodel_graphql-0.3.0.tar.gz (124.8 kB view details)

Uploaded Source

Built Distribution

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

sqlmodel_graphql-0.3.0-py3-none-any.whl (44.1 kB view details)

Uploaded Python 3

File details

Details for the file sqlmodel_graphql-0.3.0.tar.gz.

File metadata

  • Download URL: sqlmodel_graphql-0.3.0.tar.gz
  • Upload date:
  • Size: 124.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.10.9 {"installer":{"name":"uv","version":"0.10.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for sqlmodel_graphql-0.3.0.tar.gz
Algorithm Hash digest
SHA256 bf4bad71bace74a1241aecdd96fa8024501456673de5383def60e28eaadaa3ec
MD5 41c771c1541933746e5b9a87d51e5356
BLAKE2b-256 e4fb21efa9dd8bb9209035953eb330062152270e561dd5d077b6e1d45bdcd32c

See more details on using hashes here.

File details

Details for the file sqlmodel_graphql-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: sqlmodel_graphql-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 44.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.10.9 {"installer":{"name":"uv","version":"0.10.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for sqlmodel_graphql-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 ded92f00c2c5a9409c3dd2f85eb1a571ba073d449f5a032b1283c4b7e440dcc9
MD5 79bf3a9411b3d01349b6f8d074cbb939
BLAKE2b-256 54db720b06a490f5fe9a7a4f38473f757400d79e7177aec3d025397f9fc97a8d

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