diff --git a/pyproject.toml b/pyproject.toml index 2c0b5e7..2a42696 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -92,3 +92,10 @@ exclude-newer-package = { reannotate = "1 hour" } [tool.ruff] extend-exclude = ["src/ducktools/classbuilder/_cached_methods.py"] + +[tool.ruff.lint] +ignore = ["BLE001", "I001", "PLC0414", "RUF023", "S102"] + +[tool.ruff.lint.extend-per-file-ignores] +"*.pyi" = ["PYI042"] +"src/ducktools/classbuilder/constants.py" = ["RUF012"] # Non-annotated dict diff --git a/src/ducktools/classbuilder/__init__.py b/src/ducktools/classbuilder/__init__.py index 37f9c9b..1c9f999 100644 --- a/src/ducktools/classbuilder/__init__.py +++ b/src/ducktools/classbuilder/__init__.py @@ -206,12 +206,15 @@ def builder( # Assign all of the method generators internal_methods = add_methods(cls, methods, internals=internals) - if "__eq__" in internal_methods and "__hash__" not in internal_methods: + if ( + "__eq__" in internal_methods + and "__hash__" not in internal_methods + and "__hash__" not in cls.__dict__ + ): # If an eq method has been defined and a hash method has not # Then the class is not frozen unless the user has # defined a hash method - if "__hash__" not in cls.__dict__: - setattr(cls, "__hash__", None) + cls.__hash__ = None # Add attribute indicating build completed internals["build_complete"] = True @@ -376,21 +379,24 @@ def __new__( # Dict access is faster if there is a __dict__ available. cached_properties = {} - if "__dict__" not in slot_values and "__dict__" not in base_attribs: - # Don't import functools - if functools := sys.modules.get("functools"): - # Iterate over a copy as we will mutate the original - for k, v in ns.copy().items(): - if isinstance(v, functools.cached_property): - cached_properties[k] = v - del ns[k] - # Add to slots only if it is not already a slot - slot_attrib = base_attribs.get(k, NOTHING) - if ( - slot_attrib is NOTHING - or type(slot_attrib) not in existing_slot_types - ): - slot_values[k] = None + # Check for cached properties - don't import functools if it's not imported + if ( + (functools := sys.modules.get("functools")) + and "__dict__" not in slot_values + and "__dict__" not in base_attribs + ): + # Iterate over a copy as we will mutate the original + for k, v in ns.copy().items(): + if isinstance(v, functools.cached_property): + cached_properties[k] = v + del ns[k] + # Add to slots only if it is not already a slot + slot_attrib = base_attribs.get(k, NOTHING) + if ( + slot_attrib is NOTHING + or type(slot_attrib) not in existing_slot_types + ): + slot_values[k] = None # Place slots *after* everything else to be safe ns["__slots__"] = slot_values @@ -404,9 +410,9 @@ def __new__( # Now reconstruct cached properties if cached_properties: # Now the class and slots have been created, create any new cached properties - for name, prop in cached_properties.items(): + for attrname, prop in cached_properties.items(): # This may be inherited, which is fine - slot = getattr(new_cls, name) + slot = getattr(new_cls, attrname) # May be a replaced cached property already, if so extract the actual slot if isinstance(slot, _SlottedCachedProperty): @@ -415,10 +421,10 @@ def __new__( slotted_property = _SlottedCachedProperty( slot=slot, func=prop.func, - attrname=name, + attrname=attrname, ) - setattr(new_cls, name, slotted_property) + setattr(new_cls, attrname, slotted_property) else: if gatherer is not None: @@ -569,7 +575,7 @@ def from_field(cls, fld, /, **kwargs): """ # type is special cased to get the internal value inst_fields = { - k: getattr(fld, k) if k != "type" else getattr(fld, "_type") + k: getattr(fld, k) if k != "type" else fld._type for k in get_fields(type(fld)) } argument_dict = {**inst_fields, **kwargs} @@ -803,8 +809,7 @@ def field_attribute_gatherer(cls_or_ns): cls_modifications = {} - for name in cls_attributes.keys(): - attrib = cls_attributes[name] + for name, attrib in cls_attributes.items(): if leave_default_values: cls_modifications[name] = attrib.default else: diff --git a/src/ducktools/classbuilder/__init__.pyi b/src/ducktools/classbuilder/__init__.pyi index 39e1f1e..41524e6 100644 --- a/src/ducktools/classbuilder/__init__.pyi +++ b/src/ducktools/classbuilder/__init__.pyi @@ -35,17 +35,16 @@ if sys.version_info >= (3, 14): import reannotate import annotationlib - _private_type = reannotate.DeferredAnnotation | type | str - _field_type = annotationlib.ForwardRef | type | str + type _private_type = reannotate.DeferredAnnotation | type | str + type _field_type = annotationlib.ForwardRef | type | str else: - _private_type = _field_type = type | str + type _private_type = type | str + type _field_type = type | str -_CopiableMappings = dict[str, typing.Any] | MappingProxyType[str, typing.Any] +type _CopiableMappings = dict[str, typing.Any] | MappingProxyType[str, typing.Any] -_T = typing.TypeVar("_T") -_FieldType = typing.TypeVar("_FieldType", bound=Field) -_gatherer_argtype = type | _CopiableMappings -_gatherer_returntype = tuple[dict[str, Field], dict[str, typing.Any]] +type _gatherer_argtype = type | _CopiableMappings +type _gatherer_returntype = tuple[dict[str, Field], dict[str, typing.Any]] __version__: str __version_tuple__: tuple[str | int, ...] @@ -59,7 +58,10 @@ class GetFieldsProtocol(typing.Protocol): @typing.type_check_only class NoArgGathererProtocol(typing.Protocol): def __call__( - self, cls_or_ns: _gatherer_argtype, *, cls_annotations: None | dict[str, typing.Any] + self, + cls_or_ns: _gatherer_argtype, + *, + cls_annotations: None | dict[str, typing.Any] ) -> tuple[dict[str, Field], dict[str, typing.Any]]: ... @typing.type_check_only @@ -69,39 +71,39 @@ class NoArgAnnotationGathererProtocol(typing.Protocol): ) -> tuple[dict[str, Field], dict[str, typing.Any]]: ... @typing.type_check_only -class GathererProtocol(typing.Protocol, typing.Generic[_FieldType]): +class GathererProtocol[FT: Field](typing.Protocol): def __call__( self, cls_or_ns: _gatherer_argtype, - ) -> tuple[dict[str, _FieldType], dict[str, typing.Any]]: ... + ) -> tuple[dict[str, FT], dict[str, typing.Any]]: ... @typing.type_check_only -class AnnotationGathererProtocol(typing.Protocol, typing.Generic[_FieldType]): +class AnnotationGathererProtocol[FT: Field](typing.Protocol): def __call__( self, cls_or_ns: _gatherer_argtype, *, cls_annotations: None | dict[str, typing.Any], - ) -> tuple[dict[str, _FieldType], dict[str, typing.Any]]: ... + ) -> tuple[dict[str, FT], dict[str, typing.Any]]: ... default_methods: frozenset[MethodMaker] -_TypeT = typing.TypeVar("_TypeT", bound=type) +# _TypeT = typing.TypeVar("_TypeT", bound=type) # Construction functions @typing.overload -def builder( - cls: _TypeT, +def builder[TypeT: type]( + cls: TypeT, /, *, gatherer: GathererProtocol[Field] | NoArgGathererProtocol, methods: frozenset[MethodMaker] | set[MethodMaker], flags: dict[str, bool] | None = None, field_getter: GetFieldsProtocol = ..., -) -> _TypeT: ... +) -> TypeT: ... @typing.overload -def builder( +def builder[TypeT: type]( cls: None = None, /, *, @@ -109,13 +111,13 @@ def builder( methods: frozenset[MethodMaker] | set[MethodMaker], flags: dict[str, bool] | None = None, field_getter: GetFieldsProtocol = ..., -) -> Callable[[_TypeT], _TypeT]: ... +) -> Callable[[TypeT], TypeT]: ... class SlotFields(dict): ... -class SlotMakerMeta(type): +class SlotMakerMeta[TypeT: type](type): def __new__( - cls: type[_TypeT], + cls: type[TypeT], name: str, bases: tuple[type, ...], ns: dict[str, typing.Any], @@ -123,10 +125,10 @@ class SlotMakerMeta(type): gatherer: GathererProtocol | None = ..., ignore_annotations: bool | None = ..., **kwargs: typing.Any, - ) -> _TypeT: ... + ) -> TypeT: ... class GatheredFields: - __slots__: tuple[str, ...] + __slots__: tuple[str, ...] = ... fields: dict[str, Field] modifications: dict[str, typing.Any] @@ -134,7 +136,6 @@ class GatheredFields: def __init__( self, fields: dict[str, Field], modifications: dict[str, typing.Any] ) -> None: ... - def __repr__(self) -> str: ... def __eq__(self, other) -> bool: ... def __call__( self, cls_or_ns: _gatherer_argtype, @@ -153,7 +154,7 @@ class Field(metaclass=SlotMakerMeta): compare: bool kw_only: bool - __slots__: dict[str, str] + __slots__: typing.ClassVar[dict[str, str]] = ... __classbuilder_internals__: dict def __init__( @@ -169,7 +170,6 @@ class Field(metaclass=SlotMakerMeta): kw_only: bool = ..., ) -> None: ... def __init_subclass__(cls, frozen: bool = ..., ignore_annotations: bool = ...): ... - def __repr__(self) -> str: ... def __eq__(self, other: Field | object) -> bool: ... def __replace__(self, **kwargs) -> typing.Self: ... def validate_field(self) -> None: ... @@ -181,47 +181,27 @@ class Field(metaclass=SlotMakerMeta): # These types only exist because type[Field] doesn't seem to resolve correctly # Technically they're wrong as `isinstance` gets used -_ReturnsField = Callable[..., Field] # Gatherers -@typing.overload -def make_slot_gatherer( - field_type: _ReturnsField = ..., -) -> NoArgGathererProtocol: ... -@typing.overload def make_slot_gatherer( - field_type: type[_FieldType], -) -> GathererProtocol[_FieldType]: ... -@typing.overload -def make_annotation_gatherer( - field_type: _ReturnsField = ..., - leave_default_values: bool = False, -) -> NoArgAnnotationGathererProtocol: ... -@typing.overload + field_type: type[Field] = ..., +) -> GathererProtocol[Field]: ... + def make_annotation_gatherer( - field_type: type[_FieldType], + field_type: type[Field] = ..., leave_default_values: bool = False, -) -> AnnotationGathererProtocol[_FieldType]: ... -@typing.overload -def make_field_gatherer( - field_type: _ReturnsField = ..., - leave_default_values: bool = False, -) -> NoArgGathererProtocol: ... -@typing.overload +) -> AnnotationGathererProtocol[Field]: ... + def make_field_gatherer( - field_type: type[_FieldType], + field_type: type[Field] = ..., leave_default_values: bool = False, -) -> GathererProtocol[_FieldType]: ... -@typing.overload -def make_unified_gatherer( - field_type: _ReturnsField = ..., - leave_default_values: bool = ..., -) -> NoArgGathererProtocol: ... -@typing.overload +) -> GathererProtocol[Field]: ... + def make_unified_gatherer( - field_type: type[_FieldType], + field_type: type[Field] = ..., leave_default_values: bool = ..., -) -> GathererProtocol[_FieldType]: ... +) -> GathererProtocol[Field]: ... + def slot_gatherer(cls_or_ns: type | _CopiableMappings) -> _gatherer_returntype: ... def annotation_gatherer( cls_or_ns: type | _CopiableMappings, @@ -232,22 +212,22 @@ def unified_gatherer(cls_or_ns: type | _CopiableMappings) -> _gatherer_returntyp def check_argument_order(cls: type) -> None: ... # Generic replace function -def replace(obj: _T, /, **changes: typing.Any) -> _T: ... +def replace[T](obj: T, /, **changes: typing.Any) -> T: ... # Basic slotclass example @typing.overload -def slotclass( - cls: _TypeT, +def slotclass[TypeT: type]( + cls: TypeT, /, *, methods: frozenset[MethodMaker] | set[MethodMaker] = default_methods, syntax_check: bool = True, -) -> _TypeT: ... +) -> TypeT: ... @typing.overload -def slotclass( +def slotclass[TypeT: type]( cls: None = None, /, *, methods: frozenset[MethodMaker] | set[MethodMaker] = default_methods, syntax_check: bool = True, -) -> Callable[[_TypeT], _TypeT]: ... +) -> Callable[[TypeT], TypeT]: ... diff --git a/src/ducktools/classbuilder/annotations.pyi b/src/ducktools/classbuilder/annotations.pyi index cf89f3d..14f5db8 100644 --- a/src/ducktools/classbuilder/annotations.pyi +++ b/src/ducktools/classbuilder/annotations.pyi @@ -24,7 +24,7 @@ import sys import typing import types -_CopiableMappings = dict[str, typing.Any] | types.MappingProxyType[str, typing.Any] +type _CopiableMappings = dict[str, typing.Any] | types.MappingProxyType[str, typing.Any] def get_func_annotations( func: types.FunctionType, diff --git a/src/ducktools/classbuilder/annotations/__init__.py b/src/ducktools/classbuilder/annotations/__init__.py index 6ef21d4..dfed007 100644 --- a/src/ducktools/classbuilder/annotations/__init__.py +++ b/src/ducktools/classbuilder/annotations/__init__.py @@ -67,12 +67,9 @@ def is_type(hint, t): # Strip `Annotated` if _get_origin(hint) is _Annotated: - hint = hint.__origin__ + hint = hint.__origin__ # type: ignore - if hint is t or getattr(hint, "__origin__", None) is t: - return True - - return False + return (hint is t or getattr(hint, "__origin__", None) is t) def replace_generic_with_arg(hint): diff --git a/src/ducktools/classbuilder/constants.pyi b/src/ducktools/classbuilder/constants.pyi index 0b7d37e..c89379c 100644 --- a/src/ducktools/classbuilder/constants.pyi +++ b/src/ducktools/classbuilder/constants.pyi @@ -31,12 +31,11 @@ REPLACE_NAME: str class _NothingType: custom: str | None def __new__(cls, custom: str | None = ...) -> typing.Self: ... - def __repr__(self) -> str: ... NOTHING: _NothingType FIELD_NOTHING: _NothingType class _KW_ONLY_META(type): - def __repr__(self) -> str: ... + ... class KW_ONLY(metaclass=_KW_ONLY_META): ... diff --git a/src/ducktools/classbuilder/methods.pyi b/src/ducktools/classbuilder/methods.pyi index bb75c41..b210725 100644 --- a/src/ducktools/classbuilder/methods.pyi +++ b/src/ducktools/classbuilder/methods.pyi @@ -73,7 +73,7 @@ type _ArgcountCodegenType = _InitArgcountCodegenType | _SetattrArgcountCodegenTy class GeneratedCode: - __slots__: tuple[str, ...] + __slots__: tuple[str, ...] = ... source_code: str globs: dict[str, typing.Any] annotations: dict[str, typing.Any] @@ -84,11 +84,10 @@ class GeneratedCode: globs: dict[str, typing.Any] | None = ..., annotations: dict[str, typing.Any] | None = ..., ) -> None: ... - def __repr__(self) -> str: ... def generate(self) -> types.FunctionType: ... class MethodMaker: - __slots__: tuple[str, ...] + __slots__: tuple[str, ...] = ... funcname: str code_generator: _CodegenType cached_generator: _CachedFunctionBuilder @@ -101,12 +100,11 @@ class MethodMaker: cached_generator: None | _CachedFunctionBuilder = ..., decorator: None | Callable[[types.FunctionType], types.FunctionType] = ..., ) -> None: ... - def __repr__(self) -> str: ... def attach(self, cls: type) -> None: ... def generate(self, cls: type) -> types.FunctionType: ... class _AttachedMethod: - __slots__: tuple[str, ...] + __slots__: tuple[str, ...] = ... maker: MethodMaker cls: type @@ -118,7 +116,6 @@ class _AttachedMethod: maker: MethodMaker, cls: type, ) -> None: ... - def __repr__(self) -> str: ... def __eq__(self, other) -> bool: ... def generate(self) -> types.FunctionType: ... def __call__(self, *args, **kwargs) -> typing.Any: ... @@ -163,7 +160,7 @@ def get_init_parameters(cls: type) -> _FunctionParameterType: ... def get_counter_field_names(argcount: int) -> list[str]: ... class _CacheStats: - __slots__: tuple[str, ...] + __slots__: tuple[str, ...] = ... hits: int misses: int skips: int @@ -179,10 +176,9 @@ class _CacheStats: @property def hit_percent(self) -> float: ... def __init__(self) -> None: ... - def __repr__(self) -> str: ... class _SimpleCache: - __slots__: tuple[str, ...] + __slots__: tuple[str, ...] = ... _func: Callable[..., types.FunctionType] _internal_cache: dict[tuple, types.FunctionType] _stats: _CacheStats @@ -193,7 +189,6 @@ class _SimpleCache: *, cache_seed: dict[tuple, types.FunctionType] | None = ..., ) -> None: ... - def __repr__(self) -> str: ... def clear( self, new_cache: dict[tuple, types.FunctionType] | None = ..., diff --git a/src/ducktools/classbuilder/prefab.py b/src/ducktools/classbuilder/prefab.py index f2bdb2f..244fb61 100644 --- a/src/ducktools/classbuilder/prefab.py +++ b/src/ducktools/classbuilder/prefab.py @@ -29,8 +29,6 @@ "ducktools.classbuilder.annotations", ] -import sys - try: from _types import GenericAlias, NoneType, MappingProxyType # type: ignore except ImportError: @@ -52,7 +50,7 @@ FIELD_NOTHING, INTERNALS_DICT, NOTHING, - KW_ONLY as KW_ONLY, + KW_ONLY as KW_ONLY, # re-export ) from .functions import ( build_completed, @@ -240,12 +238,12 @@ def init_generator(cls, funcname="__init__"): kw_only_arglist.append(arg) else: pos_arglist.append(arg) - # Not in init, but need to set defaults else: + # Not in init, but need to set defaults if attrib.default is not NOTHING: if type(attrib.default) not in LITERAL_TYPES: globs[f"_{name}_default"] = attrib.default - elif attrib.default_factory is not NOTHING: + elif attrib.default_factory is not NOTHING: # ruff: ignore[SIM102] # written to match the DEFAULT condition if attrib.default_factory not in LITERAL_CONTAINERS: globs[f"_{name}_factory"] = attrib.default_factory @@ -591,7 +589,7 @@ def _prefab_preprocess( raise TypeError("Cannot inherit non-frozen prefab from a frozen one") slots = cls_dict.get("__slots__") - slotted = False if slots is None else True + slotted = slots is not None if gathered_fields is not None: gatherer = gathered_fields @@ -703,23 +701,21 @@ def _prefab_postprocess(cls, /, *, fields, kw_only): # Error check: After inheritance, for name, attrib in fields.items(): - if not kw_only: + if (not kw_only and attrib.init and not attrib.kw_only): # Syntax check arguments for __init__ don't have non-default after default - if attrib.init and not attrib.kw_only: - if attrib.default is not NOTHING or attrib.default_factory is not NOTHING: - default_defined.append(name) - else: - if default_defined: - names = ", ".join(default_defined) + if attrib.default is not NOTHING or attrib.default_factory is not NOTHING: + default_defined.append(name) + else: + if default_defined: + names = ", ".join(default_defined) - err = SyntaxError( - "non-default argument follows default argument" - ) - if sys.version_info >= (3, 11): - err.add_note(f"defaults: {names}") - err.add_note(f"non_default after default: {name}") + err = SyntaxError( + "non-default argument follows default argument" + ) + err.add_note(f"defaults: {names}") + err.add_note(f"non_default after default: {name}") - raise err + raise err def _make_prefab( @@ -791,11 +787,7 @@ def _make_prefab( setattr(cls, PREFAB_FIELDS, list(fields.keys())) if match_args and "__match_args__" not in cls.__dict__: - setattr( - cls, - "__match_args__", - tuple(k for k, v in fields.items() if v.init) - ) + cls.__match_args__ = tuple(k for k, v in fields.items() if v.init) # Post construction checks _prefab_postprocess(cls, kw_only=kw_only, fields=fields) @@ -851,9 +843,8 @@ def __init_subclass__( # Remove the value of slotted if it exists flags.pop("slotted", None) - for k in default_values: + for k, default in default_values.items(): kwarg_value = kwargs.pop(k, None) - default = default_values[k] if kwarg_value is not None: flags[k] = kwarg_value diff --git a/src/ducktools/classbuilder/prefab.pyi b/src/ducktools/classbuilder/prefab.pyi index c6eb5c9..f419aff 100644 --- a/src/ducktools/classbuilder/prefab.pyi +++ b/src/ducktools/classbuilder/prefab.pyi @@ -23,10 +23,9 @@ __lazy_modules__: list[str] import typing from types import GenericAlias, MappingProxyType -from typing_extensions import dataclass_transform +from typing import dataclass_transform -# Suppress weird pylance error -from collections.abc import Callable # type: ignore +from collections.abc import Callable from . import ( Field, @@ -47,8 +46,6 @@ from .constants import ( from .methods import GeneratedCode, MethodMaker -_T = typing.TypeVar("_T") - PREFAB_FIELDS: str PREFAB_INIT_FUNC: str PRE_INIT_FUNC: str @@ -57,13 +54,11 @@ POST_INIT_FUNC: str LITERAL_TYPES: frozenset[type] LITERAL_CONTAINERS: frozenset[type] -_CopiableMappings = dict[str, typing.Any] | MappingProxyType[str, typing.Any] - class PrefabError(Exception): ... -class InitParam(typing.Any): +class InitParam[T](typing.Any): def __class_getitem__(cls, t: type) -> GenericAlias: ... - def __new__(cls, arg: _T) -> _T: ... # type: ignore + def __new__(cls, arg: T) -> T: ... # type: ignore def get_attributes(cls: type, *, local: bool = ...) -> dict[str, Attribute]: ... @@ -86,7 +81,7 @@ iter_maker: MethodMaker asdict_maker: MethodMaker class Attribute(Field): - __slots__: dict + __slots__: typing.ClassVar[dict[str, str]] = ... __classbuilder_gathered_fields__: tuple[dict[str, Field], dict[str, typing.Any]] __classbuilder_meta_gatherer__: GathererProtocol @@ -110,14 +105,13 @@ class Attribute(Field): metadata: dict | None = ..., ) -> None: ... - def __repr__(self) -> str: ... def __eq__(self, other: Attribute | object) -> bool: ... def validate_field(self) -> None: ... @typing.overload -def attribute( +def attribute[T]( *, - default: _T, + default: T, default_factory: _NothingType = NOTHING, init: bool = ..., repr: bool = ..., @@ -130,13 +124,13 @@ def attribute( doc: str | None = ..., metadata: dict | None = ..., type: type | _NothingType = ..., -) -> _T: ... +) -> T: ... @typing.overload -def attribute( +def attribute[T]( *, default: _NothingType = NOTHING, - default_factory: Callable[[], _T], + default_factory: Callable[[], T], init: bool = ..., repr: bool = ..., compare: bool = ..., @@ -148,7 +142,7 @@ def attribute( doc: str | None = ..., metadata: dict | None = ..., type: type | _NothingType = ..., -) -> _T: ... +) -> T: ... @typing.overload def attribute( @@ -213,40 +207,29 @@ class Prefab(metaclass=SlotMakerMeta): # As far as I can tell these are the correct types # But mypy.stubtest crashes trying to analyse them # Due to the combination of overload and dataclass_transform -# @typing.overload -# def prefab( -# cls: None = None, -# *, -# init: bool = ..., -# repr: bool = ..., -# eq: bool = ..., -# iter: bool = ..., -# match_args: bool = ..., -# kw_only: bool = ..., -# frozen: bool = ..., -# dict_method: bool = ..., -# ) -> Callable[[type[_T]], type[_T]]: ... - -# @dataclass_transform(field_specifiers=(Attribute, attribute)) -# @typing.overload -# def prefab( -# cls: type[_T], -# *, -# init: bool = ..., -# repr: bool = ..., -# eq: bool = ..., -# iter: bool = ..., -# match_args: bool = ..., -# kw_only: bool = ..., -# frozen: bool = ..., -# dict_method: bool = ..., -# ) -> type[_T]: ... +@dataclass_transform(field_specifiers=(Attribute, attribute)) +@typing.overload +def prefab[T]( + cls: None = None, + *, + init: bool = ..., + repr: bool = ..., + eq: bool = ..., + order: bool = ..., + iter: bool = ..., + match_args: bool = ..., + kw_only: bool = ..., + frozen: bool = ..., + replace: bool = ..., + dict_method: bool = ..., + gatherer: GathererProtocol[Attribute] = ..., + ignore_annotations: bool = ..., +) -> Callable[[type[T]], type[T]]: ... -# As mypy crashes, and the only difference is the return type -# just return `Any` for now to avoid the overload. @dataclass_transform(field_specifiers=(Attribute, attribute)) -def prefab( - cls: type[_T] | None = ..., +@typing.overload +def prefab[T]( + cls: type[T], *, init: bool = ..., repr: bool = ..., @@ -260,7 +243,7 @@ def prefab( dict_method: bool = ..., gatherer: GathererProtocol[Attribute] = ..., ignore_annotations: bool = ..., -) -> typing.Any: ... +) -> type[T]: ... def build_prefab( class_name: str,