sqlmodel-graphql 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sqlmodel_graphql-0.1.0/.claude/settings.local.json +10 -0
- sqlmodel_graphql-0.1.0/.github/workflows/ci.yml +40 -0
- sqlmodel_graphql-0.1.0/.github/workflows/publish.yml +41 -0
- sqlmodel_graphql-0.1.0/.gitignore +48 -0
- sqlmodel_graphql-0.1.0/.python-version +1 -0
- sqlmodel_graphql-0.1.0/CHANGELOG.md +46 -0
- sqlmodel_graphql-0.1.0/LICENSE +21 -0
- sqlmodel_graphql-0.1.0/PKG-INFO +312 -0
- sqlmodel_graphql-0.1.0/PUBLISHING.md +245 -0
- sqlmodel_graphql-0.1.0/README.md +278 -0
- sqlmodel_graphql-0.1.0/RELEASE.md +250 -0
- sqlmodel_graphql-0.1.0/UV_PUBLISH.md +106 -0
- sqlmodel_graphql-0.1.0/demo/README.md +116 -0
- sqlmodel_graphql-0.1.0/demo/__init__.py +1 -0
- sqlmodel_graphql-0.1.0/demo/app.py +257 -0
- sqlmodel_graphql-0.1.0/demo/database.py +71 -0
- sqlmodel_graphql-0.1.0/demo/models.py +207 -0
- sqlmodel_graphql-0.1.0/how_it_works.md +652 -0
- sqlmodel_graphql-0.1.0/pyproject.toml +73 -0
- sqlmodel_graphql-0.1.0/src/sqlmodel_graphql/__init__.py +56 -0
- sqlmodel_graphql-0.1.0/src/sqlmodel_graphql/decorator.py +145 -0
- sqlmodel_graphql-0.1.0/src/sqlmodel_graphql/handler.py +398 -0
- sqlmodel_graphql-0.1.0/src/sqlmodel_graphql/introspection.py +396 -0
- sqlmodel_graphql-0.1.0/src/sqlmodel_graphql/query_parser.py +82 -0
- sqlmodel_graphql-0.1.0/src/sqlmodel_graphql/sdl_generator.py +257 -0
- sqlmodel_graphql-0.1.0/src/sqlmodel_graphql/type_converter.py +158 -0
- sqlmodel_graphql-0.1.0/src/sqlmodel_graphql/types.py +155 -0
- sqlmodel_graphql-0.1.0/tests/__init__.py +0 -0
- sqlmodel_graphql-0.1.0/tests/test_introspection.py +266 -0
- sqlmodel_graphql-0.1.0/tests/test_query_meta_integration.py +336 -0
- sqlmodel_graphql-0.1.0/tests/test_sdl_generator.py +110 -0
- sqlmodel_graphql-0.1.0/uv.lock +799 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
name: Test
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
paths:
|
|
6
|
+
- 'src/sqlmodel_graphql/**'
|
|
7
|
+
- 'tests/**'
|
|
8
|
+
- 'pyproject.toml'
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
test:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
strategy:
|
|
14
|
+
matrix:
|
|
15
|
+
python-version: ["3.10", "3.11", "3.12"]
|
|
16
|
+
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v5
|
|
19
|
+
|
|
20
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
21
|
+
uses: actions/setup-python@v6
|
|
22
|
+
with:
|
|
23
|
+
python-version: ${{ matrix.python-version }}
|
|
24
|
+
|
|
25
|
+
- name: Set up uv
|
|
26
|
+
uses: astral-sh/setup-uv@v7
|
|
27
|
+
with:
|
|
28
|
+
enable-cache: true
|
|
29
|
+
|
|
30
|
+
- name: Install dependencies
|
|
31
|
+
run: uv sync --all-extras
|
|
32
|
+
|
|
33
|
+
- name: Run linter
|
|
34
|
+
run: uv run ruff check src/
|
|
35
|
+
|
|
36
|
+
- name: Run formatter check
|
|
37
|
+
run: uv run ruff format --check src/
|
|
38
|
+
|
|
39
|
+
- name: Test with pytest
|
|
40
|
+
run: uv run pytest tests/ -v
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
name: Publish to PyPI via uv
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
push:
|
|
6
|
+
tags:
|
|
7
|
+
- "v*"
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
publish:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
|
|
13
|
+
permissions:
|
|
14
|
+
contents: write
|
|
15
|
+
|
|
16
|
+
steps:
|
|
17
|
+
- name: Checkout repository
|
|
18
|
+
uses: actions/checkout@v5
|
|
19
|
+
|
|
20
|
+
- name: Set up uv
|
|
21
|
+
uses: astral-sh/setup-uv@v7
|
|
22
|
+
|
|
23
|
+
- name: Set up Python
|
|
24
|
+
uses: actions/setup-python@v6
|
|
25
|
+
with:
|
|
26
|
+
python-version: "3.12"
|
|
27
|
+
|
|
28
|
+
- name: Build the package
|
|
29
|
+
run: uv build
|
|
30
|
+
|
|
31
|
+
- name: Publish to PyPI
|
|
32
|
+
run: uv publish --token ${{ secrets.PYPI_PUBLISHER }}
|
|
33
|
+
|
|
34
|
+
- name: Create GitHub Release
|
|
35
|
+
uses: softprops/action-gh-release@v2
|
|
36
|
+
with:
|
|
37
|
+
body: See [CHANGELOG](https://github.com/allmonday/sqlmodel-graphql/blob/master/CHANGELOG.md) for details.
|
|
38
|
+
generate_release_notes: true
|
|
39
|
+
files: |
|
|
40
|
+
dist/*.tar.gz
|
|
41
|
+
dist/*.whl
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Python-generated files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[oc]
|
|
4
|
+
build/
|
|
5
|
+
dist/
|
|
6
|
+
wheels/
|
|
7
|
+
*.egg-info
|
|
8
|
+
*.egg
|
|
9
|
+
|
|
10
|
+
# Virtual environments
|
|
11
|
+
.venv
|
|
12
|
+
venv/
|
|
13
|
+
env/
|
|
14
|
+
ENV/
|
|
15
|
+
|
|
16
|
+
# Database files
|
|
17
|
+
*.db
|
|
18
|
+
*.sqlite
|
|
19
|
+
*.sqlite3
|
|
20
|
+
|
|
21
|
+
# IDE
|
|
22
|
+
.vscode/
|
|
23
|
+
.idea/
|
|
24
|
+
*.swp
|
|
25
|
+
*.swo
|
|
26
|
+
*~
|
|
27
|
+
|
|
28
|
+
# Testing
|
|
29
|
+
.pytest_cache/
|
|
30
|
+
.coverage
|
|
31
|
+
htmlcov/
|
|
32
|
+
.tox/
|
|
33
|
+
|
|
34
|
+
# MyPy
|
|
35
|
+
.mypy_cache/
|
|
36
|
+
.dmypy.json
|
|
37
|
+
dmypy.json
|
|
38
|
+
|
|
39
|
+
# Ruff
|
|
40
|
+
.ruff_cache/
|
|
41
|
+
|
|
42
|
+
# OS
|
|
43
|
+
.DS_Store
|
|
44
|
+
Thumbs.db
|
|
45
|
+
|
|
46
|
+
# Distribution
|
|
47
|
+
*.manifest
|
|
48
|
+
*.spec
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.12
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.1.0] - 2025-03-06
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- Initial release
|
|
12
|
+
- Automatic GraphQL SDL generation from SQLModel classes
|
|
13
|
+
- `@query` and `@mutation` decorators for defining GraphQL operations
|
|
14
|
+
- `GraphQLHandler` for executing GraphQL queries with auto-discovery support
|
|
15
|
+
- `QueryParser` for extracting QueryMeta from GraphQL queries
|
|
16
|
+
- `QueryMeta.to_options()` for generating SQLAlchemy query optimizations
|
|
17
|
+
- Support for nested relationship queries (User → Posts → Comments)
|
|
18
|
+
- Auto-discovery of entities from SQLModel or custom base classes
|
|
19
|
+
- Recursive collection of related entities through Relationship fields
|
|
20
|
+
- Type extraction from complex type hints (Optional, List, forward references)
|
|
21
|
+
- FastAPI integration example with GraphiQL interface
|
|
22
|
+
- Comprehensive documentation and examples
|
|
23
|
+
|
|
24
|
+
### Features
|
|
25
|
+
- **Auto-discovery**: Automatically finds all SQLModel entities with `@query/@mutation` decorators
|
|
26
|
+
- **Relationship Support**: Includes all related entities through Relationship fields
|
|
27
|
+
- **N+1 Prevention**: Automatic `selectinload` and `load_only` generation based on query fields
|
|
28
|
+
- **Type Safety**: Full support for Python type hints including forward references
|
|
29
|
+
- **Framework Agnostic**: Can be integrated with any web framework (FastAPI, Flask, etc.)
|
|
30
|
+
|
|
31
|
+
### Documentation
|
|
32
|
+
- README with quick start guide
|
|
33
|
+
- API reference documentation
|
|
34
|
+
- How it works explanation
|
|
35
|
+
- Demo application with examples
|
|
36
|
+
- Release guide for maintainers
|
|
37
|
+
|
|
38
|
+
## [Unreleased]
|
|
39
|
+
|
|
40
|
+
### Planned
|
|
41
|
+
- Subscription support
|
|
42
|
+
- More database backends (PostgreSQL, MySQL)
|
|
43
|
+
- Custom scalar types
|
|
44
|
+
- Input type generation
|
|
45
|
+
- Better error messages
|
|
46
|
+
- Performance benchmarks
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 tangkikodo
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sqlmodel-graphql
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: GraphQL SDL generation and query optimization for SQLModel
|
|
5
|
+
Project-URL: Homepage, https://github.com/allmonday/sqlmodel-graphql
|
|
6
|
+
Project-URL: Repository, https://github.com/allmonday/sqlmodel-graphql
|
|
7
|
+
Author-email: tangkikodo <allmonday@126.com>
|
|
8
|
+
License: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: fastapi,graphql,sqlalchemy,sqlmodel
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Requires-Dist: aiosqlite>=0.22.1
|
|
20
|
+
Requires-Dist: graphql-core>=3.2.0
|
|
21
|
+
Requires-Dist: greenlet>=3.3.2
|
|
22
|
+
Requires-Dist: sqlmodel>=0.0.14
|
|
23
|
+
Provides-Extra: demo
|
|
24
|
+
Requires-Dist: aiosqlite>=0.19.0; extra == 'demo'
|
|
25
|
+
Requires-Dist: fastapi>=0.100.0; extra == 'demo'
|
|
26
|
+
Requires-Dist: uvicorn>=0.23.0; extra == 'demo'
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: aiosqlite>=0.19.0; extra == 'dev'
|
|
29
|
+
Requires-Dist: mypy>=1.0.0; extra == 'dev'
|
|
30
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
|
|
31
|
+
Requires-Dist: pytest>=7.0.0; extra == 'dev'
|
|
32
|
+
Requires-Dist: ruff>=0.1.0; extra == 'dev'
|
|
33
|
+
Description-Content-Type: text/markdown
|
|
34
|
+
|
|
35
|
+
# SQLModel GraphQL
|
|
36
|
+
|
|
37
|
+
GraphQL SDL generation and query optimization for SQLModel.
|
|
38
|
+
|
|
39
|
+
## Features
|
|
40
|
+
|
|
41
|
+
- **Automatic SDL Generation**: Generate GraphQL schema from SQLModel classes
|
|
42
|
+
- **@query/@mutation Decorators**: Mark methods as GraphQL operations
|
|
43
|
+
- **Query Optimization**: Parse GraphQL queries to generate optimized SQLAlchemy queries
|
|
44
|
+
- **N+1 Prevention**: Automatic `selectinload` and `load_only` generation
|
|
45
|
+
|
|
46
|
+
## Installation
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pip install sqlmodel-graphql
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Or with uv:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
uv add sqlmodel-graphql
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Quick Start
|
|
59
|
+
|
|
60
|
+
### 1. Define Your Models
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
from typing import Optional
|
|
64
|
+
from sqlmodel import SQLModel, Field, Relationship, select
|
|
65
|
+
from sqlmodel_graphql import query, mutation, QueryMeta
|
|
66
|
+
|
|
67
|
+
class User(SQLModel, table=True):
|
|
68
|
+
id: Optional[int] = Field(default=None, primary_key=True)
|
|
69
|
+
name: str
|
|
70
|
+
email: str
|
|
71
|
+
posts: list["Post"] = Relationship(back_populates="author")
|
|
72
|
+
|
|
73
|
+
@query(name='users')
|
|
74
|
+
async def get_all(cls, limit: int = 10, query_meta: Optional[QueryMeta] = None) -> list['User']:
|
|
75
|
+
"""Get all users with optional query optimization."""
|
|
76
|
+
from demo.database import async_session
|
|
77
|
+
|
|
78
|
+
async with async_session() as session:
|
|
79
|
+
stmt = select(cls).limit(limit)
|
|
80
|
+
if query_meta:
|
|
81
|
+
# Apply optimization: only load requested fields and relationships
|
|
82
|
+
stmt = stmt.options(*query_meta.to_options(cls))
|
|
83
|
+
result = await session.exec(stmt)
|
|
84
|
+
return list(result.all())
|
|
85
|
+
|
|
86
|
+
@query(name='user')
|
|
87
|
+
async def get_by_id(cls, id: int, query_meta: Optional[QueryMeta] = None) -> Optional['User']:
|
|
88
|
+
return await fetch_user(id, query_meta)
|
|
89
|
+
|
|
90
|
+
@mutation(name='createUser')
|
|
91
|
+
async def create(cls, name: str, email: str) -> 'User':
|
|
92
|
+
return await create_user(name, email)
|
|
93
|
+
|
|
94
|
+
class Post(SQLModel, table=True):
|
|
95
|
+
id: Optional[int] = Field(default=None, primary_key=True)
|
|
96
|
+
title: str
|
|
97
|
+
content: str = ""
|
|
98
|
+
author_id: int = Field(foreign_key="user.id")
|
|
99
|
+
author: User = Relationship(back_populates="posts")
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### 2. Generate GraphQL SDL
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
from sqlmodel_graphql import SDLGenerator
|
|
106
|
+
|
|
107
|
+
generator = SDLGenerator([User, Post])
|
|
108
|
+
sdl = generator.generate()
|
|
109
|
+
print(sdl)
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Output:
|
|
113
|
+
|
|
114
|
+
```graphql
|
|
115
|
+
type User {
|
|
116
|
+
id: Int
|
|
117
|
+
name: String!
|
|
118
|
+
email: String!
|
|
119
|
+
posts: [Post!]!
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
type Post {
|
|
123
|
+
id: Int
|
|
124
|
+
title: String!
|
|
125
|
+
content: String!
|
|
126
|
+
author_id: Int!
|
|
127
|
+
author: User!
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
type Query {
|
|
131
|
+
users(limit: Int): [User!]!
|
|
132
|
+
user(id: Int!): User
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
type Mutation {
|
|
136
|
+
createUser(name: String!, email: String!): User!
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### 3. Execute Queries with GraphQLHandler
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
from sqlmodel_graphql import GraphQLHandler
|
|
144
|
+
|
|
145
|
+
# Option 1: Auto-discover all entities (recommended)
|
|
146
|
+
handler = GraphQLHandler()
|
|
147
|
+
|
|
148
|
+
# Option 2: Use a custom base class
|
|
149
|
+
class Base(SQLModel):
|
|
150
|
+
pass
|
|
151
|
+
|
|
152
|
+
class User(Base, table=True):
|
|
153
|
+
...
|
|
154
|
+
|
|
155
|
+
handler = GraphQLHandler(base=Base)
|
|
156
|
+
|
|
157
|
+
# Option 3: Explicitly specify entities
|
|
158
|
+
handler = GraphQLHandler(entities=[User, Post])
|
|
159
|
+
|
|
160
|
+
# Execute a GraphQL query
|
|
161
|
+
result = await handler.execute("""
|
|
162
|
+
{
|
|
163
|
+
users(limit: 5) {
|
|
164
|
+
id
|
|
165
|
+
name
|
|
166
|
+
posts {
|
|
167
|
+
title
|
|
168
|
+
comments {
|
|
169
|
+
content
|
|
170
|
+
author {
|
|
171
|
+
name
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
""")
|
|
178
|
+
|
|
179
|
+
# Result includes nested relationships automatically:
|
|
180
|
+
# {
|
|
181
|
+
# "data": {
|
|
182
|
+
# "users": [
|
|
183
|
+
# {
|
|
184
|
+
# "id": 1,
|
|
185
|
+
# "name": "Alice",
|
|
186
|
+
# "posts": [
|
|
187
|
+
# {
|
|
188
|
+
# "title": "Hello World",
|
|
189
|
+
# "comments": [
|
|
190
|
+
# {"content": "Great post!", "author": {"name": "Bob"}}
|
|
191
|
+
# ]
|
|
192
|
+
# }
|
|
193
|
+
# ]
|
|
194
|
+
# }
|
|
195
|
+
# ]
|
|
196
|
+
# }
|
|
197
|
+
# }
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## How It Works
|
|
201
|
+
|
|
202
|
+
```
|
|
203
|
+
GraphQL Query QueryMeta
|
|
204
|
+
───────────── ─────────
|
|
205
|
+
{ users { QueryMeta(
|
|
206
|
+
id fields=[FieldSelection('id'), FieldSelection('name')],
|
|
207
|
+
name relationships={
|
|
208
|
+
posts { 'posts': RelationshipSelection(
|
|
209
|
+
title fields=[FieldSelection('title')]
|
|
210
|
+
} }
|
|
211
|
+
} }
|
|
212
|
+
)
|
|
213
|
+
↓
|
|
214
|
+
query_meta.to_options(User)
|
|
215
|
+
↓
|
|
216
|
+
select(User).options(
|
|
217
|
+
load_only(User.id, User.name),
|
|
218
|
+
selectinload(User.posts).options(load_only(Post.title))
|
|
219
|
+
)
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### Query Optimization Flow
|
|
223
|
+
|
|
224
|
+
1. **GraphQLHandler** receives the query
|
|
225
|
+
2. **QueryParser** parses the selection set into **QueryMeta**
|
|
226
|
+
3. **QueryMeta** is injected into your `@query` method as `query_meta` parameter
|
|
227
|
+
4. **query_meta.to_options(entity)** generates SQLAlchemy options:
|
|
228
|
+
- `load_only()` for requested scalar fields
|
|
229
|
+
- `selectinload()` for requested relationships
|
|
230
|
+
5. Database query only fetches what's needed, preventing N+1 problems
|
|
231
|
+
|
|
232
|
+
## API Reference
|
|
233
|
+
|
|
234
|
+
### `@query(name=None, description=None)`
|
|
235
|
+
|
|
236
|
+
Mark a method as a GraphQL query.
|
|
237
|
+
|
|
238
|
+
```python
|
|
239
|
+
@query(name='users', description='Get all users')
|
|
240
|
+
async def get_all(cls, limit: int = 10, query_meta: Optional[QueryMeta] = None) -> list['User']:
|
|
241
|
+
...
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### `@mutation(name=None, description=None)`
|
|
245
|
+
|
|
246
|
+
Mark a method as a GraphQL mutation.
|
|
247
|
+
|
|
248
|
+
```python
|
|
249
|
+
@mutation(name='createUser')
|
|
250
|
+
async def create(cls, name: str, email: str) -> 'User':
|
|
251
|
+
...
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### `SDLGenerator(entities)`
|
|
255
|
+
|
|
256
|
+
Generate GraphQL SDL from SQLModel classes.
|
|
257
|
+
|
|
258
|
+
```python
|
|
259
|
+
generator = SDLGenerator([User, Post])
|
|
260
|
+
sdl = generator.generate()
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### `GraphQLHandler(entities=None, base=None)`
|
|
264
|
+
|
|
265
|
+
Execute GraphQL queries against SQLModel entities with auto-discovery support.
|
|
266
|
+
|
|
267
|
+
```python
|
|
268
|
+
# Auto-discover from SQLModel (default)
|
|
269
|
+
handler = GraphQLHandler()
|
|
270
|
+
|
|
271
|
+
# Use custom base class
|
|
272
|
+
handler = GraphQLHandler(base=MyBase)
|
|
273
|
+
|
|
274
|
+
# Explicit entities
|
|
275
|
+
handler = GraphQLHandler(entities=[User, Post])
|
|
276
|
+
|
|
277
|
+
result = await handler.execute("{ users { id name } }")
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
**Auto-Discovery Features:**
|
|
281
|
+
- Automatically finds all SQLModel subclasses with `@query/@mutation` decorators
|
|
282
|
+
- Includes all related entities through Relationship fields
|
|
283
|
+
- Supports custom base classes for better organization
|
|
284
|
+
- Recursive discovery of nested relationships
|
|
285
|
+
|
|
286
|
+
### `QueryParser()`
|
|
287
|
+
|
|
288
|
+
Parse GraphQL queries to QueryMeta.
|
|
289
|
+
|
|
290
|
+
```python
|
|
291
|
+
parser = QueryParser()
|
|
292
|
+
metas = parser.parse("{ users { id name } }")
|
|
293
|
+
# metas['users'] -> QueryMeta(fields=[...], relationships={...})
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### `QueryMeta`
|
|
297
|
+
|
|
298
|
+
Metadata extracted from GraphQL selection set.
|
|
299
|
+
|
|
300
|
+
```python
|
|
301
|
+
@dataclass
|
|
302
|
+
class QueryMeta:
|
|
303
|
+
fields: list[FieldSelection]
|
|
304
|
+
relationships: dict[str, RelationshipSelection]
|
|
305
|
+
|
|
306
|
+
def to_options(self, entity: type[SQLModel]) -> list[Any]:
|
|
307
|
+
"""Convert to SQLAlchemy options for query optimization."""
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
## License
|
|
311
|
+
|
|
312
|
+
MIT License
|