Skip to content

Commit 1c69de2

Browse files
committed
enable and pass mypy --strict
1 parent 483734f commit 1c69de2

File tree

4 files changed

+237
-99
lines changed

4 files changed

+237
-99
lines changed

mypy.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
[mypy]
22
ignore_missing_imports = True
3+
strict = True

src/desert/__init__.py

Lines changed: 115 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.Optional[t.Mapping[object, object]] = None,
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.Optional[t.Mapping[object, object]] = None,
85+
**kw: object,
86+
) -> T:
87+
...
88+
89+
90+
@t.overload
91+
def field(
92+
marshmallow_field: marshmallow.fields.Field,
93+
*,
94+
metadata: t.Optional[t.Mapping[object, object]] = None,
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.Optional[t.Mapping[object, object]] = None,
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,61 @@ 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+
if metadata is None:
118+
metadata = {}
119+
120+
meta: t.Dict[object, object] = create_metadata(marshmallow_field)
121+
meta.update(metadata)
122+
123+
# call-overload and new_field intermediary:
124+
# https://github.com/python/typeshed/pull/5823
125+
new_field: dataclasses.Field[object] = dataclasses.field(**kw, metadata=meta) # type: ignore[call-overload]
126+
return new_field
127+
128+
129+
# These overloads lie about their return type just as both attrs and dataclasses
130+
# do so as to support the normal usage of `attribute: int = field()`
131+
@t.overload
132+
def ib(
133+
marshmallow_field: marshmallow.fields.Field,
134+
*,
135+
default: t.Union[T, t.Callable[[], T]],
136+
metadata: t.Optional[t.Mapping[object, object]] = None,
137+
**kw: object,
138+
) -> T:
139+
...
140+
141+
142+
@t.overload
143+
def ib(
144+
marshmallow_field: marshmallow.fields.Field,
145+
*,
146+
factory: t.Callable[[], T],
147+
metadata: t.Optional[t.Mapping[object, object]] = None,
148+
**kw: object,
149+
) -> T:
150+
...
151+
152+
153+
@t.overload
154+
def ib(
155+
marshmallow_field: marshmallow.fields.Field,
156+
*,
157+
metadata: t.Optional[t.Mapping[object, object]] = None,
158+
**kw: object,
159+
) -> object:
160+
...
161+
162+
163+
# The return type hint of object is certainly a lie but fits a lot better with
164+
# the normal use of `x: int = desert.ib()`. Both dataclasses and attrs
165+
# prioritize hinting for this usage as well. Perhaps someday we'll have a
166+
# plugin that indicates the actual type.
167+
def ib(
168+
marshmallow_field: marshmallow.fields.Field,
169+
metadata: t.Optional[t.Mapping[object, object]] = None,
170+
**kw: object,
171+
) -> object:
77172
"""Specify a marshmallow field in the metadata for an ``attr.dataclass``.
78173
79174
.. code-block:: python
@@ -82,9 +177,13 @@ def ib(marshmallow_field: marshmallow.fields.Field, **kw) -> attr._make._Countin
82177
class A:
83178
x: int = desert.ib(marshmallow.fields.Int())
84179
"""
85-
meta = metadata(marshmallow_field)
86-
meta.update(kw.pop("metadata", {}))
87-
return attr.ib(**kw, metadata=meta)
180+
if metadata is None:
181+
metadata = {}
182+
183+
meta: t.Dict[object, object] = create_metadata(marshmallow_field)
184+
meta.update(metadata)
185+
new_field: attr._make._CountingAttr = attr.ib(**kw, metadata=meta) # type: ignore[call-overload]
186+
return new_field
88187

89188

90189
__version__ = desert._version.__version__

src/desert/_make.py

Lines changed: 20 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,24 @@ 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+
) -> object:
317325
"""
318326
Return a marshmallow default value given a dataclass default value
319327
>>> _get_field_default(dataclasses.field())
320328
<marshmallow.missing>
321329
"""
322330
if isinstance(field, dataclasses.Field):
323-
# https://github.com/python/mypy/issues/10750
324-
if field.default_factory != dataclasses.MISSING: # type: ignore[misc]
331+
# misc: https://github.com/python/mypy/issues/10750
332+
# comparison-overlap: https://github.com/python/typeshed/pull/5900
333+
if field.default_factory != dataclasses.MISSING: # type: ignore[misc,comparison-overlap]
325334
return dataclasses.MISSING
326335
if field.default is dataclasses.MISSING:
327336
return marshmallow.missing
@@ -333,9 +342,9 @@ def _get_field_default(field: t.Union[dataclasses.Field, attr.Attribute]):
333342
# attrs specifically doesn't support this so as to support the
334343
# primary use case.
335344
# https://github.com/python-attrs/attrs/blob/38580632ceac1cd6e477db71e1d190a4130beed4/src/attr/__init__.pyi#L63-L65
336-
if field.default.takes_self: # type: ignore[union-attr]
345+
if field.default.takes_self: # type: ignore[attr-defined]
337346
return attr.NOTHING
338-
return field.default.factory # type: ignore[union-attr]
347+
return field.default.factory # type: ignore[attr-defined]
339348
return field.default
340349
else:
341350
raise TypeError(field)

0 commit comments

Comments
 (0)