Skip to content

Commit 6036e09

Browse files
authored
Merge pull request #49 from python/master
Sync Fork from Upstream Repo
2 parents 1a8030f + 8780d45 commit 6036e09

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+1186
-388
lines changed

MANIFEST.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ graft mypyc/doc
2929

3030
# files necessary for testing sdist
3131
include mypy-requirements.txt
32+
include build-requirements.txt
3233
include test-requirements.txt
3334
include mypy_self_check.ini
3435
prune misc

build-requirements.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
-r mypy-requirements.txt
2+
types-typed-ast>=1.4.0,<1.5.0
3+
types-toml>=0.0
4+
types-enum34>=0.0; python_version == '3.5'

docs/source/command_line.rst

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,30 @@ for full details, see :ref:`running-mypy`.
4949
Asks mypy to type check the provided string as a program.
5050

5151

52+
.. option:: --exclude
53+
54+
A regular expression that matches file names, directory names and paths
55+
which mypy should ignore while recursively discovering files to check.
56+
Use forward slashes on all platforms.
57+
58+
For instance, to avoid discovering any files named `setup.py` you could
59+
pass ``--exclude '/setup\.py$'``. Similarly, you can ignore discovering
60+
directories with a given name by e.g. ``--exclude /build/`` or
61+
those matching a subpath with ``--exclude /project/vendor/``.
62+
63+
Note that this flag only affects recursive discovery, that is, when mypy is
64+
discovering files within a directory tree or submodules of a package to
65+
check. If you pass a file or module explicitly it will still be checked. For
66+
instance, ``mypy --exclude '/setup.py$' but_still_check/setup.py``.
67+
68+
Note that mypy will never recursively discover files and directories named
69+
"site-packages", "node_modules" or "__pycache__", or those whose name starts
70+
with a period, exactly as ``--exclude
71+
'/(site-packages|node_modules|__pycache__|\..*)/$'`` would. Mypy will also
72+
never recursively discover files with extensions other than ``.py`` or
73+
``.pyi``.
74+
75+
5276
Optional arguments
5377
******************
5478

@@ -435,7 +459,7 @@ potentially problematic or redundant in some way.
435459
.. code-block:: python
436460
437461
def process(x: int) -> None:
438-
# Error: Right operand of 'or' is never evaluated
462+
# Error: Right operand of "or" is never evaluated
439463
if isinstance(x, int) or x > 7:
440464
# Error: Unsupported operand types for + ("int" and "str")
441465
print(x + "bad")

docs/source/common_issues.rst

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -772,3 +772,30 @@ False:
772772
773773
If you use the :option:`--warn-unreachable <mypy --warn-unreachable>` flag, mypy will generate
774774
an error about each unreachable code block.
775+
776+
777+
Narrowing and inner functions
778+
-----------------------------
779+
780+
Because closures in Python are late-binding (https://docs.python-guide.org/writing/gotchas/#late-binding-closures),
781+
mypy will not narrow the type of a captured variable in an inner function.
782+
This is best understood via an example:
783+
784+
.. code-block:: python
785+
786+
def foo(x: Optional[int]) -> Callable[[], int]:
787+
if x is None:
788+
x = 5
789+
print(x + 1) # mypy correctly deduces x must be an int here
790+
def inner() -> int:
791+
return x + 1 # but (correctly) complains about this line
792+
793+
x = None # because x could later be assigned None
794+
return inner
795+
796+
inner = foo(5)
797+
inner() # this will raise an error when called
798+
799+
To get this code to type check, you could assign ``y = x`` after ``x`` has been
800+
narrowed, and use ``y`` in the inner function, or add an assert in the inner
801+
function.

docs/source/config_file.rst

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,18 @@ section of the command line docs.
197197

198198
This option may only be set in the global section (``[mypy]``).
199199

200+
.. confval:: exclude
201+
202+
:type: regular expression
203+
204+
A regular expression that matches file names, directory names and paths
205+
which mypy should ignore while recursively discovering files to check.
206+
Use forward slashes on all platforms.
207+
208+
For more details, see :option:`--exclude <mypy --exclude>`.
209+
210+
This option may only be set in the global section (``[mypy]``).
211+
200212
.. confval:: namespace_packages
201213

202214
:type: boolean
@@ -207,6 +219,19 @@ section of the command line docs.
207219

208220
This option may only be set in the global section (``[mypy]``).
209221

222+
.. confval:: explicit_package_bases
223+
224+
:type: boolean
225+
:default: False
226+
227+
This flag tells mypy that top-level packages will be based in either the
228+
current directory, or a member of the ``MYPYPATH`` environment variable or
229+
:confval:`mypy_path` config option. This option is only useful in
230+
conjunction with :confval:`namespace_packages`. See :ref:`Mapping file
231+
paths to modules <mapping-paths-to-modules>` for details.
232+
233+
This option may only be set in the global section (``[mypy]``).
234+
210235
.. confval:: ignore_missing_imports
211236

212237
:type: boolean
@@ -250,6 +275,10 @@ section of the command line docs.
250275
Used in conjunction with :confval:`follow_imports=error <follow_imports>`, this can be used
251276
to make any use of a particular ``typeshed`` module an error.
252277

278+
.. note::
279+
280+
This is not supported by the mypy daemon.
281+
253282
.. confval:: python_executable
254283

255284
:type: string

docs/source/error_code_list.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ Example with an error:
208208
209209
class Bundle:
210210
def __init__(self) -> None:
211-
# Error: Need type annotation for 'items'
211+
# Error: Need type annotation for "items"
212212
# (hint: "items: List[<type>] = ...") [var-annotated]
213213
self.items = []
214214

docs/source/error_code_list2.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ incorrect control flow or conditional checks that are accidentally always true o
187187
# mypy: warn-unreachable
188188
189189
def example(x: int) -> None:
190-
# Error: Right operand of 'or' is never evaluated [unreachable]
190+
# Error: Right operand of "or" is never evaluated [unreachable]
191191
assert isinstance(x, int) or x == 'unused'
192192
193193
return
@@ -205,7 +205,7 @@ mypy generates an error if it thinks that an expression is redundant.
205205
# mypy: enable-error-code redundant-expr
206206
207207
def example(x: int) -> None:
208-
# Error: Left operand of 'and' is always true [redundant-expr]
208+
# Error: Left operand of "and" is always true [redundant-expr]
209209
if isinstance(x, int) and x > 0:
210210
pass
211211

docs/source/error_codes.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ Displaying error codes
2424
----------------------
2525

2626
Error codes are not displayed by default. Use :option:`--show-error-codes <mypy --show-error-codes>`
27-
to display error codes. Error codes are shown inside square brackets:
27+
or config `show_error_codes = True` to display error codes. Error codes are shown inside square brackets:
2828

2929
.. code-block:: text
3030

docs/source/getting_started.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,7 @@ for example, when assigning an empty dictionary to some global value:
302302

303303
.. code-block:: python
304304
305-
my_global_dict = {} # Error: Need type annotation for 'my_global_dict'
305+
my_global_dict = {} # Error: Need type annotation for "my_global_dict"
306306
307307
You can teach mypy what type ``my_global_dict`` is meant to have by giving it
308308
a type hint. For example, if you knew this variable is supposed to be a dict

docs/source/protocols.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,8 @@ support for basic runtime structural checks:
429429
def __init__(self) -> None:
430430
self.handles = 1
431431
432+
def use(handles: int) -> None: ...
433+
432434
mug = Mug()
433435
if isinstance(mug, Portable):
434436
use(mug.handles) # Works statically and at runtime

docs/source/running_mypy.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -390,7 +390,8 @@ to modules to type check.
390390
- Mypy will check all paths provided that correspond to files.
391391

392392
- Mypy will recursively discover and check all files ending in ``.py`` or
393-
``.pyi`` in directory paths provided.
393+
``.pyi`` in directory paths provided, after accounting for
394+
:option:`--exclude <mypy --exclude>`.
394395

395396
- For each file to be checked, mypy will attempt to associate the file (e.g.
396397
``project/foo/bar/baz.py``) with a fully qualified module name (e.g.

docs/source/type_inference_and_annotations.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ without some help:
7878

7979
.. code-block:: python
8080
81-
l = [] # Error: Need type annotation for 'l'
81+
l = [] # Error: Need type annotation for "l"
8282
8383
In these cases you can give the type explicitly using a type annotation:
8484

@@ -162,7 +162,7 @@ in the following statement:
162162
def foo(arg: List[int]) -> None:
163163
print('Items:', ', '.join(arg))
164164
165-
a = [] # Error: Need type annotation for 'a'
165+
a = [] # Error: Need type annotation for "a"
166166
foo(a)
167167
168168
Working around the issue is easy by adding a type annotation:

misc/build_wheel.py

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
"""Script to build compiled binary wheels that can be uploaded to PyPI.
2+
3+
The main GitHub workflow where this script is used:
4+
https://github.com/mypyc/mypy_mypyc-wheels/blob/master/.github/workflows/build.yml
5+
6+
This uses cibuildwheel (https://github.com/joerick/cibuildwheel) to
7+
build the wheels.
8+
9+
Usage:
10+
11+
build_wheel_ci.py --python-version <python-version> \
12+
--output-dir <dir>
13+
14+
Wheels for the given Python version will be created in the given directory.
15+
Python version is in form "39".
16+
17+
This works on macOS, Windows and Linux.
18+
19+
You can test locally by using --extra-opts. macOS example:
20+
21+
mypy/misc/build_wheel_ci.py --python-version 39 --output-dir out --extra-opts="--platform macos"
22+
23+
Other supported values for platform: linux, windows
24+
"""
25+
26+
import argparse
27+
import os
28+
import subprocess
29+
import sys
30+
from typing import Dict
31+
32+
# Clang package we use on Linux
33+
LLVM_URL = 'https://github.com/mypyc/mypy_mypyc-wheels/releases/download/llvm/llvm-centos-5.tar.gz'
34+
35+
# Mypy repository root
36+
ROOT_DIR = os.path.dirname(os.path.dirname(__file__))
37+
38+
39+
def create_environ(python_version: str) -> Dict[str, str]:
40+
"""Set up environment variables for cibuildwheel."""
41+
env = os.environ.copy()
42+
43+
env['CIBW_BUILD'] = "cp{}-*".format(python_version)
44+
45+
# Don't build 32-bit wheels
46+
env['CIBW_SKIP'] = "*-manylinux_i686 *-win32"
47+
48+
env['CIBW_BUILD_VERBOSITY'] = '1'
49+
50+
# mypy's isolated builds don't specify the requirements mypyc needs, so install
51+
# requirements and don't use isolated builds. we need to use build-requirements.txt
52+
# with recent mypy commits to get stub packages needed for compilation.
53+
#
54+
# TODO: remove use of mypy-requirements.txt once we no longer need to support
55+
# building pre modular typeshed releases
56+
env['CIBW_BEFORE_BUILD'] = """
57+
pip install -r {package}/mypy-requirements.txt &&
58+
(pip install -r {package}/build-requirements.txt || true)
59+
""".replace('\n', ' ')
60+
61+
# download a copy of clang to use to compile on linux. this was probably built in 2018,
62+
# speeds up compilation 2x
63+
env['CIBW_BEFORE_BUILD_LINUX'] = """
64+
(cd / && curl -L %s | tar xzf -) &&
65+
pip install -r {package}/mypy-requirements.txt &&
66+
(pip install -r {package}/build-requirements.txt || true)
67+
""".replace('\n', ' ') % LLVM_URL
68+
69+
# the double negative is counterintuitive, https://github.com/pypa/pip/issues/5735
70+
env['CIBW_ENVIRONMENT'] = 'MYPY_USE_MYPYC=1 MYPYC_OPT_LEVEL=3 PIP_NO_BUILD_ISOLATION=no'
71+
env['CIBW_ENVIRONMENT_LINUX'] = (
72+
'MYPY_USE_MYPYC=1 MYPYC_OPT_LEVEL=3 PIP_NO_BUILD_ISOLATION=no ' +
73+
'CC=/opt/llvm/bin/clang'
74+
)
75+
env['CIBW_ENVIRONMENT_WINDOWS'] = (
76+
'MYPY_USE_MYPYC=1 MYPYC_OPT_LEVEL=2 PIP_NO_BUILD_ISOLATION=no'
77+
)
78+
79+
# lxml is slow to build wheels for new releases, so allow installing reqs to fail
80+
# if we failed to install lxml, we'll skip tests, but allow the build to succeed
81+
env['CIBW_BEFORE_TEST'] = (
82+
'pip install -r {project}/mypy/test-requirements.txt || true'
83+
)
84+
85+
# pytest looks for configuration files in the parent directories of where the tests live.
86+
# since we are trying to run the tests from their installed location, we copy those into
87+
# the venv. Ew ew ew.
88+
env['CIBW_TEST_COMMAND'] = """
89+
( ! pip list | grep lxml ) || (
90+
DIR=$(python -c 'import mypy, os; dn = os.path.dirname; print(dn(dn(mypy.__path__[0])))')
91+
&& TEST_DIR=$(python -c 'import mypy.test; print(mypy.test.__path__[0])')
92+
&& cp '{project}/mypy/pytest.ini' '{project}/mypy/conftest.py' $DIR
93+
&& MYPY_TEST_PREFIX='{project}/mypy' pytest $TEST_DIR
94+
)
95+
""".replace('\n', ' ')
96+
97+
# i ran into some flaky tests on windows, so only run testcheck. it looks like we
98+
# previously didn't run any tests on windows wheels, so this is a net win.
99+
env['CIBW_TEST_COMMAND_WINDOWS'] = """
100+
bash -c "
101+
( ! pip list | grep lxml ) || (
102+
DIR=$(python -c 'import mypy, os; dn = os.path.dirname; print(dn(dn(mypy.__path__[0])))')
103+
&& TEST_DIR=$(python -c 'import mypy.test; print(mypy.test.__path__[0])')
104+
&& cp '{project}/mypy/pytest.ini' '{project}/mypy/conftest.py' $DIR
105+
&& MYPY_TEST_PREFIX='{project}/mypy' pytest $TEST_DIR/testcheck.py
106+
)
107+
"
108+
""".replace('\n', ' ')
109+
return env
110+
111+
112+
def main() -> None:
113+
parser = argparse.ArgumentParser()
114+
parser.add_argument('--python-version', required=True, metavar='XY',
115+
help='Python version (e.g. 38 or 39)')
116+
parser.add_argument('--output-dir', required=True, metavar='DIR',
117+
help='Output directory for created wheels')
118+
parser.add_argument('--extra-opts', default='', metavar='OPTIONS',
119+
help='Extra options passed to cibuildwheel verbatim')
120+
args = parser.parse_args()
121+
python_version = args.python_version
122+
output_dir = args.output_dir
123+
extra_opts = args.extra_opts
124+
environ = create_environ(python_version)
125+
script = 'python -m cibuildwheel {} --output-dir {} {}'.format(extra_opts, output_dir,
126+
ROOT_DIR)
127+
subprocess.check_call(script, shell=True, env=environ)
128+
129+
130+
if __name__ == '__main__':
131+
main()

0 commit comments

Comments
 (0)