Python w/ strict typechecker
Tags: python, git, beginners, tutorial
Published: 2022-12-23 23:26:22

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l3umllza1vx3zt7u9bcc.png)


## 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:

```python
# file: example.py

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

add_numbers(True, {"something": "somethingElse"})
```

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](http://mypy-lang.org/); 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`:

```bash
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)
```

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:

```bash
[mypy]
strict = true # run type checker through all the included files
exclude = .venv/
```

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sdu9a6wdjz0xdajlkud4.png)


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

![vscode-inline-linting-error](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jrhtiofzh73v1dzq7izz.png)


## 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](https://pre-commit.com/). 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:

```bash
pip install pre-commit && pip freeze > requirements.txt
```

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

```yaml
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
```

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:

```bash
pre-commit install
```

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0scappb3uvzsw4m5ebzr.png)


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.

```yaml
# 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
```

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

![github-action](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ki8k9mvdi8n8gubg1hjc.png)


## 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.

<br />

![it-is-done](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v4wwkpe66ekb6q83y8eu.png)


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.