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

Adds type hinting; drops python 3.8 #169

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open

Adds type hinting; drops python 3.8 #169

wants to merge 2 commits into from

Conversation

mfulgo
Copy link

@mfulgo mfulgo commented Oct 11, 2024

Closes #163.

Drops support for Python 3.8, which went end of life on 2024-10-07. Supporting it while adding type hints presented additional challenges.

Adds type hints in as narrow a way as I could manage while trying to avoid changes to the API. Also adds mypy checking to the tox file.

Python 3.8 is end of life as of 2024-10-07. Supporting it would add some
challenges to typehinting this library.
@mfulgo
Copy link
Author

mfulgo commented Oct 11, 2024

I happened to come across this project, saw the request for type hints, and thought it might be fun... feel free to take of this what you will. There were a few interesting parts I came across while going through the code, which I will point out in review comments on this PR.

@@ -176,7 +214,9 @@ def is_not_instance(a, b, msg=""):
return False


def almost_equal(a, b, rel=None, abs=None, msg=""):
def almost_equal(
a: object, b: object, rel: Any = None, abs: Any = None, msg: str = ""
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nailing down types for rel and abs was proving to be a pain. So... Any it is!

return _failures


def log_failure(msg="", check_str="", tb=None):
def log_failure(
msg: object = "", check_str: str = "", tb: Iterable[str] | None = None
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had hoped to make tb a TracebackType, but the usage in context_manager.py#L39,41 passes lists of strings.

Comment on lines +9 to +15
# TODO: Returning Any isn't ideal, but returning CheckRaisesContext | None
# would require callers to type ignore or declare the type when using `with`.
# Or, it could always return CheckRaisesContext, just an empty one after
# calling the passed function.
def raises(
expected_exception: type | Iterable[type], *args: Any, **kwargs: object
) -> Any:
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be worth splitting this into two different methods for the two different use cases. I didn't do it here because I wanted avoid breaking changes in this PR. (Pyhon 3.8, excepted...)

Comment on lines -54 to +63
return CheckRaisesContext(expected_exception, msg=msg)
return CheckRaisesContext(*expected_exceptions, msg=msg)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The typing on CheckRaisesContext could probably be expanded to be _TypeTuple (like in check_functions.py), but the var-arg seemed a bit cleaner.

@@ -97,3 +106,6 @@ def __exit__(self, exc_type, exc_val, exc_tb):
# without raising an error, hence `return True`.
log_failure(self.msg if self.msg else exc_val)
return True

# Stop on fail, so return True
return False
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was a bit of a guess, but reading the comments, returning False seemed like the right thing to do...

Comment on lines +70 to +72
# FIXME - the next two lines have broken types
reprtraceback = ExceptionRepr(reprcrash, excinfo) # type: ignore
chain_repr = ExceptionChainRepr([(reprtraceback, reprcrash, str(e))]) # type: ignore
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing the # type: ignores here raises mypy errors...

src/pytest_check/plugin.py:71: error: Argument 1 to "ExceptionRepr" has incompatible type "ReprFileLocation"; expected "ReprTraceback"  [arg-type]
                        reprtraceback = ExceptionRepr(reprcrash, excinfo)
                                                      ^~~~~~~~~
src/pytest_check/plugin.py:71: error: Argument 2 to "ExceptionRepr" has incompatible type "ExceptionInfo[BaseException]"; expected "ReprFileLocation | None"  [arg-type]
                        reprtraceback = ExceptionRepr(reprcrash, excinfo)
                                                                 ^~~~~~~
src/pytest_check/plugin.py:72: error: List item 0 has incompatible type "tuple[ExceptionRepr, ReprFileLocation, str]"; expected "tuple[ReprTraceback, ReprFileLocation | None, str | None]"  [list-item]
                        chain_repr = ExceptionChainRepr([(reprtraceback, reprcrash, str(e))])
                                                         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

(Not sure if the classes changed from when this library was first written.)

Comment on lines +42 to +43
mypy --strict --pretty src
mypy --pretty tests
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I decided to add mypy to the linting step rather than creating a separate one. 🤷

Attempts to add type hints as much as possible, without breaking
usage. However, some methods may deserve refactoring if the intended
behavior differed from what mypy found. (e.g. Returning None when a
boolean was desired)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

type-hinting the project
1 participant