Skip to content

Commit 0dee1f4

Browse files
committed
Major update to "using mypy with existing codebase"
See python#13681 In particular, it's really common to want to make progress towards `--strict`, and we currently don't really have any guidance on doing so. Per-module ignore_errors is also a really useful tool for adopting mypy. Also try to link more to existing documentation elsewhere.
1 parent 11be378 commit 0dee1f4

File tree

1 file changed

+155
-90
lines changed

1 file changed

+155
-90
lines changed

docs/source/existing_code.rst

Lines changed: 155 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -7,46 +7,94 @@ This section explains how to get started using mypy with an existing,
77
significant codebase that has little or no type annotations. If you are
88
a beginner, you can skip this section.
99

10-
These steps will get you started with mypy on an existing codebase:
10+
Start small
11+
-----------
1112

12-
1. Start small -- get a clean mypy build for some files, with few
13-
annotations
13+
If your codebase is large, pick a subset of your codebase (say, 5,000 to 50,000
14+
lines) and get mypy to run successfully only on this subset at first, *before
15+
adding annotations*. This should be doable in a day or two. The sooner you get
16+
some form of mypy passing on your codebase, the sooner you benefit.
1417

15-
2. Write a mypy runner script to ensure consistent results
18+
You'll likely need to fix some mypy errors, either by inserting
19+
annotations requested by mypy or by adding ``# type: ignore``
20+
comments to silence errors you don't want to fix now.
1621

17-
3. Run mypy in Continuous Integration to prevent type errors
22+
We'll mention some tips for getting mypy passing on your codebase in various
23+
sections below.
1824

19-
4. Gradually annotate commonly imported modules
25+
Run mypy consistently and prevent regressions
26+
---------------------------------------------
2027

21-
5. Write annotations as you modify existing code and write new code
28+
Make sure all developers on your codebase run mypy the same way.
29+
One way to ensure this is adding a small script with your mypy
30+
invocation to your codebase, or adding your mypy invocation to
31+
existing tools you use to run tests, like ``tox``.
2232

23-
6. Use :doc:`monkeytype:index` or `PyAnnotate`_ to automatically annotate legacy code
33+
* Make sure everyone runs mypy with the same options. Checking a mypy
34+
:ref:`configuration file <config-file>` into your codebase can help
35+
with this.
2436

25-
We discuss all of these points in some detail below, and a few optional
26-
follow-up steps.
37+
* Make sure everyone type checks the same set of files. See
38+
:ref:`specifying-code-to-be-checked` for details.
2739

28-
Start small
29-
-----------
40+
* Make sure everyone runs mypy with the same version of mypy, for instance
41+
by pinning mypy with the rest of your dev requirements.
3042

31-
If your codebase is large, pick a subset of your codebase (say, 5,000
32-
to 50,000 lines) and run mypy only on this subset at first,
33-
*without any annotations*. This shouldn't take more than a day or two
34-
to implement, so you start enjoying benefits soon.
43+
In particular, you'll want to make sure to run mypy as part of your
44+
Continuous Integration (CI) system as soon as possible. This will
45+
prevent new type errors from being introduced into your codebase.
3546

36-
You'll likely need to fix some mypy errors, either by inserting
37-
annotations requested by mypy or by adding ``# type: ignore``
38-
comments to silence errors you don't want to fix now.
47+
A simple CI script could look something like this:
48+
49+
.. code-block:: text
50+
51+
python3 -m pip install mypy==0.971
52+
# Run your standardised mypy invocation, e.g.
53+
mypy my_project
54+
# This could also look like `scripts/run_mypy.sh`, `tox -e mypy`, `make mypy`, etc
55+
56+
Ignoring errors from certain modules
57+
------------------------------------
3958

40-
In particular, mypy often generates errors about modules that it can't
41-
find or that don't have stub files:
59+
By default mypy will follow imports in your code and try to check everything.
60+
This means even if you only pass in a few files to mypy, it may still process a
61+
large number of imported files. This could potentially result in lots of errors
62+
you don't want to deal with at the moment.
63+
64+
One way to deal with this is to ignore errors in modules you aren't yet ready to
65+
type check. The :confval:`ignore_errors` option is useful for this, for instance,
66+
if you aren't yet ready to deal with errors from ``package_to_fix_later``:
67+
68+
.. code-block:: text
69+
70+
[mypy-package_to_fix_later.*]
71+
ignore_errors = True
72+
73+
You could even invert this, by setting ``ignore_errors = True`` in your global
74+
config section and only enabling error reporting with ``ignore_errors = False``
75+
for the set of modules you are ready to type check.
76+
77+
Fixing errors related to imports
78+
--------------------------------
79+
80+
A common class of error you will encounter is errors from mypy about modules
81+
that it can't find, that don't have types, or don't have stub files:
4282

4383
.. code-block:: text
4484
4585
core/config.py:7: error: Cannot find implementation or library stub for module named 'frobnicate'
4686
core/model.py:9: error: Cannot find implementation or library stub for module named 'acme'
4787
...
4888
49-
This is normal, and you can easily ignore these errors. For example,
89+
Sometimes these can be fixed by installing the relevant packages or
90+
stub libraries in the environment you're running ``mypy`` in.
91+
92+
See :ref:`ignore-missing-imports` for a complete reference on these errors
93+
and the ways in which you can fix them.
94+
95+
You'll likely find that you want to suppress all errors from importing
96+
a given module that doesn't have types. If you only import that module
97+
in one or two places, you can use ``# type: ignore`` comments. For example,
5098
here we ignore an error about a third-party module ``frobnicate`` that
5199
doesn't have stubs using ``# type: ignore``:
52100

@@ -56,9 +104,9 @@ doesn't have stubs using ``# type: ignore``:
56104
...
57105
frobnicate.initialize() # OK (but not checked)
58106
59-
You can also use a mypy configuration file, which is convenient if
60-
there are a large number of errors to ignore. For example, to disable
61-
errors about importing ``frobnicate`` and ``acme`` everywhere in your
107+
But if you import the module in many places, this becomes unwieldy. In this
108+
case, we recommend using a :ref:`configuration file <config-file>`. For example,
109+
to disable errors about importing ``frobnicate`` and ``acme`` everywhere in your
62110
codebase, use a config like this:
63111

64112
.. code-block:: text
@@ -69,69 +117,33 @@ codebase, use a config like this:
69117
[mypy-acme.*]
70118
ignore_missing_imports = True
71119
72-
You can add multiple sections for different modules that should be
73-
ignored.
74-
75-
If your config file is named ``mypy.ini``, this is how you run mypy:
76-
77-
.. code-block:: text
78-
79-
mypy --config-file mypy.ini mycode/
80-
81120
If you get a large number of errors, you may want to ignore all errors
82-
about missing imports. This can easily cause problems later on and
83-
hide real errors, and it's only recommended as a last resort.
84-
For more details, look :ref:`here <follow-imports>`.
121+
about missing imports, for instance by setting :confval:`ignore_missing_imports`
122+
to true globally. This can hide errors later on, so we recommend avoiding this
123+
if possible.
85124

86-
Mypy follows imports by default. This can result in a few files passed
87-
on the command line causing mypy to process a large number of imported
88-
files, resulting in lots of errors you don't want to deal with at the
89-
moment. There is a config file option to disable this behavior, but
90-
since this can hide errors, it's not recommended for most users.
125+
Finally, mypy allows fine-grained control over specific import following
126+
behaviour. It's very easy to silently shoot yourself in the foot when playing
127+
around with these, so it's mostly recommended as a last resort. For more
128+
details, look :ref:`here <follow-imports>`.
91129

92-
Mypy runner script
93-
------------------
94-
95-
Introduce a mypy runner script that runs mypy, so that every developer
96-
will use mypy consistently. Here are some things you may want to do in
97-
the script:
98-
99-
* Ensure that the correct version of mypy is installed.
100-
101-
* Specify mypy config file or command-line options.
102-
103-
* Provide set of files to type check. You may want to implement
104-
inclusion and exclusion filters for full control of the file
105-
list.
106-
107-
Continuous Integration
108-
----------------------
109-
110-
Once you have a clean mypy run and a runner script for a part
111-
of your codebase, set up your Continuous Integration (CI) system to
112-
run mypy to ensure that developers won't introduce bad annotations.
113-
A simple CI script could look something like this:
114-
115-
.. code-block:: text
116-
117-
python3 -m pip install mypy==0.790 # Pinned version avoids surprises
118-
scripts/mypy # Run the mypy runner script you set up
119-
120-
Annotate widely imported modules
121-
--------------------------------
130+
Prioritise annotating widely imported modules
131+
---------------------------------------------
122132

123133
Most projects have some widely imported modules, such as utilities or
124134
model classes. It's a good idea to annotate these pretty early on,
125135
since this allows code using these modules to be type checked more
126-
effectively. Since mypy supports gradual typing, it's okay to leave
127-
some of these modules unannotated. The more you annotate, the more
128-
useful mypy will be, but even a little annotation coverage is useful.
136+
effectively.
137+
138+
Mypy is designed to support gradual typing, i.e. letting you add annotations at
139+
your own pace, so it's okay to leave some of these modules unannotated. The more
140+
you annotate, the more useful mypy will be, but even a little annotation
141+
coverage is useful.
129142

130143
Write annotations as you go
131144
---------------------------
132145

133-
Now you are ready to include type annotations in your development
134-
workflows. Consider adding something like these in your code style
146+
Consider adding something like these in your code style
135147
conventions:
136148

137149
1. Developers should add annotations for any new code.
@@ -143,9 +155,9 @@ codebase without much effort.
143155
Automate annotation of legacy code
144156
----------------------------------
145157

146-
There are tools for automatically adding draft annotations
147-
based on type profiles collected at runtime. Tools include
148-
:doc:`monkeytype:index` (Python 3) and `PyAnnotate`_.
158+
There are tools for automatically adding draft annotations based on simple
159+
static analysis or on type profiles collected at runtime. Tools include
160+
:doc:`monkeytype:index`, `autotyping`_ and `PyAnnotate`_.
149161

150162
A simple approach is to collect types from test runs. This may work
151163
well if your test coverage is good (and if your tests aren't very
@@ -156,6 +168,68 @@ fraction of production network requests. This clearly requires more
156168
care, as type collection could impact the reliability or the
157169
performance of your service.
158170

171+
Introduce stricter options
172+
--------------------------
173+
174+
Mypy is very configurable. Once you get started with static typing, you may want
175+
to explore the various strictness options mypy provides to catch more bugs. For
176+
example, you can ask mypy to require annotations for all functions in certain
177+
modules to avoid accidentally introducing code that won't be type checked using
178+
:confval:`disallow_untyped_defs`. Refer to :ref:`config-file` for the details.
179+
180+
An excellent goal to aim for is to have your codebase pass when run against ``mypy --strict``.
181+
This basically ensures that you will never have a type related error without an explicit
182+
circumvention somewhere (such as a ``# type: ignore`` comment).
183+
184+
The following config is equivalent to ``--strict``:
185+
186+
.. code-block:: text
187+
188+
# Start off with these
189+
warn_unused_configs = True
190+
warn_redundant_casts = True
191+
warn_unused_ignores = True
192+
no_implicit_optional = True
193+
194+
# Getting these passing should be easy
195+
strict_equality = True
196+
strict_concatenate = True
197+
198+
# Strongly recommend enabling this one as soon as you can
199+
check_untyped_defs = True
200+
201+
# These shouldn't be too much additional work, but may be tricky to
202+
# get passing if you use a lot of untyped libraries
203+
disallow_subclassing_any = True
204+
disallow_untyped_decorators = True
205+
disallow_any_generics = True
206+
207+
# These next few are various gradations of forcing use of type annotations
208+
disallow_untyped_calls = True
209+
disallow_incomplete_defs = True
210+
disallow_untyped_defs = True
211+
212+
# This one isn't too hard to get passing, but return on investment is lower
213+
no_implicit_reexport = True
214+
215+
# This one can be tricky to get passing if you use a lot of untyped libraries
216+
warn_return_any = True
217+
218+
Note that you can also start with ``--strict`` and subtract, for instance:
219+
220+
.. code-block:: text
221+
222+
strict = True
223+
warn_return_any = False
224+
225+
Remember that many of these options can be enabled on a per-module basis. For instance,
226+
you may want to enable ``disallow_untyped_defs`` for modules which you've completed
227+
annotations for, in order to prevent new code from being added without annotations.
228+
229+
And if you want, it doesn't stop at ``--strict``. Mypy has additional checks
230+
that are not part of ``--strict`` that can be useful. See the complete
231+
:ref:`command-line` reference and :ref:`error-codes-optional`.
232+
159233
Speed up mypy runs
160234
------------------
161235

@@ -165,14 +239,5 @@ this will be. If your project has at least 100,000 lines of code or
165239
so, you may also want to set up :ref:`remote caching <remote-cache>`
166240
for further speedups.
167241

168-
Introduce stricter options
169-
--------------------------
170-
171-
Mypy is very configurable. Once you get started with static typing, you may want
172-
to explore the various strictness options mypy provides to catch more bugs. For
173-
example, you can ask mypy to require annotations for all functions in certain
174-
modules to avoid accidentally introducing code that won't be type checked using
175-
:confval:`disallow_untyped_defs`, or type check code without annotations as well
176-
with :confval:`check_untyped_defs`. Refer to :ref:`config-file` for the details.
177-
178242
.. _PyAnnotate: https://github.com/dropbox/pyannotate
243+
.. _autotyping: https://github.com/JelleZijlstra/autotyping

0 commit comments

Comments
 (0)