Skip to content
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

#125 Use no_type_check instead of hack #126

Merged
merged 2 commits into from
Jan 26, 2024
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
114 changes: 43 additions & 71 deletions megamock/megamocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
cast,
get_origin,
get_type_hints,
no_type_check,
overload,
)
from unittest import mock
Expand Down Expand Up @@ -685,15 +686,7 @@ def __init__(
**kwargs,
)

##################
# DRAGON WARNING
##################
# Do NOT type annotate the return type of MegaMock.it and MegaMock.this
# Doing this will cause mypy to complain about the union of MegaMock and the spec
# because MyPy / python do not (yet) have a true union / merge type.
# The hack used here circumvents the attr-check error you would get attempting to
# access any attributes of MegaMock

@no_type_check
@staticmethod
def it(
spec: type[T] | None = None,
Expand All @@ -711,7 +704,7 @@ def it(
_parent_mega_mock: _MegaMockMixin | None = None,
# warning: kwargs to MagicMock may not work correctly! Use at your own risk!
**kwargs,
):
) -> T | MegaMock[T, MegaMock | T]:
"""
MegaMock a class instance.

Expand All @@ -734,31 +727,27 @@ def it(
a class instance, it would be setting the return value of __call__
"""

# hack to get static type inference to think this is a true union
# of the two classes
def helper(obj) -> type[T | MegaMock[T, MegaMock | T]]:
return cast(type[MegaMock | T], lambda: obj)

return helper(
MegaMock(
spec=spec,
spec_set=spec_set,
instance=True,
side_effect=side_effect,
return_value=return_value,
_wraps_mock=_wraps_mock,
_parent_mega_mock=_parent_mega_mock,
_merged_type=type(MegaMock | spec.__class__), # type: ignore
**kwargs,
),
)()
return MegaMock(
spec=spec,
spec_set=spec_set,
instance=True,
side_effect=side_effect,
return_value=return_value,
_wraps_mock=_wraps_mock,
_parent_mega_mock=_parent_mega_mock,
_merged_type=type(MegaMock | spec.__class__), # type: ignore
**kwargs,
)

@no_type_check
@staticmethod
def the_class(spec: type[T], *, spec_set: bool = True, **kwargs):
def the_class(
spec: type[T], *, spec_set: bool = True, **kwargs
) -> type[T] | MegaMock[type[T], MegaMock[Any, Any] | T]:
"""
MegaMock a class.

Use `it_class` for creating mock of classes. Use `this` for everything else.
Use `the_class` for creating mocks of classes. Use `this` for everything else.

Note that spec_set defaults to True, which means attempts to set
an attribute that doesn't exist will result in an error.
Expand All @@ -778,6 +767,7 @@ def the_class(spec: type[T], *, spec_set: bool = True, **kwargs):
"""
return MegaMock._the_class(spec, spec_set=spec_set, _spec_too=spec, **kwargs)

@no_type_check
@staticmethod
def _the_class(
spec: type[T],
Expand All @@ -791,23 +781,16 @@ def _the_class(
raise Exception("MegaMock.the_class should be used with classes")
return_value = MegaMock.it(spec, spec_set=spec_set)

# hack to get static type inference to think this is a true union
# of the two classes
def helper(obj) -> type[M | MegaMock[M, MegaMock[T, T] | T]]:
return cast(type[MegaMock[M, MegaMock[T, T] | T]], lambda _=_spec_too: obj)

ret = helper( # type: ignore # seems fine to me...
MegaMock(
spec=spec,
spec_set=spec_set,
instance=False,
return_value=return_value,
_merged_type=type(MegaMock | spec),
**kwargs,
),
)()
return ret
return MegaMock(
spec=spec,
spec_set=spec_set,
instance=False,
return_value=return_value,
_merged_type=type(MegaMock | spec),
**kwargs,
)

@no_type_check
@staticmethod
def this(
spec: T | None = None,
Expand All @@ -827,7 +810,7 @@ def this(
_parent_mega_mock: _MegaMockMixin | None = None,
# warning: kwargs to MagicMock may not work correctly! Use at your own risk!
**kwargs,
):
) -> T | MegaMock[T, MegaMock | T]:
"""
MegaMock something.

Expand All @@ -844,30 +827,19 @@ def this(
:param return_value: The return value to use for the mock.
"""

# hack to get static type inference to think this is a true union
# of the two classes
def helper(obj) -> type[T | MegaMock[T, MegaMock | T]]:
return cast(type[MegaMock | T], lambda: obj)

return helper(
MegaMock(
spec=spec,
wraps=wraps,
spy=spy,
spec_set=spec_set,
instance=False,
side_effect=side_effect,
return_value=return_value,
_wraps_mock=_wraps_mock,
_parent_mega_mock=_parent_mega_mock,
_merged_type=type(MegaMock | spec.__class__),
**kwargs,
),
)()

######################
# END DRAGON WARNING
######################
return MegaMock(
spec=spec,
wraps=wraps,
spy=spy,
spec_set=spec_set,
instance=False,
side_effect=side_effect,
return_value=return_value,
_wraps_mock=_wraps_mock,
_parent_mega_mock=_parent_mega_mock,
_merged_type=type(MegaMock | spec.__class__),
**kwargs,
)

@staticmethod
def from_legacy_mock(
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "megamock"
version = "0.1.0-beta.9"
version = "0.1.0-beta.10"
description = "Mega mocking capabilities - stop using dot-notated paths!"
authors = ["James Hutchison <[email protected]>"]
readme = "README.md"
Expand Down
Loading