graphql-api 1.6.7__tar.gz → 1.6.9__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.7 → graphql_api-1.6.9}/.github/workflows/pages.yml +1 -1
  2. {graphql_api-1.6.7 → graphql_api-1.6.9}/AGENTS.md +2 -1
  3. {graphql_api-1.6.7 → graphql_api-1.6.9}/CLAUDE.md +2 -1
  4. {graphql_api-1.6.7 → graphql_api-1.6.9}/PKG-INFO +36 -26
  5. {graphql_api-1.6.7 → graphql_api-1.6.9}/README.md +35 -25
  6. graphql_api-1.6.9/VERSION +1 -0
  7. {graphql_api-1.6.7 → graphql_api-1.6.9}/docs/content/docs/fundamentals/async-and-subscriptions.md +23 -0
  8. {graphql_api-1.6.7 → graphql_api-1.6.9}/docs/content/docs/fundamentals/defining-schemas.md +33 -1
  9. {graphql_api-1.6.7 → graphql_api-1.6.9}/docs/content/docs/fundamentals/getting-started.md +40 -5
  10. {graphql_api-1.6.7 → graphql_api-1.6.9}/docs/content/docs/fundamentals/mutations.md +28 -1
  11. {graphql_api-1.6.7 → graphql_api-1.6.9}/docs/content/docs/reference/api-reference.md +49 -0
  12. {graphql_api-1.6.7 → graphql_api-1.6.9}/graphql_api/api.py +232 -1
  13. {graphql_api-1.6.7 → graphql_api-1.6.9}/graphql_api/executor.py +75 -25
  14. {graphql_api-1.6.7 → graphql_api-1.6.9}/graphql_api/middleware.py +95 -0
  15. {graphql_api-1.6.7 → graphql_api-1.6.9}/graphql_api/utils.py +6 -0
  16. {graphql_api-1.6.7 → graphql_api-1.6.9}/graphql_api.egg-info/PKG-INFO +36 -26
  17. {graphql_api-1.6.7 → graphql_api-1.6.9}/graphql_api.egg-info/SOURCES.txt +1 -0
  18. graphql_api-1.6.9/tests/test_plain_functions.py +461 -0
  19. graphql_api-1.6.7/VERSION +0 -1
  20. {graphql_api-1.6.7 → graphql_api-1.6.9}/.github/workflows/python-package.yml +0 -0
  21. {graphql_api-1.6.7 → graphql_api-1.6.9}/.gitignore +0 -0
  22. {graphql_api-1.6.7 → graphql_api-1.6.9}/.vscode/settings.json +0 -0
  23. {graphql_api-1.6.7 → graphql_api-1.6.9}/LICENSE +0 -0
  24. {graphql_api-1.6.7 → graphql_api-1.6.9}/docs/README.md +0 -0
  25. {graphql_api-1.6.7 → graphql_api-1.6.9}/docs/content/_index.md +0 -0
  26. {graphql_api-1.6.7 → graphql_api-1.6.9}/docs/content/docs/_index.md +0 -0
  27. {graphql_api-1.6.7 → graphql_api-1.6.9}/docs/content/docs/advanced-features/_index.md +0 -0
  28. {graphql_api-1.6.7 → graphql_api-1.6.9}/docs/content/docs/advanced-features/directives.md +0 -0
  29. {graphql_api-1.6.7 → graphql_api-1.6.9}/docs/content/docs/advanced-features/performance.md +0 -0
  30. {graphql_api-1.6.7 → graphql_api-1.6.9}/docs/content/docs/advanced-features/schema-filtering.md +0 -0
  31. {graphql_api-1.6.7 → graphql_api-1.6.9}/docs/content/docs/building-applications/_index.md +0 -0
  32. {graphql_api-1.6.7 → graphql_api-1.6.9}/docs/content/docs/building-applications/documentation.md +0 -0
  33. {graphql_api-1.6.7 → graphql_api-1.6.9}/docs/content/docs/building-applications/error-handling.md +0 -0
  34. {graphql_api-1.6.7 → graphql_api-1.6.9}/docs/content/docs/distributed-systems/_index.md +0 -0
  35. {graphql_api-1.6.7 → graphql_api-1.6.9}/docs/content/docs/distributed-systems/federation.md +0 -0
  36. {graphql_api-1.6.7 → graphql_api-1.6.9}/docs/content/docs/distributed-systems/pagination-relay.md +0 -0
  37. {graphql_api-1.6.7 → graphql_api-1.6.9}/docs/content/docs/distributed-systems/remote-graphql.md +0 -0
  38. {graphql_api-1.6.7 → graphql_api-1.6.9}/docs/content/docs/fundamentals/_index.md +0 -0
  39. {graphql_api-1.6.7 → graphql_api-1.6.9}/docs/content/docs/fundamentals/enums-interfaces.md +0 -0
  40. {graphql_api-1.6.7 → graphql_api-1.6.9}/docs/content/docs/fundamentals/field-types.md +0 -0
  41. {graphql_api-1.6.7 → graphql_api-1.6.9}/docs/content/docs/fundamentals/object-types.md +0 -0
  42. {graphql_api-1.6.7 → graphql_api-1.6.9}/docs/content/docs/fundamentals/pydantic-and-dataclasses.md +0 -0
  43. {graphql_api-1.6.7 → graphql_api-1.6.9}/docs/content/docs/production/_index.md +0 -0
  44. {graphql_api-1.6.7 → graphql_api-1.6.9}/docs/content/docs/production/context-metadata.md +0 -0
  45. {graphql_api-1.6.7 → graphql_api-1.6.9}/docs/content/docs/production/middleware.md +0 -0
  46. {graphql_api-1.6.7 → graphql_api-1.6.9}/docs/content/docs/reference/_index.md +0 -0
  47. {graphql_api-1.6.7 → graphql_api-1.6.9}/docs/content/docs/reference/contributing.md +0 -0
  48. {graphql_api-1.6.7 → graphql_api-1.6.9}/docs/content/docs/reference/examples.md +0 -0
  49. {graphql_api-1.6.7 → graphql_api-1.6.9}/docs/hugo.toml +0 -0
  50. {graphql_api-1.6.7 → graphql_api-1.6.9}/docs/index.md +0 -0
  51. {graphql_api-1.6.7 → graphql_api-1.6.9}/graphql_api/__init__.py +0 -0
  52. {graphql_api-1.6.7 → graphql_api-1.6.9}/graphql_api/context.py +0 -0
  53. {graphql_api-1.6.7 → graphql_api-1.6.9}/graphql_api/dataclass_mapping.py +0 -0
  54. {graphql_api-1.6.7 → graphql_api-1.6.9}/graphql_api/decorators.py +0 -0
  55. {graphql_api-1.6.7 → graphql_api-1.6.9}/graphql_api/directives.py +0 -0
  56. {graphql_api-1.6.7 → graphql_api-1.6.9}/graphql_api/error.py +0 -0
  57. {graphql_api-1.6.7 → graphql_api-1.6.9}/graphql_api/exception.py +0 -0
  58. {graphql_api-1.6.7 → graphql_api-1.6.9}/graphql_api/federation/__init__.py +0 -0
  59. {graphql_api-1.6.7 → graphql_api-1.6.9}/graphql_api/federation/directives.py +0 -0
  60. {graphql_api-1.6.7 → graphql_api-1.6.9}/graphql_api/federation/federation.py +0 -0
  61. {graphql_api-1.6.7 → graphql_api-1.6.9}/graphql_api/federation/types.py +0 -0
  62. {graphql_api-1.6.7 → graphql_api-1.6.9}/graphql_api/mapper.py +0 -0
  63. {graphql_api-1.6.7 → graphql_api-1.6.9}/graphql_api/mutation_schema.py +0 -0
  64. {graphql_api-1.6.7 → graphql_api-1.6.9}/graphql_api/pydantic.py +0 -0
  65. {graphql_api-1.6.7 → graphql_api-1.6.9}/graphql_api/pydantic_utils.py +0 -0
  66. {graphql_api-1.6.7 → graphql_api-1.6.9}/graphql_api/reduce.py +0 -0
  67. {graphql_api-1.6.7 → graphql_api-1.6.9}/graphql_api/relay.py +0 -0
  68. {graphql_api-1.6.7 → graphql_api-1.6.9}/graphql_api/remote.py +0 -0
  69. {graphql_api-1.6.7 → graphql_api-1.6.9}/graphql_api/schema.py +0 -0
  70. {graphql_api-1.6.7 → graphql_api-1.6.9}/graphql_api/schema_filter.py +0 -0
  71. {graphql_api-1.6.7 → graphql_api-1.6.9}/graphql_api/type_utils.py +0 -0
  72. {graphql_api-1.6.7 → graphql_api-1.6.9}/graphql_api/types.py +0 -0
  73. {graphql_api-1.6.7 → graphql_api-1.6.9}/graphql_api.egg-info/dependency_links.txt +0 -0
  74. {graphql_api-1.6.7 → graphql_api-1.6.9}/graphql_api.egg-info/requires.txt +0 -0
  75. {graphql_api-1.6.7 → graphql_api-1.6.9}/graphql_api.egg-info/top_level.txt +0 -0
  76. {graphql_api-1.6.7 → graphql_api-1.6.9}/pyproject.toml +0 -0
  77. {graphql_api-1.6.7 → graphql_api-1.6.9}/setup.cfg +0 -0
  78. {graphql_api-1.6.7 → graphql_api-1.6.9}/tests/__init__.py +0 -0
  79. {graphql_api-1.6.7 → graphql_api-1.6.9}/tests/test_async.py +0 -0
  80. {graphql_api-1.6.7 → graphql_api-1.6.9}/tests/test_calculator.py +0 -0
  81. {graphql_api-1.6.7 → graphql_api-1.6.9}/tests/test_custom_suffixes.py +0 -0
  82. {graphql_api-1.6.7 → graphql_api-1.6.9}/tests/test_custom_types.py +0 -0
  83. {graphql_api-1.6.7 → graphql_api-1.6.9}/tests/test_dataclass.py +0 -0
  84. {graphql_api-1.6.7 → graphql_api-1.6.9}/tests/test_dataclass_relationships.py +0 -0
  85. {graphql_api-1.6.7 → graphql_api-1.6.9}/tests/test_decorator_patterns.py +0 -0
  86. {graphql_api-1.6.7 → graphql_api-1.6.9}/tests/test_directives.py +0 -0
  87. {graphql_api-1.6.7 → graphql_api-1.6.9}/tests/test_docs_advanced.py +0 -0
  88. {graphql_api-1.6.7 → graphql_api-1.6.9}/tests/test_docs_async_subscriptions.py +0 -0
  89. {graphql_api-1.6.7 → graphql_api-1.6.9}/tests/test_docs_defining_schemas.py +0 -0
  90. {graphql_api-1.6.7 → graphql_api-1.6.9}/tests/test_docs_field_types.py +0 -0
  91. {graphql_api-1.6.7 → graphql_api-1.6.9}/tests/test_docs_getting_started.py +0 -0
  92. {graphql_api-1.6.7 → graphql_api-1.6.9}/tests/test_docs_relay.py +0 -0
  93. {graphql_api-1.6.7 → graphql_api-1.6.9}/tests/test_docstrings.py +0 -0
  94. {graphql_api-1.6.7 → graphql_api-1.6.9}/tests/test_error.py +0 -0
  95. {graphql_api-1.6.7 → graphql_api-1.6.9}/tests/test_federation.py +0 -0
  96. {graphql_api-1.6.7 → graphql_api-1.6.9}/tests/test_federation_example.graphql +0 -0
  97. {graphql_api-1.6.7 → graphql_api-1.6.9}/tests/test_federation_example.py +0 -0
  98. {graphql_api-1.6.7 → graphql_api-1.6.9}/tests/test_filtering.py +0 -0
  99. {graphql_api-1.6.7 → graphql_api-1.6.9}/tests/test_graphql.py +0 -0
  100. {graphql_api-1.6.7 → graphql_api-1.6.9}/tests/test_middleware.py +0 -0
  101. {graphql_api-1.6.7 → graphql_api-1.6.9}/tests/test_performance.py +0 -0
  102. {graphql_api-1.6.7 → graphql_api-1.6.9}/tests/test_pydantic.py +0 -0
  103. {graphql_api-1.6.7 → graphql_api-1.6.9}/tests/test_relay.py +0 -0
  104. {graphql_api-1.6.7 → graphql_api-1.6.9}/tests/test_remote.py +0 -0
  105. {graphql_api-1.6.7 → graphql_api-1.6.9}/tests/test_schema.py +0 -0
  106. {graphql_api-1.6.7 → graphql_api-1.6.9}/tests/test_subscriptions.py +0 -0
  107. {graphql_api-1.6.7 → graphql_api-1.6.9}/tests/test_unified_vs_explicit_schema.py +0 -0
  108. {graphql_api-1.6.7 → graphql_api-1.6.9}/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
@@ -30,8 +30,9 @@ uv run flake8 graphql_api tests
30
30
 
31
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
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`)
33
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
34
- - Schema setup: `GraphQLAPI(query_type=Q, mutation_type=M, subscription_type=S)` or legacy `root_type=Root`
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`)
35
36
 
36
37
  ## Releasing
37
38
 
@@ -30,8 +30,9 @@ uv run flake8 graphql_api tests
30
30
 
31
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
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`)
33
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
34
- - Schema setup: `GraphQLAPI(query_type=Q, mutation_type=M, subscription_type=S)` or legacy `root_type=Root`
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`)
35
36
 
36
37
  ## Releasing
37
38
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: graphql-api
3
- Version: 1.6.7
3
+ Version: 1.6.9
4
4
  Summary: A framework for building Python GraphQL APIs.
5
5
  Author-email: Robert Parker <rob@parob.com>
6
6
  License: MIT
@@ -57,46 +57,56 @@ pip install graphql-api
57
57
 
58
58
  ## Quick Start
59
59
 
60
- Create a simple GraphQL API in just a few lines of code.
60
+ For simple APIs, register plain functions directly — no class required:
61
61
 
62
62
  ```python
63
63
  # example.py
64
64
  from graphql_api.api import GraphQLAPI
65
65
 
66
- # 1. Initialize the API
67
66
  api = GraphQLAPI()
68
67
 
69
- # 2. Define your root type with decorators
70
- @api.type(is_root_type=True)
71
- class Query:
72
- """
73
- The root query for our amazing API.
74
- """
75
- @api.field
76
- def hello(self, name: str = "World") -> str:
77
- """
78
- A classic greeting.
79
- """
80
- return f"Hello, {name}!"
68
+ @api.query
69
+ def hello(name: str = "World") -> str:
70
+ """A classic greeting."""
71
+ return f"Hello, {name}!"
81
72
 
82
- # 3. Define a query
83
- graphql_query = """
84
- query Greetings {
85
- hello(name: "Developer")
86
- }
87
- """
73
+ @api.mutation
74
+ def set_name(name: str) -> str:
75
+ return f"Name set to {name}"
88
76
 
89
- # 4. Execute the query
90
77
  if __name__ == "__main__":
91
- result = api.execute(graphql_query)
78
+ result = api.execute('query { hello(name: "Developer") }')
92
79
  print(result.data)
93
80
  ```
94
81
 
95
- Running this script will produce:
96
-
97
82
  ```bash
98
83
  $ python example.py
99
- {'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!'}
100
110
  ```
101
111
 
102
112
  ## Examples
@@ -32,46 +32,56 @@ pip install graphql-api
32
32
 
33
33
  ## Quick Start
34
34
 
35
- Create a simple GraphQL API in just a few lines of code.
35
+ For simple APIs, register plain functions directly — no class required:
36
36
 
37
37
  ```python
38
38
  # example.py
39
39
  from graphql_api.api import GraphQLAPI
40
40
 
41
- # 1. Initialize the API
42
41
  api = GraphQLAPI()
43
42
 
44
- # 2. Define your root type with decorators
45
- @api.type(is_root_type=True)
46
- class Query:
47
- """
48
- The root query for our amazing API.
49
- """
50
- @api.field
51
- def hello(self, name: str = "World") -> str:
52
- """
53
- A classic greeting.
54
- """
55
- return f"Hello, {name}!"
43
+ @api.query
44
+ def hello(name: str = "World") -> str:
45
+ """A classic greeting."""
46
+ return f"Hello, {name}!"
56
47
 
57
- # 3. Define a query
58
- graphql_query = """
59
- query Greetings {
60
- hello(name: "Developer")
61
- }
62
- """
48
+ @api.mutation
49
+ def set_name(name: str) -> str:
50
+ return f"Name set to {name}"
63
51
 
64
- # 4. Execute the query
65
52
  if __name__ == "__main__":
66
- result = api.execute(graphql_query)
53
+ result = api.execute('query { hello(name: "Developer") }')
67
54
  print(result.data)
68
55
  ```
69
56
 
70
- Running this script will produce:
71
-
72
57
  ```bash
73
58
  $ python example.py
74
- {'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!'}
75
85
  ```
76
86
 
77
87
  ## Examples
@@ -0,0 +1 @@
1
+ 1.6.9
@@ -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.