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.
Files changed (108) hide show
  1. {graphql_api-1.6.6 → graphql_api-1.6.8}/.github/workflows/pages.yml +1 -1
  2. graphql_api-1.6.8/AGENTS.md +47 -0
  3. graphql_api-1.6.8/CLAUDE.md +47 -0
  4. {graphql_api-1.6.6 → graphql_api-1.6.8}/PKG-INFO +39 -32
  5. {graphql_api-1.6.6 → graphql_api-1.6.8}/README.md +39 -32
  6. graphql_api-1.6.8/VERSION +1 -0
  7. {graphql_api-1.6.6 → graphql_api-1.6.8}/docs/content/docs/fundamentals/async-and-subscriptions.md +23 -0
  8. {graphql_api-1.6.6 → graphql_api-1.6.8}/docs/content/docs/fundamentals/defining-schemas.md +33 -1
  9. {graphql_api-1.6.6 → graphql_api-1.6.8}/docs/content/docs/fundamentals/getting-started.md +40 -5
  10. {graphql_api-1.6.6 → graphql_api-1.6.8}/docs/content/docs/fundamentals/mutations.md +28 -1
  11. {graphql_api-1.6.6 → graphql_api-1.6.8}/docs/content/docs/reference/api-reference.md +49 -0
  12. {graphql_api-1.6.6 → graphql_api-1.6.8}/graphql_api/api.py +208 -0
  13. {graphql_api-1.6.6 → graphql_api-1.6.8}/graphql_api/executor.py +69 -11
  14. {graphql_api-1.6.6 → graphql_api-1.6.8}/graphql_api/pydantic.py +39 -5
  15. {graphql_api-1.6.6 → graphql_api-1.6.8}/graphql_api/utils.py +6 -0
  16. {graphql_api-1.6.6 → graphql_api-1.6.8}/graphql_api.egg-info/PKG-INFO +39 -32
  17. {graphql_api-1.6.6 → graphql_api-1.6.8}/graphql_api.egg-info/SOURCES.txt +3 -0
  18. graphql_api-1.6.8/tests/test_plain_functions.py +461 -0
  19. {graphql_api-1.6.6 → graphql_api-1.6.8}/tests/test_pydantic.py +98 -0
  20. graphql_api-1.6.6/VERSION +0 -1
  21. {graphql_api-1.6.6 → graphql_api-1.6.8}/.github/workflows/python-package.yml +0 -0
  22. {graphql_api-1.6.6 → graphql_api-1.6.8}/.gitignore +0 -0
  23. {graphql_api-1.6.6 → graphql_api-1.6.8}/.vscode/settings.json +0 -0
  24. {graphql_api-1.6.6 → graphql_api-1.6.8}/LICENSE +0 -0
  25. {graphql_api-1.6.6 → graphql_api-1.6.8}/docs/README.md +0 -0
  26. {graphql_api-1.6.6 → graphql_api-1.6.8}/docs/content/_index.md +0 -0
  27. {graphql_api-1.6.6 → graphql_api-1.6.8}/docs/content/docs/_index.md +0 -0
  28. {graphql_api-1.6.6 → graphql_api-1.6.8}/docs/content/docs/advanced-features/_index.md +0 -0
  29. {graphql_api-1.6.6 → graphql_api-1.6.8}/docs/content/docs/advanced-features/directives.md +0 -0
  30. {graphql_api-1.6.6 → graphql_api-1.6.8}/docs/content/docs/advanced-features/performance.md +0 -0
  31. {graphql_api-1.6.6 → graphql_api-1.6.8}/docs/content/docs/advanced-features/schema-filtering.md +0 -0
  32. {graphql_api-1.6.6 → graphql_api-1.6.8}/docs/content/docs/building-applications/_index.md +0 -0
  33. {graphql_api-1.6.6 → graphql_api-1.6.8}/docs/content/docs/building-applications/documentation.md +0 -0
  34. {graphql_api-1.6.6 → graphql_api-1.6.8}/docs/content/docs/building-applications/error-handling.md +0 -0
  35. {graphql_api-1.6.6 → graphql_api-1.6.8}/docs/content/docs/distributed-systems/_index.md +0 -0
  36. {graphql_api-1.6.6 → graphql_api-1.6.8}/docs/content/docs/distributed-systems/federation.md +0 -0
  37. {graphql_api-1.6.6 → graphql_api-1.6.8}/docs/content/docs/distributed-systems/pagination-relay.md +0 -0
  38. {graphql_api-1.6.6 → graphql_api-1.6.8}/docs/content/docs/distributed-systems/remote-graphql.md +0 -0
  39. {graphql_api-1.6.6 → graphql_api-1.6.8}/docs/content/docs/fundamentals/_index.md +0 -0
  40. {graphql_api-1.6.6 → graphql_api-1.6.8}/docs/content/docs/fundamentals/enums-interfaces.md +0 -0
  41. {graphql_api-1.6.6 → graphql_api-1.6.8}/docs/content/docs/fundamentals/field-types.md +0 -0
  42. {graphql_api-1.6.6 → graphql_api-1.6.8}/docs/content/docs/fundamentals/object-types.md +0 -0
  43. {graphql_api-1.6.6 → graphql_api-1.6.8}/docs/content/docs/fundamentals/pydantic-and-dataclasses.md +0 -0
  44. {graphql_api-1.6.6 → graphql_api-1.6.8}/docs/content/docs/production/_index.md +0 -0
  45. {graphql_api-1.6.6 → graphql_api-1.6.8}/docs/content/docs/production/context-metadata.md +0 -0
  46. {graphql_api-1.6.6 → graphql_api-1.6.8}/docs/content/docs/production/middleware.md +0 -0
  47. {graphql_api-1.6.6 → graphql_api-1.6.8}/docs/content/docs/reference/_index.md +0 -0
  48. {graphql_api-1.6.6 → graphql_api-1.6.8}/docs/content/docs/reference/contributing.md +0 -0
  49. {graphql_api-1.6.6 → graphql_api-1.6.8}/docs/content/docs/reference/examples.md +0 -0
  50. {graphql_api-1.6.6 → graphql_api-1.6.8}/docs/hugo.toml +0 -0
  51. {graphql_api-1.6.6 → graphql_api-1.6.8}/docs/index.md +0 -0
  52. {graphql_api-1.6.6 → graphql_api-1.6.8}/graphql_api/__init__.py +0 -0
  53. {graphql_api-1.6.6 → graphql_api-1.6.8}/graphql_api/context.py +0 -0
  54. {graphql_api-1.6.6 → graphql_api-1.6.8}/graphql_api/dataclass_mapping.py +0 -0
  55. {graphql_api-1.6.6 → graphql_api-1.6.8}/graphql_api/decorators.py +0 -0
  56. {graphql_api-1.6.6 → graphql_api-1.6.8}/graphql_api/directives.py +0 -0
  57. {graphql_api-1.6.6 → graphql_api-1.6.8}/graphql_api/error.py +0 -0
  58. {graphql_api-1.6.6 → graphql_api-1.6.8}/graphql_api/exception.py +0 -0
  59. {graphql_api-1.6.6 → graphql_api-1.6.8}/graphql_api/federation/__init__.py +0 -0
  60. {graphql_api-1.6.6 → graphql_api-1.6.8}/graphql_api/federation/directives.py +0 -0
  61. {graphql_api-1.6.6 → graphql_api-1.6.8}/graphql_api/federation/federation.py +0 -0
  62. {graphql_api-1.6.6 → graphql_api-1.6.8}/graphql_api/federation/types.py +0 -0
  63. {graphql_api-1.6.6 → graphql_api-1.6.8}/graphql_api/mapper.py +0 -0
  64. {graphql_api-1.6.6 → graphql_api-1.6.8}/graphql_api/middleware.py +0 -0
  65. {graphql_api-1.6.6 → graphql_api-1.6.8}/graphql_api/mutation_schema.py +0 -0
  66. {graphql_api-1.6.6 → graphql_api-1.6.8}/graphql_api/pydantic_utils.py +0 -0
  67. {graphql_api-1.6.6 → graphql_api-1.6.8}/graphql_api/reduce.py +0 -0
  68. {graphql_api-1.6.6 → graphql_api-1.6.8}/graphql_api/relay.py +0 -0
  69. {graphql_api-1.6.6 → graphql_api-1.6.8}/graphql_api/remote.py +0 -0
  70. {graphql_api-1.6.6 → graphql_api-1.6.8}/graphql_api/schema.py +0 -0
  71. {graphql_api-1.6.6 → graphql_api-1.6.8}/graphql_api/schema_filter.py +0 -0
  72. {graphql_api-1.6.6 → graphql_api-1.6.8}/graphql_api/type_utils.py +0 -0
  73. {graphql_api-1.6.6 → graphql_api-1.6.8}/graphql_api/types.py +0 -0
  74. {graphql_api-1.6.6 → graphql_api-1.6.8}/graphql_api.egg-info/dependency_links.txt +0 -0
  75. {graphql_api-1.6.6 → graphql_api-1.6.8}/graphql_api.egg-info/requires.txt +0 -0
  76. {graphql_api-1.6.6 → graphql_api-1.6.8}/graphql_api.egg-info/top_level.txt +0 -0
  77. {graphql_api-1.6.6 → graphql_api-1.6.8}/pyproject.toml +0 -0
  78. {graphql_api-1.6.6 → graphql_api-1.6.8}/setup.cfg +0 -0
  79. {graphql_api-1.6.6 → graphql_api-1.6.8}/tests/__init__.py +0 -0
  80. {graphql_api-1.6.6 → graphql_api-1.6.8}/tests/test_async.py +0 -0
  81. {graphql_api-1.6.6 → graphql_api-1.6.8}/tests/test_calculator.py +0 -0
  82. {graphql_api-1.6.6 → graphql_api-1.6.8}/tests/test_custom_suffixes.py +0 -0
  83. {graphql_api-1.6.6 → graphql_api-1.6.8}/tests/test_custom_types.py +0 -0
  84. {graphql_api-1.6.6 → graphql_api-1.6.8}/tests/test_dataclass.py +0 -0
  85. {graphql_api-1.6.6 → graphql_api-1.6.8}/tests/test_dataclass_relationships.py +0 -0
  86. {graphql_api-1.6.6 → graphql_api-1.6.8}/tests/test_decorator_patterns.py +0 -0
  87. {graphql_api-1.6.6 → graphql_api-1.6.8}/tests/test_directives.py +0 -0
  88. {graphql_api-1.6.6 → graphql_api-1.6.8}/tests/test_docs_advanced.py +0 -0
  89. {graphql_api-1.6.6 → graphql_api-1.6.8}/tests/test_docs_async_subscriptions.py +0 -0
  90. {graphql_api-1.6.6 → graphql_api-1.6.8}/tests/test_docs_defining_schemas.py +0 -0
  91. {graphql_api-1.6.6 → graphql_api-1.6.8}/tests/test_docs_field_types.py +0 -0
  92. {graphql_api-1.6.6 → graphql_api-1.6.8}/tests/test_docs_getting_started.py +0 -0
  93. {graphql_api-1.6.6 → graphql_api-1.6.8}/tests/test_docs_relay.py +0 -0
  94. {graphql_api-1.6.6 → graphql_api-1.6.8}/tests/test_docstrings.py +0 -0
  95. {graphql_api-1.6.6 → graphql_api-1.6.8}/tests/test_error.py +0 -0
  96. {graphql_api-1.6.6 → graphql_api-1.6.8}/tests/test_federation.py +0 -0
  97. {graphql_api-1.6.6 → graphql_api-1.6.8}/tests/test_federation_example.graphql +0 -0
  98. {graphql_api-1.6.6 → graphql_api-1.6.8}/tests/test_federation_example.py +0 -0
  99. {graphql_api-1.6.6 → graphql_api-1.6.8}/tests/test_filtering.py +0 -0
  100. {graphql_api-1.6.6 → graphql_api-1.6.8}/tests/test_graphql.py +0 -0
  101. {graphql_api-1.6.6 → graphql_api-1.6.8}/tests/test_middleware.py +0 -0
  102. {graphql_api-1.6.6 → graphql_api-1.6.8}/tests/test_performance.py +0 -0
  103. {graphql_api-1.6.6 → graphql_api-1.6.8}/tests/test_relay.py +0 -0
  104. {graphql_api-1.6.6 → graphql_api-1.6.8}/tests/test_remote.py +0 -0
  105. {graphql_api-1.6.6 → graphql_api-1.6.8}/tests/test_schema.py +0 -0
  106. {graphql_api-1.6.6 → graphql_api-1.6.8}/tests/test_subscriptions.py +0 -0
  107. {graphql_api-1.6.6 → graphql_api-1.6.8}/tests/test_unified_vs_explicit_schema.py +0 -0
  108. {graphql_api-1.6.6 → graphql_api-1.6.8}/uv.lock +0 -0
@@ -22,7 +22,7 @@ jobs:
22
22
  build:
23
23
  runs-on: ubuntu-latest
24
24
  env:
25
- HUGO_VERSION: 0.146.0
25
+ HUGO_VERSION: 0.160.1
26
26
  steps:
27
27
  - name: Checkout
28
28
  uses: actions/checkout@v4
@@ -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.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
  [![PyPI version](https://badge.fury.io/py/graphql-api.svg)](https://badge.fury.io/py/graphql-api)
29
29
  [![Python versions](https://img.shields.io/pypi/pyversions/graphql-api.svg)](https://pypi.org/project/graphql-api/)
30
30
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
31
- [![Coverage](https://gitlab.com/parob/graphql-api/badges/master/coverage.svg)](https://gitlab.com/parob/graphql-api/commits/master)
32
- [![Pipeline](https://gitlab.com/parob/graphql-api/badges/master/pipeline.svg)](https://gitlab.com/parob/graphql-api/commits/master)
33
31
 
34
- **[📚 Documentation](https://graphql-api.parob.com/)** | **[📦 PyPI](https://pypi.org/project/graphql-api/)** | **[🔧 GitHub](https://github.com/parob/graphql-api)**
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
- Create a simple GraphQL API in just a few lines of code.
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
- # 2. Define your root type with decorators
72
- @api.type(is_root_type=True)
73
- class Query:
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
- # 3. Define a query
85
- graphql_query = """
86
- query Greetings {
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(graphql_query)
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
- pip install pipenv
238
- pipenv install --dev
245
+ uv sync
239
246
 
240
247
  # Run tests
241
- pipenv run pytest
248
+ uv run pytest
242
249
  ```
@@ -3,10 +3,8 @@
3
3
  [![PyPI version](https://badge.fury.io/py/graphql-api.svg)](https://badge.fury.io/py/graphql-api)
4
4
  [![Python versions](https://img.shields.io/pypi/pyversions/graphql-api.svg)](https://pypi.org/project/graphql-api/)
5
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
- [![Coverage](https://gitlab.com/parob/graphql-api/badges/master/coverage.svg)](https://gitlab.com/parob/graphql-api/commits/master)
7
- [![Pipeline](https://gitlab.com/parob/graphql-api/badges/master/pipeline.svg)](https://gitlab.com/parob/graphql-api/commits/master)
8
6
 
9
- **[📚 Documentation](https://graphql-api.parob.com/)** | **[📦 PyPI](https://pypi.org/project/graphql-api/)** | **[🔧 GitHub](https://github.com/parob/graphql-api)**
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
- Create a simple GraphQL API in just a few lines of code.
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
- # 2. Define your root type with decorators
47
- @api.type(is_root_type=True)
48
- class Query:
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
- # 3. Define a query
60
- graphql_query = """
61
- query Greetings {
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(graphql_query)
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
- pip install pipenv
213
- pipenv install --dev
220
+ uv sync
214
221
 
215
222
  # Run tests
216
- pipenv run pytest
217
- ```
223
+ uv run pytest
224
+ ```
@@ -0,0 +1 @@
1
+ 1.6.8
@@ -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 two distinct approaches for organizing your GraphQL operations (queries, mutations, subscriptions):
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
- ### 1. Initialize the API
22
+ There are two ways to define root fields — pick whichever fits the shape of your API:
23
23
 
24
- First, create a new Python file (e.g., `main.py`) and initialize `GraphQLAPI`:
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
- ### 2. Define the Root Query
68
+ #### 2. Define the Root Query
34
69
 
35
- Next, 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.
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
- ### 3. Execute a Query
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
- ## Basic Mutations
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.