Skip to content
This repository has been archived by the owner on Jul 16, 2022. It is now read-only.

Add mypy type annotations #23

Closed
wants to merge 2 commits into from

Conversation

DarwinAwardWinner
Copy link
Contributor

So, I've been playing with the MyPy static type checker lately, and I decided spend some downtime adding type annotations to Python projects I've worked on.

I had to change the logic a bit in order to make the code type check successfully, mainly by more eagerly converting a "unicode or bytes" path to unambiguously unicode when the object is constructed.

I also made it so that tox -e py-typecheck will run the type checker.

@DarwinAwardWinner
Copy link
Contributor Author

Oh, I just remembered that Python 2 doesn't support the type annotation syntax, so they have to be done as comments instead.

@DarwinAwardWinner DarwinAwardWinner force-pushed the mypy branch 2 times, most recently from 65f4498 to b1bd9e2 Compare November 13, 2016 06:43
I had to change the logic a bit in order to make the code type check
successfully, mainly just converting a "unicode or bytes" path to
unambiguously unicode ASAP.
I only enabled mypy for python 3.3 and above, since I don't think
there's a python 2 version. (The python 3 version can check python 2
types, but I'm not sure how to do that within tox.)
@untitaker
Copy link
Owner

I dislike that this adds runtime cost :( If type annotations are comments, do we need the imports?

@DarwinAwardWinner
Copy link
Contributor Author

I'm actually not sure if the comments need the imports, since I only use Python 3 these days. I'll look into it and get back to you.

_proper_fsync = os.fsync


if sys.platform != 'win32':
if hasattr(fcntl, 'F_FULLFSYNC'):
def _proper_fsync(fd):
# type: (int) -> None
Copy link
Owner

Choose a reason for hiding this comment

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

Those are all internal functions, are type annotations really necessary?

self._path = path
self._mode = mode
self._overwrite = overwrite
self._path = _path_to_unicode(path) # type: Text
Copy link
Owner

Choose a reason for hiding this comment

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

Paths are whichever type str is in the respective language. Unicode paths break under Python 2 when using non-ASCII characters.

Copy link
Owner

Choose a reason for hiding this comment

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

Also in principle I don't want to force-convert something here just for the type annotations.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So paths are non-Unicode in Python 2 but Unicode in Python 3?

Copy link
Owner

Choose a reason for hiding this comment

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

Yes I think so. How is open described in MyPy?

self._mode = mode
self._overwrite = overwrite
self._path = _path_to_unicode(path) # type: Text
self._mode = mode # type: Text
Copy link
Owner

Choose a reason for hiding this comment

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

Flake8 hates this, please only two spaces of padding.

'''
Open the temporary file.
'''
return self._open(self.get_fileobject)

@contextlib.contextmanager
def _open(self, get_fileobject):
# type: (Callable) -> Iterator[IO]
Copy link
Owner

Choose a reason for hiding this comment

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

This is a context manager, not a generator/iterator.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not exactly sure what's going on here, but I think maybe it type-checks the un-decorated function, which, when read as written, is a generator that yields an IO object (i.e. an open file). So this is the return type that mypy accepts.

Copy link
Owner

Choose a reason for hiding this comment

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

mypy is wrong then and doesn't know how to deal with decorators.

@DarwinAwardWinner
Copy link
Contributor Author

Regarding the differences between Python 2 and 3, as well as the issue of imposing the runtime cost of loading the typing module, I think there's a good solution to both of these: instead of defining the types inline, I can define them in a stub file in the typeshed repo: https://github.com/python/typeshed#third_party

Furthermore, I can provide a different stub file for each version of Python, so the Py2 vs 3, bytes vs unicode issue goes away. How does that sound? Would you be ok with me submitting a PR to typeshed adding types for atomicwrites?

@untitaker
Copy link
Owner

Yes please do that instead (though there appears to be one mayor issue with this repo: python/typeshed#153)

@DarwinAwardWinner
Copy link
Contributor Author

That issue doesn't apply here. It's talking about multiple API-incompatible versions of a module for a single Python version, not the same module for different Python versions.

So, I'll go ahead and submit Python 2 & Python 3 stub files for atomicwrites. Thanks for your feedback!

@untitaker
Copy link
Owner

That's the issue I'm talking about too. If I were to release a new version of atomicwrites with API-breakage there would be no way to know if the stubs in typeshed are up to date.

@untitaker
Copy link
Owner

I'll close this, please cc me on the respective PR to typeshed.

@DarwinAwardWinner
Copy link
Contributor Author

I think that's only a problem if you plan to maintain two or more API-incompatible versions of atomicwrites at the same time under the same module name. (Is that even possible on PyPI?)

Currently the type annotation stubs are only used by static type checkers such as MyPy. Python itself ignores them completely at runtime. So out-of-date type stubs will not prevent people from using your module. Furthermore, the typeshed project has CI checking all the type stubs with ever commit, so if you update your module in a way that is incompatible with the type stub in typeshed, it should show up in the next CI run and be fixed quickly.

@untitaker
Copy link
Owner

I think that's only a problem if you plan to maintain two or more API-incompatible versions of atomicwrites at the same time under the same module name. (Is that even possible on PyPI?)

A total ordering by time is generally not possible. Not every version is immediately deprecated the second the next one comes out. Backports exist, maintenance versions exist.

Furthermore, the typeshed project has CI checking all the type stubs with ever commit, so if you update your module in a way that is incompatible with the type stub in typeshed, it should show up in the next CI run and be fixed quickly.

Fair enough.

@DarwinAwardWinner
Copy link
Contributor Author

My guess is that typeshed is intended to correspond to the latest released version (i.e. whatever pip would install when you don't request a specific version). I can look more into this issue if you want.

@untitaker
Copy link
Owner

Not necessary (it's your freetime!)

@untitaker untitaker closed this Nov 14, 2016
DarwinAwardWinner added a commit to DarwinAwardWinner/typeshed that referenced this pull request Nov 23, 2016
@gvanrossum
Copy link

There still seems to be some confusion regarding what to do about evolution of the atomicwrite package. Until python/typeshed#153 is resolved, typeshed/mypy can only hold a single version of the stubs for a package. @untitaker please let us know whether you're okay with having the stubs in typeshed despite that concern. If you are not, we should just close python/typeshed#705. If you are okay with this, we can go ahead and merge that. But we currently don't have a way to automatically test that stubs match the real library.

Alternative you may be able to add type annotations in the form of comments to the atomicwriter package itself.

@DarwinAwardWinner
Copy link
Contributor Author

@gvanrossum This pull request was about adding type comments to the atomicwriter code. @untitaker didn't want to add the typing module as a dependency, which Is why I pursued adding the annotations to the typeshed instead. If the type annotations are in comments, is the import typing statement still required?

@gvanrossum
Copy link

If everything you import from typing is hidden inside a # type: comment you can use the following trick:

MYPY = False
if MYPY:
    from typing import Any, .....

This hides the import at runtime but MYPY itself processes the block as if MYPY == True.

@untitaker
Copy link
Owner

I'm fine with the typeshed stub. I don't want annotations in my own sourcetree that incur any runtime cost.

DarwinAwardWinner added a commit to DarwinAwardWinner/typeshed that referenced this pull request Jan 9, 2017
gvanrossum pushed a commit to python/typeshed that referenced this pull request Jan 9, 2017
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants