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.
- {graphql_api-1.6.4 → graphql_api-1.6.6}/PKG-INFO +1 -1
- graphql_api-1.6.6/VERSION +1 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/graphql_api/pydantic.py +23 -1
- {graphql_api-1.6.4 → graphql_api-1.6.6}/graphql_api/utils.py +11 -4
- {graphql_api-1.6.4 → graphql_api-1.6.6}/graphql_api.egg-info/PKG-INFO +1 -1
- {graphql_api-1.6.4 → graphql_api-1.6.6}/tests/test_pydantic.py +92 -0
- graphql_api-1.6.4/VERSION +0 -1
- {graphql_api-1.6.4 → graphql_api-1.6.6}/.github/workflows/pages.yml +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/.github/workflows/python-package.yml +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/.gitignore +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/.vscode/settings.json +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/LICENSE +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/README.md +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/docs/README.md +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/docs/content/_index.md +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/docs/content/docs/_index.md +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/docs/content/docs/advanced-features/_index.md +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/docs/content/docs/advanced-features/directives.md +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/docs/content/docs/advanced-features/performance.md +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/docs/content/docs/advanced-features/schema-filtering.md +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/docs/content/docs/building-applications/_index.md +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/docs/content/docs/building-applications/documentation.md +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/docs/content/docs/building-applications/error-handling.md +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/docs/content/docs/distributed-systems/_index.md +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/docs/content/docs/distributed-systems/federation.md +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/docs/content/docs/distributed-systems/pagination-relay.md +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/docs/content/docs/distributed-systems/remote-graphql.md +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/docs/content/docs/fundamentals/_index.md +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/docs/content/docs/fundamentals/async-and-subscriptions.md +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/docs/content/docs/fundamentals/defining-schemas.md +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/docs/content/docs/fundamentals/enums-interfaces.md +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/docs/content/docs/fundamentals/field-types.md +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/docs/content/docs/fundamentals/getting-started.md +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/docs/content/docs/fundamentals/mutations.md +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/docs/content/docs/fundamentals/object-types.md +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/docs/content/docs/fundamentals/pydantic-and-dataclasses.md +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/docs/content/docs/production/_index.md +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/docs/content/docs/production/context-metadata.md +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/docs/content/docs/production/middleware.md +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/docs/content/docs/reference/_index.md +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/docs/content/docs/reference/api-reference.md +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/docs/content/docs/reference/contributing.md +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/docs/content/docs/reference/examples.md +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/docs/hugo.toml +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/docs/index.md +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/graphql_api/__init__.py +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/graphql_api/api.py +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/graphql_api/context.py +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/graphql_api/dataclass_mapping.py +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/graphql_api/decorators.py +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/graphql_api/directives.py +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/graphql_api/error.py +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/graphql_api/exception.py +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/graphql_api/executor.py +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/graphql_api/federation/__init__.py +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/graphql_api/federation/directives.py +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/graphql_api/federation/federation.py +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/graphql_api/federation/types.py +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/graphql_api/mapper.py +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/graphql_api/middleware.py +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/graphql_api/mutation_schema.py +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/graphql_api/pydantic_utils.py +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/graphql_api/reduce.py +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/graphql_api/relay.py +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/graphql_api/remote.py +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/graphql_api/schema.py +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/graphql_api/schema_filter.py +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/graphql_api/type_utils.py +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/graphql_api/types.py +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/graphql_api.egg-info/SOURCES.txt +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/graphql_api.egg-info/dependency_links.txt +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/graphql_api.egg-info/requires.txt +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/graphql_api.egg-info/top_level.txt +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/pyproject.toml +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/setup.cfg +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/tests/__init__.py +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/tests/test_async.py +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/tests/test_calculator.py +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/tests/test_custom_suffixes.py +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/tests/test_custom_types.py +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/tests/test_dataclass.py +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/tests/test_dataclass_relationships.py +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/tests/test_decorator_patterns.py +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/tests/test_directives.py +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/tests/test_docs_advanced.py +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/tests/test_docs_async_subscriptions.py +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/tests/test_docs_defining_schemas.py +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/tests/test_docs_field_types.py +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/tests/test_docs_getting_started.py +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/tests/test_docs_relay.py +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/tests/test_docstrings.py +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/tests/test_error.py +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/tests/test_federation.py +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/tests/test_federation_example.graphql +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/tests/test_federation_example.py +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/tests/test_filtering.py +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/tests/test_graphql.py +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/tests/test_middleware.py +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/tests/test_performance.py +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/tests/test_relay.py +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/tests/test_remote.py +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/tests/test_schema.py +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/tests/test_subscriptions.py +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/tests/test_unified_vs_explicit_schema.py +0 -0
- {graphql_api-1.6.4 → graphql_api-1.6.6}/uv.lock +0 -0
|
@@ -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]
|
|
35
|
-
value = prefix + "".join(x
|
|
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
|
|
|
@@ -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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{graphql_api-1.6.4 → graphql_api-1.6.6}/docs/content/docs/advanced-features/schema-filtering.md
RENAMED
|
File without changes
|
|
File without changes
|
{graphql_api-1.6.4 → graphql_api-1.6.6}/docs/content/docs/building-applications/documentation.md
RENAMED
|
File without changes
|
{graphql_api-1.6.4 → graphql_api-1.6.6}/docs/content/docs/building-applications/error-handling.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{graphql_api-1.6.4 → graphql_api-1.6.6}/docs/content/docs/distributed-systems/pagination-relay.md
RENAMED
|
File without changes
|
{graphql_api-1.6.4 → graphql_api-1.6.6}/docs/content/docs/distributed-systems/remote-graphql.md
RENAMED
|
File without changes
|
|
File without changes
|
{graphql_api-1.6.4 → graphql_api-1.6.6}/docs/content/docs/fundamentals/async-and-subscriptions.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{graphql_api-1.6.4 → graphql_api-1.6.6}/docs/content/docs/fundamentals/pydantic-and-dataclasses.md
RENAMED
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|