Modern Python part 4: facing reality
January 03, 2023
In three previous articles, I presented one possible flow to work with Python projects. After two years of professional experience I'd like to elaborate on some points.
Managing python environments
My advise for juniors would be to build projects using different tools: pip
, poetry
, conda
or others. Diversify your technical toolset. I focused on poetry
for my article series but be aware that your company might not use the last fancy open source tools to manage your python environments and packages. Right now at my company, I am using miniconda
for managing python environments and pip
+ requirements-*.txt
files for package management. Note the wildcard *
. We use different suffixes to specify the type of coding environments the packages are needed for.
- Our
requirements.txt
file catalogs libraries strictly required to use your package / code. - Our
requirements-dev.txt
file catalogs libraries required for your developer environment for testing testing or linting your code. - Our
requirements-ci.txt
file catalogs libraries required to inject when building some of of docker images for continuous integration.
Testing your code will save you a lot of time
We all heard this sentence one time in our engineer life: "testing your code is critical". But between the theory and the reality, there is a huge gap. I have seen codebases with almost zero test in very different environments (banking, academia, startups). I also faced the situation where I was kindly advised not to spend to much time writing tests as this may alter my velocity on tightly budgeted projects. This is really a pity. Now, I am working on codebases highly covered by both unit and integration tests. Having experienced both situations, I can guarantee you that testing your code WILL SAVE YOU a lot of time. This is a long-term investment that your future self or colleague will bless. With tests you can easily spend time refactoring your code, add new features without any fear and this is priceless.
Along the way I learnt new testing pattern with pytest
. On my previous articles I focused on fixtures
to reuse pieces of data or code. But since, I learnt to parametrize my testing functions with pytest.mark.parametrize
and also "monkey patch" some piece of code with the monkeypatch
pytest fixture. Drill these if you can.
pre-commit
is the king
I am using pre-commit
for all our company projects. Clearly having it installed is a must. With a proper list of pre-commit
tasks you will always ship good quality code when pushing your commits. Be aware that the pre-commit
you install may depend on your company / team guidelines in term of code quality. A typical .pre-commit-config.yaml
file looks like this:
---
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.3.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-json
- id: check-case-conflict
- id: check-builtin-literals
- id: check-docstring-first
- id: check-merge-conflict
- id: debug-statements
- id: requirements-txt-fixer
- id: detect-private-key
- id: pretty-format-json
args: ["--autofix", "--indent", "4", "--no-sort-keys"]
- repo: https://github.com/adrienverge/yamllint.git
rev: v1.28.0
hooks:
- id: yamllint
- repo: https://github.com/asottile/pyupgrade
rev: v2.38.0
hooks:
- id: pyupgrade
args: ["--py3-plus", "--py39-plus"]
- repo: https://github.com/psf/black
rev: 22.8.0
hooks:
- id: black
- repo: https://github.com/PyCQA/isort
rev: 5.10.1
hooks:
- id: isort
args: ["--profile", "black"]
- repo: https://github.com/pycqa/flake8
rev: 5.0.4
hooks:
- id: flake8
args: ["--max-line-length", "88", "--ignore", "E203,W50,E501"]
additional_dependencies: [flake8-bugbear]
- repo: https://github.com/PyCQA/bandit
rev: 1.7.4
hooks:
- id: bandit
# .bandit config is not read when used by
# pre-commit (ie with explicit file in input)
# https://github.com/PyCQA/bandit/issues/332
args: ["--skip", "B101"]
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v0.971
hooks:
- id: mypy
types: [python]
args: ["--ignore-missing-imports"]
additional_dependencies:
[pydantic, types-pytz, types-requests, types-protobuf]
All of the above-mentionned checks will be run each before each commit. If your code does not comply to the rules enforced by any of them, your code will not be commited to your repository.
Really learn GIT
This one is pretty trivial. But I really advise you to learn git and understand how it works under the hood. Do not embrace its complexity but rather focus on the mechanism. Once you realized that what git
does is mainly writing hashed content with pointers on your computer, you may apprehend it differently, without fear and hesitation. Watch this video. It gave me a lot of these AHAAAAA
moments and made me feel smarter. Knowing how to rebase, rolling back to specific reflogs
will save you at your job and most importantly, your colleagues will trust you and give you more responsibility not fearing you may break / re-write history.
Makefile for automation
There are a lot of redundant tasks that I am doing on a daily basis:
- testing your code
- running your code against your linters / formaters
- building container images
- deploying resources on cloud using the provider CLI
I tend to automate all of them using Makefile
rules. It works and save me a lot of time.
Conclusion
My main objective here was to give some kind of feedback based on my own experience as a python engineer. Cultures are different across company and there is no absolute workflow to learn. Nevertheless, there are some foundations that need to be here if you want your life as a developer to be productive. Testing your code is a game changer for me. During interview ask recruiters questions about their software engineering culture. This is super important especially if you want to work on high quality coding environment.