Skip to content

Commit 32d4f68

Browse files
authored
Merge pull request #138 from altendky/mypy_strict
2 parents adb5951 + 1db6845 commit 32d4f68

File tree

4 files changed

+240
-99
lines changed

4 files changed

+240
-99
lines changed

mypy.ini

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,11 @@
11
[mypy]
2+
strict = True
3+
4+
[mypy-marshmallow_enum.*]
5+
ignore_missing_imports = True
6+
7+
[mypy-marshmallow_union.*]
8+
ignore_missing_imports = True
9+
10+
[mypy-typing_inspect.*]
211
ignore_missing_imports = True

src/desert/__init__.py

Lines changed: 109 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,11 @@
1212
import attr._make
1313

1414

15+
T = t.TypeVar("T")
16+
17+
1518
def schema(
16-
cls: t.Type, many: bool = False, meta: t.Dict[str, t.Any] = {}
19+
cls: type, many: bool = False, meta: t.Dict[str, t.Any] = {}
1720
) -> marshmallow.Schema:
1821
"""Build a marshmallow schema *instance* for the class.
1922
@@ -29,7 +32,7 @@ def schema(
2932

3033

3134
def schema_class(
32-
cls: t.Type, meta: t.Dict[str, t.Any] = {}
35+
cls: type, meta: t.Dict[str, t.Any] = {}
3336
) -> t.Type[marshmallow.Schema]:
3437
"""Build a marshmallow schema *class* for the class.
3538
@@ -46,7 +49,7 @@ def schema_class(
4649

4750
def metadata(
4851
field: marshmallow.fields.Field,
49-
) -> t.Dict["desert._make._DesertSentinel", t.Dict[t.Any, marshmallow.fields.Field]]:
52+
) -> t.Dict[object, object]:
5053
"""Specify a marshmallow field in the field metadata.
5154
5255
.. code-block:: python
@@ -56,7 +59,53 @@ def metadata(
5659
return {desert._make._DESERT_SENTINEL: {"marshmallow_field": field}}
5760

5861

59-
def field(marshmallow_field: marshmallow.fields.Field, **kw) -> dataclasses.Field:
62+
# TODO: maybe deprecate and rename metadata()
63+
create_metadata = metadata
64+
65+
66+
# These overloads lie about their return type just as both attrs and dataclasses
67+
# do so as to support the normal usage of `attribute: int = field()`
68+
@t.overload
69+
def field(
70+
marshmallow_field: marshmallow.fields.Field,
71+
*,
72+
default: T,
73+
metadata: t.Mapping[object, object] = {},
74+
**kw: object,
75+
) -> T:
76+
...
77+
78+
79+
@t.overload
80+
def field(
81+
marshmallow_field: marshmallow.fields.Field,
82+
*,
83+
default_factory: t.Callable[[], T],
84+
metadata: t.Mapping[object, object] = {},
85+
**kw: object,
86+
) -> T:
87+
...
88+
89+
90+
@t.overload
91+
def field(
92+
marshmallow_field: marshmallow.fields.Field,
93+
*,
94+
metadata: t.Mapping[object, object] = {},
95+
**kw: object,
96+
) -> object:
97+
...
98+
99+
100+
# The return type hint of object is certainly a lie but fits a lot better with
101+
# the normal use of `x: int = desert.field()`. Both dataclasses and attrs
102+
# prioritize hinting for this usage as well. Perhaps someday we'll have a
103+
# plugin that indicates the actual type.
104+
def field(
105+
marshmallow_field: marshmallow.fields.Field,
106+
metadata: t.Mapping[object, object] = {},
107+
**kw: object,
108+
) -> object:
60109
"""Specify a marshmallow field in the metadata for a ``dataclasses.dataclass``.
61110
62111
.. code-block:: python
@@ -65,15 +114,58 @@ def field(marshmallow_field: marshmallow.fields.Field, **kw) -> dataclasses.Fiel
65114
class A:
66115
x: int = desert.field(marshmallow.fields.Int())
67116
"""
68-
meta = metadata(marshmallow_field)
69-
meta.update(kw.pop("metadata", {}))
70-
# typeshed hints it as Mapping[str, Any] without any obvious reason
71-
# https://github.com/python/typeshed/blob/95a45eb4abd0c25849268983cb614e3bf6b9b264/stdlib/dataclasses.pyi#L81
72-
# https://github.com/python/typeshed/pull/5823
73-
return dataclasses.field(**kw, metadata=meta) # type: ignore[arg-type]
74-
75-
76-
def ib(marshmallow_field: marshmallow.fields.Field, **kw) -> attr._make._CountingAttr:
117+
meta: t.Dict[object, object] = create_metadata(marshmallow_field)
118+
meta.update(metadata)
119+
120+
# call-overload and new_field intermediary:
121+
# https://github.com/python/typeshed/pull/5823
122+
new_field: dataclasses.Field[object] = dataclasses.field(**kw, metadata=meta) # type: ignore[call-overload]
123+
return new_field
124+
125+
126+
# These overloads lie about their return type just as both attrs and dataclasses
127+
# do so as to support the normal usage of `attribute: int = field()`
128+
@t.overload
129+
def ib(
130+
marshmallow_field: marshmallow.fields.Field,
131+
*,
132+
default: t.Union[T, t.Callable[[], T]],
133+
metadata: t.Mapping[object, object] = {},
134+
**kw: object,
135+
) -> T:
136+
...
137+
138+
139+
@t.overload
140+
def ib(
141+
marshmallow_field: marshmallow.fields.Field,
142+
*,
143+
factory: t.Callable[[], T],
144+
metadata: t.Mapping[object, object] = {},
145+
**kw: object,
146+
) -> T:
147+
...
148+
149+
150+
@t.overload
151+
def ib(
152+
marshmallow_field: marshmallow.fields.Field,
153+
*,
154+
metadata: t.Mapping[object, object] = {},
155+
**kw: object,
156+
) -> object:
157+
...
158+
159+
160+
# The return type hint of object is certainly a lie but fits a lot better with
161+
# the normal use of `x: int = desert.ib()`. Both dataclasses and attrs
162+
# prioritize hinting for this usage as well. Perhaps someday we'll have a
163+
# plugin that indicates the actual type.
164+
def ib(
165+
marshmallow_field: marshmallow.fields.Field,
166+
metadata: t.Mapping[object, object] = {},
167+
**kw: object,
168+
) -> object:
77169
"""Specify a marshmallow field in the metadata for an ``attr.dataclass``.
78170
79171
.. code-block:: python
@@ -82,9 +174,10 @@ def ib(marshmallow_field: marshmallow.fields.Field, **kw) -> attr._make._Countin
82174
class A:
83175
x: int = desert.ib(marshmallow.fields.Int())
84176
"""
85-
meta = metadata(marshmallow_field)
86-
meta.update(kw.pop("metadata", {}))
87-
return attr.ib(**kw, metadata=meta)
177+
meta: t.Dict[object, object] = create_metadata(marshmallow_field)
178+
meta.update(metadata)
179+
new_field: attr._make._CountingAttr = attr.ib(**kw, metadata=meta) # type: ignore[call-overload]
180+
return new_field
88181

89182

90183
__version__ = desert._version.__version__

src/desert/_make.py

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,9 @@ def class_schema(
9797
``marshmallow_field`` key in the metadata dictionary.
9898
"""
9999

100-
fields: t.Union[t.Tuple[dataclasses.Field, ...], t.Tuple[attr.Attribute, ...]]
100+
fields: t.Union[
101+
t.Tuple[dataclasses.Field[object], ...], t.Tuple[attr.Attribute[object], ...]
102+
]
101103

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

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

167169

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

173175

174176
def field_for_schema(
175-
typ: type, default=marshmallow.missing, metadata: t.Mapping[t.Any, t.Any] = None
177+
typ: type,
178+
default: object = marshmallow.missing,
179+
metadata: t.Optional[t.Mapping[t.Any, t.Any]] = None,
176180
) -> marshmallow.fields.Field:
177181
"""
178182
Get a marshmallow Field corresponding to the given python type.
@@ -221,7 +225,9 @@ def field_for_schema(
221225
field = None
222226

223227
# If the field was already defined by the user
224-
predefined_field = desert_metadata.get("marshmallow_field")
228+
predefined_field = t.cast(
229+
marshmallow.fields.Field, desert_metadata.get("marshmallow_field")
230+
)
225231

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

244250
if origin in (tuple, t.Tuple) and Ellipsis not in arguments:
245-
field = marshmallow.fields.Tuple(
251+
field = marshmallow.fields.Tuple( # type: ignore[no-untyped-call]
246252
tuple(field_for_schema(arg) for arg in arguments)
247253
)
248254
elif origin in (tuple, t.Tuple) and Ellipsis in arguments:
@@ -307,21 +313,25 @@ def field_for_schema(
307313
def _base_schema(clazz: type) -> t.Type[marshmallow.Schema]:
308314
class BaseSchema(marshmallow.Schema):
309315
@marshmallow.post_load
310-
def make_data_class(self, data, **_):
316+
def make_data_class(self, data: t.Mapping[str, object], **_: object) -> object:
311317
return clazz(**data)
312318

313319
return BaseSchema
314320

315321

316-
def _get_field_default(field: t.Union[dataclasses.Field, attr.Attribute]):
322+
def _get_field_default(
323+
# field: t.Union[dataclasses.Field[object], "attr.Attribute[object]"],
324+
field: t.Union["dataclasses.Field[object]", "attr.Attribute[object]"],
325+
) -> object:
317326
"""
318327
Return a marshmallow default value given a dataclass default value
319328
>>> _get_field_default(dataclasses.field())
320329
<marshmallow.missing>
321330
"""
322331
if isinstance(field, dataclasses.Field):
323-
# https://github.com/python/mypy/issues/10750
324-
if field.default_factory != dataclasses.MISSING: # type: ignore[misc]
332+
# misc: https://github.com/python/mypy/issues/10750
333+
# comparison-overlap: https://github.com/python/typeshed/pull/5900
334+
if field.default_factory != dataclasses.MISSING: # type: ignore[misc,comparison-overlap]
325335
return dataclasses.MISSING
326336
if field.default is dataclasses.MISSING:
327337
return marshmallow.missing
@@ -333,9 +343,9 @@ def _get_field_default(field: t.Union[dataclasses.Field, attr.Attribute]):
333343
# attrs specifically doesn't support this so as to support the
334344
# primary use case.
335345
# https://github.com/python-attrs/attrs/blob/38580632ceac1cd6e477db71e1d190a4130beed4/src/attr/__init__.pyi#L63-L65
336-
if field.default.takes_self: # type: ignore[union-attr]
346+
if field.default.takes_self: # type: ignore[attr-defined]
337347
return attr.NOTHING
338-
return field.default.factory # type: ignore[union-attr]
348+
return field.default.factory # type: ignore[attr-defined]
339349
return field.default
340350
else:
341351
raise TypeError(field)

0 commit comments

Comments
 (0)