Skip to content

enable and pass mypy --strict #138

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

Merged
merged 7 commits into from
Aug 18, 2021
Merged
Show file tree
Hide file tree
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
9 changes: 9 additions & 0 deletions mypy.ini
Original file line number Diff line number Diff line change
@@ -1,2 +1,11 @@
[mypy]
strict = True

[mypy-marshmallow_enum.*]
ignore_missing_imports = True

[mypy-marshmallow_union.*]
ignore_missing_imports = True

[mypy-typing_inspect.*]
ignore_missing_imports = True
125 changes: 109 additions & 16 deletions src/desert/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@
import attr._make


T = t.TypeVar("T")


def schema(
cls: t.Type, many: bool = False, meta: t.Dict[str, t.Any] = {}
cls: type, many: bool = False, meta: t.Dict[str, t.Any] = {}
) -> marshmallow.Schema:
"""Build a marshmallow schema *instance* for the class.

Expand All @@ -29,7 +32,7 @@ def schema(


def schema_class(
cls: t.Type, meta: t.Dict[str, t.Any] = {}
cls: type, meta: t.Dict[str, t.Any] = {}
) -> t.Type[marshmallow.Schema]:
"""Build a marshmallow schema *class* for the class.

Expand All @@ -46,7 +49,7 @@ def schema_class(

def metadata(
field: marshmallow.fields.Field,
) -> t.Dict["desert._make._DesertSentinel", t.Dict[t.Any, marshmallow.fields.Field]]:
) -> t.Dict[object, object]:
"""Specify a marshmallow field in the field metadata.

.. code-block:: python
Expand All @@ -56,7 +59,53 @@ def metadata(
return {desert._make._DESERT_SENTINEL: {"marshmallow_field": field}}


def field(marshmallow_field: marshmallow.fields.Field, **kw) -> dataclasses.Field:
# TODO: maybe deprecate and rename metadata()
create_metadata = metadata


# These overloads lie about their return type just as both attrs and dataclasses
# do so as to support the normal usage of `attribute: int = field()`
@t.overload
def field(
marshmallow_field: marshmallow.fields.Field,
*,
default: T,
metadata: t.Mapping[object, object] = {},
**kw: object,
) -> T:
...


@t.overload
def field(
marshmallow_field: marshmallow.fields.Field,
*,
default_factory: t.Callable[[], T],
metadata: t.Mapping[object, object] = {},
**kw: object,
) -> T:
...


@t.overload
def field(
marshmallow_field: marshmallow.fields.Field,
*,
metadata: t.Mapping[object, object] = {},
**kw: object,
) -> object:
...


# The return type hint of object is certainly a lie but fits a lot better with
# the normal use of `x: int = desert.field()`. Both dataclasses and attrs
# prioritize hinting for this usage as well. Perhaps someday we'll have a
# plugin that indicates the actual type.
def field(
marshmallow_field: marshmallow.fields.Field,
metadata: t.Mapping[object, object] = {},
**kw: object,
) -> object:
"""Specify a marshmallow field in the metadata for a ``dataclasses.dataclass``.

.. code-block:: python
Expand All @@ -65,15 +114,58 @@ def field(marshmallow_field: marshmallow.fields.Field, **kw) -> dataclasses.Fiel
class A:
x: int = desert.field(marshmallow.fields.Int())
"""
meta = metadata(marshmallow_field)
meta.update(kw.pop("metadata", {}))
# typeshed hints it as Mapping[str, Any] without any obvious reason
# https://github.com/python/typeshed/blob/95a45eb4abd0c25849268983cb614e3bf6b9b264/stdlib/dataclasses.pyi#L81
# https://github.com/python/typeshed/pull/5823
return dataclasses.field(**kw, metadata=meta) # type: ignore[arg-type]


def ib(marshmallow_field: marshmallow.fields.Field, **kw) -> attr._make._CountingAttr:
meta: t.Dict[object, object] = create_metadata(marshmallow_field)
meta.update(metadata)

# call-overload and new_field intermediary:
# https://github.com/python/typeshed/pull/5823
new_field: dataclasses.Field[object] = dataclasses.field(**kw, metadata=meta) # type: ignore[call-overload]
return new_field


# These overloads lie about their return type just as both attrs and dataclasses
# do so as to support the normal usage of `attribute: int = field()`
@t.overload
def ib(
marshmallow_field: marshmallow.fields.Field,
*,
default: t.Union[T, t.Callable[[], T]],
metadata: t.Mapping[object, object] = {},
**kw: object,
) -> T:
...


@t.overload
def ib(
marshmallow_field: marshmallow.fields.Field,
*,
factory: t.Callable[[], T],
metadata: t.Mapping[object, object] = {},
**kw: object,
) -> T:
...


@t.overload
def ib(
marshmallow_field: marshmallow.fields.Field,
*,
metadata: t.Mapping[object, object] = {},
**kw: object,
) -> object:
...


# The return type hint of object is certainly a lie but fits a lot better with
# the normal use of `x: int = desert.ib()`. Both dataclasses and attrs
# prioritize hinting for this usage as well. Perhaps someday we'll have a
# plugin that indicates the actual type.
def ib(
marshmallow_field: marshmallow.fields.Field,
metadata: t.Mapping[object, object] = {},
**kw: object,
) -> object:
"""Specify a marshmallow field in the metadata for an ``attr.dataclass``.

.. code-block:: python
Expand All @@ -82,9 +174,10 @@ def ib(marshmallow_field: marshmallow.fields.Field, **kw) -> attr._make._Countin
class A:
x: int = desert.ib(marshmallow.fields.Int())
"""
meta = metadata(marshmallow_field)
meta.update(kw.pop("metadata", {}))
return attr.ib(**kw, metadata=meta)
meta: t.Dict[object, object] = create_metadata(marshmallow_field)
meta.update(metadata)
new_field: attr._make._CountingAttr = attr.ib(**kw, metadata=meta) # type: ignore[call-overload]
return new_field


__version__ = desert._version.__version__
32 changes: 21 additions & 11 deletions src/desert/_make.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,9 @@ def class_schema(
``marshmallow_field`` key in the metadata dictionary.
"""

fields: t.Union[t.Tuple[dataclasses.Field, ...], t.Tuple[attr.Attribute, ...]]
fields: t.Union[
t.Tuple[dataclasses.Field[object], ...], t.Tuple[attr.Attribute[object], ...]
]

if not isinstance(clazz, type):
raise desert.exceptions.UnknownType(
Expand Down Expand Up @@ -161,7 +163,7 @@ def class_schema(
class VariadicTuple(marshmallow.fields.List):
"""Homogenous tuple with variable number of entries."""

def _deserialize(self, *args, **kwargs):
def _deserialize(self, *args: object, **kwargs: object) -> t.Tuple[object, ...]: # type: ignore[override]
return tuple(super()._deserialize(*args, **kwargs))


Expand All @@ -172,7 +174,9 @@ def only(items: t.Iterable[T]) -> T:


def field_for_schema(
typ: type, default=marshmallow.missing, metadata: t.Mapping[t.Any, t.Any] = None
typ: type,
default: object = marshmallow.missing,
metadata: t.Optional[t.Mapping[t.Any, t.Any]] = None,
) -> marshmallow.fields.Field:
"""
Get a marshmallow Field corresponding to the given python type.
Expand Down Expand Up @@ -221,7 +225,9 @@ def field_for_schema(
field = None

# If the field was already defined by the user
predefined_field = desert_metadata.get("marshmallow_field")
predefined_field = t.cast(
marshmallow.fields.Field, desert_metadata.get("marshmallow_field")
)

if predefined_field:
field = predefined_field
Expand All @@ -242,7 +248,7 @@ def field_for_schema(
field = marshmallow.fields.List(field_for_schema(arguments[0]))

if origin in (tuple, t.Tuple) and Ellipsis not in arguments:
field = marshmallow.fields.Tuple(
field = marshmallow.fields.Tuple( # type: ignore[no-untyped-call]
tuple(field_for_schema(arg) for arg in arguments)
)
elif origin in (tuple, t.Tuple) and Ellipsis in arguments:
Expand Down Expand Up @@ -307,21 +313,25 @@ def field_for_schema(
def _base_schema(clazz: type) -> t.Type[marshmallow.Schema]:
class BaseSchema(marshmallow.Schema):
@marshmallow.post_load
def make_data_class(self, data, **_):
def make_data_class(self, data: t.Mapping[str, object], **_: object) -> object:
return clazz(**data)

return BaseSchema


def _get_field_default(field: t.Union[dataclasses.Field, attr.Attribute]):
def _get_field_default(
# field: t.Union[dataclasses.Field[object], "attr.Attribute[object]"],
field: t.Union["dataclasses.Field[object]", "attr.Attribute[object]"],
) -> object:
"""
Return a marshmallow default value given a dataclass default value
>>> _get_field_default(dataclasses.field())
<marshmallow.missing>
"""
if isinstance(field, dataclasses.Field):
# https://github.com/python/mypy/issues/10750
if field.default_factory != dataclasses.MISSING: # type: ignore[misc]
# misc: https://github.com/python/mypy/issues/10750
# comparison-overlap: https://github.com/python/typeshed/pull/5900
if field.default_factory != dataclasses.MISSING: # type: ignore[misc,comparison-overlap]
return dataclasses.MISSING
if field.default is dataclasses.MISSING:
return marshmallow.missing
Expand All @@ -333,9 +343,9 @@ def _get_field_default(field: t.Union[dataclasses.Field, attr.Attribute]):
# attrs specifically doesn't support this so as to support the
# primary use case.
# https://github.com/python-attrs/attrs/blob/38580632ceac1cd6e477db71e1d190a4130beed4/src/attr/__init__.pyi#L63-L65
if field.default.takes_self: # type: ignore[union-attr]
if field.default.takes_self: # type: ignore[attr-defined]
return attr.NOTHING
return field.default.factory # type: ignore[union-attr]
return field.default.factory # type: ignore[attr-defined]
return field.default
else:
raise TypeError(field)
Expand Down
Loading