-
Notifications
You must be signed in to change notification settings - Fork 76
Add Generic dataclasses #259
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 12 commits
bc46a23
db32163
8b82276
f2734cc
9a5dc09
8443336
8a0f837
f484596
80dab91
4531c35
dd34efc
7ac088d
b3362ba
db95e64
a494984
78fcd4a
8797b2b
c361f3a
c3f5da1
231b3b2
2ef5a71
fc66fc9
740fa49
4e0f214
b47f754
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -44,15 +44,9 @@ class User: | |||||||||||||||||||||||||||||||||||||||||||
import warnings | ||||||||||||||||||||||||||||||||||||||||||||
from enum import Enum | ||||||||||||||||||||||||||||||||||||||||||||
from functools import lru_cache, partial | ||||||||||||||||||||||||||||||||||||||||||||
from typing import Any, Callable, Dict, FrozenSet, Generic, List, Mapping | ||||||||||||||||||||||||||||||||||||||||||||
from typing import NewType as typing_NewType | ||||||||||||||||||||||||||||||||||||||||||||
from typing import ( | ||||||||||||||||||||||||||||||||||||||||||||
Any, | ||||||||||||||||||||||||||||||||||||||||||||
Callable, | ||||||||||||||||||||||||||||||||||||||||||||
Dict, | ||||||||||||||||||||||||||||||||||||||||||||
FrozenSet, | ||||||||||||||||||||||||||||||||||||||||||||
Generic, | ||||||||||||||||||||||||||||||||||||||||||||
List, | ||||||||||||||||||||||||||||||||||||||||||||
Mapping, | ||||||||||||||||||||||||||||||||||||||||||||
NewType as typing_NewType, | ||||||||||||||||||||||||||||||||||||||||||||
Optional, | ||||||||||||||||||||||||||||||||||||||||||||
Sequence, | ||||||||||||||||||||||||||||||||||||||||||||
Set, | ||||||||||||||||||||||||||||||||||||||||||||
|
@@ -69,6 +63,12 @@ class User: | |||||||||||||||||||||||||||||||||||||||||||
import typing_extensions | ||||||||||||||||||||||||||||||||||||||||||||
import typing_inspect | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
from marshmallow_dataclass.generic_resolver import ( | ||||||||||||||||||||||||||||||||||||||||||||
UnboundTypeVarError, | ||||||||||||||||||||||||||||||||||||||||||||
get_generic_dataclass_fields, | ||||||||||||||||||||||||||||||||||||||||||||
is_generic_alias, | ||||||||||||||||||||||||||||||||||||||||||||
is_generic_type, | ||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||
from marshmallow_dataclass.lazy_class_attribute import lazy_class_attribute | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
if sys.version_info >= (3, 9): | ||||||||||||||||||||||||||||||||||||||||||||
|
@@ -139,6 +139,18 @@ def _maybe_get_callers_frame( | |||||||||||||||||||||||||||||||||||||||||||
del frame | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
def _check_decorated_type(cls: object) -> None: | ||||||||||||||||||||||||||||||||||||||||||||
if not isinstance(cls, type): | ||||||||||||||||||||||||||||||||||||||||||||
raise TypeError(f"expected a class not {cls!r}") | ||||||||||||||||||||||||||||||||||||||||||||
if is_generic_alias(cls): | ||||||||||||||||||||||||||||||||||||||||||||
# A .Schema attribute doesn't make sense on a generic alias — there's | ||||||||||||||||||||||||||||||||||||||||||||
# no way for it to know the generic parameters at run time. | ||||||||||||||||||||||||||||||||||||||||||||
raise TypeError( | ||||||||||||||||||||||||||||||||||||||||||||
"decorator does not support generic aliasses " | ||||||||||||||||||||||||||||||||||||||||||||
"(hint: use class_schema directly instead)" | ||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
@overload | ||||||||||||||||||||||||||||||||||||||||||||
def dataclass( | ||||||||||||||||||||||||||||||||||||||||||||
_cls: Type[_U], | ||||||||||||||||||||||||||||||||||||||||||||
|
@@ -214,12 +226,18 @@ def dataclass( | |||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
def decorator(cls: Type[_U], stacklevel: int = 1) -> Type[_U]: | ||||||||||||||||||||||||||||||||||||||||||||
if cls is not None: | ||||||||||||||||||||||||||||||||||||||||||||
mvanderlee marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||||
_check_decorated_type(cls) | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
return add_schema( | ||||||||||||||||||||||||||||||||||||||||||||
dc(cls), base_schema, cls_frame=cls_frame, stacklevel=stacklevel + 1 | ||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
if _cls is None: | ||||||||||||||||||||||||||||||||||||||||||||
return decorator | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
if _cls is not None: | ||||||||||||||||||||||||||||||||||||||||||||
_check_decorated_type(_cls) | ||||||||||||||||||||||||||||||||||||||||||||
mvanderlee marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||||
return decorator(_cls, stacklevel=stacklevel + 1) | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
|
@@ -268,6 +286,8 @@ def add_schema(_cls=None, base_schema=None, cls_frame=None, stacklevel=1): | |||||||||||||||||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
def decorator(clazz: Type[_U], stacklevel: int = stacklevel) -> Type[_U]: | ||||||||||||||||||||||||||||||||||||||||||||
_check_decorated_type(clazz) | ||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think so because we already have a big warning when a class is not a dataclass in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've just run a simple test. You are correct in that decorating non-data class classes with It appears, however, that, as things stand in this PR, no big warning is emitted in that case. Further investigation reveals that In any case, I think that either:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've had to do some digging.
I don't disagree with removing support for non-dataclasses, but don't see why that should be part of this PR. |
||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
if cls_frame is not None: | ||||||||||||||||||||||||||||||||||||||||||||
frame = cls_frame | ||||||||||||||||||||||||||||||||||||||||||||
else: | ||||||||||||||||||||||||||||||||||||||||||||
|
@@ -453,7 +473,7 @@ def class_schema( | |||||||||||||||||||||||||||||||||||||||||||
>>> class_schema(Custom)().load({}) | ||||||||||||||||||||||||||||||||||||||||||||
Custom(name=None) | ||||||||||||||||||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||||||||||||||||||
if not dataclasses.is_dataclass(clazz): | ||||||||||||||||||||||||||||||||||||||||||||
if not dataclasses.is_dataclass(clazz) and not is_generic_alias_of_dataclass(clazz): | ||||||||||||||||||||||||||||||||||||||||||||
clazz = dataclasses.dataclass(clazz) | ||||||||||||||||||||||||||||||||||||||||||||
if localns is None: | ||||||||||||||||||||||||||||||||||||||||||||
if clazz_frame is None: | ||||||||||||||||||||||||||||||||||||||||||||
|
@@ -518,13 +538,15 @@ def _internal_class_schema( | |||||||||||||||||||||||||||||||||||||||||||
# https://github.com/python/cpython/blob/3.10/Lib/typing.py#L977 | ||||||||||||||||||||||||||||||||||||||||||||
class_name = clazz._name or clazz.__origin__.__name__ # type: ignore[attr-defined] | ||||||||||||||||||||||||||||||||||||||||||||
else: | ||||||||||||||||||||||||||||||||||||||||||||
class_name = clazz.__name__ | ||||||||||||||||||||||||||||||||||||||||||||
# generic aliases do not have a __name__ prior python 3.10 | ||||||||||||||||||||||||||||||||||||||||||||
class_name = getattr(clazz, "__name__", repr(clazz)) | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
schema_ctx.seen_classes[clazz] = class_name | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
try: | ||||||||||||||||||||||||||||||||||||||||||||
# noinspection PyDataclass | ||||||||||||||||||||||||||||||||||||||||||||
fields: Tuple[dataclasses.Field, ...] = dataclasses.fields(clazz) | ||||||||||||||||||||||||||||||||||||||||||||
fields = _dataclass_fields(clazz) | ||||||||||||||||||||||||||||||||||||||||||||
except UnboundTypeVarError: | ||||||||||||||||||||||||||||||||||||||||||||
raise | ||||||||||||||||||||||||||||||||||||||||||||
except TypeError: # Not a dataclass | ||||||||||||||||||||||||||||||||||||||||||||
try: | ||||||||||||||||||||||||||||||||||||||||||||
warnings.warn( | ||||||||||||||||||||||||||||||||||||||||||||
|
@@ -540,6 +562,8 @@ def _internal_class_schema( | |||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||
created_dataclass: type = dataclasses.dataclass(clazz) | ||||||||||||||||||||||||||||||||||||||||||||
return _internal_class_schema(created_dataclass, base_schema) | ||||||||||||||||||||||||||||||||||||||||||||
except UnboundTypeVarError: | ||||||||||||||||||||||||||||||||||||||||||||
raise | ||||||||||||||||||||||||||||||||||||||||||||
except Exception as exc: | ||||||||||||||||||||||||||||||||||||||||||||
raise TypeError( | ||||||||||||||||||||||||||||||||||||||||||||
f"{getattr(clazz, '__name__', repr(clazz))} is not a dataclass and cannot be turned into one." | ||||||||||||||||||||||||||||||||||||||||||||
|
@@ -556,23 +580,19 @@ def _internal_class_schema( | |||||||||||||||||||||||||||||||||||||||||||
include_non_init = getattr(getattr(clazz, "Meta", None), "include_non_init", False) | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
# Update the schema members to contain marshmallow fields instead of dataclass fields | ||||||||||||||||||||||||||||||||||||||||||||
type_hints = {} | ||||||||||||||||||||||||||||||||||||||||||||
if not is_generic_type(clazz): | ||||||||||||||||||||||||||||||||||||||||||||
type_hints = _get_type_hints(clazz, schema_ctx) | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
if sys.version_info >= (3, 9): | ||||||||||||||||||||||||||||||||||||||||||||
type_hints = get_type_hints( | ||||||||||||||||||||||||||||||||||||||||||||
clazz, | ||||||||||||||||||||||||||||||||||||||||||||
globalns=schema_ctx.globalns, | ||||||||||||||||||||||||||||||||||||||||||||
localns=schema_ctx.localns, | ||||||||||||||||||||||||||||||||||||||||||||
include_extras=True, | ||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||
else: | ||||||||||||||||||||||||||||||||||||||||||||
type_hints = get_type_hints( | ||||||||||||||||||||||||||||||||||||||||||||
clazz, globalns=schema_ctx.globalns, localns=schema_ctx.localns | ||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||
attributes.update( | ||||||||||||||||||||||||||||||||||||||||||||
( | ||||||||||||||||||||||||||||||||||||||||||||
field.name, | ||||||||||||||||||||||||||||||||||||||||||||
_field_for_schema( | ||||||||||||||||||||||||||||||||||||||||||||
type_hints[field.name], | ||||||||||||||||||||||||||||||||||||||||||||
( | ||||||||||||||||||||||||||||||||||||||||||||
type_hints[field.name] | ||||||||||||||||||||||||||||||||||||||||||||
if not is_generic_type(clazz) | ||||||||||||||||||||||||||||||||||||||||||||
else _get_generic_type_hints(field.type, schema_ctx) | ||||||||||||||||||||||||||||||||||||||||||||
), | ||||||||||||||||||||||||||||||||||||||||||||
_get_field_default(field), | ||||||||||||||||||||||||||||||||||||||||||||
field.metadata, | ||||||||||||||||||||||||||||||||||||||||||||
base_schema, | ||||||||||||||||||||||||||||||||||||||||||||
|
@@ -582,7 +602,7 @@ def _internal_class_schema( | |||||||||||||||||||||||||||||||||||||||||||
if field.init or include_non_init | ||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
schema_class = type(clazz.__name__, (_base_schema(clazz, base_schema),), attributes) | ||||||||||||||||||||||||||||||||||||||||||||
schema_class = type(class_name, (_base_schema(clazz, base_schema),), attributes) | ||||||||||||||||||||||||||||||||||||||||||||
return cast(Type[marshmallow.Schema], schema_class) | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
|
@@ -705,7 +725,7 @@ def _field_for_generic_type( | |||||||||||||||||||||||||||||||||||||||||||
), | ||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||
return tuple_type(children, **metadata) | ||||||||||||||||||||||||||||||||||||||||||||
elif origin in (dict, Dict, collections.abc.Mapping, Mapping): | ||||||||||||||||||||||||||||||||||||||||||||
if origin in (dict, Dict, collections.abc.Mapping, Mapping): | ||||||||||||||||||||||||||||||||||||||||||||
dict_type = type_mapping.get(Dict, marshmallow.fields.Dict) | ||||||||||||||||||||||||||||||||||||||||||||
return dict_type( | ||||||||||||||||||||||||||||||||||||||||||||
keys=_field_for_schema(arguments[0], base_schema=base_schema), | ||||||||||||||||||||||||||||||||||||||||||||
|
@@ -729,8 +749,12 @@ def _field_for_annotated_type( | |||||||||||||||||||||||||||||||||||||||||||
marshmallow_annotations = [ | ||||||||||||||||||||||||||||||||||||||||||||
arg | ||||||||||||||||||||||||||||||||||||||||||||
for arg in arguments[1:] | ||||||||||||||||||||||||||||||||||||||||||||
if (inspect.isclass(arg) and issubclass(arg, marshmallow.fields.Field)) | ||||||||||||||||||||||||||||||||||||||||||||
or isinstance(arg, marshmallow.fields.Field) | ||||||||||||||||||||||||||||||||||||||||||||
if _is_marshmallow_field(arg) | ||||||||||||||||||||||||||||||||||||||||||||
# Support `CustomGenericField[mf.String]` | ||||||||||||||||||||||||||||||||||||||||||||
or ( | ||||||||||||||||||||||||||||||||||||||||||||
is_generic_type(arg) | ||||||||||||||||||||||||||||||||||||||||||||
and _is_marshmallow_field(typing_extensions.get_origin(arg)) | ||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||
] | ||||||||||||||||||||||||||||||||||||||||||||
if marshmallow_annotations: | ||||||||||||||||||||||||||||||||||||||||||||
if len(marshmallow_annotations) > 1: | ||||||||||||||||||||||||||||||||||||||||||||
|
@@ -838,6 +862,9 @@ def _field_for_schema( | |||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
if isinstance(typ, TypeVar): | ||||||||||||||||||||||||||||||||||||||||||||
raise UnboundTypeVarError(f"can not resolve type variable {typ.__name__}") | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
metadata = {} if metadata is None else dict(metadata) | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
if default is not marshmallow.missing: | ||||||||||||||||||||||||||||||||||||||||||||
|
@@ -867,7 +894,7 @@ def _field_for_schema( | |||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
# i.e.: Literal['abc'] | ||||||||||||||||||||||||||||||||||||||||||||
if typing_inspect.is_literal_type(typ): | ||||||||||||||||||||||||||||||||||||||||||||
arguments = typing_inspect.get_args(typ) | ||||||||||||||||||||||||||||||||||||||||||||
arguments = typing_extensions.get_args(typ) | ||||||||||||||||||||||||||||||||||||||||||||
return marshmallow.fields.Raw( | ||||||||||||||||||||||||||||||||||||||||||||
validate=( | ||||||||||||||||||||||||||||||||||||||||||||
marshmallow.validate.Equal(arguments[0]) | ||||||||||||||||||||||||||||||||||||||||||||
|
@@ -879,7 +906,7 @@ def _field_for_schema( | |||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
# i.e.: Final[str] = 'abc' | ||||||||||||||||||||||||||||||||||||||||||||
if typing_inspect.is_final_type(typ): | ||||||||||||||||||||||||||||||||||||||||||||
arguments = typing_inspect.get_args(typ) | ||||||||||||||||||||||||||||||||||||||||||||
arguments = typing_extensions.get_args(typ) | ||||||||||||||||||||||||||||||||||||||||||||
if arguments: | ||||||||||||||||||||||||||||||||||||||||||||
subtyp = arguments[0] | ||||||||||||||||||||||||||||||||||||||||||||
elif default is not marshmallow.missing: | ||||||||||||||||||||||||||||||||||||||||||||
|
@@ -952,7 +979,7 @@ def _field_for_schema( | |||||||||||||||||||||||||||||||||||||||||||
nested_schema | ||||||||||||||||||||||||||||||||||||||||||||
or forward_reference | ||||||||||||||||||||||||||||||||||||||||||||
or _schema_ctx_stack.top.seen_classes.get(typ) | ||||||||||||||||||||||||||||||||||||||||||||
or _internal_class_schema(typ, base_schema) # type: ignore[arg-type] # FIXME | ||||||||||||||||||||||||||||||||||||||||||||
or _internal_class_schema(typ, base_schema) # type: ignore [arg-type] | ||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
return marshmallow.fields.Nested(nested, **metadata) | ||||||||||||||||||||||||||||||||||||||||||||
|
@@ -996,6 +1023,66 @@ def _get_field_default(field: dataclasses.Field): | |||||||||||||||||||||||||||||||||||||||||||
return field.default | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
def is_generic_alias_of_dataclass(clazz: type) -> bool: | ||||||||||||||||||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||||||||||||||||||
Check if given class is a generic alias of a dataclass, if the dataclass is | ||||||||||||||||||||||||||||||||||||||||||||
defined as `class A(Generic[T])`, this method will return true if `A[int]` is passed | ||||||||||||||||||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||||||||||||||||||
is_generic = is_generic_type(clazz) | ||||||||||||||||||||||||||||||||||||||||||||
mvanderlee marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||||
type_arguments = typing_extensions.get_args(clazz) | ||||||||||||||||||||||||||||||||||||||||||||
origin_class = typing_extensions.get_origin(clazz) | ||||||||||||||||||||||||||||||||||||||||||||
return ( | ||||||||||||||||||||||||||||||||||||||||||||
is_generic | ||||||||||||||||||||||||||||||||||||||||||||
and len(type_arguments) > 0 | ||||||||||||||||||||||||||||||||||||||||||||
and dataclasses.is_dataclass(origin_class) | ||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
def _get_type_hints( | ||||||||||||||||||||||||||||||||||||||||||||
obj, | ||||||||||||||||||||||||||||||||||||||||||||
schema_ctx: _SchemaContext, | ||||||||||||||||||||||||||||||||||||||||||||
): | ||||||||||||||||||||||||||||||||||||||||||||
if sys.version_info >= (3, 9): | ||||||||||||||||||||||||||||||||||||||||||||
type_hints = get_type_hints( | ||||||||||||||||||||||||||||||||||||||||||||
obj, | ||||||||||||||||||||||||||||||||||||||||||||
globalns=schema_ctx.globalns, | ||||||||||||||||||||||||||||||||||||||||||||
localns=schema_ctx.localns, | ||||||||||||||||||||||||||||||||||||||||||||
include_extras=True, | ||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||
else: | ||||||||||||||||||||||||||||||||||||||||||||
type_hints = get_type_hints( | ||||||||||||||||||||||||||||||||||||||||||||
obj, globalns=schema_ctx.globalns, localns=schema_ctx.localns | ||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
return type_hints | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
def _get_generic_type_hints( | ||||||||||||||||||||||||||||||||||||||||||||
obj, | ||||||||||||||||||||||||||||||||||||||||||||
schema_ctx: _SchemaContext, | ||||||||||||||||||||||||||||||||||||||||||||
) -> type: | ||||||||||||||||||||||||||||||||||||||||||||
"""typing.get_type_hints doesn't work with generic aliasses. But this 'hack' works.""" | ||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps this function should be renamed
(Spelling nit: "aliases") There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, the forward refs have already been resolved. This really exists purely to get the typehint for generics. import typing
T = typing.TypeVar('T')
class A(Generic[T]):
a: T
class B(A[int]):
pass
print(typing.get_type_hints(B))
print(typing.get_type_hints(A[int]))
>=========================
{'a': ~T}
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[51], [line 12](vscode-notebook-cell:?execution_count=51&line=12)
[9](vscode-notebook-cell:?execution_count=51&line=9) pass
[11](vscode-notebook-cell:?execution_count=51&line=11) print(typing.get_type_hints(B))
---> [12](vscode-notebook-cell:?execution_count=51&line=12) print(typing.get_type_hints(A[int]))
File ~\.pyenv\pyenv-win\versions\3.11.3\Lib\typing.py:2347, in get_type_hints(obj, globalns, localns, include_extras)
~/.pyenv/pyenv-win/versions/3.11.3/Lib/typing.py:2345) return {}
~/.pyenv/pyenv-win/versions/3.11.3/Lib/typing.py:2346) else:
-> ~/.pyenv/pyenv-win/versions/3.11.3/Lib/typing.py:2347) raise TypeError('{!r} is not a module, class, method, '
~/.pyenv/pyenv-win/versions/3.11.3/Lib/typing.py:2348) 'or function.'.format(obj))
~/.pyenv/pyenv-win/versions/3.11.3/Lib/typing.py:2349) hints = dict(hints)
~/.pyenv/pyenv-win/versions/3.11.3/Lib/typing.py:2350) for name, value in hints.items():
TypeError: __main__.A[int] is not a module, class, method, or function. def _get_generic_type_hints(obj) -> type:
"""typing.get_type_hints doesn't work with generic aliases. But this 'hack' works."""
class X:
x: obj # type: ignore[name-defined]
return typing.get_type_hints(X)['x']
print(_get_generic_type_hints(B))
print(_get_generic_type_hints(A[int]))
>=========================
<class '__main__.B'>
__main__.A[int] There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In your last examples, >>> _get_generic_type_hints(B) is B
True
>>> _get_generic_type_hints(A[int]) is A[int]
True But, the value of >>> _get_generic_type_hints(A["int"])
__main__.A[int]
>>> _get_generic_type_hints(A["int"]) is A[int]
False There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe what is really wanted is (untested) def _get_generic_type_hints(generic_alias):
class X(generic_alias): pass
return typing.get_type_hints(X) Or, in one line:
That version of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wow, good catch! You're absolutely right.
(
type_hints[field.name]
if not is_generic_type(clazz)
else field.type
)
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And, if that works, maybe just roll this into There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Does not work. print(_get_generic_type_hints(A[int]))
>=====================
{'a': ~T} Instead of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
That's too bad. I still think that refactoring so that marshmallow_dataclass/marshmallow_dataclass/__init__.py Lines 583 to 603 in 7ac088d
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't disagree but I've already sunk too many hours into this particular problem. If it is even possible to do, it'll have to be someone else as I don't have the knowledge to take this further. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @dairiki I found a way around it by resolving the forward refs when we get the dataclass fields. We now no longer call We were already looping over the mro and fields just like |
||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
class X: | ||||||||||||||||||||||||||||||||||||||||||||
x: obj # type: ignore[name-defined] | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
return _get_type_hints(X, schema_ctx)["x"] | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
def _dataclass_fields(clazz: type) -> Tuple[dataclasses.Field, ...]: | ||||||||||||||||||||||||||||||||||||||||||||
if not is_generic_type(clazz): | ||||||||||||||||||||||||||||||||||||||||||||
return dataclasses.fields(clazz) | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
else: | ||||||||||||||||||||||||||||||||||||||||||||
return get_generic_dataclass_fields(clazz) | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
def _is_marshmallow_field(obj) -> bool: | ||||||||||||||||||||||||||||||||||||||||||||
return ( | ||||||||||||||||||||||||||||||||||||||||||||
inspect.isclass(obj) and issubclass(obj, marshmallow.fields.Field) | ||||||||||||||||||||||||||||||||||||||||||||
) or isinstance(obj, marshmallow.fields.Field) | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
def NewType( | ||||||||||||||||||||||||||||||||||||||||||||
name: str, | ||||||||||||||||||||||||||||||||||||||||||||
typ: Type[_U], | ||||||||||||||||||||||||||||||||||||||||||||
|
Uh oh!
There was an error while loading. Please reload this page.