Skip to content

add slots, match_args, kw_only to dataclass func #252

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

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 49 additions & 4 deletions marshmallow_dataclass/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,9 @@ def dataclass(
order: bool = False,
unsafe_hash: bool = False,
frozen: bool = False,
match_args: bool = True,
kw_only: bool = False,
slots: bool = False,
base_schema: Optional[Type[marshmallow.Schema]] = None,
cls_frame: Optional[types.FrameType] = None,
) -> Type[_U]:
Expand All @@ -117,6 +120,9 @@ def dataclass(
order: bool = False,
unsafe_hash: bool = False,
frozen: bool = False,
match_args: bool = True,
kw_only: bool = False,
slots: bool = False,
base_schema: Optional[Type[marshmallow.Schema]] = None,
cls_frame: Optional[types.FrameType] = None,
) -> Callable[[Type[_U]], Type[_U]]:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel these overloads should be conditional on python version. I.e. The prototypes should not declare unsupported options.

Since (I think) we want marshmallow_dataclass.dataclass to have the same signature as dataclasses.dataclass, we should probably just copy typeshed's interface definition for dataclass.

https://github.com/python/typeshed/blob/8b9b0f4d031fe614a0211e1faadf57b3340e643e/stdlib/dataclasses.pyi#L57C1-L104C1.

Expand All @@ -135,6 +141,9 @@ def dataclass(
order: bool = False,
unsafe_hash: bool = False,
frozen: bool = False,
match_args: bool = True,
kw_only: bool = False,
slots: bool = False,
base_schema: Optional[Type[marshmallow.Schema]] = None,
cls_frame: Optional[types.FrameType] = None,
) -> Union[Type[_U], Callable[[Type[_U]], Type[_U]]]:
Expand Down Expand Up @@ -163,10 +172,46 @@ def dataclass(
>>> Point.Schema().load({'x':0, 'y':0}) # This line can be statically type checked
Point(x=0.0, y=0.0)
"""
# dataclass's typing doesn't expect it to be called as a function, so ignore type check
dc = dataclasses.dataclass( # type: ignore
_cls, repr=repr, eq=eq, order=order, unsafe_hash=unsafe_hash, frozen=frozen
)
# Check python version for dataclass params only available for python >= 3.10.
# If python version is below 3.10 and any of these params are set to anything
# other than their default, raise ValueError
if sys.version_info >= (3, 10):
# dataclass's typing doesn't expect it to be called as a function, so ignore type check
dc = dataclasses.dataclass( # type: ignore
_cls,
repr=repr,
eq=eq,
order=order,
unsafe_hash=unsafe_hash,
frozen=frozen,
match_args=match_args,
kw_only=kw_only,
slots=slots,
)
else:
args = {
"match_args": {
"error_message": "'match_args' argument is only available for python >= 3.10",
"default_value": True,
},
"kw_only": {
"error_message": "'kw_only' argument is only available for python >= 3.10",
"default_value": False,
},
"slots": {
"error_message": "'slots' argument is only available for python >= 3.10",
"default_value": False,
},
}

for arg, arg_info in args.items():
if locals()[arg] is not arg_info["default_value"]:
raise ValueError(arg_info["error_message"])

# dataclass's typing doesn't expect it to be called as a function, so ignore type check
dc = dataclasses.dataclass( # type: ignore
_cls, repr=repr, eq=eq, order=order, unsafe_hash=unsafe_hash, frozen=frozen
)
Comment on lines +192 to +214
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this ? Can't we just forward the arguments we got to the base dataclass implementation, and let it error if it wants to ?

Copy link
Collaborator

@dairiki dairiki Jan 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree. For the implementation can we not just do

def dataclass(
    __cls: Optional[Type[_U]] = None,
   *,
   base_schema: Optional[Type[marshmallow.Schema]] = None,
   cls_frame: Optional[types.FrameType] = None,
   **kwargs: bool,
 ) -> Union[Type[_U], Callable[[Type[_U]], Type[_U]]]:
    dc = dataclasses.dataclass(**kwargs)

    if not cls_frame:
        current_frame = inspect.currentframe()
        if current_frame:
            cls_frame = current_frame.f_back
        # Per https://docs.python.org/3/library/inspect.html#the-interpreter-stack
        del current_frame

    def decorate(cls: Type[_U]) -> Type[_U]:
        return add_schema(dc(cls), base_schema, cls_frame=cls_frame)

    if _cls is None:
        return decorate
    return decorate(cls)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This breaks the type signature.

if not cls_frame:
current_frame = inspect.currentframe()
if current_frame:
Expand Down