lovasoa / marshmallow_dataclass

Automatic generation of marshmallow schemas from dataclasses.
https://lovasoa.github.io/marshmallow_dataclass/html/marshmallow_dataclass.html
MIT License
456 stars 78 forks source link

Refactor 'NewType' as it no longer conforms to the typing spec #256

Closed mvanderlee closed 2 months ago

mvanderlee commented 6 months ago

Python is updating the Typing spec which causes Variable not allowed in type expression for types created with marshmallow_dataclass's NewType

https://github.com/microsoft/pyright/issues/7268

The modern way is to use typing.Annotated. (Python >=3.9)

I propose deprecating NewType and adding Annotated support.

Example:

from typing import Annotated
import marshmallow.fields as mf
import marshmallow.validate as mv
from marshmallow_dataclass import dataclass

Url = Annotated[str, mf.Url]
Email = Annotated[str, mf.Email]

@dataclass
class ContactInfo:
    mail: Email

@dataclass
class Network:
    ipv4: Annotated[str, mf.String(validate=mv.Regexp(r'^([0-9]{1,3}\\.){3}[0-9]{1,3}$'))]
dairiki commented 4 months ago

Not a problem, but something to be aware of:

The proposed use of Annotated types is not equivalent to the old custom NewType with regards to static type checking. (NewType introduces a new type, while Annotated adds annotations to an existing type.)

Consider:

from typing import Annotated
from marshmallow import fields
from marshmallow_dataclass import dataclass, NewType

Email1 = NewType("Email", str, field=fields.Email)
Email2 = Annotated[str, fields.Email]

@dataclass
class Record:
    email1: Email1
    email2: Email2

r = Record(email1="joe@example.org", email2="jane@example.net")

Mypy will complain that Argument "email1" to "Record" has incompatible type "str"; expected "Email1". Passing the str for email2 is fine.

dairiki commented 4 months ago

Python is updating the Typing spec which causes Variable not allowed in type expression for types created with marshmallow_dataclass's NewType

FWIW: For mypy, at least, use of the marshmallow_dataclass.mypy mypy plugin fixes this.