Every Python developer is challenged by the size and velocity of the Python ecosystem π€
This post provides clarity with the Hypermodern Python Toolbox - tools that are setting the standard for Python in 2023.
Python 3.10 added better error messages - improving the information available for developers during development and debugging.
The code below has a mistake. We want to assign a value to the first element of data
, but the code refers to a non-existent variable datas
:
With older versions of Python, this results in an error traceback that points out that the variable datas
doesn’t exist:
Python 3.10 takes it’s diagnosis one step further and also offers a solution that the variable should be called data
instead:
So much of programming is reading & responding error messages - these improvements are a great quality of life improvement for Python developers in 2023.
The hardest thing about learning Python is learning to install & manage Python.
Even senior developers can struggle with it, especially if Python is not their main language.
Working with Python requires being able to work both with different versions of Python and with different Python virtual environments.
pyenv is a tool for managing different versions of Python. It’s an alternative to using miniconda or installing Python from a downloaded installer.
$ pyenv versions
shows that three versions of Python are installed & managed by pyenv:
Installing a specific version of Python is as simple as $ pyenv install {version}
:
One trick with using pyenv is getting compiler flags correct - if you are having an trouble, take a look at this installer script for Ubuntu, installer script for MacOS and these compiler flags.
pyenv-virtualenv is a tool for managing virtual environments in Python. It’s an alternative to venv or miniconda. Virtual environments allow separate installations of Python to live side-by-side.
pyenv-virtualenv plays well with our pyenv installations of Python. We can create a new virtual environment with $ pyenv virtualenv {version} {name}
.
Below we create a 3.10.6 Python virtual environment called default
:
We now have a new virtual environment called default
using Python version 3.10.6
:
Tip - create a .python-version
file to automatically switch to a pyenv-virtualenv virtual environment when you enter a directory.
Poetry is a tool for managing Python dependencies and packages. It’s an alternative to pip. Both pip and Poetry are used to install and upgrade third party packages.
There is not a one-to-one mapping between the files used by pip and Poetry.
Pip uses two files to manage a Python package:
requirements.txt
- a list of Python dependencies,setup.py
- a Python script that describes our package.Poetry uses two different files:
pyproject.toml
to describe our Python package,poetry.lock
to define and lock all dependencies - similar to the output of $ pip freeze
.These two files are can be generated automatically - poetry.lock
is only ever generated automatically.
Poetry has two ways to start a new project:
$ poetry new
- if you are starting from scratch,$ poetry init
- if you are adding Poetry to an existing project.We can create a pyproject.toml
for a project in an interactive way by first installing Poetry with pip, then running $ poetry init
to create a pyproject.toml
:
After running through the interactive session (where we specify our Python version and add the package mypy), we end up with a pyproject.toml
:
If we didn’t add any dependencies we needed during the generation of pyproject.toml
, we can add packages using:
At this point we have added but not installed our mypy dependency - we can do so with poetry install
:
The install operation also creates a poetry.lock
file:
While Poetry is great, we still need pip. Poetry itself needs to be installed with pip.
Poetry and pip play well together - we can export our dependencies to a pip compatible requirements.txt
:
Watch out for - Poetry has the ability to create it’s own virtual environments. It’s common to turn this off in some environments - such as inside Docker images, where you want to use the default installation of Python.
Black & isort are tools to format Python code - they are alternatives to tools like autopep8.
The code below in bad_format.py
is poorly formatted:
We can run Black from a terminal, pointing it at bad_format.py
:
The result is that our bad_format.py
now has nicely formatted Python code:
The code below in bad_imports.py
has imports that are out of order alphabetically and grouped incorrectly:
We can use isort to fix these imports:
Our fixed file has nicely formatted imports:
Tip - it’s common to run these formatters on file save or in continuous integration - consider adding a format on save to your text editor.
Ruff is a Python linter - it’s alternative to Flake8. Ruff will check code based on rules - it will not format code like Black and isort.
Ruff’s big thing is being written in Rust - this makes it fast. When used with Black to ensure consistent code style, Ruff covers much of the Flake8 rule set, along with other rules such as isort.
A great way to use Ruff is with the defaults and check everything.
The code below has three problems - we use an undefined variable datas
, it has imports in the wrong place and imports something we don’t use:
Running Ruff in the same directory points out the issues:
Tip - Ruff is quick enough to run on file save during development - your text editor will allow this somehow!
mypy is a tool for enforcing type safety in Python - it’s an alternative to type declarations remaining as only unexecuted documentation.
Recently Python has undergone a similar transition to the Javascript to Typescript transition, with static typing being improved in the standard library and with third party tooling. Statically typed Python is the standard for many teams developing Python in 2023.
The code below in mypy_error.py
has a problem - we attempt to divide a string by 10
:
We can catch this error by running mypy - catching the error without actually executing the Python code:
These first errors are because our Python code has zero typing - let’s add two type annotations:
user: dict[str,str]
- user
is a dictionary with strings as keys and values,-> None:
- the process
function returns None.Running mypy on mypy_intermediate.py
, mypy points out the error in our code:
This is a test we can run without writing any specific test logic - very cool!
Static type checking will catch some bugs that many unit test suites won’t. Static typing will check more paths than a single unit test often does - catching edge cases that would otherwise only occur in production.
Tip - add mypy as an additional layer of testing to your test suite.
pydantic is a tool for organizing and validating data in Python - it’s an alternative to using dictionaries or dataclasses.
pydantic is part of Python’s typing revolution - pydantic’s ability to create custom types makes writing typed Python a joy.
pydantic uses Python type hints to define data types. Imagine we want a user with a name
and id
:
We could model this with pydantic - introducing a class that inherits from pydantic.BaseModel
:
A strength of pydantic is validation - we can introduce some validation of our user ids - below checking that the id
is a valid GUID - otherwise setting to None
:
Running the code above, our pydantic model has rejected one of our ids - our omega
has had it’s original ID of invalid
rejected and ends up with an id=None
:
These pydantic types can become the primitive data structures in your Python programs (instead of dictionaries) - making it eaiser for other developers to understand what is going on.
Tip - you can generate Typescript types from pydantic models - making it possible to share the same data structures with your Typescript frontend and Python backend.
Typer is a tool for building command line interfaces (CLIs) using type hints in Python - it’s an alternative to sys.argv or argparse.
We can build a Python CLI with Poetry and Typer by first creating a Python package with Poetry, adding typer
as a dependency).
Here we use $ poetry new
to create a new Poetry project from scratch:
We then add a Python file ./general/cli.py
with our Typer CLI:
We can now run this CLI by running python ./general/cli.py
:
Typer gives us a --help
flag for free:
We can take this one step further, by adding a script to our pyproject.toml
. general-cli
will now point towards the main
function in general.cli
:
This then allows us to run our Typer CLI using $ poetry run general-cli
:
Tip - you can create nested CLI groups using commands and command groups.
zxpy is a tool for running shell commands inside Python.
We will use the Github CLI as a source of shell commands - it is a nice way to get data about your code on Github.
Below we get all the issues for the mypy repository on Github:
This JSON array (or list of dictionaries in Python) is data we want to work on in Python. We could read the issues.json
file in Python - this would involve running the shell command and Python interpreter separately.
With zxpy we can run the shell command right in Python - using the ~"shell-command"
syntax:
We can then run this script using the zxpy interperter:
Tip - f-strings in zxpy are written ~f"gh search issues --repo {repo}
.
Rich is a tool for printing pretty text to a terminal - it’s an alternative to the monotone terminal output of most Python programs.
One of Rich’s most useful features is pretty printing of color and emojis:
If you are happy with Rich you can simplify your code by replacing the built-in print with the Rich print:
Tipβ-βRich offers much more than color and emojisβ-βincluding displaying tabular data and better trackbacks of Python errors.
The Hypermodern Python Toolbox is: