Python

Builds and packaging tools

In the Python Packaging User Guide1:

Hatch

Hatch is maintained by PyPA (Python Packing Authority), same as other standard tools like pip, setuptools, virtualenv, and twine.

Installing

You are probably going to want to install Hatch from PyPI rather than your distros package manager, since Hatch is under active development and new versions are released somewhat freqnely.

$ python3 -m pip install --user pipx
$ pipx install hatch

You may of course also install it directly with pip install, though using pipx is a much better approach in general.

Switching project to hatch

Moving a project from poetry to hatch is easy, but not trivial.

Poetry

Poetry is mainly used for managing an application and its dependencies whereas Hatch is more agnostic to the project type and offers plugin-based functionality for the entire workflow (versioning, tox-like environments, publishing) so you can easily build things other than wheel/sdist, test in a Docker container, etc.2

Versioning

Use poetry-bumpversion with Poetry to make managing the project's version easier. Its a plugin for Poetry itself, and thus not tied to the project (which would have been nice) or it's pyproject.toml file.

$ poetry self add poetry-bumpversion

And in your pyproject.toml, configure as needed:

[tool.poetry_bumpversion.file."${module_name}/__init__.py"]
[tool.poetry_bumpversion.file."tests/test_version.py"]

With this example it will update __version__ in ${module_name}/__init__.py to the version in pyproject.toml, and also keep the version number in tests/test_version.py up to date.

Decorators

Creating a decorator3 that accepts a named argument foo and passes it to the decorated function:

def bar_decorator(foo):
    def inner(f):
        def wrapper(*args, **kwargs):
            return f(*args, foo=foo, **kwargs)
        return wrapper
    return inner

@bar_decorator("bar")
def foobar(foo):
    return foo

Since "bar" was passed to the foobar function as foo:

>>> foobar()
'bar'

Decorators that without arguments can also be created:

def decorator(f):
    def wrapper(*args, **kwargs):
        return f(*args, bar="baz", **kwargs)
    return wrapper

@decorator
def foobar(bar):
    return bar

Now the foobar function always gets passed "baz" for its bar parameter, it is "hardcoded" in the decorator since it does not accept paramaters:

>>> foobar()
'baz'

The functools library provies the update_wrapper along with the convenience function wraps4 for invoking it.

from functools import update_wrapper, wraps

def some_decorator(foo):
    def inner(f):
        def wrapper(*args, **kwargs):
            return f(*args, foo=foo, **kwargs)
        return update_wrapper(wrapper, f)
    return inner

def another_decorator(foo):
    def inner(f):
        @wraps(f)
        def wrapper(*args, **kwargs):
            return f(*args, bar="bar", **kwargs)
        return wrapper
    return inner

Both of these decorators work the same way as the first bar_decorator.

References