graphql-api 1.6.4__tar.gz → 1.6.6__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 (105) hide show
  1. {graphql_api-1.6.4 → graphql_api-1.6.6}/PKG-INFO +1 -1
  2. graphql_api-1.6.6/VERSION +1 -0
  3. {graphql_api-1.6.4 → graphql_api-1.6.6}/graphql_api/pydantic.py +23 -1
  4. {graphql_api-1.6.4 → graphql_api-1.6.6}/graphql_api/utils.py +11 -4
  5. {graphql_api-1.6.4 → graphql_api-1.6.6}/graphql_api.egg-info/PKG-INFO +1 -1
  6. {graphql_api-1.6.4 → graphql_api-1.6.6}/tests/test_pydantic.py +92 -0
  7. graphql_api-1.6.4/VERSION +0 -1
  8. {graphql_api-1.6.4 → graphql_api-1.6.6}/.github/workflows/pages.yml +0 -0
  9. {graphql_api-1.6.4 → graphql_api-1.6.6}/.github/workflows/python-package.yml +0 -0
  10. {graphql_api-1.6.4 → graphql_api-1.6.6}/.gitignore +0 -0
  11. {graphql_api-1.6.4 → graphql_api-1.6.6}/.vscode/settings.json +0 -0
  12. {graphql_api-1.6.4 → graphql_api-1.6.6}/LICENSE +0 -0
  13. {graphql_api-1.6.4 → graphql_api-1.6.6}/README.md +0 -0
  14. {graphql_api-1.6.4 → graphql_api-1.6.6}/docs/README.md +0 -0
  15. {graphql_api-1.6.4 → graphql_api-1.6.6}/docs/content/_index.md +0 -0
  16. {graphql_api-1.6.4 → graphql_api-1.6.6}/docs/content/docs/_index.md +0 -0
  17. {graphql_api-1.6.4 → graphql_api-1.6.6}/docs/content/docs/advanced-features/_index.md +0 -0
  18. {graphql_api-1.6.4 → graphql_api-1.6.6}/docs/content/docs/advanced-features/directives.md +0 -0
  19. {graphql_api-1.6.4 → graphql_api-1.6.6}/docs/content/docs/advanced-features/performance.md +0 -0
  20. {graphql_api-1.6.4 → graphql_api-1.6.6}/docs/content/docs/advanced-features/schema-filtering.md +0 -0
  21. {graphql_api-1.6.4 → graphql_api-1.6.6}/docs/content/docs/building-applications/_index.md +0 -0
  22. {graphql_api-1.6.4 → graphql_api-1.6.6}/docs/content/docs/building-applications/documentation.md +0 -0
  23. {graphql_api-1.6.4 → graphql_api-1.6.6}/docs/content/docs/building-applications/error-handling.md +0 -0
  24. {graphql_api-1.6.4 → graphql_api-1.6.6}/docs/content/docs/distributed-systems/_index.md +0 -0
  25. {graphql_api-1.6.4 → graphql_api-1.6.6}/docs/content/docs/distributed-systems/federation.md +0 -0
  26. {graphql_api-1.6.4 → graphql_api-1.6.6}/docs/content/docs/distributed-systems/pagination-relay.md +0 -0
  27. {graphql_api-1.6.4 → graphql_api-1.6.6}/docs/content/docs/distributed-systems/remote-graphql.md +0 -0
  28. {graphql_api-1.6.4 → graphql_api-1.6.6}/docs/content/docs/fundamentals/_index.md +0 -0
  29. {graphql_api-1.6.4 → graphql_api-1.6.6}/docs/content/docs/fundamentals/async-and-subscriptions.md +0 -0
  30. {graphql_api-1.6.4 → graphql_api-1.6.6}/docs/content/docs/fundamentals/defining-schemas.md +0 -0
  31. {graphql_api-1.6.4 → graphql_api-1.6.6}/docs/content/docs/fundamentals/enums-interfaces.md +0 -0
  32. {graphql_api-1.6.4 → graphql_api-1.6.6}/docs/content/docs/fundamentals/field-types.md +0 -0
  33. {graphql_api-1.6.4 → graphql_api-1.6.6}/docs/content/docs/fundamentals/getting-started.md +0 -0
  34. {graphql_api-1.6.4 → graphql_api-1.6.6}/docs/content/docs/fundamentals/mutations.md +0 -0
  35. {graphql_api-1.6.4 → graphql_api-1.6.6}/docs/content/docs/fundamentals/object-types.md +0 -0
  36. {graphql_api-1.6.4 → graphql_api-1.6.6}/docs/content/docs/fundamentals/pydantic-and-dataclasses.md +0 -0
  37. {graphql_api-1.6.4 → graphql_api-1.6.6}/docs/content/docs/production/_index.md +0 -0
  38. {graphql_api-1.6.4 → graphql_api-1.6.6}/docs/content/docs/production/context-metadata.md +0 -0
  39. {graphql_api-1.6.4 → graphql_api-1.6.6}/docs/content/docs/production/middleware.md +0 -0
  40. {graphql_api-1.6.4 → graphql_api-1.6.6}/docs/content/docs/reference/_index.md +0 -0
  41. {graphql_api-1.6.4 → graphql_api-1.6.6}/docs/content/docs/reference/api-reference.md +0 -0
  42. {graphql_api-1.6.4 → graphql_api-1.6.6}/docs/content/docs/reference/contributing.md +0 -0
  43. {graphql_api-1.6.4 → graphql_api-1.6.6}/docs/content/docs/reference/examples.md +0 -0
  44. {graphql_api-1.6.4 → graphql_api-1.6.6}/docs/hugo.toml +0 -0
  45. {graphql_api-1.6.4 → graphql_api-1.6.6}/docs/index.md +0 -0
  46. {graphql_api-1.6.4 → graphql_api-1.6.6}/graphql_api/__init__.py +0 -0
  47. {graphql_api-1.6.4 → graphql_api-1.6.6}/graphql_api/api.py +0 -0
  48. {graphql_api-1.6.4 → graphql_api-1.6.6}/graphql_api/context.py +0 -0
  49. {graphql_api-1.6.4 → graphql_api-1.6.6}/graphql_api/dataclass_mapping.py +0 -0
  50. {graphql_api-1.6.4 → graphql_api-1.6.6}/graphql_api/decorators.py +0 -0
  51. {graphql_api-1.6.4 → graphql_api-1.6.6}/graphql_api/directives.py +0 -0
  52. {graphql_api-1.6.4 → graphql_api-1.6.6}/graphql_api/error.py +0 -0
  53. {graphql_api-1.6.4 → graphql_api-1.6.6}/graphql_api/exception.py +0 -0
  54. {graphql_api-1.6.4 → graphql_api-1.6.6}/graphql_api/executor.py +0 -0
  55. {graphql_api-1.6.4 → graphql_api-1.6.6}/graphql_api/federation/__init__.py +0 -0
  56. {graphql_api-1.6.4 → graphql_api-1.6.6}/graphql_api/federation/directives.py +0 -0
  57. {graphql_api-1.6.4 → graphql_api-1.6.6}/graphql_api/federation/federation.py +0 -0
  58. {graphql_api-1.6.4 → graphql_api-1.6.6}/graphql_api/federation/types.py +0 -0
  59. {graphql_api-1.6.4 → graphql_api-1.6.6}/graphql_api/mapper.py +0 -0
  60. {graphql_api-1.6.4 → graphql_api-1.6.6}/graphql_api/middleware.py +0 -0
  61. {graphql_api-1.6.4 → graphql_api-1.6.6}/graphql_api/mutation_schema.py +0 -0
  62. {graphql_api-1.6.4 → graphql_api-1.6.6}/graphql_api/pydantic_utils.py +0 -0
  63. {graphql_api-1.6.4 → graphql_api-1.6.6}/graphql_api/reduce.py +0 -0
  64. {graphql_api-1.6.4 → graphql_api-1.6.6}/graphql_api/relay.py +0 -0
  65. {graphql_api-1.6.4 → graphql_api-1.6.6}/graphql_api/remote.py +0 -0
  66. {graphql_api-1.6.4 → graphql_api-1.6.6}/graphql_api/schema.py +0 -0
  67. {graphql_api-1.6.4 → graphql_api-1.6.6}/graphql_api/schema_filter.py +0 -0
  68. {graphql_api-1.6.4 → graphql_api-1.6.6}/graphql_api/type_utils.py +0 -0
  69. {graphql_api-1.6.4 → graphql_api-1.6.6}/graphql_api/types.py +0 -0
  70. {graphql_api-1.6.4 → graphql_api-1.6.6}/graphql_api.egg-info/SOURCES.txt +0 -0
  71. {graphql_api-1.6.4 → graphql_api-1.6.6}/graphql_api.egg-info/dependency_links.txt +0 -0
  72. {graphql_api-1.6.4 → graphql_api-1.6.6}/graphql_api.egg-info/requires.txt +0 -0
  73. {graphql_api-1.6.4 → graphql_api-1.6.6}/graphql_api.egg-info/top_level.txt +0 -0
  74. {graphql_api-1.6.4 → graphql_api-1.6.6}/pyproject.toml +0 -0
  75. {graphql_api-1.6.4 → graphql_api-1.6.6}/setup.cfg +0 -0
  76. {graphql_api-1.6.4 → graphql_api-1.6.6}/tests/__init__.py +0 -0
  77. {graphql_api-1.6.4 → graphql_api-1.6.6}/tests/test_async.py +0 -0
  78. {graphql_api-1.6.4 → graphql_api-1.6.6}/tests/test_calculator.py +0 -0
  79. {graphql_api-1.6.4 → graphql_api-1.6.6}/tests/test_custom_suffixes.py +0 -0
  80. {graphql_api-1.6.4 → graphql_api-1.6.6}/tests/test_custom_types.py +0 -0
  81. {graphql_api-1.6.4 → graphql_api-1.6.6}/tests/test_dataclass.py +0 -0
  82. {graphql_api-1.6.4 → graphql_api-1.6.6}/tests/test_dataclass_relationships.py +0 -0
  83. {graphql_api-1.6.4 → graphql_api-1.6.6}/tests/test_decorator_patterns.py +0 -0
  84. {graphql_api-1.6.4 → graphql_api-1.6.6}/tests/test_directives.py +0 -0
  85. {graphql_api-1.6.4 → graphql_api-1.6.6}/tests/test_docs_advanced.py +0 -0
  86. {graphql_api-1.6.4 → graphql_api-1.6.6}/tests/test_docs_async_subscriptions.py +0 -0
  87. {graphql_api-1.6.4 → graphql_api-1.6.6}/tests/test_docs_defining_schemas.py +0 -0
  88. {graphql_api-1.6.4 → graphql_api-1.6.6}/tests/test_docs_field_types.py +0 -0
  89. {graphql_api-1.6.4 → graphql_api-1.6.6}/tests/test_docs_getting_started.py +0 -0
  90. {graphql_api-1.6.4 → graphql_api-1.6.6}/tests/test_docs_relay.py +0 -0
  91. {graphql_api-1.6.4 → graphql_api-1.6.6}/tests/test_docstrings.py +0 -0
  92. {graphql_api-1.6.4 → graphql_api-1.6.6}/tests/test_error.py +0 -0
  93. {graphql_api-1.6.4 → graphql_api-1.6.6}/tests/test_federation.py +0 -0
  94. {graphql_api-1.6.4 → graphql_api-1.6.6}/tests/test_federation_example.graphql +0 -0
  95. {graphql_api-1.6.4 → graphql_api-1.6.6}/tests/test_federation_example.py +0 -0
  96. {graphql_api-1.6.4 → graphql_api-1.6.6}/tests/test_filtering.py +0 -0
  97. {graphql_api-1.6.4 → graphql_api-1.6.6}/tests/test_graphql.py +0 -0
  98. {graphql_api-1.6.4 → graphql_api-1.6.6}/tests/test_middleware.py +0 -0
  99. {graphql_api-1.6.4 → graphql_api-1.6.6}/tests/test_performance.py +0 -0
  100. {graphql_api-1.6.4 → graphql_api-1.6.6}/tests/test_relay.py +0 -0
  101. {graphql_api-1.6.4 → graphql_api-1.6.6}/tests/test_remote.py +0 -0
  102. {graphql_api-1.6.4 → graphql_api-1.6.6}/tests/test_schema.py +0 -0
  103. {graphql_api-1.6.4 → graphql_api-1.6.6}/tests/test_subscriptions.py +0 -0
  104. {graphql_api-1.6.4 → graphql_api-1.6.6}/tests/test_unified_vs_explicit_schema.py +0 -0
  105. {graphql_api-1.6.4 → graphql_api-1.6.6}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: graphql-api
3
- Version: 1.6.4
3
+ Version: 1.6.6
4
4
  Summary: A framework for building Python GraphQL APIs.
5
5
  Author-email: Robert Parker <rob@parob.com>
6
6
  License: MIT
@@ -0,0 +1 @@
1
+ 1.6.6
@@ -7,7 +7,7 @@ from graphql import GraphQLField, GraphQLObjectType, GraphQLOutputType, GraphQLI
7
7
  from graphql.type.definition import is_output_type, is_input_type
8
8
  from pydantic import BaseModel
9
9
 
10
- from graphql_api.utils import to_camel_case
10
+ from graphql_api.utils import to_camel_case, to_snake_case
11
11
 
12
12
  if typing.TYPE_CHECKING:
13
13
  from graphql_api.mapper import GraphQLTypeMapper
@@ -77,9 +77,31 @@ def type_from_pydantic_model(
77
77
  )
78
78
  return fields
79
79
 
80
+ def _make_out_type(model_cls: Type[BaseModel]):
81
+ """Create an out_type that converts GraphQL input dicts to Pydantic models.
82
+
83
+ Mirrors the pattern in mapper.py's map_to_input for dataclass types.
84
+ """
85
+ def out_type(data):
86
+ # Convert camelCase GraphQL keys to snake_case Python keys
87
+ converted = {to_snake_case(key): value for key, value in data.items()}
88
+ # Strip None values so Pydantic uses its field defaults
89
+ # (graphql-core sets None for nullable fields not provided in input)
90
+ fields_with_defaults = {
91
+ name for name, info in model_cls.model_fields.items()
92
+ if info.default is not None or info.default_factory is not None
93
+ }
94
+ converted = {
95
+ k: v for k, v in converted.items()
96
+ if v is not None or k not in fields_with_defaults
97
+ }
98
+ return model_cls(**converted)
99
+ return out_type
100
+
80
101
  return GraphQLInputObjectType(
81
102
  name=f"{pydantic_model.__name__}Input",
82
103
  fields=get_input_fields,
104
+ out_type=_make_out_type(pydantic_model),
83
105
  description=_get_pydantic_model_description(
84
106
  pydantic_model, mapper.max_docstring_length),
85
107
  )
@@ -20,6 +20,15 @@ from graphql.type.definition import GraphQLInterfaceType, GraphQLType
20
20
 
21
21
  # From this response in Stackoverflow
22
22
  # http://stackoverflow.com/a/19053800/1072990
23
+ def _capitalize_first(s):
24
+ """Capitalize only the first character, preserving the rest.
25
+
26
+ Unlike str.title(), this doesn't capitalize letters after digits.
27
+ e.g. "30d" stays "30d" instead of becoming "30D".
28
+ """
29
+ return s[0].upper() + s[1:] if s else s
30
+
31
+
23
32
  def to_camel_case(snake_str, title=False):
24
33
  underscore_prefix = False
25
34
  if snake_str.startswith("_"):
@@ -27,12 +36,10 @@ def to_camel_case(snake_str, title=False):
27
36
  snake_str = snake_str[1:]
28
37
 
29
38
  components = snake_str.split("_")
30
- # We capitalize the first letter of each component except the first one
31
- # with the 'title' method and join them together.
32
39
  if not snake_str:
33
40
  return ""
34
- prefix = components[0].title() if title else components[0]
35
- value = prefix + "".join(x.title() if x else "_" for x in components[1:])
41
+ prefix = _capitalize_first(components[0]) if title else components[0]
42
+ value = prefix + "".join(_capitalize_first(x) if x else "_" for x in components[1:])
36
43
  return ("_" if underscore_prefix else "") + value
37
44
 
38
45
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: graphql-api
3
- Version: 1.6.4
3
+ Version: 1.6.6
4
4
  Summary: A framework for building Python GraphQL APIs.
5
5
  Author-email: Robert Parker <rob@parob.com>
6
6
  License: MIT
@@ -1170,3 +1170,95 @@ class TestPydantic:
1170
1170
  "tagCount": 0
1171
1171
  }
1172
1172
  }
1173
+
1174
+ def test_pydantic_input_with_variables_and_enum_defaults(self) -> None:
1175
+ """Test that List[PydanticModel] with enum defaults works via GraphQL variables.
1176
+
1177
+ When using variables (as graphql-mcp does), graphql-core coerces input
1178
+ objects via out_type. Without out_type, dicts pass through uncoerced.
1179
+ With out_type, Pydantic models should be properly constructed with
1180
+ defaults applied for omitted fields.
1181
+ """
1182
+ class Role(str, Enum):
1183
+ USER = "user"
1184
+ ADMIN = "admin"
1185
+
1186
+ class Message(BaseModel):
1187
+ text: str
1188
+ role: Role = Role.USER
1189
+
1190
+ class Result(BaseModel):
1191
+ count: int
1192
+ roles: List[str]
1193
+
1194
+ class VariableAPI:
1195
+ @field(mutable=True)
1196
+ def send(self, messages: List[Message]) -> Result:
1197
+ """Send messages."""
1198
+ roles = []
1199
+ for msg in messages:
1200
+ assert isinstance(
1201
+ msg, Message), f"Expected Message, got {type(msg)}"
1202
+ roles.append(msg.role.value)
1203
+ return Result(count=len(messages), roles=roles)
1204
+
1205
+ api = GraphQLAPI(root_type=VariableAPI)
1206
+
1207
+ # Test with variables (the path graphql-mcp uses)
1208
+ mutation = """
1209
+ mutation ($messages: [MessageInput!]!) {
1210
+ send(messages: $messages) {
1211
+ count
1212
+ roles
1213
+ }
1214
+ }
1215
+ """
1216
+ # Omit 'role' — should default to "user"
1217
+ variables = {
1218
+ "messages": [
1219
+ {"text": "hello"},
1220
+ {"text": "world", "role": "ADMIN"},
1221
+ ]
1222
+ }
1223
+ result = api.execute(mutation, variables=variables)
1224
+ assert result.errors is None
1225
+ assert result.data == {
1226
+ "send": {
1227
+ "count": 2,
1228
+ "roles": ["user", "admin"]
1229
+ }
1230
+ }
1231
+
1232
+ def test_pydantic_input_out_type_converts_to_model(self) -> None:
1233
+ """Test that Pydantic input types have out_type that produces model instances."""
1234
+ class Item(BaseModel):
1235
+ name: str
1236
+ active: bool = True
1237
+
1238
+ class ItemResult(BaseModel):
1239
+ is_model: bool
1240
+
1241
+ class OutTypeAPI:
1242
+ @field(mutable=True)
1243
+ def check_item(self, item: Item) -> ItemResult:
1244
+ """Check if item is a Pydantic model instance."""
1245
+ return ItemResult(is_model=isinstance(item, Item))
1246
+
1247
+ api = GraphQLAPI(root_type=OutTypeAPI)
1248
+
1249
+ # With inline args
1250
+ result = api.execute("""
1251
+ mutation {
1252
+ checkItem(item: {name: "test"}) { isModel }
1253
+ }
1254
+ """)
1255
+ assert result.errors is None
1256
+ assert result.data["checkItem"]["isModel"] is True
1257
+
1258
+ # With variables
1259
+ result = api.execute(
1260
+ 'mutation ($item: ItemInput!) { checkItem(item: $item) { isModel } }',
1261
+ variables={"item": {"name": "test"}}
1262
+ )
1263
+ assert result.errors is None
1264
+ assert result.data["checkItem"]["isModel"] is True
graphql_api-1.6.4/VERSION DELETED
@@ -1 +0,0 @@
1
- 1.6.4
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes