diff --git a/strawberry/types/field.py b/strawberry/types/field.py index a6d6c1adc2..a3cbd2393f 100644 --- a/strawberry/types/field.py +++ b/strawberry/types/field.py @@ -68,7 +68,7 @@ def _is_generic(resolver_type: Union[StrawberryType, type]) -> bool: return False -class StrawberryField(dataclasses.Field): +class StrawberryField: type_annotation: Optional[StrawberryAnnotation] default_resolver: Callable[[Any, str], object] = getattr @@ -89,27 +89,11 @@ def __init__( directives: Sequence[object] = (), extensions: list[FieldExtension] = (), # type: ignore ) -> None: - # basic fields are fields with no provided resolver - is_basic_field = not base_resolver - - kwargs: Any = {} - - # kw_only was added to python 3.10 and it is required - if sys.version_info >= (3, 10): - kwargs["kw_only"] = dataclasses.MISSING - - super().__init__( - default=default, - default_factory=default_factory, # type: ignore - init=is_basic_field, - repr=is_basic_field, - compare=is_basic_field, - hash=None, - metadata=metadata or {}, - **kwargs, - ) - + self.default = default + self.default_factory = default_factory self.graphql_name = graphql_name + self.metadata = metadata + if python_name is not None: self.python_name = python_name @@ -155,6 +139,27 @@ def __init__( ) self.deprecation_reason = deprecation_reason + def as_dataclass_field(self) -> dataclasses.Field: + # basic fields are fields with no provided resolver + is_basic_field = not self.base_resolver + + kwargs: Any = {} + + # kw_only was added to python 3.10 and it is required + if sys.version_info >= (3, 10): + kwargs["kw_only"] = dataclasses.MISSING + + return dataclasses.field( + default=self.default, + default_factory=self.default_factory, # type: ignore + init=is_basic_field, + repr=is_basic_field, + compare=is_basic_field, + hash=None, + metadata=self.metadata or {}, + **kwargs, + ) + def __copy__(self) -> Self: new_field = type(self)( python_name=self.python_name, diff --git a/strawberry/types/info.py b/strawberry/types/info.py index 78626d3ed9..e24e01d2bc 100644 --- a/strawberry/types/info.py +++ b/strawberry/types/info.py @@ -78,7 +78,7 @@ def __class_getitem__(cls, types: Union[type, tuple[type, ...]]) -> type[Info]: https://discuss.python.org/t/passing-only-one-typevar-of-two-when-using-defaults/49134 """ if not isinstance(types, tuple): - types = (types, Any) # type: ignore + types = (types, Any) return super().__class_getitem__(types) # type: ignore diff --git a/strawberry/types/object_type.py b/strawberry/types/object_type.py index 1c05113d4b..56ba715e4b 100644 --- a/strawberry/types/object_type.py +++ b/strawberry/types/object_type.py @@ -133,6 +133,24 @@ def _inject_default_for_maybe_annotations( setattr(cls, name, None) +def _preprocess_type( + cls: T, original_type_annotations: dict[str, Any], is_input: bool +) -> None: + annotations = getattr(cls, "__annotations__", {}) + + for field_name in annotations: + field = getattr(cls, field_name, None) + + if field and isinstance(field, StrawberryField): + if field.type_annotation: + original_type_annotations[field_name] = field.type_annotation.annotation + + field = field.as_dataclass_field() + + if is_input: + _inject_default_for_maybe_annotations(cls, annotations) + + def _process_type( cls: T, *, @@ -142,8 +160,20 @@ def _process_type( description: Optional[str] = None, directives: Optional[Sequence[object]] = (), extend: bool = False, - original_type_annotations: Optional[dict[str, Any]] = None, ) -> T: + # when running `_wrap_dataclass` we lose some of the information about the + # the passed types, especially the type_annotation inside the StrawberryField + # this makes it impossible to customise the field type, like this: + # >>> @strawberry.type + # >>> class Query: + # >>> a: int = strawberry.field(graphql_type=str) + # so we need to extract the information before running `_wrap_dataclass` + original_type_annotations: dict[str, Any] = {} + + _preprocess_type(cls, original_type_annotations, is_input) + + cls = _wrap_dataclass(cls) + name = name or to_camel_case(cls.__name__) original_type_annotations = original_type_annotations or {} @@ -286,35 +316,14 @@ def wrap(cls: T) -> T: exc = ObjectIsNotClassError.type raise exc(cls) - # when running `_wrap_dataclass` we lose some of the information about the - # the passed types, especially the type_annotation inside the StrawberryField - # this makes it impossible to customise the field type, like this: - # >>> @strawberry.type - # >>> class Query: - # >>> a: int = strawberry.field(graphql_type=str) - # so we need to extract the information before running `_wrap_dataclass` - original_type_annotations: dict[str, Any] = {} - - annotations = getattr(cls, "__annotations__", {}) - - for field_name in annotations: - field = getattr(cls, field_name, None) - - if field and isinstance(field, StrawberryField) and field.type_annotation: - original_type_annotations[field_name] = field.type_annotation.annotation - if is_input: - _inject_default_for_maybe_annotations(cls, annotations) - wrapped = _wrap_dataclass(cls) - return _process_type( # type: ignore - wrapped, + cls, name=name, is_input=is_input, is_interface=is_interface, description=description, directives=directives, extend=extend, - original_type_annotations=original_type_annotations, ) if cls is None: diff --git a/strawberry/utils/typing.py b/strawberry/utils/typing.py index 54a7efaf69..0f187fc254 100644 --- a/strawberry/utils/typing.py +++ b/strawberry/utils/typing.py @@ -130,10 +130,7 @@ def is_concrete_generic(annotation: type) -> bool: def is_generic_subclass(annotation: type) -> bool: - return isinstance(annotation, type) and issubclass( - annotation, - Generic, # type:ignore - ) + return isinstance(annotation, type) and issubclass(annotation, Generic) def is_generic(annotation: type) -> bool: @@ -185,7 +182,7 @@ def get_parameters(annotation: type) -> Union[tuple[object], tuple[()]]: and issubclass(annotation, Generic) # type:ignore and annotation is not Generic ): - return annotation.__parameters__ # type: ignore[union-attr] + return annotation.__parameters__ return () # pragma: no cover