#8813 [RFE] type annotations
Opened 2 years ago by slev. Modified 2 years ago

Goals:

  • PEP 484:

    This PEP aims to provide a standard syntax for type annotations, opening up Python code to easier static analysis and refactoring, potential runtime type checking, and (perhaps, in some contexts) code generation utilizing type information.

    Of these goals, static analysis is the most important. This includes support for off-line type checkers such as mypy, as well as providing a standard notation that can be used by IDEs for code completion and refactoring.

  • Mypy:

    Mypy is a static type checker for Python 3 and Python 2.7. If you sprinkle your code with type annotations, mypy can type check your code and find common bugs. As mypy is a static analyzer, or a lint-like tool, the type annotations are just hints for mypy and don’t interfere when running your program. You run your program with a standard Python interpreter, and the annotations are treated effectively as comments.

    Using the Python 3 annotation syntax (using PEP 484 and PEP 526 notation) or a comment-based annotation syntax for Python 2 code, you will be able to efficiently annotate your code and use mypy to check the code for common errors. Mypy has a powerful and easy-to-use type system with modern features such as type inference, generics, callable types, tuple types, union types, and structural subtyping.

  • today's Pylint cannot analyze funtions/methods for types errors
    classic example:

    [root@af0fbc6cbf31 test]# cat foo.py
    def foo(bar: int) -> None:
        print(bar << 1)
    
    foo("foo")
    
    [root@af0fbc6cbf31 test]# python3 -m pylint -d 'disallowed-name, missing-module-docstring, missing-function-docstring' foo.py
    
    --------------------------------------------------------------------
    Your code has been rated at 10.00/10 (previous run: 10.00/10, +0.00)
    
    [root@af0fbc6cbf31 test]# python3 -m mypy foo.py
    foo.py:4: error: Argument 1 to "foo" has incompatible type "str"; expected "int"
    Found 1 error in 1 file (checked 1 source file)
    

See PEP 484, PEP 526, PEP 544, PEP 586, PEP 589 and PEP 591 for details.

Possible adaptation of annotations and Mypy checking suggested by upstream

  1. howto: https://mypy.readthedocs.io/en/stable/existing_code.html
  2. runtime issues https://mypy.readthedocs.io/en/stable/runtime_troubles.html

My own experience with annotating pytest_ipa.integration.*

Refactoring of integration tests in https://github.com/freeipa/freeipa/pull/5656 shows me that Pylint doesn't catch most of the errors I made during the process. So, I wanted to try mypy.

  1. As a developer, you decide how to use mypy in your workflow. You can always escape to dynamic typing as mypy’s approach to static typing doesn’t restrict what you can do in your programs. Using mypy will make your programs easier to understand, debug, and maintain.

  2. Mypy doesn't support any combination of its options '-p' (packages), '-m' (modules) and files. So, mypy task was splitted into 2 steps - packages are followed by files (scripts and standalone modules).

  3. default Mypy shows thousands of 'errors', most of which stand for Mypy cannot infer type.
    So, for now globally ignore_errors = True.

  4. Mypy allows the per-module/package configuration, but not the standalone script/module.
    For example, while adding annotations for module ipatests.pytest_ipa.integration.host the upper rule is less strict:

    [mypy-ipatests.pytest_ipa.integration.*]
    ignore_errors = False
    disallow_untyped_defs = False
    
    [mypy-ipatests.pytest_ipa.integration.host]
    disallow_untyped_defs = True
    
  5. Not all the packages are annotated yet in upstream (if ever). The stubs help.
    Stubs can be generated at runtime with several tools. I checked mypy's stubgen, for not annotated code it is just template generator (most of the types are Any). So, the stubs of pytest_multihost were prepared manually and verified (as far as possible) by mypy's stubtest.

  6. Not all the packages are annotated yet in downstream (upstream released typed version according to PEP 561, but downstream did not package this version). This is the case for cryptography and pytest for Fedora32. Unfortunately, I have to use hack there. With the help of mypy's stubgen I've prepared the type stubs from that versions of cryptography and pytest, which are known as annotated versions. On running mypy task gen_mypypath.py script generate the value of MYPYPATH env variable based on versions of these packages.

  7. Future annotations import (PEP 563)
    From Python 3.10 on, Python will no longer attempt to evaluate function and variable annotations. This behavior is made available in Python 3.7 and later through the use of from __future__ import annotations.

  8. typing.TYPE_CHECKING
    The typing module defines a TYPE_CHECKING constant that is False at runtime but treated as True while type checking. Since code inside if TYPE_CHECKING: is not executed at runtime, it provides a convenient way to tell mypy something without the code being evaluated at runtime. This is most useful for resolving import cycles, but doesn't catch the missing imports if import was made under conditional TYPE_CHECKING (this is my the most common error).

  9. code adaptation may be required.

  10. uncached make mypy task takes ~60sec, the cached one ~30sec.

So, my long term proposal is type annotating the code base where applicable and make IPA packages compatible with PEP 561.


Login to comment on this ticket.

Metadata