Skip to content

Create mypy_extensions package. #2228

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

Merged
merged 2 commits into from
Oct 15, 2016
Merged

Create mypy_extensions package. #2228

merged 2 commits into from
Oct 15, 2016

Conversation

davidfstr
Copy link
Contributor

See discussion here: #2210

scripts=[],
data_files=mypy_setup.find_data_files('mypy_extensions', ['*.py', '*.pyi']),
classifiers=mypy_setup.setup_kwargs['classifiers'],
)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This setup() call has all the same keys as the one in setup.py for mypy with one exception: There is no cmdclass key. I wasn't able to prove that it made sense here, so I did not include it.

Copy link
Member

Choose a reason for hiding this comment

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

Good call.


new_dict.__name__ = typename # type: ignore # https://github.com/python/mypy/issues/708
new_dict.__supertype__ = dict # type: ignore # https://github.com/python/mypy/issues/708
return cast(Type[dict], new_dict)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

If we don't want to add anything to mypy_extensions/typing.py initially, deferring to #2206 , we could blank it out.

Copy link
Member

Choose a reason for hiding this comment

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

I think it would be better to leave this empty for now.

@@ -1,18 +1,28 @@
"""Utilities for verifying git integrity."""

from __future__ import print_function
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This module must now support running in a Python 2.x context so that python setup.py in mypy_extensions works.

/mypy_extensions/setup.py --> /setup.py --> mypy/git.py

Copy link
Member

@gvanrossum gvanrossum Oct 10, 2016

Choose a reason for hiding this comment

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

You need at least a comment reminding the reader. It would also be nice if the unit tests somehow enforced this. (If someone accidentally breaks this and the tests don't catch it, it's a real problem.)

But honestly I would much prefer if the setup.py for mypy_extensions didn't depend on this module at all.

packages=['mypy_extensions'],
scripts=[],
data_files=mypy_setup.find_data_files('mypy_extensions', ['*.py', '*.pyi']),
classifiers=mypy_setup.setup_kwargs['classifiers'],
Copy link
Contributor Author

Choose a reason for hiding this comment

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

It occurs to me that at least 'Programming Language :: Python :: 2' should probably be added to the classifiers for mypy_extensions.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, though I'm not sure whether anything actually checks those...

Copy link
Member

@gvanrossum gvanrossum left a comment

Choose a reason for hiding this comment

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

Sorry for saying the same thing in seven different ways... I feel quite strongly about not complexificating setup files.

@@ -0,0 +1,18 @@
from typing import cast, Dict, Type, TypeVar
Copy link
Member

@gvanrossum gvanrossum Oct 10, 2016

Choose a reason for hiding this comment

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

The more I think about the less I like that this module is named "typing.py". Yes, it's in a module namespace, but many people will probably prefer to write "from mypy_extensions import typing" and then use e.g. "typing.TypedDict", but if they also write "import typing" they have a name clash. And even if they don't it might confuse human readers to see "typing.TypedDict" (e.g. when using an IDE that hides the imports). Better not to invite confusion and give this a different name.

And honestly I also more and more believe that making this a package is over-engineering it. The cost of making it a package in the future are slight, and the probability that we'll never need that is high.

Also we need a module docstring and probably a comment explaining that cast..TypeVar are not intended to be exported.

Come to think of it, maybe we should just add a .pyi file adjacent with the signatures, and remove any type annotations from this file. In my experience with the actual top-level typing.py, I've found that the code implementing this doesn't benefit much from being type-checked itself, and there's a real cost (e.g. cast() is a pure-Python function and calling it is not particualrly cheap).

@@ -0,0 +1,18 @@
from typing import cast, Dict, Type, TypeVar


Copy link
Member

Choose a reason for hiding this comment

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

Don't need two blank lines here.


new_dict.__name__ = typename # type: ignore # https://github.com/python/mypy/issues/708
new_dict.__supertype__ = dict # type: ignore # https://github.com/python/mypy/issues/708
return cast(Type[dict], new_dict)
Copy link
Member

Choose a reason for hiding this comment

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

I think it would be better to leave this empty for now.

import sys

if '..' not in sys.path:
sys.path.insert(0, '..')
Copy link
Member

Choose a reason for hiding this comment

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

Have you tested this much with different installers (e.g. "python setup.py" as well as "pip install ")?

sys.path manipulations in a setup.py file give me the heebie-jeebies.


if '..' not in sys.path:
sys.path.insert(0, '..')
import setup as mypy_setup
Copy link
Member

Choose a reason for hiding this comment

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

This (importing the top-level setup.py) also scares the living daylight out of me.

Most setup.py files are purely cargo-culted and work by accident.

packages=['mypy_extensions'],
scripts=[],
data_files=mypy_setup.find_data_files('mypy_extensions', ['*.py', '*.pyi']),
classifiers=mypy_setup.setup_kwargs['classifiers'],
Copy link
Member

Choose a reason for hiding this comment

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

Yeah, though I'm not sure whether anything actually checks those...

scripts=[],
data_files=mypy_setup.find_data_files('mypy_extensions', ['*.py', '*.pyi']),
classifiers=mypy_setup.setup_kwargs['classifiers'],
)
Copy link
Member

Choose a reason for hiding this comment

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

Good call.

@@ -1,18 +1,28 @@
"""Utilities for verifying git integrity."""

from __future__ import print_function
Copy link
Member

@gvanrossum gvanrossum Oct 10, 2016

Choose a reason for hiding this comment

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

You need at least a comment reminding the reader. It would also be nice if the unit tests somehow enforced this. (If someone accidentally breaks this and the tests don't catch it, it's a real problem.)

But honestly I would much prefer if the setup.py for mypy_extensions didn't depend on this module at all.

# NOTE: Cannot use typing.TYPE_CHECKING guard here because cannot import
# "typing" in this file at all.
if False:
from typing import Iterator
Copy link
Member

Choose a reason for hiding this comment

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

I prefer this:

MYPY = False
if MYPY:
    from typing import Iterator

We'll have to support MYPY basically forever, while "if False" might eventually cause the body to be considered unreachable.

)

if __name__ == '__main__':
setup(**setup_kwargs)
Copy link
Member

Choose a reason for hiding this comment

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

Oh, I don't like this at all. All this linkage between setup.py and extensions/setup.py is just extremely fragile.

@davidfstr
Copy link
Contributor Author

davidfstr commented Oct 11, 2016

I hear you loud and clear to be gentle with setup.py files. Here's a different approach:

Instead of having /mypy_extensions/setup.py depend directly on /setup.py, have both of them depend on a simpler shared module. Perhaps /mypy_extensions/setup_common.py . No more black magic with sys.path. No more if __name__ == '__main__' in setup.py, which may not even work.

Additionally mypy/git.py would be moved into this shared module since only the setup.py files use it. (There is an import in mypy/main.py but it is unused.) Can't remove the dependence entirely since the module version number is computed with git. Since mypy.git feels like an internal module (and therefore not part of the mypy API) I believe it should be safe to move without breaking external code.


And honestly I also more and more believe that making this a package is over-engineering it. The cost of making it a package in the future are slight, and the probability that we'll never need that is high.

Did you have another approach in mind for having experimental extensions to mypy? It's not clear to me what other less-engineered approaches would work.

...unless we actually have control over the 3rd party typing module (as opposed to the one in the stdlib). Then we could add experimental extensions directly, although it still feels a little weird to be putting experimental extensions in typing.


The more I think about the less I like that this module is named "typing.py". Yes, it's in a module namespace, but many people will probably prefer to write "from mypy_extensions import typing" and then use e.g. "typing.TypedDict", but if they also write "import typing" they have a name clash. And even if they don't it might confuse human readers to see "typing.TypedDict" (e.g. when using an IDE that hides the imports). Better not to invite confusion and give this a different name.

Perhaps mypy_extensions.mypy_typing as an alternate name. Here's some potential usage patterns:

from mypy_extensions.mypy_typing import TypedDict
Point = TypedDict('Point', dict(x=int, y=int))
from mypy_extensions import mypy_typing
Point = mypy_typing.TypedDict('Point', dict(x=int, y=int))

@JukkaL
Copy link
Collaborator

JukkaL commented Oct 11, 2016

Why not just from mypy_extensions import TypedDict? I wonder if this is what Guido suggested.

I don't think that mypy_extensions should depend on anything currently in setup.py or mypy/git.py. Let's just keep the codebases separate -- the little sharing we could have is not worth the extra complexity and loss of flexibility. The git checks aren't relevant here, as they are primarily for typeshed, and mypy_extensions doesn't need it.

So here's how this would look better to me:

  • Only add new files mypy_extensions/mypy_extensions.py and mypy_extensions/setup.py (+ perhaps a readme and tests).
  • setup.py can be minimal. Here's an example for typing that is comparable, as it only installs a single file: https://github.com/python/typing/blob/master/setup.py
  • If we need separate implementations for Python 2 and 3, maybe have mypy_extensions/2.7 and mypy_extensions/3 subdirectories. However, perhaps a single implementation is sufficient, at least for now.
  • Version mypy_extensions independently of mypy.

@gvanrossum
Copy link
Member

gvanrossum commented Oct 11, 2016 via email

@davidfstr
Copy link
Contributor Author

Sounds good. I'll see about another iteration some time this week.

@davidfstr
Copy link
Contributor Author

I've pushed a significantly leaner version of this package for review.

I've left mypy_extensions.py empty for the time being. If the thrust of this PR is generally good I might opt to merge it with #2206 so that I can actually put something in mypy_extensions, namely TypedDict.

Copy link
Member

@gvanrossum gvanrossum left a comment

Choose a reason for hiding this comment

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

Thanks! I think the classifiers could be simpler, and can you please add a comment explaining what this is to mypy_extensions.py? Would also be handy if it said explicitly it has to be compatible with Python 2 and 3. Once you've got those two I'm ready to merge!

'Topic :: Software Development',
]

classifiers = _mypy_classifiers + [
Copy link
Member

Choose a reason for hiding this comment

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

I see no reason not to make this into a single longer list.

@davidfstr
Copy link
Contributor Author

Feedback integrated.

@gvanrossum gvanrossum merged commit 47b1d6a into python:master Oct 15, 2016
@gvanrossum
Copy link
Member

Thanks for your patience! I really like the simplicity of the result.

@davidfstr
Copy link
Contributor Author

Sure. These things take time.

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.

3 participants