General Guidelines
Dependencies
Any library that is used explicitly in a project should be added to pyproject.toml
, either as a direct dependency or an extra to another dependency. If possible, favor extras since that will allow upstream maintainers to specify version compatibility. Do not rely on transitive dependencies since they can change at any time. Add transitive dependencies explicitly even if you are using their features indirectly (e.g. via environment variables) to guarantee the availability of those features.
Development dependencies should be in the dev group. Third party type stubs (e.g. those from typeshed) should be added as development dependencies. Development dependencies should not be used in non-development code nor should they be used in the final stage of a Dockerfile
. typing.TYPE_CHECKING
can be surprising, so try to avoid using it or importing type stubs directly when possible.
typing-extensions, which is effectively an extension to typing
in the standard library, is the sole typing library that can be used outside of the development group.
deptry has a good list of rules to run as a baseline.
Maintenance
At any given time, try to keep your pyproject.toml
in a place where it is safe to poetry lock
. Tighten or even fully pin versions so that they do not change if you do not want them to. Keep in mind that not all libraries follow semver.
Similarly, make sure you update the pyproject.toml
to new minimum versions if you are relying on newly features. This may mean adjusting “minor” or “patch” versions even if they safely lock.
Workflows
Updating All Dependencies
- See what can be updated using
poetry show --outdated
- Make changes to
pyproject.toml
if needed to target an update (generally needed for unpinning or major version upgrades) - Actually run
poetry lock
- If updating dependencies used in
pre-commit
(e.g.ruff
orpyright
), make sure to update.pre-commit-config.yaml
with the new version - Update any CI configs that need their tools specified by version as well.
Adding Dependencies
In theory, poetry add
can be used to add new dependencies to a project. I have run into bugs in the past where other dependencies are also upgraded unexpectedly. As a result, I generally add dependencies manually as if selectively updating a single dependency.
Selectively Updating Dependencies
- Make changes to
pyproject.toml
to selectively target the wanted version of the dependencies - Use
poetry lock --no-update
, which will re-lock with only the minimal changes needed to match the changes - If the change should not have actually needed a targeting change (e.g. you want
1.5.0
and are targeting^1.2.0
), remove the changes frompyproject.toml
and re-runpoetry lock --no-update
which should only change thecontent-hash
at the very end of the lock file. This should only really happen in situations where doing a fullpoetry lock
is broken. - Follow any related rules from the update all section that may apply to your change
Changing Python Versions
Poetry lists Python as a dependency. So this is the same as manually changing any dependency.
Use tilde requirements to target only a single version of Python, making lockfiles smaller and simpler.
Keep in mind that Poetry will not work if the Python environment does not match. This is why I tend to avoid using poetry
in CI or build flows, since pinning a specific version of Python is not always simple.