graphql-api 1.6.6__tar.gz → 1.6.8__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.
- {graphql_api-1.6.6 → graphql_api-1.6.8}/.github/workflows/pages.yml +1 -1
- graphql_api-1.6.8/AGENTS.md +47 -0
- graphql_api-1.6.8/CLAUDE.md +47 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/PKG-INFO +39 -32
- {graphql_api-1.6.6 → graphql_api-1.6.8}/README.md +39 -32
- graphql_api-1.6.8/VERSION +1 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/docs/content/docs/fundamentals/async-and-subscriptions.md +23 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/docs/content/docs/fundamentals/defining-schemas.md +33 -1
- {graphql_api-1.6.6 → graphql_api-1.6.8}/docs/content/docs/fundamentals/getting-started.md +40 -5
- {graphql_api-1.6.6 → graphql_api-1.6.8}/docs/content/docs/fundamentals/mutations.md +28 -1
- {graphql_api-1.6.6 → graphql_api-1.6.8}/docs/content/docs/reference/api-reference.md +49 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/graphql_api/api.py +208 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/graphql_api/executor.py +69 -11
- {graphql_api-1.6.6 → graphql_api-1.6.8}/graphql_api/pydantic.py +39 -5
- {graphql_api-1.6.6 → graphql_api-1.6.8}/graphql_api/utils.py +6 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/graphql_api.egg-info/PKG-INFO +39 -32
- {graphql_api-1.6.6 → graphql_api-1.6.8}/graphql_api.egg-info/SOURCES.txt +3 -0
- graphql_api-1.6.8/tests/test_plain_functions.py +461 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/tests/test_pydantic.py +98 -0
- graphql_api-1.6.6/VERSION +0 -1
- {graphql_api-1.6.6 → graphql_api-1.6.8}/.github/workflows/python-package.yml +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/.gitignore +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/.vscode/settings.json +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/LICENSE +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/docs/README.md +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/docs/content/_index.md +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/docs/content/docs/_index.md +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/docs/content/docs/advanced-features/_index.md +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/docs/content/docs/advanced-features/directives.md +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/docs/content/docs/advanced-features/performance.md +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/docs/content/docs/advanced-features/schema-filtering.md +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/docs/content/docs/building-applications/_index.md +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/docs/content/docs/building-applications/documentation.md +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/docs/content/docs/building-applications/error-handling.md +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/docs/content/docs/distributed-systems/_index.md +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/docs/content/docs/distributed-systems/federation.md +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/docs/content/docs/distributed-systems/pagination-relay.md +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/docs/content/docs/distributed-systems/remote-graphql.md +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/docs/content/docs/fundamentals/_index.md +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/docs/content/docs/fundamentals/enums-interfaces.md +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/docs/content/docs/fundamentals/field-types.md +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/docs/content/docs/fundamentals/object-types.md +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/docs/content/docs/fundamentals/pydantic-and-dataclasses.md +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/docs/content/docs/production/_index.md +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/docs/content/docs/production/context-metadata.md +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/docs/content/docs/production/middleware.md +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/docs/content/docs/reference/_index.md +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/docs/content/docs/reference/contributing.md +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/docs/content/docs/reference/examples.md +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/docs/hugo.toml +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/docs/index.md +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/graphql_api/__init__.py +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/graphql_api/context.py +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/graphql_api/dataclass_mapping.py +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/graphql_api/decorators.py +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/graphql_api/directives.py +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/graphql_api/error.py +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/graphql_api/exception.py +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/graphql_api/federation/__init__.py +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/graphql_api/federation/directives.py +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/graphql_api/federation/federation.py +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/graphql_api/federation/types.py +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/graphql_api/mapper.py +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/graphql_api/middleware.py +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/graphql_api/mutation_schema.py +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/graphql_api/pydantic_utils.py +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/graphql_api/reduce.py +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/graphql_api/relay.py +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/graphql_api/remote.py +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/graphql_api/schema.py +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/graphql_api/schema_filter.py +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/graphql_api/type_utils.py +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/graphql_api/types.py +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/graphql_api.egg-info/dependency_links.txt +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/graphql_api.egg-info/requires.txt +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/graphql_api.egg-info/top_level.txt +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/pyproject.toml +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/setup.cfg +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/tests/__init__.py +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/tests/test_async.py +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/tests/test_calculator.py +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/tests/test_custom_suffixes.py +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/tests/test_custom_types.py +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/tests/test_dataclass.py +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/tests/test_dataclass_relationships.py +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/tests/test_decorator_patterns.py +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/tests/test_directives.py +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/tests/test_docs_advanced.py +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/tests/test_docs_async_subscriptions.py +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/tests/test_docs_defining_schemas.py +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/tests/test_docs_field_types.py +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/tests/test_docs_getting_started.py +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/tests/test_docs_relay.py +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/tests/test_docstrings.py +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/tests/test_error.py +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/tests/test_federation.py +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/tests/test_federation_example.graphql +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/tests/test_federation_example.py +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/tests/test_filtering.py +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/tests/test_graphql.py +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/tests/test_middleware.py +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/tests/test_performance.py +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/tests/test_relay.py +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/tests/test_remote.py +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/tests/test_schema.py +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/tests/test_subscriptions.py +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/tests/test_unified_vs_explicit_schema.py +0 -0
- {graphql_api-1.6.6 → graphql_api-1.6.8}/uv.lock +0 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# GraphQL-API
|
|
2
|
+
|
|
3
|
+
> Compatibility note: `AGENTS.md` and `CLAUDE.md` are both supported in this repo.
|
|
4
|
+
> Keep these files identical. Any change in one must be mirrored in the other.
|
|
5
|
+
|
|
6
|
+
Code-first, decorator-based GraphQL framework for Python. Published on [PyPI](https://pypi.org/project/graphql-api/).
|
|
7
|
+
|
|
8
|
+
## Project Structure
|
|
9
|
+
|
|
10
|
+
| Directory | Description |
|
|
11
|
+
|-----------|-------------|
|
|
12
|
+
| `graphql_api/` | Main package source |
|
|
13
|
+
| `tests/` | Test suite (34 files, pytest) |
|
|
14
|
+
| `docs/` | Sphinx documentation |
|
|
15
|
+
|
|
16
|
+
## Development
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
# Install dependencies
|
|
20
|
+
uv sync
|
|
21
|
+
|
|
22
|
+
# Run tests
|
|
23
|
+
uv run pytest
|
|
24
|
+
|
|
25
|
+
# Run linter
|
|
26
|
+
uv run flake8 graphql_api tests
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Key Patterns
|
|
30
|
+
|
|
31
|
+
- **Automatic snake_case to camelCase**: All Python field names, arguments, and dataclass/Pydantic fields are auto-converted to camelCase in GraphQL. Input arguments are converted back to snake_case. Conversion lives in `graphql_api/utils.py` (`to_camel_case()`, `to_snake_case()`)
|
|
32
|
+
- `@field` marks a method as a query; `@field(mutable=True)` makes it a mutation; `AsyncGenerator` return type or `subscription=True` makes it a subscription
|
|
33
|
+
- Plain functions: `@api.query` / `@api.mutation` / `@api.subscription` register a free function as a root-level field — no class required. Composes with class-based roots (registered fns are folded onto the user-provided `query_type` / `mutation_type` / `subscription_type` / `root_type`)
|
|
34
|
+
- Python types auto-map: `str`→String, `int`→Int, `bool`→Boolean, `float`→Float, `UUID`→UUID scalar, `datetime`→DateTime, `Optional[T]`→nullable, `List[T]`→list, dataclasses/Pydantic models→object types, enums→enum types
|
|
35
|
+
- Schema setup: `GraphQLAPI(query_type=Q, mutation_type=M, subscription_type=S)` or legacy `root_type=Root` (or neither — just `@api.query`/`@api.mutation`/`@api.subscription`)
|
|
36
|
+
|
|
37
|
+
## Releasing
|
|
38
|
+
|
|
39
|
+
See the ecosystem-level `CLAUDE.md` in the parent workspace for the full release process. In short:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# Ensure CI is green on main, then:
|
|
43
|
+
git tag X.Y.Z
|
|
44
|
+
git push origin X.Y.Z
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
CI publishes to PyPI and creates a GitHub Release automatically.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# GraphQL-API
|
|
2
|
+
|
|
3
|
+
> Compatibility note: `AGENTS.md` and `CLAUDE.md` are both supported in this repo.
|
|
4
|
+
> Keep these files identical. Any change in one must be mirrored in the other.
|
|
5
|
+
|
|
6
|
+
Code-first, decorator-based GraphQL framework for Python. Published on [PyPI](https://pypi.org/project/graphql-api/).
|
|
7
|
+
|
|
8
|
+
## Project Structure
|
|
9
|
+
|
|
10
|
+
| Directory | Description |
|
|
11
|
+
|-----------|-------------|
|
|
12
|
+
| `graphql_api/` | Main package source |
|
|
13
|
+
| `tests/` | Test suite (34 files, pytest) |
|
|
14
|
+
| `docs/` | Sphinx documentation |
|
|
15
|
+
|
|
16
|
+
## Development
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
# Install dependencies
|
|
20
|
+
uv sync
|
|
21
|
+
|
|
22
|
+
# Run tests
|
|
23
|
+
uv run pytest
|
|
24
|
+
|
|
25
|
+
# Run linter
|
|
26
|
+
uv run flake8 graphql_api tests
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Key Patterns
|
|
30
|
+
|
|
31
|
+
- **Automatic snake_case to camelCase**: All Python field names, arguments, and dataclass/Pydantic fields are auto-converted to camelCase in GraphQL. Input arguments are converted back to snake_case. Conversion lives in `graphql_api/utils.py` (`to_camel_case()`, `to_snake_case()`)
|
|
32
|
+
- `@field` marks a method as a query; `@field(mutable=True)` makes it a mutation; `AsyncGenerator` return type or `subscription=True` makes it a subscription
|
|
33
|
+
- Plain functions: `@api.query` / `@api.mutation` / `@api.subscription` register a free function as a root-level field — no class required. Composes with class-based roots (registered fns are folded onto the user-provided `query_type` / `mutation_type` / `subscription_type` / `root_type`)
|
|
34
|
+
- Python types auto-map: `str`→String, `int`→Int, `bool`→Boolean, `float`→Float, `UUID`→UUID scalar, `datetime`→DateTime, `Optional[T]`→nullable, `List[T]`→list, dataclasses/Pydantic models→object types, enums→enum types
|
|
35
|
+
- Schema setup: `GraphQLAPI(query_type=Q, mutation_type=M, subscription_type=S)` or legacy `root_type=Root` (or neither — just `@api.query`/`@api.mutation`/`@api.subscription`)
|
|
36
|
+
|
|
37
|
+
## Releasing
|
|
38
|
+
|
|
39
|
+
See the ecosystem-level `CLAUDE.md` in the parent workspace for the full release process. In short:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# Ensure CI is green on main, then:
|
|
43
|
+
git tag X.Y.Z
|
|
44
|
+
git push origin X.Y.Z
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
CI publishes to PyPI and creates a GitHub Release automatically.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: graphql-api
|
|
3
|
-
Version: 1.6.
|
|
3
|
+
Version: 1.6.8
|
|
4
4
|
Summary: A framework for building Python GraphQL APIs.
|
|
5
5
|
Author-email: Robert Parker <rob@parob.com>
|
|
6
6
|
License: MIT
|
|
@@ -28,10 +28,8 @@ Dynamic: license-file
|
|
|
28
28
|
[](https://badge.fury.io/py/graphql-api)
|
|
29
29
|
[](https://pypi.org/project/graphql-api/)
|
|
30
30
|
[](https://opensource.org/licenses/MIT)
|
|
31
|
-
[](https://gitlab.com/parob/graphql-api/commits/master)
|
|
32
|
-
[](https://gitlab.com/parob/graphql-api/commits/master)
|
|
33
31
|
|
|
34
|
-
**[
|
|
32
|
+
**[Documentation](https://graphql-api.parob.com/)** | **[PyPI](https://pypi.org/project/graphql-api/)** | **[GitHub](https://github.com/parob/graphql-api)**
|
|
35
33
|
|
|
36
34
|
---
|
|
37
35
|
|
|
@@ -59,46 +57,56 @@ pip install graphql-api
|
|
|
59
57
|
|
|
60
58
|
## Quick Start
|
|
61
59
|
|
|
62
|
-
|
|
60
|
+
For simple APIs, register plain functions directly — no class required:
|
|
63
61
|
|
|
64
62
|
```python
|
|
65
63
|
# example.py
|
|
66
64
|
from graphql_api.api import GraphQLAPI
|
|
67
65
|
|
|
68
|
-
# 1. Initialize the API
|
|
69
66
|
api = GraphQLAPI()
|
|
70
67
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
""
|
|
75
|
-
The root query for our amazing API.
|
|
76
|
-
"""
|
|
77
|
-
@api.field
|
|
78
|
-
def hello(self, name: str = "World") -> str:
|
|
79
|
-
"""
|
|
80
|
-
A classic greeting.
|
|
81
|
-
"""
|
|
82
|
-
return f"Hello, {name}!"
|
|
68
|
+
@api.query
|
|
69
|
+
def hello(name: str = "World") -> str:
|
|
70
|
+
"""A classic greeting."""
|
|
71
|
+
return f"Hello, {name}!"
|
|
83
72
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
hello(name: "Developer")
|
|
88
|
-
}
|
|
89
|
-
"""
|
|
73
|
+
@api.mutation
|
|
74
|
+
def set_name(name: str) -> str:
|
|
75
|
+
return f"Name set to {name}"
|
|
90
76
|
|
|
91
|
-
# 4. Execute the query
|
|
92
77
|
if __name__ == "__main__":
|
|
93
|
-
result = api.execute(
|
|
78
|
+
result = api.execute('query { hello(name: "Developer") }')
|
|
94
79
|
print(result.data)
|
|
95
80
|
```
|
|
96
81
|
|
|
97
|
-
Running this script will produce:
|
|
98
|
-
|
|
99
82
|
```bash
|
|
100
83
|
$ python example.py
|
|
101
|
-
{'hello': 'Hello, Developer'}
|
|
84
|
+
{'hello': 'Hello, Developer!'}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Use `@api.query`, `@api.mutation`, and `@api.subscription` for root-level
|
|
88
|
+
fields. For nested object types, reach for the class-based form below.
|
|
89
|
+
|
|
90
|
+
### Class-based form
|
|
91
|
+
|
|
92
|
+
When you need object types with their own fields (relationships, computed
|
|
93
|
+
properties, nested resolvers), define a root class:
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
from graphql_api.api import GraphQLAPI
|
|
97
|
+
|
|
98
|
+
api = GraphQLAPI()
|
|
99
|
+
|
|
100
|
+
@api.type(is_root_type=True)
|
|
101
|
+
class Query:
|
|
102
|
+
"""The root query for our amazing API."""
|
|
103
|
+
@api.field
|
|
104
|
+
def hello(self, name: str = "World") -> str:
|
|
105
|
+
return f"Hello, {name}!"
|
|
106
|
+
|
|
107
|
+
result = api.execute('query { hello(name: "Developer") }')
|
|
108
|
+
print(result.data)
|
|
109
|
+
# {'hello': 'Hello, Developer!'}
|
|
102
110
|
```
|
|
103
111
|
|
|
104
112
|
## Examples
|
|
@@ -234,9 +242,8 @@ To contribute or run the test suite locally:
|
|
|
234
242
|
|
|
235
243
|
```bash
|
|
236
244
|
# Install dependencies
|
|
237
|
-
|
|
238
|
-
pipenv install --dev
|
|
245
|
+
uv sync
|
|
239
246
|
|
|
240
247
|
# Run tests
|
|
241
|
-
|
|
248
|
+
uv run pytest
|
|
242
249
|
```
|
|
@@ -3,10 +3,8 @@
|
|
|
3
3
|
[](https://badge.fury.io/py/graphql-api)
|
|
4
4
|
[](https://pypi.org/project/graphql-api/)
|
|
5
5
|
[](https://opensource.org/licenses/MIT)
|
|
6
|
-
[](https://gitlab.com/parob/graphql-api/commits/master)
|
|
7
|
-
[](https://gitlab.com/parob/graphql-api/commits/master)
|
|
8
6
|
|
|
9
|
-
**[
|
|
7
|
+
**[Documentation](https://graphql-api.parob.com/)** | **[PyPI](https://pypi.org/project/graphql-api/)** | **[GitHub](https://github.com/parob/graphql-api)**
|
|
10
8
|
|
|
11
9
|
---
|
|
12
10
|
|
|
@@ -34,46 +32,56 @@ pip install graphql-api
|
|
|
34
32
|
|
|
35
33
|
## Quick Start
|
|
36
34
|
|
|
37
|
-
|
|
35
|
+
For simple APIs, register plain functions directly — no class required:
|
|
38
36
|
|
|
39
37
|
```python
|
|
40
38
|
# example.py
|
|
41
39
|
from graphql_api.api import GraphQLAPI
|
|
42
40
|
|
|
43
|
-
# 1. Initialize the API
|
|
44
41
|
api = GraphQLAPI()
|
|
45
42
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
""
|
|
50
|
-
The root query for our amazing API.
|
|
51
|
-
"""
|
|
52
|
-
@api.field
|
|
53
|
-
def hello(self, name: str = "World") -> str:
|
|
54
|
-
"""
|
|
55
|
-
A classic greeting.
|
|
56
|
-
"""
|
|
57
|
-
return f"Hello, {name}!"
|
|
43
|
+
@api.query
|
|
44
|
+
def hello(name: str = "World") -> str:
|
|
45
|
+
"""A classic greeting."""
|
|
46
|
+
return f"Hello, {name}!"
|
|
58
47
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
hello(name: "Developer")
|
|
63
|
-
}
|
|
64
|
-
"""
|
|
48
|
+
@api.mutation
|
|
49
|
+
def set_name(name: str) -> str:
|
|
50
|
+
return f"Name set to {name}"
|
|
65
51
|
|
|
66
|
-
# 4. Execute the query
|
|
67
52
|
if __name__ == "__main__":
|
|
68
|
-
result = api.execute(
|
|
53
|
+
result = api.execute('query { hello(name: "Developer") }')
|
|
69
54
|
print(result.data)
|
|
70
55
|
```
|
|
71
56
|
|
|
72
|
-
Running this script will produce:
|
|
73
|
-
|
|
74
57
|
```bash
|
|
75
58
|
$ python example.py
|
|
76
|
-
{'hello': 'Hello, Developer'}
|
|
59
|
+
{'hello': 'Hello, Developer!'}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Use `@api.query`, `@api.mutation`, and `@api.subscription` for root-level
|
|
63
|
+
fields. For nested object types, reach for the class-based form below.
|
|
64
|
+
|
|
65
|
+
### Class-based form
|
|
66
|
+
|
|
67
|
+
When you need object types with their own fields (relationships, computed
|
|
68
|
+
properties, nested resolvers), define a root class:
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
from graphql_api.api import GraphQLAPI
|
|
72
|
+
|
|
73
|
+
api = GraphQLAPI()
|
|
74
|
+
|
|
75
|
+
@api.type(is_root_type=True)
|
|
76
|
+
class Query:
|
|
77
|
+
"""The root query for our amazing API."""
|
|
78
|
+
@api.field
|
|
79
|
+
def hello(self, name: str = "World") -> str:
|
|
80
|
+
return f"Hello, {name}!"
|
|
81
|
+
|
|
82
|
+
result = api.execute('query { hello(name: "Developer") }')
|
|
83
|
+
print(result.data)
|
|
84
|
+
# {'hello': 'Hello, Developer!'}
|
|
77
85
|
```
|
|
78
86
|
|
|
79
87
|
## Examples
|
|
@@ -209,9 +217,8 @@ To contribute or run the test suite locally:
|
|
|
209
217
|
|
|
210
218
|
```bash
|
|
211
219
|
# Install dependencies
|
|
212
|
-
|
|
213
|
-
pipenv install --dev
|
|
220
|
+
uv sync
|
|
214
221
|
|
|
215
222
|
# Run tests
|
|
216
|
-
|
|
217
|
-
```
|
|
223
|
+
uv run pytest
|
|
224
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
1.6.8
|
{graphql_api-1.6.6 → graphql_api-1.6.8}/docs/content/docs/fundamentals/async-and-subscriptions.md
RENAMED
|
@@ -101,6 +101,29 @@ Subscriptions can be defined using either of the two schema definition modes des
|
|
|
101
101
|
|
|
102
102
|
### Subscription Examples
|
|
103
103
|
|
|
104
|
+
**Using Plain Functions (Simplest):**
|
|
105
|
+
|
|
106
|
+
Register an async generator directly with `@api.subscription`:
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
import asyncio
|
|
110
|
+
from typing import AsyncGenerator
|
|
111
|
+
from graphql_api.api import GraphQLAPI
|
|
112
|
+
|
|
113
|
+
api = GraphQLAPI()
|
|
114
|
+
|
|
115
|
+
@api.query
|
|
116
|
+
def placeholder() -> str:
|
|
117
|
+
return "ok"
|
|
118
|
+
|
|
119
|
+
@api.subscription
|
|
120
|
+
async def count(to: int = 5) -> AsyncGenerator[int, None]:
|
|
121
|
+
"""Counts up to a given number, yielding each number."""
|
|
122
|
+
for i in range(1, to + 1):
|
|
123
|
+
await asyncio.sleep(1)
|
|
124
|
+
yield i
|
|
125
|
+
```
|
|
126
|
+
|
|
104
127
|
**Using Mode 1 (Single Root Type):**
|
|
105
128
|
|
|
106
129
|
```python
|
|
@@ -82,7 +82,39 @@ Global decorators avoid this issue since they don't require importing an API ins
|
|
|
82
82
|
|
|
83
83
|
## Schema Definition Modes
|
|
84
84
|
|
|
85
|
-
`graphql-api` offers
|
|
85
|
+
`graphql-api` offers three approaches for organizing your GraphQL operations (queries, mutations, subscriptions). They can be mixed in the same API.
|
|
86
|
+
|
|
87
|
+
### Mode 0: Plain Functions (Simplest)
|
|
88
|
+
|
|
89
|
+
Register free functions directly on the API — no class required. Good for small APIs, scripts, and cases where you don't need nested object types at the root.
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
from graphql_api.api import GraphQLAPI
|
|
93
|
+
|
|
94
|
+
api = GraphQLAPI()
|
|
95
|
+
|
|
96
|
+
@api.query
|
|
97
|
+
def hello(name: str = "World") -> str:
|
|
98
|
+
return f"Hello, {name}!"
|
|
99
|
+
|
|
100
|
+
@api.mutation
|
|
101
|
+
def set_counter(value: int) -> int:
|
|
102
|
+
return value
|
|
103
|
+
|
|
104
|
+
@api.subscription
|
|
105
|
+
async def ticks(to: int = 3): # AsyncGenerator[int, None]
|
|
106
|
+
for i in range(1, to + 1):
|
|
107
|
+
yield i
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Registered functions compose with any of the class-based modes below — they're folded onto whatever root/query/mutation/subscription type you supply, so you can gradually migrate from plain functions to classes (or vice versa) without breaking changes.
|
|
111
|
+
|
|
112
|
+
**Use when:**
|
|
113
|
+
- Building a small API or script where class boilerplate isn't earning its keep
|
|
114
|
+
- You want the lowest-friction onboarding for teammates new to GraphQL
|
|
115
|
+
- Mixing a handful of root fields onto an existing class-based schema
|
|
116
|
+
|
|
117
|
+
**Limitations:** plain functions have no `self` — so they can't share instance state. For request-scoped state, declare `context: GraphQLContext` as a parameter (same mechanism used by class-based fields). For nested object types, define them as classes and return instances from your registered function.
|
|
86
118
|
|
|
87
119
|
### Mode 1: Single Root Type (Strongly Recommended)
|
|
88
120
|
|
|
@@ -19,9 +19,44 @@ pip install graphql-api
|
|
|
19
19
|
|
|
20
20
|
Let's create a simple GraphQL API that returns a classic "Hello, World!" greeting.
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
There are two ways to define root fields — pick whichever fits the shape of your API:
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
- **Plain functions** — register a free function as a root query/mutation/subscription. Great for small APIs, scripts, and anything that doesn't need nested object types.
|
|
25
|
+
- **Class-based** — define a root class and decorate its methods. Use this when you have nested object types, shared state, or interrelated resolvers.
|
|
26
|
+
|
|
27
|
+
You can mix both in the same API. We'll show the plain-function version first, then the class-based version.
|
|
28
|
+
|
|
29
|
+
### Option A: Plain functions (simplest)
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
# main.py
|
|
33
|
+
from graphql_api.api import GraphQLAPI
|
|
34
|
+
|
|
35
|
+
api = GraphQLAPI()
|
|
36
|
+
|
|
37
|
+
@api.query
|
|
38
|
+
def hello(name: str = "World") -> str:
|
|
39
|
+
"""A classic greeting. This docstring becomes the field description."""
|
|
40
|
+
return f"Hello, {name}!"
|
|
41
|
+
|
|
42
|
+
if __name__ == "__main__":
|
|
43
|
+
result = api.execute('query { hello(name: "Developer") }')
|
|
44
|
+
print(result.data) # {'hello': 'Hello, Developer!'}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Three decorators are available on the API instance:
|
|
48
|
+
|
|
49
|
+
- `@api.query` — register a query field
|
|
50
|
+
- `@api.mutation` — register a mutation field
|
|
51
|
+
- `@api.subscription` — register a subscription field (function must return `AsyncGenerator[T, None]`)
|
|
52
|
+
|
|
53
|
+
They accept the same `meta={...}` and `directives=[...]` parameters as `@api.field`. Type hints, defaults, `Optional[T]`, dataclass/Pydantic inputs, and `GraphQLContext` injection all work identically to class-based fields.
|
|
54
|
+
|
|
55
|
+
### Option B: Class-based
|
|
56
|
+
|
|
57
|
+
Use a class when you need nested object types, shared state, or multiple interrelated resolvers.
|
|
58
|
+
|
|
59
|
+
#### 1. Initialize the API
|
|
25
60
|
|
|
26
61
|
```python
|
|
27
62
|
# main.py
|
|
@@ -30,9 +65,9 @@ from graphql_api.api import GraphQLAPI
|
|
|
30
65
|
api = GraphQLAPI()
|
|
31
66
|
```
|
|
32
67
|
|
|
33
|
-
|
|
68
|
+
#### 2. Define the Root Query
|
|
34
69
|
|
|
35
|
-
|
|
70
|
+
Define a class that will serve as your root query object. Use the `@api.type` decorator to mark it as the root type for your schema and the `@api.field` decorator to expose a method as a GraphQL field.
|
|
36
71
|
|
|
37
72
|
```python
|
|
38
73
|
# main.py
|
|
@@ -74,7 +109,7 @@ This generates:
|
|
|
74
109
|
example(requiredArg: String!, optionalArg: String): String!
|
|
75
110
|
```
|
|
76
111
|
|
|
77
|
-
|
|
112
|
+
#### 3. Execute a Query
|
|
78
113
|
|
|
79
114
|
Now you're ready to execute a query against your API.
|
|
80
115
|
|
|
@@ -9,7 +9,34 @@ description: >
|
|
|
9
9
|
|
|
10
10
|
GraphQL mutations allow clients to modify data on the server. This guide covers how to create mutations and define input types for complex data operations.
|
|
11
11
|
|
|
12
|
-
##
|
|
12
|
+
## Plain Functions (Simplest)
|
|
13
|
+
|
|
14
|
+
The lowest-friction way to define a mutation is a free function decorated with `@api.mutation` — no class required:
|
|
15
|
+
|
|
16
|
+
```python
|
|
17
|
+
from graphql_api.api import GraphQLAPI
|
|
18
|
+
|
|
19
|
+
api = GraphQLAPI()
|
|
20
|
+
|
|
21
|
+
@api.query
|
|
22
|
+
def get_user(user_id: str):
|
|
23
|
+
return find_user_by_id(user_id)
|
|
24
|
+
|
|
25
|
+
@api.mutation
|
|
26
|
+
def create_user(name: str, email: str) -> User:
|
|
27
|
+
"""Create a new user account."""
|
|
28
|
+
user = User(id=generate_id(), name=name, email=email)
|
|
29
|
+
save_user(user)
|
|
30
|
+
return user
|
|
31
|
+
|
|
32
|
+
@api.mutation
|
|
33
|
+
def delete_user(user_id: str) -> bool:
|
|
34
|
+
return delete_user_by_id(user_id)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Input types, `Optional[T]`, defaults, dataclass/Pydantic inputs, and `GraphQLContext` injection all work exactly as they do for class-based mutations (covered below).
|
|
38
|
+
|
|
39
|
+
## Basic Mutations (Class-based)
|
|
13
40
|
|
|
14
41
|
In Mode 1 (Single Root Type), mark mutation fields with `mutable=True`:
|
|
15
42
|
|
|
@@ -191,6 +191,55 @@ async def user_updates(self) -> AsyncGenerator[User, None]:
|
|
|
191
191
|
```
|
|
192
192
|
|
|
193
193
|
|
|
194
|
+
### `@api.query`, `@api.mutation`, `@api.subscription`
|
|
195
|
+
|
|
196
|
+
Register a plain (free) function as a root-level GraphQL field. Useful when you don't want to wrap root fields in a class.
|
|
197
|
+
|
|
198
|
+
```python
|
|
199
|
+
@api.query(
|
|
200
|
+
meta: Optional[Dict] = None,
|
|
201
|
+
directives: Optional[List] = None,
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
@api.mutation(
|
|
205
|
+
meta: Optional[Dict] = None,
|
|
206
|
+
directives: Optional[List] = None,
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
@api.subscription(
|
|
210
|
+
meta: Optional[Dict] = None,
|
|
211
|
+
directives: Optional[List] = None,
|
|
212
|
+
)
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
**Parameters:**
|
|
216
|
+
- `meta`: Optional metadata dictionary, retrievable in middleware (same semantics as `@api.field(meta=...)`).
|
|
217
|
+
- `directives`: Optional list of applied schema directives.
|
|
218
|
+
|
|
219
|
+
**Behavior:**
|
|
220
|
+
- `@api.query` adds the function to the root `Query` type.
|
|
221
|
+
- `@api.mutation` adds the function to the root `Mutation` type.
|
|
222
|
+
- `@api.subscription` adds the function to the root `Subscription` type. The function must return `AsyncGenerator[T, None]`.
|
|
223
|
+
- Registered functions compose with `query_type=` / `mutation_type=` / `subscription_type=` / `root_type=` — they are folded onto the user-supplied class so you can mix class-based and function-based root fields freely.
|
|
224
|
+
- Type hints, default values, `Optional[T]`, dataclass/Pydantic inputs, enum args, and `context: GraphQLContext` injection all work identically to `@api.field`.
|
|
225
|
+
- Plain functions have no `self`; use a `GraphQLContext` parameter for request-scoped state, and module-level or captured state otherwise.
|
|
226
|
+
|
|
227
|
+
**Bare and parameterized usage:**
|
|
228
|
+
```python
|
|
229
|
+
@api.query
|
|
230
|
+
def hello(name: str = "World") -> str:
|
|
231
|
+
return f"Hello, {name}!"
|
|
232
|
+
|
|
233
|
+
@api.mutation(meta={"audit": True})
|
|
234
|
+
def set_counter(value: int) -> int:
|
|
235
|
+
return value
|
|
236
|
+
|
|
237
|
+
@api.subscription
|
|
238
|
+
async def ticks(to: int = 3) -> AsyncGenerator[int, None]:
|
|
239
|
+
for i in range(1, to + 1):
|
|
240
|
+
yield i
|
|
241
|
+
```
|
|
242
|
+
|
|
194
243
|
### `@api.enum`
|
|
195
244
|
|
|
196
245
|
Mark an enum class as a GraphQL enum.
|