DEV Community

Cover image for Python w/ strict typechecker
Shabd Saran
Shabd Saran

Posted on

Python w/ strict typechecker

I’m embarrassed that I didn’t know Python has support for strict type-checking. It was tricky to embed type checking with Python2.x but that was changed with the introduction of Python3.x.

If you didn’t know about this either then it's going to blow your mind!

mind-blown

Setting up pretext

I will assume that you are already familiar with the concept of “typing” in Python3.x. I’d highly recommend you do some research on the topic before moving forward.

Here is a good starting point: https://docs.python.org/3/library/typing.html

Typing isn’t helpful… on it’s own

“typing” in Python3.x is a suggester rather than a strict type checker. For example, you will not run into any problems before * actually * executing the following piece of code:

# file: example.py

def add_numbers(n1: int, n2: str) -> float:
        return n1 + n2

add_numbers(True, {"something": "somethingElse"})
Enter fullscreen mode Exit fullscreen mode

This is enough to make a grown man cry 🥲

You ideally want these types of errors to be raised * while * you are writing code instead of finding them in production logs.

Static type checkers

Google, Facebook and Microsoft have published their static type checkers for Python. The one I will cover here is mypy; it’s one of the most famous ones out there.

Fun fact: mypy has been around even before Python3.x. Programmers would add special comments to annotate types to their functions in Python2.x; similar to how types can be defined in JavaScript using JSDocs.

mypy will go through all the files (that you specify) and check the type definitions of all the functions, methods and variables. For example, you will get the following output when running the above example.py file through mypy:

mypy example.py

# --- Output ---
example.py:2: error: Unsupported operand types for + ("int" and "str")  [operator]
example.py:5: error: Argument 2 to "add_numbers" has incompatible type "Dict[str, str]"; expected "str"  [arg-type]
Found 2 errors in 1 file (checked 1 source file)
Enter fullscreen mode Exit fullscreen mode

That’s what we’re talking about! 🎉

Type checking during development

This is great but what if one of my teammates forgot to use mypy before committing the code to production? That's a solvable problem.

Firstly, we need strict type-checking during the development process.; running a command after writing each function is inconvenient. You can create a mypy.ini file at the root directory of your project with the following content:

[mypy]
strict = true # run type checker through all the included files
exclude = .venv/
Enter fullscreen mode Exit fullscreen mode

This will set global strict type-checking configurations for your project.

VSCode setup

Open the command palette using Command + P (or, Control + P for Windows/Linux users). Search for “Python: Select linter” and choose “mypy”.

select-mypy-linter-in-vscode

Now, your VSCode will inform you of the errors while you are writing code. Here’s an example:

vscode-inline-linting-error

Enforcing type checking

A programmer will still be able to push type-unsafe code to production, even after all these configurations. Because remember, these restrictions are only set at mypy-level; Python3.x interpreter itself allows type unsafe code to be executed.

Our job as a code-maintainer is to ensure that all of our code is type-safe before committing a change, and ensure the type-safety at the remote repository-level as well i.e. GitHub/GitLab.

Pre-commit scripts

We want to run mypy before the code is committed to Git. This can easily be configured using pre-commit. It’s a Python package that can be used with any language to run Git-specific scripts.

We can use the following command to install pre-commit to our project:

pip install pre-commit && pip freeze > requirements.txt
Enter fullscreen mode Exit fullscreen mode

Create a .pre-commit-config.yaml file at the root directory to run scripts before committing the code:

repos:
  - repo: local
    hooks:
      - id: typechecker
        name: typechecker # description to be displayed in STDOUT
        entry: mypy . --strict
        language: system
        always_run: true
        pass_filenames: false
Enter fullscreen mode Exit fullscreen mode

This file is pretty self-explanatory so I don’t think there’s anything to add here.

Run the following command to install pre-commit to the project Git setup:

pre-commit install
Enter fullscreen mode Exit fullscreen mode

You can now edit the .pre-commit-config.yaml file without having to run any more commands. I know that’s a relief 🤌

Here’s what you will see when you try to commit the example.py file we defined earlier:

pre-commit-error-message

The changes were not committed to Git. Pretty sweet, huh?

Quality control at remote-repository level

You still want all of these changes at the remote-repository level i.e. GitHub/GitLab. I will demonstrate the setup on GitHub; setup on GitLab wouldn’t be much different.

The aim is to run mypy when a commit is pushed to one of the “important” branches or when a pull request is created to be merged in one of these “important” branches. We can easily configure this using GitHub actions.

# file: .github/workflows/code-quality-checks.yaml

name: Code quality checks

on:
  push:
    branches:
      - main
      - production
  pull_request:
    branches:
      - main
      - production

jobs:
  build:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: ["3.10.6"]
    steps:
      - uses: actions/checkout@v3
      - name: Set up Python ${{ matrix.python-version }}
        uses: actions/setup-python@v3
        with:
          python-version: ${{ matrix.python-version }}
      - name: Install dependencies
        run: pip install -r requirements.txt
      - name: Type checker
        run: mypy . --strict
Enter fullscreen mode Exit fullscreen mode

You will be able to view the results of these actions on GitHub itself:

github-action

Conclusion

We made sure that our code is type-safe when we are committing it to VCS (Version Control System) i.e. Git, and we made sure that all the code is correctly merged at the remote-repository level i.e. GitHub.

That’s all to take care of. You can take it from here to build a Docker image or push it directly to a deployment service. You can be ABSOLUTELY SURE that you will not be surprised by any of the bugs on production unless something is wrong with the business logic itself.

it-is-done

Go on now, comrades! Write quality and type-safe code with Python3.x.


Drop a comment if you think I left something or if you have any suggestions for future posts.

Top comments (0)