strawberry-graphql 0.222.0__py3-none-any.whl → 0.224.0__py3-none-any.whl
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.
- strawberry/channels/handlers/http_handler.py +1 -0
- strawberry/experimental/__init__.py +1 -1
- strawberry/experimental/pydantic/_compat.py +211 -28
- strawberry/experimental/pydantic/error_type.py +3 -2
- strawberry/experimental/pydantic/fields.py +9 -120
- strawberry/experimental/pydantic/object_type.py +16 -8
- strawberry/experimental/pydantic/utils.py +5 -9
- strawberry/printer/printer.py +3 -1
- strawberry/schema_codegen/__init__.py +310 -32
- strawberry/types/nodes.py +1 -0
- {strawberry_graphql-0.222.0.dist-info → strawberry_graphql-0.224.0.dist-info}/METADATA +1 -1
- {strawberry_graphql-0.222.0.dist-info → strawberry_graphql-0.224.0.dist-info}/RECORD +15 -15
- {strawberry_graphql-0.222.0.dist-info → strawberry_graphql-0.224.0.dist-info}/LICENSE +0 -0
- {strawberry_graphql-0.222.0.dist-info → strawberry_graphql-0.224.0.dist-info}/WHEEL +0 -0
- {strawberry_graphql-0.222.0.dist-info → strawberry_graphql-0.224.0.dist-info}/entry_points.txt +0 -0
@@ -1,10 +1,16 @@
|
|
1
1
|
import dataclasses
|
2
2
|
from dataclasses import dataclass
|
3
|
-
from
|
3
|
+
from decimal import Decimal
|
4
|
+
from functools import cached_property
|
5
|
+
from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Type
|
6
|
+
from uuid import UUID
|
4
7
|
|
8
|
+
import pydantic
|
5
9
|
from pydantic import BaseModel
|
6
10
|
from pydantic.version import VERSION as PYDANTIC_VERSION
|
7
11
|
|
12
|
+
from strawberry.experimental.pydantic.exceptions import UnsupportedTypeError
|
13
|
+
|
8
14
|
if TYPE_CHECKING:
|
9
15
|
from pydantic.fields import FieldInfo
|
10
16
|
|
@@ -24,21 +30,101 @@ class CompatModelField:
|
|
24
30
|
allow_none: bool
|
25
31
|
has_alias: bool
|
26
32
|
description: Optional[str]
|
33
|
+
_missing_type: Any
|
34
|
+
is_v1: bool
|
27
35
|
|
36
|
+
@property
|
37
|
+
def has_default_factory(self) -> bool:
|
38
|
+
return self.default_factory is not self._missing_type
|
28
39
|
|
29
|
-
|
30
|
-
|
40
|
+
@property
|
41
|
+
def has_default(self) -> bool:
|
42
|
+
return self.default is not self._missing_type
|
31
43
|
|
32
|
-
from pydantic._internal._typing_extra import is_new_type
|
33
|
-
from pydantic._internal._utils import lenient_issubclass, smart_deepcopy
|
34
|
-
from pydantic_core import PydanticUndefined
|
35
44
|
|
36
|
-
|
45
|
+
ATTR_TO_TYPE_MAP = {
|
46
|
+
"NoneStr": Optional[str],
|
47
|
+
"NoneBytes": Optional[bytes],
|
48
|
+
"StrBytes": None,
|
49
|
+
"NoneStrBytes": None,
|
50
|
+
"StrictStr": str,
|
51
|
+
"ConstrainedBytes": bytes,
|
52
|
+
"conbytes": bytes,
|
53
|
+
"ConstrainedStr": str,
|
54
|
+
"constr": str,
|
55
|
+
"EmailStr": str,
|
56
|
+
"PyObject": None,
|
57
|
+
"ConstrainedInt": int,
|
58
|
+
"conint": int,
|
59
|
+
"PositiveInt": int,
|
60
|
+
"NegativeInt": int,
|
61
|
+
"ConstrainedFloat": float,
|
62
|
+
"confloat": float,
|
63
|
+
"PositiveFloat": float,
|
64
|
+
"NegativeFloat": float,
|
65
|
+
"ConstrainedDecimal": Decimal,
|
66
|
+
"condecimal": Decimal,
|
67
|
+
"UUID1": UUID,
|
68
|
+
"UUID3": UUID,
|
69
|
+
"UUID4": UUID,
|
70
|
+
"UUID5": UUID,
|
71
|
+
"FilePath": None,
|
72
|
+
"DirectoryPath": None,
|
73
|
+
"Json": None,
|
74
|
+
"JsonWrapper": None,
|
75
|
+
"SecretStr": str,
|
76
|
+
"SecretBytes": bytes,
|
77
|
+
"StrictBool": bool,
|
78
|
+
"StrictInt": int,
|
79
|
+
"StrictFloat": float,
|
80
|
+
"PaymentCardNumber": None,
|
81
|
+
"ByteSize": None,
|
82
|
+
"AnyUrl": str,
|
83
|
+
"AnyHttpUrl": str,
|
84
|
+
"HttpUrl": str,
|
85
|
+
"PostgresDsn": str,
|
86
|
+
"RedisDsn": str,
|
87
|
+
}
|
88
|
+
|
89
|
+
ATTR_TO_TYPE_MAP_Pydantic_V2 = {
|
90
|
+
"EmailStr": str,
|
91
|
+
"SecretStr": str,
|
92
|
+
"SecretBytes": bytes,
|
93
|
+
"AnyUrl": str,
|
94
|
+
}
|
95
|
+
|
96
|
+
ATTR_TO_TYPE_MAP_Pydantic_Core_V2 = {
|
97
|
+
"MultiHostUrl": str,
|
98
|
+
}
|
37
99
|
|
38
|
-
def new_type_supertype(type_: Any) -> Any:
|
39
|
-
return type_.__supertype__
|
40
100
|
|
41
|
-
|
101
|
+
def get_fields_map_for_v2() -> Dict[Any, Any]:
|
102
|
+
import pydantic_core
|
103
|
+
|
104
|
+
fields_map = {
|
105
|
+
getattr(pydantic, field_name): type
|
106
|
+
for field_name, type in ATTR_TO_TYPE_MAP_Pydantic_V2.items()
|
107
|
+
if hasattr(pydantic, field_name)
|
108
|
+
}
|
109
|
+
fields_map.update(
|
110
|
+
{
|
111
|
+
getattr(pydantic_core, field_name): type
|
112
|
+
for field_name, type in ATTR_TO_TYPE_MAP_Pydantic_Core_V2.items()
|
113
|
+
if hasattr(pydantic_core, field_name)
|
114
|
+
}
|
115
|
+
)
|
116
|
+
|
117
|
+
return fields_map
|
118
|
+
|
119
|
+
|
120
|
+
class PydanticV2Compat:
|
121
|
+
@property
|
122
|
+
def PYDANTIC_MISSING_TYPE(self) -> Any:
|
123
|
+
from pydantic_core import PydanticUndefined
|
124
|
+
|
125
|
+
return PydanticUndefined
|
126
|
+
|
127
|
+
def get_model_fields(self, model: Type[BaseModel]) -> Dict[str, CompatModelField]:
|
42
128
|
field_info: dict[str, FieldInfo] = model.model_fields
|
43
129
|
new_fields = {}
|
44
130
|
# Convert it into CompatModelField
|
@@ -55,24 +141,34 @@ if IS_PYDANTIC_V2:
|
|
55
141
|
allow_none=False,
|
56
142
|
has_alias=field is not None,
|
57
143
|
description=field.description,
|
144
|
+
_missing_type=self.PYDANTIC_MISSING_TYPE,
|
145
|
+
is_v1=False,
|
58
146
|
)
|
59
147
|
return new_fields
|
60
148
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
get_origin,
|
65
|
-
is_new_type,
|
66
|
-
new_type_supertype,
|
67
|
-
)
|
68
|
-
from pydantic.utils import ( # type: ignore[no-redef]
|
69
|
-
lenient_issubclass,
|
70
|
-
smart_deepcopy,
|
71
|
-
)
|
149
|
+
@cached_property
|
150
|
+
def fields_map(self) -> Dict[Any, Any]:
|
151
|
+
return get_fields_map_for_v2()
|
72
152
|
|
73
|
-
|
153
|
+
def get_basic_type(self, type_: Any) -> Type[Any]:
|
154
|
+
if type_ in self.fields_map:
|
155
|
+
type_ = self.fields_map[type_]
|
74
156
|
|
75
|
-
|
157
|
+
if type_ is None:
|
158
|
+
raise UnsupportedTypeError()
|
159
|
+
|
160
|
+
if is_new_type(type_):
|
161
|
+
return new_type_supertype(type_)
|
162
|
+
|
163
|
+
return type_
|
164
|
+
|
165
|
+
|
166
|
+
class PydanticV1Compat:
|
167
|
+
@property
|
168
|
+
def PYDANTIC_MISSING_TYPE(self) -> Any:
|
169
|
+
return dataclasses.MISSING
|
170
|
+
|
171
|
+
def get_model_fields(self, model: Type[BaseModel]) -> Dict[str, CompatModelField]:
|
76
172
|
new_fields = {}
|
77
173
|
# Convert it into CompatModelField
|
78
174
|
for name, field in model.__fields__.items(): # type: ignore[attr-defined]
|
@@ -87,17 +183,104 @@ else:
|
|
87
183
|
allow_none=field.allow_none,
|
88
184
|
has_alias=field.has_alias,
|
89
185
|
description=field.field_info.description,
|
186
|
+
_missing_type=self.PYDANTIC_MISSING_TYPE,
|
187
|
+
is_v1=True,
|
90
188
|
)
|
91
189
|
return new_fields
|
92
190
|
|
191
|
+
@cached_property
|
192
|
+
def fields_map(self) -> Dict[Any, Any]:
|
193
|
+
if IS_PYDANTIC_V2:
|
194
|
+
return {
|
195
|
+
getattr(pydantic.v1, field_name): type
|
196
|
+
for field_name, type in ATTR_TO_TYPE_MAP.items()
|
197
|
+
if hasattr(pydantic.v1, field_name)
|
198
|
+
}
|
199
|
+
|
200
|
+
return {
|
201
|
+
getattr(pydantic, field_name): type
|
202
|
+
for field_name, type in ATTR_TO_TYPE_MAP.items()
|
203
|
+
if hasattr(pydantic, field_name)
|
204
|
+
}
|
205
|
+
|
206
|
+
def get_basic_type(self, type_: Any) -> Type[Any]:
|
207
|
+
if IS_PYDANTIC_V1:
|
208
|
+
ConstrainedInt = pydantic.ConstrainedInt
|
209
|
+
ConstrainedFloat = pydantic.ConstrainedFloat
|
210
|
+
ConstrainedStr = pydantic.ConstrainedStr
|
211
|
+
ConstrainedList = pydantic.ConstrainedList
|
212
|
+
else:
|
213
|
+
ConstrainedInt = pydantic.v1.ConstrainedInt
|
214
|
+
ConstrainedFloat = pydantic.v1.ConstrainedFloat
|
215
|
+
ConstrainedStr = pydantic.v1.ConstrainedStr
|
216
|
+
ConstrainedList = pydantic.v1.ConstrainedList
|
217
|
+
|
218
|
+
if lenient_issubclass(type_, ConstrainedInt):
|
219
|
+
return int
|
220
|
+
if lenient_issubclass(type_, ConstrainedFloat):
|
221
|
+
return float
|
222
|
+
if lenient_issubclass(type_, ConstrainedStr):
|
223
|
+
return str
|
224
|
+
if lenient_issubclass(type_, ConstrainedList):
|
225
|
+
return List[self.get_basic_type(type_.item_type)] # type: ignore
|
226
|
+
|
227
|
+
if type_ in self.fields_map:
|
228
|
+
type_ = self.fields_map[type_]
|
229
|
+
|
230
|
+
if type_ is None:
|
231
|
+
raise UnsupportedTypeError()
|
232
|
+
|
233
|
+
if is_new_type(type_):
|
234
|
+
return new_type_supertype(type_)
|
235
|
+
|
236
|
+
return type_
|
237
|
+
|
238
|
+
|
239
|
+
class PydanticCompat:
|
240
|
+
def __init__(self, is_v2: bool):
|
241
|
+
if is_v2:
|
242
|
+
self._compat = PydanticV2Compat()
|
243
|
+
else:
|
244
|
+
self._compat = PydanticV1Compat() # type: ignore[assignment]
|
245
|
+
|
246
|
+
@classmethod
|
247
|
+
def from_model(cls, model: Type[BaseModel]) -> "PydanticCompat":
|
248
|
+
if hasattr(model, "model_fields"):
|
249
|
+
return cls(is_v2=True)
|
250
|
+
|
251
|
+
return cls(is_v2=False)
|
252
|
+
|
253
|
+
def __getattr__(self, name: str) -> Any:
|
254
|
+
return getattr(self._compat, name)
|
255
|
+
|
256
|
+
|
257
|
+
if IS_PYDANTIC_V2:
|
258
|
+
from typing_extensions import get_args, get_origin
|
259
|
+
|
260
|
+
from pydantic._internal._typing_extra import is_new_type
|
261
|
+
from pydantic._internal._utils import lenient_issubclass, smart_deepcopy
|
262
|
+
|
263
|
+
def new_type_supertype(type_: Any) -> Any:
|
264
|
+
return type_.__supertype__
|
265
|
+
else:
|
266
|
+
from pydantic.typing import ( # type: ignore[no-redef]
|
267
|
+
get_args,
|
268
|
+
get_origin,
|
269
|
+
is_new_type,
|
270
|
+
new_type_supertype,
|
271
|
+
)
|
272
|
+
from pydantic.utils import ( # type: ignore[no-redef]
|
273
|
+
lenient_issubclass,
|
274
|
+
smart_deepcopy,
|
275
|
+
)
|
276
|
+
|
93
277
|
|
94
278
|
__all__ = [
|
95
|
-
"
|
279
|
+
"PydanticCompat",
|
280
|
+
"is_new_type",
|
96
281
|
"lenient_issubclass",
|
97
|
-
"get_args",
|
98
282
|
"get_origin",
|
99
|
-
"
|
283
|
+
"get_args",
|
100
284
|
"new_type_supertype",
|
101
|
-
"
|
102
|
-
"PYDANTIC_MISSING_TYPE",
|
285
|
+
"smart_deepcopy",
|
103
286
|
]
|
@@ -19,7 +19,7 @@ from pydantic import BaseModel
|
|
19
19
|
from strawberry.auto import StrawberryAuto
|
20
20
|
from strawberry.experimental.pydantic._compat import (
|
21
21
|
CompatModelField,
|
22
|
-
|
22
|
+
PydanticCompat,
|
23
23
|
lenient_issubclass,
|
24
24
|
)
|
25
25
|
from strawberry.experimental.pydantic.utils import (
|
@@ -72,7 +72,8 @@ def error_type(
|
|
72
72
|
all_fields: bool = False,
|
73
73
|
) -> Callable[..., Type]:
|
74
74
|
def wrap(cls: Type) -> Type:
|
75
|
-
|
75
|
+
compat = PydanticCompat.from_model(model)
|
76
|
+
model_fields = compat.get_model_fields(model)
|
76
77
|
fields_set = set(fields) if fields else set()
|
77
78
|
|
78
79
|
if fields:
|
@@ -1,23 +1,17 @@
|
|
1
1
|
import builtins
|
2
|
-
from
|
3
|
-
from typing import Any, Dict, List, Optional, Type, Union
|
2
|
+
from typing import Any, Union
|
4
3
|
from typing_extensions import Annotated
|
5
|
-
from uuid import UUID
|
6
4
|
|
7
|
-
import pydantic
|
8
5
|
from pydantic import BaseModel
|
9
6
|
|
10
7
|
from strawberry.experimental.pydantic._compat import (
|
11
|
-
|
8
|
+
PydanticCompat,
|
12
9
|
get_args,
|
13
10
|
get_origin,
|
14
|
-
is_new_type,
|
15
11
|
lenient_issubclass,
|
16
|
-
new_type_supertype,
|
17
12
|
)
|
18
13
|
from strawberry.experimental.pydantic.exceptions import (
|
19
14
|
UnregisteredTypeException,
|
20
|
-
UnsupportedTypeError,
|
21
15
|
)
|
22
16
|
from strawberry.types.types import StrawberryObjectDefinition
|
23
17
|
|
@@ -44,115 +38,6 @@ except ImportError:
|
|
44
38
|
raise
|
45
39
|
|
46
40
|
|
47
|
-
ATTR_TO_TYPE_MAP = {
|
48
|
-
"NoneStr": Optional[str],
|
49
|
-
"NoneBytes": Optional[bytes],
|
50
|
-
"StrBytes": None,
|
51
|
-
"NoneStrBytes": None,
|
52
|
-
"StrictStr": str,
|
53
|
-
"ConstrainedBytes": bytes,
|
54
|
-
"conbytes": bytes,
|
55
|
-
"ConstrainedStr": str,
|
56
|
-
"constr": str,
|
57
|
-
"EmailStr": str,
|
58
|
-
"PyObject": None,
|
59
|
-
"ConstrainedInt": int,
|
60
|
-
"conint": int,
|
61
|
-
"PositiveInt": int,
|
62
|
-
"NegativeInt": int,
|
63
|
-
"ConstrainedFloat": float,
|
64
|
-
"confloat": float,
|
65
|
-
"PositiveFloat": float,
|
66
|
-
"NegativeFloat": float,
|
67
|
-
"ConstrainedDecimal": Decimal,
|
68
|
-
"condecimal": Decimal,
|
69
|
-
"UUID1": UUID,
|
70
|
-
"UUID3": UUID,
|
71
|
-
"UUID4": UUID,
|
72
|
-
"UUID5": UUID,
|
73
|
-
"FilePath": None,
|
74
|
-
"DirectoryPath": None,
|
75
|
-
"Json": None,
|
76
|
-
"JsonWrapper": None,
|
77
|
-
"SecretStr": str,
|
78
|
-
"SecretBytes": bytes,
|
79
|
-
"StrictBool": bool,
|
80
|
-
"StrictInt": int,
|
81
|
-
"StrictFloat": float,
|
82
|
-
"PaymentCardNumber": None,
|
83
|
-
"ByteSize": None,
|
84
|
-
"AnyUrl": str,
|
85
|
-
"AnyHttpUrl": str,
|
86
|
-
"HttpUrl": str,
|
87
|
-
"PostgresDsn": str,
|
88
|
-
"RedisDsn": str,
|
89
|
-
}
|
90
|
-
|
91
|
-
ATTR_TO_TYPE_MAP_Pydantic_V2 = {
|
92
|
-
"EmailStr": str,
|
93
|
-
"SecretStr": str,
|
94
|
-
"SecretBytes": bytes,
|
95
|
-
"AnyUrl": str,
|
96
|
-
}
|
97
|
-
|
98
|
-
ATTR_TO_TYPE_MAP_Pydantic_Core_V2 = {
|
99
|
-
"MultiHostUrl": str,
|
100
|
-
}
|
101
|
-
|
102
|
-
|
103
|
-
def get_fields_map_for_v2() -> Dict[Any, Any]:
|
104
|
-
import pydantic_core
|
105
|
-
|
106
|
-
fields_map = {
|
107
|
-
getattr(pydantic, field_name): type
|
108
|
-
for field_name, type in ATTR_TO_TYPE_MAP_Pydantic_V2.items()
|
109
|
-
if hasattr(pydantic, field_name)
|
110
|
-
}
|
111
|
-
fields_map.update(
|
112
|
-
{
|
113
|
-
getattr(pydantic_core, field_name): type
|
114
|
-
for field_name, type in ATTR_TO_TYPE_MAP_Pydantic_Core_V2.items()
|
115
|
-
if hasattr(pydantic_core, field_name)
|
116
|
-
}
|
117
|
-
)
|
118
|
-
|
119
|
-
return fields_map
|
120
|
-
|
121
|
-
|
122
|
-
FIELDS_MAP = (
|
123
|
-
{
|
124
|
-
getattr(pydantic, field_name): type
|
125
|
-
for field_name, type in ATTR_TO_TYPE_MAP.items()
|
126
|
-
if hasattr(pydantic, field_name)
|
127
|
-
}
|
128
|
-
if IS_PYDANTIC_V1
|
129
|
-
else get_fields_map_for_v2()
|
130
|
-
)
|
131
|
-
|
132
|
-
|
133
|
-
def get_basic_type(type_: Any) -> Type[Any]:
|
134
|
-
if IS_PYDANTIC_V1:
|
135
|
-
# only pydantic v1 has these
|
136
|
-
if lenient_issubclass(type_, pydantic.ConstrainedInt):
|
137
|
-
return int
|
138
|
-
if lenient_issubclass(type_, pydantic.ConstrainedFloat):
|
139
|
-
return float
|
140
|
-
if lenient_issubclass(type_, pydantic.ConstrainedStr):
|
141
|
-
return str
|
142
|
-
if lenient_issubclass(type_, pydantic.ConstrainedList):
|
143
|
-
return List[get_basic_type(type_.item_type)] # type: ignore
|
144
|
-
|
145
|
-
if type_ in FIELDS_MAP:
|
146
|
-
type_ = FIELDS_MAP.get(type_)
|
147
|
-
if type_ is None:
|
148
|
-
raise UnsupportedTypeError()
|
149
|
-
|
150
|
-
if is_new_type(type_):
|
151
|
-
return new_type_supertype(type_)
|
152
|
-
|
153
|
-
return type_
|
154
|
-
|
155
|
-
|
156
41
|
def replace_pydantic_types(type_: Any, is_input: bool) -> Any:
|
157
42
|
if lenient_issubclass(type_, BaseModel):
|
158
43
|
attr = "_strawberry_input_type" if is_input else "_strawberry_type"
|
@@ -163,17 +48,21 @@ def replace_pydantic_types(type_: Any, is_input: bool) -> Any:
|
|
163
48
|
return type_
|
164
49
|
|
165
50
|
|
166
|
-
def replace_types_recursively(
|
51
|
+
def replace_types_recursively(
|
52
|
+
type_: Any, is_input: bool, compat: PydanticCompat
|
53
|
+
) -> Any:
|
167
54
|
"""Runs the conversions recursively into the arguments of generic types if any"""
|
168
|
-
basic_type = get_basic_type(type_)
|
55
|
+
basic_type = compat.get_basic_type(type_)
|
169
56
|
replaced_type = replace_pydantic_types(basic_type, is_input)
|
170
57
|
|
171
58
|
origin = get_origin(type_)
|
59
|
+
|
172
60
|
if not origin or not hasattr(type_, "__args__"):
|
173
61
|
return replaced_type
|
174
62
|
|
175
63
|
converted = tuple(
|
176
|
-
replace_types_recursively(t, is_input=is_input
|
64
|
+
replace_types_recursively(t, is_input=is_input, compat=compat)
|
65
|
+
for t in get_args(replaced_type)
|
177
66
|
)
|
178
67
|
|
179
68
|
if isinstance(replaced_type, TypingGenericAlias):
|
@@ -19,9 +19,8 @@ from typing import (
|
|
19
19
|
from strawberry.annotation import StrawberryAnnotation
|
20
20
|
from strawberry.auto import StrawberryAuto
|
21
21
|
from strawberry.experimental.pydantic._compat import (
|
22
|
-
IS_PYDANTIC_V1,
|
23
22
|
CompatModelField,
|
24
|
-
|
23
|
+
PydanticCompat,
|
25
24
|
)
|
26
25
|
from strawberry.experimental.pydantic.conversion import (
|
27
26
|
convert_pydantic_model_to_strawberry_class,
|
@@ -44,10 +43,12 @@ if TYPE_CHECKING:
|
|
44
43
|
from graphql import GraphQLResolveInfo
|
45
44
|
|
46
45
|
|
47
|
-
def get_type_for_field(field: CompatModelField, is_input: bool): # noqa: ANN201
|
46
|
+
def get_type_for_field(field: CompatModelField, is_input: bool, compat: PydanticCompat): # noqa: ANN201
|
48
47
|
outer_type = field.outer_type_
|
49
|
-
|
50
|
-
|
48
|
+
|
49
|
+
replaced_type = replace_types_recursively(outer_type, is_input, compat=compat)
|
50
|
+
|
51
|
+
if field.is_v1:
|
51
52
|
# only pydantic v1 has this Optional logic
|
52
53
|
should_add_optional: bool = field.allow_none
|
53
54
|
if should_add_optional:
|
@@ -62,9 +63,10 @@ def _build_dataclass_creation_fields(
|
|
62
63
|
existing_fields: Dict[str, StrawberryField],
|
63
64
|
auto_fields_set: Set[str],
|
64
65
|
use_pydantic_alias: bool,
|
66
|
+
compat: PydanticCompat,
|
65
67
|
) -> DataclassCreationFields:
|
66
68
|
field_type = (
|
67
|
-
get_type_for_field(field, is_input)
|
69
|
+
get_type_for_field(field, is_input, compat=compat)
|
68
70
|
if field.name in auto_fields_set
|
69
71
|
else existing_fields[field.name].type
|
70
72
|
)
|
@@ -129,7 +131,8 @@ def type(
|
|
129
131
|
use_pydantic_alias: bool = True,
|
130
132
|
) -> Callable[..., Type[StrawberryTypeFromPydantic[PydanticModel]]]:
|
131
133
|
def wrap(cls: Any) -> Type[StrawberryTypeFromPydantic[PydanticModel]]:
|
132
|
-
|
134
|
+
compat = PydanticCompat.from_model(model)
|
135
|
+
model_fields = compat.get_model_fields(model)
|
133
136
|
original_fields_set = set(fields) if fields else set()
|
134
137
|
|
135
138
|
if fields:
|
@@ -182,7 +185,12 @@ def type(
|
|
182
185
|
|
183
186
|
all_model_fields: List[DataclassCreationFields] = [
|
184
187
|
_build_dataclass_creation_fields(
|
185
|
-
field,
|
188
|
+
field,
|
189
|
+
is_input,
|
190
|
+
extra_fields_dict,
|
191
|
+
auto_fields_set,
|
192
|
+
use_pydantic_alias,
|
193
|
+
compat=compat,
|
186
194
|
)
|
187
195
|
for field_name, field in model_fields.items()
|
188
196
|
if field_name in fields_set
|
@@ -14,9 +14,8 @@ from typing import (
|
|
14
14
|
)
|
15
15
|
|
16
16
|
from strawberry.experimental.pydantic._compat import (
|
17
|
-
PYDANTIC_MISSING_TYPE,
|
18
17
|
CompatModelField,
|
19
|
-
|
18
|
+
PydanticCompat,
|
20
19
|
smart_deepcopy,
|
21
20
|
)
|
22
21
|
from strawberry.experimental.pydantic.exceptions import (
|
@@ -83,12 +82,8 @@ def get_default_factory_for_field(
|
|
83
82
|
Returns optionally a NoArgAnyCallable representing a default_factory parameter
|
84
83
|
"""
|
85
84
|
# replace dataclasses.MISSING with our own UNSET to make comparisons easier
|
86
|
-
default_factory =
|
87
|
-
|
88
|
-
if field.default_factory is not PYDANTIC_MISSING_TYPE
|
89
|
-
else UNSET
|
90
|
-
)
|
91
|
-
default = field.default if field.default is not PYDANTIC_MISSING_TYPE else UNSET
|
85
|
+
default_factory = field.default_factory if field.has_default_factory else UNSET
|
86
|
+
default = field.default if field.has_default else UNSET
|
92
87
|
|
93
88
|
has_factory = default_factory is not None and default_factory is not UNSET
|
94
89
|
has_default = default is not None and default is not UNSET
|
@@ -126,8 +121,9 @@ def get_default_factory_for_field(
|
|
126
121
|
def ensure_all_auto_fields_in_pydantic(
|
127
122
|
model: Type[BaseModel], auto_fields: Set[str], cls_name: str
|
128
123
|
) -> None:
|
124
|
+
compat = PydanticCompat.from_model(model)
|
129
125
|
# Raise error if user defined a strawberry.auto field not present in the model
|
130
|
-
non_existing_fields = list(auto_fields - get_model_fields(model).keys())
|
126
|
+
non_existing_fields = list(auto_fields - compat.get_model_fields(model).keys())
|
131
127
|
|
132
128
|
if non_existing_fields:
|
133
129
|
raise AutoFieldsNotInBaseModelError(
|
strawberry/printer/printer.py
CHANGED
@@ -74,7 +74,9 @@ def _serialize_dataclasses(value: Dict[_T, object]) -> Dict[_T, object]:
|
|
74
74
|
|
75
75
|
|
76
76
|
@overload
|
77
|
-
def _serialize_dataclasses(
|
77
|
+
def _serialize_dataclasses(
|
78
|
+
value: Union[List[object], Tuple[object]],
|
79
|
+
) -> List[object]:
|
78
80
|
...
|
79
81
|
|
80
82
|
|
@@ -2,6 +2,9 @@ from __future__ import annotations
|
|
2
2
|
|
3
3
|
import dataclasses
|
4
4
|
import keyword
|
5
|
+
from collections import defaultdict
|
6
|
+
from typing import TYPE_CHECKING, List, Tuple, Union
|
7
|
+
from typing_extensions import Protocol, TypeAlias
|
5
8
|
|
6
9
|
import libcst as cst
|
7
10
|
from graphql import (
|
@@ -19,14 +22,28 @@ from graphql import (
|
|
19
22
|
OperationType,
|
20
23
|
ScalarTypeDefinitionNode,
|
21
24
|
SchemaDefinitionNode,
|
25
|
+
SchemaExtensionNode,
|
22
26
|
StringValueNode,
|
23
27
|
TypeNode,
|
24
28
|
UnionTypeDefinitionNode,
|
25
29
|
parse,
|
26
30
|
)
|
31
|
+
from graphql.language.ast import (
|
32
|
+
BooleanValueNode,
|
33
|
+
ConstValueNode,
|
34
|
+
ListValueNode,
|
35
|
+
)
|
27
36
|
|
28
37
|
from strawberry.utils.str_converters import to_snake_case
|
29
38
|
|
39
|
+
if TYPE_CHECKING:
|
40
|
+
from graphql.language.ast import ConstDirectiveNode
|
41
|
+
|
42
|
+
|
43
|
+
class HasDirectives(Protocol):
|
44
|
+
directives: Tuple[ConstDirectiveNode, ...]
|
45
|
+
|
46
|
+
|
30
47
|
_SCALAR_MAP = {
|
31
48
|
"Int": cst.Name("int"),
|
32
49
|
"Float": cst.Name("float"),
|
@@ -48,6 +65,48 @@ _SCALAR_MAP = {
|
|
48
65
|
}
|
49
66
|
|
50
67
|
|
68
|
+
@dataclasses.dataclass(frozen=True)
|
69
|
+
class Import:
|
70
|
+
module: str | None
|
71
|
+
imports: tuple[str]
|
72
|
+
|
73
|
+
def module_path_to_cst(self, module_path: str) -> cst.Name | cst.Attribute:
|
74
|
+
parts = module_path.split(".")
|
75
|
+
|
76
|
+
module_name: cst.Name | cst.Attribute = cst.Name(parts[0])
|
77
|
+
|
78
|
+
for part in parts[1:]:
|
79
|
+
module_name = cst.Attribute(value=module_name, attr=cst.Name(part))
|
80
|
+
|
81
|
+
return module_name
|
82
|
+
|
83
|
+
def to_cst(self) -> cst.Import | cst.ImportFrom:
|
84
|
+
if self.module is None:
|
85
|
+
return cst.Import(
|
86
|
+
names=[cst.ImportAlias(name=cst.Name(name)) for name in self.imports]
|
87
|
+
)
|
88
|
+
|
89
|
+
return cst.ImportFrom(
|
90
|
+
module=self.module_path_to_cst(self.module),
|
91
|
+
names=[cst.ImportAlias(name=cst.Name(name)) for name in self.imports],
|
92
|
+
)
|
93
|
+
|
94
|
+
|
95
|
+
def _is_federation_link_directive(directive: ConstDirectiveNode) -> bool:
|
96
|
+
if directive.name.value != "link":
|
97
|
+
return False
|
98
|
+
|
99
|
+
return next(
|
100
|
+
(
|
101
|
+
argument.value.value
|
102
|
+
for argument in directive.arguments
|
103
|
+
if argument.name.value == "url"
|
104
|
+
if isinstance(argument.value, StringValueNode)
|
105
|
+
),
|
106
|
+
"",
|
107
|
+
).startswith("https://specs.apollo.dev/federation")
|
108
|
+
|
109
|
+
|
51
110
|
def _get_field_type(
|
52
111
|
field_type: TypeNode, was_non_nullable: bool = False
|
53
112
|
) -> cst.BaseExpression:
|
@@ -85,7 +144,19 @@ def _get_field_type(
|
|
85
144
|
)
|
86
145
|
|
87
146
|
|
88
|
-
def
|
147
|
+
def _sanitize_argument(value: ArgumentValue) -> cst.SimpleString | cst.Name | cst.List:
|
148
|
+
if isinstance(value, bool):
|
149
|
+
return cst.Name(value=str(value))
|
150
|
+
|
151
|
+
if isinstance(value, list):
|
152
|
+
return cst.List(
|
153
|
+
elements=[
|
154
|
+
cst.Element(value=_sanitize_argument(item))
|
155
|
+
for item in value
|
156
|
+
if item is not None
|
157
|
+
],
|
158
|
+
)
|
159
|
+
|
89
160
|
if "\n" in value:
|
90
161
|
argument_value = cst.SimpleString(f'"""\n{value}\n"""')
|
91
162
|
elif '"' in value:
|
@@ -93,6 +164,12 @@ def _get_argument(name: str, value: str) -> cst.Arg:
|
|
93
164
|
else:
|
94
165
|
argument_value = cst.SimpleString(f'"{value}"')
|
95
166
|
|
167
|
+
return argument_value
|
168
|
+
|
169
|
+
|
170
|
+
def _get_argument(name: str, value: ArgumentValue) -> cst.Arg:
|
171
|
+
argument_value = _sanitize_argument(value)
|
172
|
+
|
96
173
|
return cst.Arg(
|
97
174
|
value=argument_value,
|
98
175
|
keyword=cst.Name(name),
|
@@ -100,7 +177,27 @@ def _get_argument(name: str, value: str) -> cst.Arg:
|
|
100
177
|
)
|
101
178
|
|
102
179
|
|
103
|
-
|
180
|
+
# TODO: this might be removed now
|
181
|
+
def _get_argument_list(name: str, values: list[ArgumentValue]) -> cst.Arg:
|
182
|
+
value = cst.List(
|
183
|
+
elements=[cst.Element(value=_sanitize_argument(value)) for value in values],
|
184
|
+
)
|
185
|
+
|
186
|
+
return cst.Arg(
|
187
|
+
value=value,
|
188
|
+
keyword=cst.Name(name),
|
189
|
+
equal=cst.AssignEqual(cst.SimpleWhitespace(""), cst.SimpleWhitespace("")),
|
190
|
+
)
|
191
|
+
|
192
|
+
|
193
|
+
def _get_field_value(
|
194
|
+
field: FieldDefinitionNode | InputValueDefinitionNode,
|
195
|
+
alias: str | None,
|
196
|
+
is_apollo_federation: bool,
|
197
|
+
imports: set[Import],
|
198
|
+
) -> cst.Call | None:
|
199
|
+
description = field.description.value if field.description else None
|
200
|
+
|
104
201
|
args = list(
|
105
202
|
filter(
|
106
203
|
None,
|
@@ -111,6 +208,24 @@ def _get_field_value(description: str | None, alias: str | None) -> cst.Call | N
|
|
111
208
|
)
|
112
209
|
)
|
113
210
|
|
211
|
+
directives = _get_directives(field)
|
212
|
+
|
213
|
+
apollo_federation_args = _get_federation_arguments(directives, imports)
|
214
|
+
|
215
|
+
if is_apollo_federation and apollo_federation_args:
|
216
|
+
args.extend(apollo_federation_args)
|
217
|
+
|
218
|
+
return cst.Call(
|
219
|
+
func=cst.Attribute(
|
220
|
+
value=cst.Attribute(
|
221
|
+
value=cst.Name("strawberry"),
|
222
|
+
attr=cst.Name("federation"),
|
223
|
+
),
|
224
|
+
attr=cst.Name("field"),
|
225
|
+
),
|
226
|
+
args=args,
|
227
|
+
)
|
228
|
+
|
114
229
|
if args:
|
115
230
|
return cst.Call(
|
116
231
|
func=cst.Attribute(
|
@@ -125,6 +240,8 @@ def _get_field_value(description: str | None, alias: str | None) -> cst.Call | N
|
|
125
240
|
|
126
241
|
def _get_field(
|
127
242
|
field: FieldDefinitionNode | InputValueDefinitionNode,
|
243
|
+
is_apollo_federation: bool,
|
244
|
+
imports: set[Import],
|
128
245
|
) -> cst.SimpleStatementLine:
|
129
246
|
name = to_snake_case(field.name.value)
|
130
247
|
alias: str | None = None
|
@@ -141,19 +258,131 @@ def _get_field(
|
|
141
258
|
_get_field_type(field.type),
|
142
259
|
),
|
143
260
|
value=_get_field_value(
|
144
|
-
|
145
|
-
alias=alias
|
261
|
+
field,
|
262
|
+
alias=alias,
|
263
|
+
is_apollo_federation=is_apollo_federation,
|
264
|
+
imports=imports,
|
146
265
|
),
|
147
266
|
)
|
148
267
|
]
|
149
268
|
)
|
150
269
|
|
151
270
|
|
271
|
+
ArgumentValue: TypeAlias = Union[str, bool, List["ArgumentValue"]]
|
272
|
+
|
273
|
+
|
274
|
+
def _get_argument_value(argument_value: ConstValueNode) -> ArgumentValue:
|
275
|
+
if isinstance(argument_value, StringValueNode):
|
276
|
+
return argument_value.value
|
277
|
+
elif isinstance(argument_value, EnumValueDefinitionNode):
|
278
|
+
return argument_value.name.value
|
279
|
+
elif isinstance(argument_value, ListValueNode):
|
280
|
+
return [_get_argument_value(arg) for arg in argument_value.values]
|
281
|
+
elif isinstance(argument_value, BooleanValueNode):
|
282
|
+
return argument_value.value
|
283
|
+
else:
|
284
|
+
raise NotImplementedError(f"Unknown argument value {argument_value}")
|
285
|
+
|
286
|
+
|
287
|
+
def _get_directives(
|
288
|
+
definition: HasDirectives,
|
289
|
+
) -> dict[str, list[dict[str, ArgumentValue]]]:
|
290
|
+
directives: dict[str, list[dict[str, ArgumentValue]]] = defaultdict(list)
|
291
|
+
|
292
|
+
for directive in definition.directives:
|
293
|
+
directive_name = directive.name.value
|
294
|
+
|
295
|
+
directives[directive_name].append(
|
296
|
+
{
|
297
|
+
argument.name.value: _get_argument_value(argument.value)
|
298
|
+
for argument in directive.arguments
|
299
|
+
}
|
300
|
+
)
|
301
|
+
|
302
|
+
return directives
|
303
|
+
|
304
|
+
|
305
|
+
def _get_federation_arguments(
|
306
|
+
directives: dict[str, list[dict[str, ArgumentValue]]],
|
307
|
+
imports: set[Import],
|
308
|
+
) -> list[cst.Arg]:
|
309
|
+
def append_arg_from_directive(
|
310
|
+
directive: str,
|
311
|
+
argument_name: str,
|
312
|
+
keyword_name: str | None = None,
|
313
|
+
flatten: bool = True,
|
314
|
+
):
|
315
|
+
keyword_name = keyword_name or directive
|
316
|
+
|
317
|
+
if directive in directives:
|
318
|
+
values = [item[argument_name] for item in directives[directive]]
|
319
|
+
|
320
|
+
if flatten:
|
321
|
+
arguments.append(_get_argument(keyword_name, values))
|
322
|
+
else:
|
323
|
+
arguments.extend(_get_argument(keyword_name, value) for value in values)
|
324
|
+
|
325
|
+
arguments: list[cst.Arg] = []
|
326
|
+
|
327
|
+
append_arg_from_directive("key", "fields", "keys")
|
328
|
+
append_arg_from_directive("requires", "fields")
|
329
|
+
append_arg_from_directive("provides", "fields")
|
330
|
+
append_arg_from_directive(
|
331
|
+
"requiresScopes", "scopes", "requires_scopes", flatten=False
|
332
|
+
)
|
333
|
+
append_arg_from_directive("policy", "policies", "policy", flatten=False)
|
334
|
+
append_arg_from_directive("tag", "name", "tags")
|
335
|
+
|
336
|
+
boolean_keys = (
|
337
|
+
"shareable",
|
338
|
+
"inaccessible",
|
339
|
+
"external",
|
340
|
+
"authenticated",
|
341
|
+
)
|
342
|
+
|
343
|
+
arguments.extend(
|
344
|
+
_get_argument(key, True) for key in boolean_keys if directives.get(key, False)
|
345
|
+
)
|
346
|
+
|
347
|
+
if overrides := directives.get("override"):
|
348
|
+
override = overrides[0]
|
349
|
+
|
350
|
+
if "label" not in override:
|
351
|
+
arguments.append(_get_argument("override", override["from"]))
|
352
|
+
else:
|
353
|
+
imports.add(
|
354
|
+
Import(
|
355
|
+
module="strawberry.federation.schema_directives",
|
356
|
+
imports=("Override",),
|
357
|
+
)
|
358
|
+
)
|
359
|
+
|
360
|
+
arguments.append(
|
361
|
+
cst.Arg(
|
362
|
+
keyword=cst.Name("override"),
|
363
|
+
value=cst.Call(
|
364
|
+
func=cst.Name("Override"),
|
365
|
+
args=[
|
366
|
+
_get_argument("override_from", override["from"]),
|
367
|
+
_get_argument("label", override["label"]),
|
368
|
+
],
|
369
|
+
),
|
370
|
+
equal=cst.AssignEqual(
|
371
|
+
cst.SimpleWhitespace(""), cst.SimpleWhitespace("")
|
372
|
+
),
|
373
|
+
)
|
374
|
+
)
|
375
|
+
|
376
|
+
return arguments
|
377
|
+
|
378
|
+
|
152
379
|
def _get_strawberry_decorator(
|
153
380
|
definition: ObjectTypeDefinitionNode
|
154
381
|
| ObjectTypeExtensionNode
|
155
382
|
| InterfaceTypeDefinitionNode
|
156
383
|
| InputObjectTypeDefinitionNode,
|
384
|
+
is_apollo_federation: bool,
|
385
|
+
imports: set[Import],
|
157
386
|
) -> cst.Decorator:
|
158
387
|
type_ = {
|
159
388
|
ObjectTypeDefinitionNode: "type",
|
@@ -168,15 +397,36 @@ def _get_strawberry_decorator(
|
|
168
397
|
else None
|
169
398
|
)
|
170
399
|
|
400
|
+
directives = _get_directives(definition)
|
401
|
+
|
171
402
|
decorator: cst.BaseExpression = cst.Attribute(
|
172
403
|
value=cst.Name("strawberry"),
|
173
404
|
attr=cst.Name(type_),
|
174
405
|
)
|
175
406
|
|
407
|
+
arguments: list[cst.Arg] = []
|
408
|
+
|
176
409
|
if description is not None:
|
410
|
+
arguments.append(_get_argument("description", description.value))
|
411
|
+
|
412
|
+
federation_arguments = _get_federation_arguments(directives, imports)
|
413
|
+
|
414
|
+
# and has any directive that is a federation directive
|
415
|
+
if is_apollo_federation and federation_arguments:
|
416
|
+
decorator = cst.Attribute(
|
417
|
+
value=cst.Attribute(
|
418
|
+
value=cst.Name("strawberry"),
|
419
|
+
attr=cst.Name("federation"),
|
420
|
+
),
|
421
|
+
attr=cst.Name(type_),
|
422
|
+
)
|
423
|
+
|
424
|
+
arguments.extend(federation_arguments)
|
425
|
+
|
426
|
+
if arguments:
|
177
427
|
decorator = cst.Call(
|
178
428
|
func=decorator,
|
179
|
-
args=
|
429
|
+
args=arguments,
|
180
430
|
)
|
181
431
|
|
182
432
|
return cst.Decorator(
|
@@ -189,8 +439,10 @@ def _get_class_definition(
|
|
189
439
|
| ObjectTypeExtensionNode
|
190
440
|
| InterfaceTypeDefinitionNode
|
191
441
|
| InputObjectTypeDefinitionNode,
|
442
|
+
is_apollo_federation: bool,
|
443
|
+
imports: set[Import],
|
192
444
|
) -> cst.ClassDef:
|
193
|
-
decorator = _get_strawberry_decorator(definition)
|
445
|
+
decorator = _get_strawberry_decorator(definition, is_apollo_federation, imports)
|
194
446
|
|
195
447
|
bases = (
|
196
448
|
[cst.Arg(cst.Name(interface.name.value)) for interface in definition.interfaces]
|
@@ -204,7 +456,12 @@ def _get_class_definition(
|
|
204
456
|
return cst.ClassDef(
|
205
457
|
name=cst.Name(definition.name.value),
|
206
458
|
bases=bases,
|
207
|
-
body=cst.IndentedBlock(
|
459
|
+
body=cst.IndentedBlock(
|
460
|
+
body=[
|
461
|
+
_get_field(field, is_apollo_federation, imports)
|
462
|
+
for field in definition.fields
|
463
|
+
]
|
464
|
+
),
|
208
465
|
decorators=[decorator],
|
209
466
|
)
|
210
467
|
|
@@ -243,6 +500,7 @@ def _get_schema_definition(
|
|
243
500
|
root_query_name: str | None,
|
244
501
|
root_mutation_name: str | None,
|
245
502
|
root_subscription_name: str | None,
|
503
|
+
is_apollo_federation: bool,
|
246
504
|
) -> cst.SimpleStatementLine | None:
|
247
505
|
if not any([root_query_name, root_mutation_name, root_subscription_name]):
|
248
506
|
return None
|
@@ -265,39 +523,45 @@ def _get_schema_definition(
|
|
265
523
|
if root_subscription_name:
|
266
524
|
args.append(_get_arg("subscription", root_subscription_name))
|
267
525
|
|
526
|
+
schema_call = cst.Call(
|
527
|
+
func=cst.Attribute(
|
528
|
+
value=cst.Name("strawberry"),
|
529
|
+
attr=cst.Name("Schema"),
|
530
|
+
),
|
531
|
+
args=args,
|
532
|
+
)
|
533
|
+
|
534
|
+
if is_apollo_federation:
|
535
|
+
args.append(
|
536
|
+
cst.Arg(
|
537
|
+
keyword=cst.Name("enable_federation_2"),
|
538
|
+
value=cst.Name("True"),
|
539
|
+
equal=cst.AssignEqual(
|
540
|
+
cst.SimpleWhitespace(""), cst.SimpleWhitespace("")
|
541
|
+
),
|
542
|
+
)
|
543
|
+
)
|
544
|
+
schema_call = cst.Call(
|
545
|
+
func=cst.Attribute(
|
546
|
+
value=cst.Attribute(
|
547
|
+
value=cst.Name(value="strawberry"),
|
548
|
+
attr=cst.Name(value="federation"),
|
549
|
+
),
|
550
|
+
attr=cst.Name(value="Schema"),
|
551
|
+
),
|
552
|
+
args=args,
|
553
|
+
)
|
554
|
+
|
268
555
|
return cst.SimpleStatementLine(
|
269
556
|
body=[
|
270
557
|
cst.Assign(
|
271
558
|
targets=[cst.AssignTarget(cst.Name("schema"))],
|
272
|
-
value=
|
273
|
-
func=cst.Attribute(
|
274
|
-
value=cst.Name("strawberry"),
|
275
|
-
attr=cst.Name("Schema"),
|
276
|
-
),
|
277
|
-
args=args,
|
278
|
-
),
|
559
|
+
value=schema_call,
|
279
560
|
)
|
280
561
|
]
|
281
562
|
)
|
282
563
|
|
283
564
|
|
284
|
-
@dataclasses.dataclass(frozen=True)
|
285
|
-
class Import:
|
286
|
-
module: str | None
|
287
|
-
imports: tuple[str]
|
288
|
-
|
289
|
-
def to_cst(self) -> cst.Import | cst.ImportFrom:
|
290
|
-
if self.module is None:
|
291
|
-
return cst.Import(
|
292
|
-
names=[cst.ImportAlias(name=cst.Name(name)) for name in self.imports]
|
293
|
-
)
|
294
|
-
|
295
|
-
return cst.ImportFrom(
|
296
|
-
module=cst.Name(self.module),
|
297
|
-
names=[cst.ImportAlias(name=cst.Name(name)) for name in self.imports],
|
298
|
-
)
|
299
|
-
|
300
|
-
|
301
565
|
def _get_union_definition(definition: UnionTypeDefinitionNode) -> cst.Assign:
|
302
566
|
name = definition.name.value
|
303
567
|
|
@@ -430,6 +694,12 @@ def codegen(schema: str) -> str:
|
|
430
694
|
|
431
695
|
object_types: dict[str, cst.ClassDef] = {}
|
432
696
|
|
697
|
+
# when we encounter a extend schema @link ..., we check if is an apollo federation schema
|
698
|
+
# and we use this variable to keep track of it, but at the moment the assumption is that
|
699
|
+
# the schema extension is always done at the top, this might not be the case all the
|
700
|
+
# time
|
701
|
+
is_apollo_federation = False
|
702
|
+
|
433
703
|
for definition in document.definitions:
|
434
704
|
if isinstance(
|
435
705
|
definition,
|
@@ -440,7 +710,9 @@ def codegen(schema: str) -> str:
|
|
440
710
|
ObjectTypeExtensionNode,
|
441
711
|
),
|
442
712
|
):
|
443
|
-
class_definition = _get_class_definition(
|
713
|
+
class_definition = _get_class_definition(
|
714
|
+
definition, is_apollo_federation, imports
|
715
|
+
)
|
444
716
|
|
445
717
|
object_types[definition.name.value] = class_definition
|
446
718
|
|
@@ -478,6 +750,11 @@ def codegen(schema: str) -> str:
|
|
478
750
|
definitions.append(cst.EmptyLine())
|
479
751
|
definitions.append(scalar_definition)
|
480
752
|
definitions.append(cst.EmptyLine())
|
753
|
+
elif isinstance(definition, SchemaExtensionNode):
|
754
|
+
is_apollo_federation = any(
|
755
|
+
_is_federation_link_directive(directive)
|
756
|
+
for directive in definition.directives
|
757
|
+
)
|
481
758
|
else:
|
482
759
|
raise NotImplementedError(f"Unknown definition {definition}")
|
483
760
|
|
@@ -496,6 +773,7 @@ def codegen(schema: str) -> str:
|
|
496
773
|
root_query_name=root_query_name,
|
497
774
|
root_mutation_name=root_mutation_name,
|
498
775
|
root_subscription_name=root_subscription_name,
|
776
|
+
is_apollo_federation=is_apollo_federation,
|
499
777
|
)
|
500
778
|
|
501
779
|
if schema_definition:
|
strawberry/types/nodes.py
CHANGED
@@ -9,6 +9,7 @@ If a node has only one useful value, it's value is inlined.
|
|
9
9
|
If a list of nodes have unique names, it's transformed into a mapping.
|
10
10
|
Note Python dicts maintain ordering (for all supported versions).
|
11
11
|
"""
|
12
|
+
|
12
13
|
from __future__ import annotations
|
13
14
|
|
14
15
|
import dataclasses
|
@@ -22,7 +22,7 @@ strawberry/channels/__init__.py,sha256=9oRdAT7uIYETF-23gZ5NteOXSjwkUtwRmwu3YCFv1
|
|
22
22
|
strawberry/channels/handlers/base.py,sha256=tgXLjKCHrP-8yOJV-qo9iOMJUgDe2wW6rGs_nDZFi80,7869
|
23
23
|
strawberry/channels/handlers/graphql_transport_ws_handler.py,sha256=UKfxRoo1pEv1CBHhkhSTkbztJlfU-OoksFwyLktj8xc,1945
|
24
24
|
strawberry/channels/handlers/graphql_ws_handler.py,sha256=PHRkwnXt3tY4E0XBVHh4hpTyFBt9jQfTreBUmEo9vhI,2497
|
25
|
-
strawberry/channels/handlers/http_handler.py,sha256=
|
25
|
+
strawberry/channels/handlers/http_handler.py,sha256=c1Jpf7L4b7LvSaOolP2e9TWcEM5IqHXhScK0kZrhSg8,9561
|
26
26
|
strawberry/channels/handlers/ws_handler.py,sha256=sHL44eay4tNoKzkrRn3WewSYH-3ZSJzxJpmBJ-aTkeM,4650
|
27
27
|
strawberry/channels/router.py,sha256=dyOBbSF8nFiygP0zz6MM14mhkvFQAEbbLBXzcpubSHM,1927
|
28
28
|
strawberry/channels/testing.py,sha256=IWj1CuIS3vOo2f2fw0W-0GCz-YSs7QSAAscC6suqtiI,5668
|
@@ -81,16 +81,16 @@ strawberry/exceptions/syntax.py,sha256=OCz2Ai1Yn0jonRZXCDI2h30tPNsa8L5wMU8vcqIO4
|
|
81
81
|
strawberry/exceptions/unresolved_field_type.py,sha256=voYLO7kj_8DW0r3jvwn82HOqFIWk_3jvoPCciXFv5JA,1857
|
82
82
|
strawberry/exceptions/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
83
83
|
strawberry/exceptions/utils/source_finder.py,sha256=AcuevLgWwOSOR5zBDJbVyXMGRUxsTzrr07XUB0UqNiI,20044
|
84
|
-
strawberry/experimental/__init__.py,sha256=
|
84
|
+
strawberry/experimental/__init__.py,sha256=2HP5XtxL8ZKsPp4EDRAbMCqiP7p2V4Cca278JUGxnt0,102
|
85
85
|
strawberry/experimental/pydantic/__init__.py,sha256=jlsYH1j_9W4ieRpUKgt5zQPERDL7nc1ZBNQ3dAhJs8w,241
|
86
|
-
strawberry/experimental/pydantic/_compat.py,sha256=
|
86
|
+
strawberry/experimental/pydantic/_compat.py,sha256=sBWil9Ggf-OqfK35XB-0-rx9MS66QFVEqptPkF3sjC0,8139
|
87
87
|
strawberry/experimental/pydantic/conversion.py,sha256=_YMz4cYCwLUazkT2oCQ4B1gusksnXDHdFm1DldIrU7Q,4213
|
88
88
|
strawberry/experimental/pydantic/conversion_types.py,sha256=psVRmKE5fVcBAUT0Z2vCOLAXHddat2A0jJ-0SB1XDn8,963
|
89
|
-
strawberry/experimental/pydantic/error_type.py,sha256=
|
89
|
+
strawberry/experimental/pydantic/error_type.py,sha256=s8v0weIVM-9c0e3zqySaaa3pGZan7RvKTGuTtXLGPmE,4366
|
90
90
|
strawberry/experimental/pydantic/exceptions.py,sha256=GJt22DTolA24A0ZHcHsy-fcRkBnavgUZYOo0MBIVcjs,1427
|
91
|
-
strawberry/experimental/pydantic/fields.py,sha256=
|
92
|
-
strawberry/experimental/pydantic/object_type.py,sha256=
|
93
|
-
strawberry/experimental/pydantic/utils.py,sha256=
|
91
|
+
strawberry/experimental/pydantic/fields.py,sha256=QP5vYb8vKf7vOC69cjUPGOdlO_j4kZRdxalWgeqJ5Hk,2588
|
92
|
+
strawberry/experimental/pydantic/object_type.py,sha256=gZ_-SJoH4ZG6CIVa2BBInzW9TIXeTw_0-q74a-x1EuI,12372
|
93
|
+
strawberry/experimental/pydantic/utils.py,sha256=9m_oKyz0K1rTmh1ZrYtbFfyUNsiV11RiEb7bLrZuOXs,3770
|
94
94
|
strawberry/ext/LICENSE,sha256=_oY0TZg0b_sW0--0T44aMTpy2e2zF1Kiyn8E1qDiivo,1249
|
95
95
|
strawberry/ext/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
96
96
|
strawberry/ext/dataclasses/LICENSE,sha256=WZgm35K_3NJwLqxpEHJJi7CWxVrwTumEz5D3Dtd7WnA,13925
|
@@ -165,7 +165,7 @@ strawberry/parent.py,sha256=mCnJcLBQKwtokXcGxkm5HqGg7Wkst97rn61ViuaotrM,829
|
|
165
165
|
strawberry/permission.py,sha256=Rm_dft2AGZCSgBRQuxWEfncgWj88nmSdL-5mJ6TvHeA,5917
|
166
166
|
strawberry/printer/__init__.py,sha256=DmepjmgtkdF5RxK_7yC6qUyRWn56U-9qeZMbkztYB9w,62
|
167
167
|
strawberry/printer/ast_from_value.py,sha256=MFIX2V51d9ocRvD0Njemjk8YIzKh2BB1g2iUcX6a3d8,4946
|
168
|
-
strawberry/printer/printer.py,sha256=
|
168
|
+
strawberry/printer/printer.py,sha256=PXuYfAJxMU92aRETeSixhJjk-XUDu2ZFAFwkogzTu_Y,17461
|
169
169
|
strawberry/private.py,sha256=fS5IbVUajzr-6NcR41yt4ukXDPAMzwe1WPNdRJP5wWk,534
|
170
170
|
strawberry/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
171
171
|
strawberry/quart/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -194,7 +194,7 @@ strawberry/schema/types/__init__.py,sha256=oHO3COWhL3L1KLYCJNY1XFf5xt2GGtHiMC-Ua
|
|
194
194
|
strawberry/schema/types/base_scalars.py,sha256=Z_BmgwLicNexLipGyw6MmZ7OBnkGJU3ySgaY9SwBWrw,1837
|
195
195
|
strawberry/schema/types/concrete_type.py,sha256=HB30G1hMUuuvjAvfSe6ADS35iI_T_wKO-EprVOWTMSs,746
|
196
196
|
strawberry/schema/types/scalar.py,sha256=SVJ8HiKncCvOw2xwABI5xYaHcC7KkGHG-tx2WDtSoCA,2802
|
197
|
-
strawberry/schema_codegen/__init__.py,sha256=
|
197
|
+
strawberry/schema_codegen/__init__.py,sha256=2TO50tEZXQkH9yLUEBCZXEd0tUuzZ_XcY5dcKaf26ps,24040
|
198
198
|
strawberry/schema_directive.py,sha256=GxiOedFB-RJAflpQNUZv00C5Z6gavR-AYdsvoCA_0jc,1963
|
199
199
|
strawberry/starlite/__init__.py,sha256=v209swT8H9MljVL-npvANhEO1zz3__PSfxb_Ix-NoeE,134
|
200
200
|
strawberry/starlite/controller.py,sha256=x6Mm3r36cRfzo6hz9B4AYWbVh2QlYtndYcXFOr_3THM,11860
|
@@ -223,7 +223,7 @@ strawberry/types/fields/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG
|
|
223
223
|
strawberry/types/fields/resolver.py,sha256=Lwy2XVKnTbDyibk9pPsWsunlm22akXjy-gKCg22Tp1A,14130
|
224
224
|
strawberry/types/graphql.py,sha256=3SWZEsa0Zy1eVW6vy75BnB7t9_lJVi6TBV3_1j3RNBs,687
|
225
225
|
strawberry/types/info.py,sha256=b1ZWW_wUop6XrGNcGHKBQeUYjlX-y8u3s2Wm_XhKPYI,3412
|
226
|
-
strawberry/types/nodes.py,sha256=
|
226
|
+
strawberry/types/nodes.py,sha256=5tTYmxGpVDshbydicHTTBWEiUe8A7p7mdiaSV8Ry80Y,5027
|
227
227
|
strawberry/types/type_resolver.py,sha256=F0z_geS4VEun8EhD571LaTgI8ypjCeLfp910gF0Q3MY,6280
|
228
228
|
strawberry/types/types.py,sha256=t5MOV4xiutPL2Ka02u3Z2V3eqy2R2JPYQxsqCnY2Aqk,7139
|
229
229
|
strawberry/union.py,sha256=5DOzsmsrI4tU4aXO7BBEN-570ABQ9_VkRDvUJUrIofY,9926
|
@@ -241,8 +241,8 @@ strawberry/utils/logging.py,sha256=flS7hV0JiIOEdXcrIjda4WyIWix86cpHHFNJL8gl1y4,7
|
|
241
241
|
strawberry/utils/operation.py,sha256=Um-tBCPl3_bVFN2Ph7o1mnrxfxBes4HFCj6T0x4kZxE,1135
|
242
242
|
strawberry/utils/str_converters.py,sha256=avIgPVLg98vZH9mA2lhzVdyyjqzLsK2NdBw9mJQ02Xk,813
|
243
243
|
strawberry/utils/typing.py,sha256=Qxz1LwyVsNGV7LQW1dFsaUbsswj5LHBOdKLMom5eyEA,13491
|
244
|
-
strawberry_graphql-0.
|
245
|
-
strawberry_graphql-0.
|
246
|
-
strawberry_graphql-0.
|
247
|
-
strawberry_graphql-0.
|
248
|
-
strawberry_graphql-0.
|
244
|
+
strawberry_graphql-0.224.0.dist-info/LICENSE,sha256=m-XnIVUKqlG_AWnfi9NReh9JfKhYOB-gJfKE45WM1W8,1072
|
245
|
+
strawberry_graphql-0.224.0.dist-info/METADATA,sha256=lahprMtPJhhknOqREgJb0Yc-JujPzgHJXA5loc8tOi8,7740
|
246
|
+
strawberry_graphql-0.224.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
247
|
+
strawberry_graphql-0.224.0.dist-info/entry_points.txt,sha256=Nk7-aT3_uEwCgyqtHESV9H6Mc31cK-VAvhnQNTzTb4k,49
|
248
|
+
strawberry_graphql-0.224.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
{strawberry_graphql-0.222.0.dist-info → strawberry_graphql-0.224.0.dist-info}/entry_points.txt
RENAMED
File without changes
|