graphql-api 1.6.6__tar.gz → 1.6.7__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 (107) hide show
  1. graphql_api-1.6.7/AGENTS.md +46 -0
  2. graphql_api-1.6.7/CLAUDE.md +46 -0
  3. {graphql_api-1.6.6 → graphql_api-1.6.7}/PKG-INFO +4 -7
  4. {graphql_api-1.6.6 → graphql_api-1.6.7}/README.md +4 -7
  5. graphql_api-1.6.7/VERSION +1 -0
  6. {graphql_api-1.6.6 → graphql_api-1.6.7}/graphql_api/pydantic.py +39 -5
  7. {graphql_api-1.6.6 → graphql_api-1.6.7}/graphql_api.egg-info/PKG-INFO +4 -7
  8. {graphql_api-1.6.6 → graphql_api-1.6.7}/graphql_api.egg-info/SOURCES.txt +2 -0
  9. {graphql_api-1.6.6 → graphql_api-1.6.7}/tests/test_pydantic.py +98 -0
  10. graphql_api-1.6.6/VERSION +0 -1
  11. {graphql_api-1.6.6 → graphql_api-1.6.7}/.github/workflows/pages.yml +0 -0
  12. {graphql_api-1.6.6 → graphql_api-1.6.7}/.github/workflows/python-package.yml +0 -0
  13. {graphql_api-1.6.6 → graphql_api-1.6.7}/.gitignore +0 -0
  14. {graphql_api-1.6.6 → graphql_api-1.6.7}/.vscode/settings.json +0 -0
  15. {graphql_api-1.6.6 → graphql_api-1.6.7}/LICENSE +0 -0
  16. {graphql_api-1.6.6 → graphql_api-1.6.7}/docs/README.md +0 -0
  17. {graphql_api-1.6.6 → graphql_api-1.6.7}/docs/content/_index.md +0 -0
  18. {graphql_api-1.6.6 → graphql_api-1.6.7}/docs/content/docs/_index.md +0 -0
  19. {graphql_api-1.6.6 → graphql_api-1.6.7}/docs/content/docs/advanced-features/_index.md +0 -0
  20. {graphql_api-1.6.6 → graphql_api-1.6.7}/docs/content/docs/advanced-features/directives.md +0 -0
  21. {graphql_api-1.6.6 → graphql_api-1.6.7}/docs/content/docs/advanced-features/performance.md +0 -0
  22. {graphql_api-1.6.6 → graphql_api-1.6.7}/docs/content/docs/advanced-features/schema-filtering.md +0 -0
  23. {graphql_api-1.6.6 → graphql_api-1.6.7}/docs/content/docs/building-applications/_index.md +0 -0
  24. {graphql_api-1.6.6 → graphql_api-1.6.7}/docs/content/docs/building-applications/documentation.md +0 -0
  25. {graphql_api-1.6.6 → graphql_api-1.6.7}/docs/content/docs/building-applications/error-handling.md +0 -0
  26. {graphql_api-1.6.6 → graphql_api-1.6.7}/docs/content/docs/distributed-systems/_index.md +0 -0
  27. {graphql_api-1.6.6 → graphql_api-1.6.7}/docs/content/docs/distributed-systems/federation.md +0 -0
  28. {graphql_api-1.6.6 → graphql_api-1.6.7}/docs/content/docs/distributed-systems/pagination-relay.md +0 -0
  29. {graphql_api-1.6.6 → graphql_api-1.6.7}/docs/content/docs/distributed-systems/remote-graphql.md +0 -0
  30. {graphql_api-1.6.6 → graphql_api-1.6.7}/docs/content/docs/fundamentals/_index.md +0 -0
  31. {graphql_api-1.6.6 → graphql_api-1.6.7}/docs/content/docs/fundamentals/async-and-subscriptions.md +0 -0
  32. {graphql_api-1.6.6 → graphql_api-1.6.7}/docs/content/docs/fundamentals/defining-schemas.md +0 -0
  33. {graphql_api-1.6.6 → graphql_api-1.6.7}/docs/content/docs/fundamentals/enums-interfaces.md +0 -0
  34. {graphql_api-1.6.6 → graphql_api-1.6.7}/docs/content/docs/fundamentals/field-types.md +0 -0
  35. {graphql_api-1.6.6 → graphql_api-1.6.7}/docs/content/docs/fundamentals/getting-started.md +0 -0
  36. {graphql_api-1.6.6 → graphql_api-1.6.7}/docs/content/docs/fundamentals/mutations.md +0 -0
  37. {graphql_api-1.6.6 → graphql_api-1.6.7}/docs/content/docs/fundamentals/object-types.md +0 -0
  38. {graphql_api-1.6.6 → graphql_api-1.6.7}/docs/content/docs/fundamentals/pydantic-and-dataclasses.md +0 -0
  39. {graphql_api-1.6.6 → graphql_api-1.6.7}/docs/content/docs/production/_index.md +0 -0
  40. {graphql_api-1.6.6 → graphql_api-1.6.7}/docs/content/docs/production/context-metadata.md +0 -0
  41. {graphql_api-1.6.6 → graphql_api-1.6.7}/docs/content/docs/production/middleware.md +0 -0
  42. {graphql_api-1.6.6 → graphql_api-1.6.7}/docs/content/docs/reference/_index.md +0 -0
  43. {graphql_api-1.6.6 → graphql_api-1.6.7}/docs/content/docs/reference/api-reference.md +0 -0
  44. {graphql_api-1.6.6 → graphql_api-1.6.7}/docs/content/docs/reference/contributing.md +0 -0
  45. {graphql_api-1.6.6 → graphql_api-1.6.7}/docs/content/docs/reference/examples.md +0 -0
  46. {graphql_api-1.6.6 → graphql_api-1.6.7}/docs/hugo.toml +0 -0
  47. {graphql_api-1.6.6 → graphql_api-1.6.7}/docs/index.md +0 -0
  48. {graphql_api-1.6.6 → graphql_api-1.6.7}/graphql_api/__init__.py +0 -0
  49. {graphql_api-1.6.6 → graphql_api-1.6.7}/graphql_api/api.py +0 -0
  50. {graphql_api-1.6.6 → graphql_api-1.6.7}/graphql_api/context.py +0 -0
  51. {graphql_api-1.6.6 → graphql_api-1.6.7}/graphql_api/dataclass_mapping.py +0 -0
  52. {graphql_api-1.6.6 → graphql_api-1.6.7}/graphql_api/decorators.py +0 -0
  53. {graphql_api-1.6.6 → graphql_api-1.6.7}/graphql_api/directives.py +0 -0
  54. {graphql_api-1.6.6 → graphql_api-1.6.7}/graphql_api/error.py +0 -0
  55. {graphql_api-1.6.6 → graphql_api-1.6.7}/graphql_api/exception.py +0 -0
  56. {graphql_api-1.6.6 → graphql_api-1.6.7}/graphql_api/executor.py +0 -0
  57. {graphql_api-1.6.6 → graphql_api-1.6.7}/graphql_api/federation/__init__.py +0 -0
  58. {graphql_api-1.6.6 → graphql_api-1.6.7}/graphql_api/federation/directives.py +0 -0
  59. {graphql_api-1.6.6 → graphql_api-1.6.7}/graphql_api/federation/federation.py +0 -0
  60. {graphql_api-1.6.6 → graphql_api-1.6.7}/graphql_api/federation/types.py +0 -0
  61. {graphql_api-1.6.6 → graphql_api-1.6.7}/graphql_api/mapper.py +0 -0
  62. {graphql_api-1.6.6 → graphql_api-1.6.7}/graphql_api/middleware.py +0 -0
  63. {graphql_api-1.6.6 → graphql_api-1.6.7}/graphql_api/mutation_schema.py +0 -0
  64. {graphql_api-1.6.6 → graphql_api-1.6.7}/graphql_api/pydantic_utils.py +0 -0
  65. {graphql_api-1.6.6 → graphql_api-1.6.7}/graphql_api/reduce.py +0 -0
  66. {graphql_api-1.6.6 → graphql_api-1.6.7}/graphql_api/relay.py +0 -0
  67. {graphql_api-1.6.6 → graphql_api-1.6.7}/graphql_api/remote.py +0 -0
  68. {graphql_api-1.6.6 → graphql_api-1.6.7}/graphql_api/schema.py +0 -0
  69. {graphql_api-1.6.6 → graphql_api-1.6.7}/graphql_api/schema_filter.py +0 -0
  70. {graphql_api-1.6.6 → graphql_api-1.6.7}/graphql_api/type_utils.py +0 -0
  71. {graphql_api-1.6.6 → graphql_api-1.6.7}/graphql_api/types.py +0 -0
  72. {graphql_api-1.6.6 → graphql_api-1.6.7}/graphql_api/utils.py +0 -0
  73. {graphql_api-1.6.6 → graphql_api-1.6.7}/graphql_api.egg-info/dependency_links.txt +0 -0
  74. {graphql_api-1.6.6 → graphql_api-1.6.7}/graphql_api.egg-info/requires.txt +0 -0
  75. {graphql_api-1.6.6 → graphql_api-1.6.7}/graphql_api.egg-info/top_level.txt +0 -0
  76. {graphql_api-1.6.6 → graphql_api-1.6.7}/pyproject.toml +0 -0
  77. {graphql_api-1.6.6 → graphql_api-1.6.7}/setup.cfg +0 -0
  78. {graphql_api-1.6.6 → graphql_api-1.6.7}/tests/__init__.py +0 -0
  79. {graphql_api-1.6.6 → graphql_api-1.6.7}/tests/test_async.py +0 -0
  80. {graphql_api-1.6.6 → graphql_api-1.6.7}/tests/test_calculator.py +0 -0
  81. {graphql_api-1.6.6 → graphql_api-1.6.7}/tests/test_custom_suffixes.py +0 -0
  82. {graphql_api-1.6.6 → graphql_api-1.6.7}/tests/test_custom_types.py +0 -0
  83. {graphql_api-1.6.6 → graphql_api-1.6.7}/tests/test_dataclass.py +0 -0
  84. {graphql_api-1.6.6 → graphql_api-1.6.7}/tests/test_dataclass_relationships.py +0 -0
  85. {graphql_api-1.6.6 → graphql_api-1.6.7}/tests/test_decorator_patterns.py +0 -0
  86. {graphql_api-1.6.6 → graphql_api-1.6.7}/tests/test_directives.py +0 -0
  87. {graphql_api-1.6.6 → graphql_api-1.6.7}/tests/test_docs_advanced.py +0 -0
  88. {graphql_api-1.6.6 → graphql_api-1.6.7}/tests/test_docs_async_subscriptions.py +0 -0
  89. {graphql_api-1.6.6 → graphql_api-1.6.7}/tests/test_docs_defining_schemas.py +0 -0
  90. {graphql_api-1.6.6 → graphql_api-1.6.7}/tests/test_docs_field_types.py +0 -0
  91. {graphql_api-1.6.6 → graphql_api-1.6.7}/tests/test_docs_getting_started.py +0 -0
  92. {graphql_api-1.6.6 → graphql_api-1.6.7}/tests/test_docs_relay.py +0 -0
  93. {graphql_api-1.6.6 → graphql_api-1.6.7}/tests/test_docstrings.py +0 -0
  94. {graphql_api-1.6.6 → graphql_api-1.6.7}/tests/test_error.py +0 -0
  95. {graphql_api-1.6.6 → graphql_api-1.6.7}/tests/test_federation.py +0 -0
  96. {graphql_api-1.6.6 → graphql_api-1.6.7}/tests/test_federation_example.graphql +0 -0
  97. {graphql_api-1.6.6 → graphql_api-1.6.7}/tests/test_federation_example.py +0 -0
  98. {graphql_api-1.6.6 → graphql_api-1.6.7}/tests/test_filtering.py +0 -0
  99. {graphql_api-1.6.6 → graphql_api-1.6.7}/tests/test_graphql.py +0 -0
  100. {graphql_api-1.6.6 → graphql_api-1.6.7}/tests/test_middleware.py +0 -0
  101. {graphql_api-1.6.6 → graphql_api-1.6.7}/tests/test_performance.py +0 -0
  102. {graphql_api-1.6.6 → graphql_api-1.6.7}/tests/test_relay.py +0 -0
  103. {graphql_api-1.6.6 → graphql_api-1.6.7}/tests/test_remote.py +0 -0
  104. {graphql_api-1.6.6 → graphql_api-1.6.7}/tests/test_schema.py +0 -0
  105. {graphql_api-1.6.6 → graphql_api-1.6.7}/tests/test_subscriptions.py +0 -0
  106. {graphql_api-1.6.6 → graphql_api-1.6.7}/tests/test_unified_vs_explicit_schema.py +0 -0
  107. {graphql_api-1.6.6 → graphql_api-1.6.7}/uv.lock +0 -0
@@ -0,0 +1,46 @@
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
+ - 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
+
36
+ ## Releasing
37
+
38
+ See the ecosystem-level `CLAUDE.md` in the parent workspace for the full release process. In short:
39
+
40
+ ```bash
41
+ # Ensure CI is green on main, then:
42
+ git tag X.Y.Z
43
+ git push origin X.Y.Z
44
+ ```
45
+
46
+ CI publishes to PyPI and creates a GitHub Release automatically.
@@ -0,0 +1,46 @@
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
+ - 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
+
36
+ ## Releasing
37
+
38
+ See the ecosystem-level `CLAUDE.md` in the parent workspace for the full release process. In short:
39
+
40
+ ```bash
41
+ # Ensure CI is green on main, then:
42
+ git tag X.Y.Z
43
+ git push origin X.Y.Z
44
+ ```
45
+
46
+ 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.7
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
 
@@ -234,9 +232,8 @@ To contribute or run the test suite locally:
234
232
 
235
233
  ```bash
236
234
  # Install dependencies
237
- pip install pipenv
238
- pipenv install --dev
235
+ uv sync
239
236
 
240
237
  # Run tests
241
- pipenv run pytest
238
+ uv run pytest
242
239
  ```
@@ -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
 
@@ -209,9 +207,8 @@ To contribute or run the test suite locally:
209
207
 
210
208
  ```bash
211
209
  # Install dependencies
212
- pip install pipenv
213
- pipenv install --dev
210
+ uv sync
214
211
 
215
212
  # Run tests
216
- pipenv run pytest
217
- ```
213
+ uv run pytest
214
+ ```
@@ -0,0 +1 @@
1
+ 1.6.7
@@ -3,7 +3,7 @@ import typing
3
3
 
4
4
  from typing import Any, Type, cast, Optional
5
5
 
6
- from graphql import GraphQLField, GraphQLObjectType, GraphQLOutputType, GraphQLInputField, GraphQLInputObjectType, GraphQLInputType
6
+ from graphql import GraphQLField, GraphQLObjectType, GraphQLOutputType, GraphQLInputField, GraphQLInputObjectType, GraphQLInputType, GraphQLNonNull
7
7
  from graphql.type.definition import is_output_type, is_input_type
8
8
  from pydantic import BaseModel
9
9
 
@@ -20,6 +20,19 @@ def type_is_pydantic_model(type_: Any) -> bool:
20
20
  return False
21
21
 
22
22
 
23
+ def _annotation_allows_none(annotation: Any) -> bool:
24
+ """True if the annotation explicitly permits ``None`` (e.g. ``Optional[X]``).
25
+
26
+ Pydantic v2 distinguishes "required" (must be supplied) from
27
+ "non-nullable" (cannot be None). ``email: Optional[str]`` with no default
28
+ is required but nullable, so it must NOT become a NonNull GraphQL field.
29
+ """
30
+ if annotation is type(None):
31
+ return True
32
+ args = typing.get_args(annotation)
33
+ return type(None) in args
34
+
35
+
23
36
  def _get_pydantic_model_description(pydantic_model: Type[BaseModel], max_docstring_length: Optional[int] = None) -> str:
24
37
  """
25
38
  Get description for a Pydantic model, filtering out default BaseModel docstring.
@@ -72,9 +85,19 @@ def type_from_pydantic_model(
72
85
  f"Mapped type for pydantic field '{name}' is not a valid GraphQL Input Type."
73
86
  )
74
87
 
75
- fields[to_camel_case(name)] = GraphQLInputField(
76
- cast(GraphQLInputType, graphql_type)
77
- )
88
+ gql_type = cast(GraphQLInputType, graphql_type)
89
+ # Required, non-nullable Pydantic fields become NonNull GraphQL
90
+ # inputs so the GraphQL layer enforces the same contract.
91
+ # ``Optional[X]`` (no default) stays nullable: it's required to
92
+ # supply but allowed to be None.
93
+ if (
94
+ field.is_required()
95
+ and not _annotation_allows_none(field.annotation)
96
+ and not isinstance(gql_type, GraphQLNonNull)
97
+ ):
98
+ gql_type = GraphQLNonNull(gql_type)
99
+
100
+ fields[to_camel_case(name)] = GraphQLInputField(gql_type)
78
101
  return fields
79
102
 
80
103
  def _make_out_type(model_cls: Type[BaseModel]):
@@ -128,8 +151,19 @@ def type_from_pydantic_model(
128
151
 
129
152
  return resolver
130
153
 
154
+ gql_type = cast(GraphQLOutputType, graphql_type)
155
+ # Required, non-nullable Pydantic fields become NonNull GraphQL
156
+ # outputs so the schema accurately advertises which fields are
157
+ # guaranteed. ``Optional[X]`` (no default) stays nullable.
158
+ if (
159
+ field.is_required()
160
+ and not _annotation_allows_none(field.annotation)
161
+ and not isinstance(gql_type, GraphQLNonNull)
162
+ ):
163
+ gql_type = GraphQLNonNull(gql_type)
164
+
131
165
  fields[to_camel_case(name)] = GraphQLField(
132
- cast(GraphQLOutputType, graphql_type), resolve=create_resolver(name)
166
+ gql_type, resolve=create_resolver(name)
133
167
  )
134
168
  return fields
135
169
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: graphql-api
3
- Version: 1.6.6
3
+ Version: 1.6.7
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
 
@@ -234,9 +232,8 @@ To contribute or run the test suite locally:
234
232
 
235
233
  ```bash
236
234
  # Install dependencies
237
- pip install pipenv
238
- pipenv install --dev
235
+ uv sync
239
236
 
240
237
  # Run tests
241
- pipenv run pytest
238
+ uv run pytest
242
239
  ```
@@ -1,4 +1,6 @@
1
1
  .gitignore
2
+ AGENTS.md
3
+ CLAUDE.md
2
4
  LICENSE
3
5
  README.md
4
6
  VERSION
@@ -1229,6 +1229,104 @@ class TestPydantic:
1229
1229
  }
1230
1230
  }
1231
1231
 
1232
+ def test_pydantic_required_input_fields_become_nonnull(self) -> None:
1233
+ """Required Pydantic fields on input types must be GraphQLNonNull.
1234
+
1235
+ Without this, the GraphQL API silently accepts ``null`` for fields the
1236
+ Pydantic model considers required, and downstream JSON Schema (e.g.
1237
+ graphql-mcp) loses required-ness for LLM tool calls.
1238
+ """
1239
+ from graphql import GraphQLNonNull, GraphQLInputObjectType
1240
+
1241
+ class CreateUserInput(BaseModel):
1242
+ name: str # required
1243
+ email: str # required
1244
+ age: Optional[int] = None # optional
1245
+
1246
+ class API:
1247
+ @field(mutable=True)
1248
+ def create_user(self, input: CreateUserInput) -> str:
1249
+ return f"created:{input.name}"
1250
+
1251
+ api = GraphQLAPI(root_type=API)
1252
+ schema = api.schema()
1253
+
1254
+ input_arg_type = schema.mutation_type.fields["createUser"].args["input"].type
1255
+ # `input` itself is non-null (method arg with no default).
1256
+ assert isinstance(input_arg_type, GraphQLNonNull)
1257
+ input_object: GraphQLInputObjectType = input_arg_type.of_type
1258
+ assert isinstance(input_object, GraphQLInputObjectType)
1259
+
1260
+ fields = input_object.fields
1261
+ assert isinstance(fields["name"].type, GraphQLNonNull), (
1262
+ "Required Pydantic field 'name' should map to a NonNull GraphQL type"
1263
+ )
1264
+ assert isinstance(fields["email"].type, GraphQLNonNull), (
1265
+ "Required Pydantic field 'email' should map to a NonNull GraphQL type"
1266
+ )
1267
+ assert not isinstance(fields["age"].type, GraphQLNonNull), (
1268
+ "Optional Pydantic field 'age' should remain nullable"
1269
+ )
1270
+
1271
+ def test_pydantic_required_input_rejects_null_at_graphql_layer(self) -> None:
1272
+ """Once required fields are NonNull, the GraphQL layer should reject
1273
+ a mutation that omits or nulls a required input field."""
1274
+
1275
+ class CreateUserInput(BaseModel):
1276
+ name: str
1277
+ email: str
1278
+
1279
+ class API:
1280
+ @field(mutable=True)
1281
+ def create_user(self, input: CreateUserInput) -> str:
1282
+ return f"created:{input.name}"
1283
+
1284
+ api = GraphQLAPI(root_type=API)
1285
+
1286
+ # Omitting a required field should be a GraphQL validation error,
1287
+ # not a silent success or a runtime Pydantic error.
1288
+ result = api.execute(
1289
+ 'mutation { createUser(input: {name: "Alice"}) }'
1290
+ )
1291
+ assert result.errors, "GraphQL should reject omission of required input field"
1292
+ assert any(
1293
+ "email" in str(err) for err in result.errors
1294
+ ), f"Error should mention the missing 'email' field, got: {result.errors}"
1295
+
1296
+ def test_pydantic_required_output_fields_become_nonnull(self) -> None:
1297
+ """Required Pydantic fields on output types must be GraphQLNonNull,
1298
+ so the schema correctly advertises which fields are guaranteed."""
1299
+ from graphql import GraphQLNonNull, GraphQLObjectType
1300
+
1301
+ class User(BaseModel):
1302
+ id: str # required
1303
+ name: str # required
1304
+ nickname: Optional[str] = None # optional
1305
+
1306
+ class API:
1307
+ @field
1308
+ def get_user(self) -> User:
1309
+ return User(id="1", name="Alice")
1310
+
1311
+ api = GraphQLAPI(root_type=API)
1312
+ schema = api.schema()
1313
+
1314
+ return_type = schema.query_type.fields["getUser"].type
1315
+ assert isinstance(return_type, GraphQLNonNull)
1316
+ user_type: GraphQLObjectType = return_type.of_type
1317
+ assert isinstance(user_type, GraphQLObjectType)
1318
+
1319
+ fields = user_type.fields
1320
+ assert isinstance(fields["id"].type, GraphQLNonNull), (
1321
+ "Required Pydantic output field 'id' should map to NonNull"
1322
+ )
1323
+ assert isinstance(fields["name"].type, GraphQLNonNull), (
1324
+ "Required Pydantic output field 'name' should map to NonNull"
1325
+ )
1326
+ assert not isinstance(fields["nickname"].type, GraphQLNonNull), (
1327
+ "Optional Pydantic output field 'nickname' should remain nullable"
1328
+ )
1329
+
1232
1330
  def test_pydantic_input_out_type_converts_to_model(self) -> None:
1233
1331
  """Test that Pydantic input types have out_type that produces model instances."""
1234
1332
  class Item(BaseModel):
graphql_api-1.6.6/VERSION DELETED
@@ -1 +0,0 @@
1
- 1.6.6
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes