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

  1. See what can be updated using poetry show --outdated
  2. Make changes to pyproject.toml if needed to target an update (generally needed for unpinning or major version upgrades)
  3. Actually run poetry lock
  4. If updating dependencies used in pre-commit (e.g. ruff or pyright), make sure to update .pre-commit-config.yaml with the new version
  5. 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

  1. Make changes to pyproject.toml to selectively target the wanted version of the dependencies
  2. Use poetry lock --no-update, which will re-lock with only the minimal changes needed to match the changes
  3. 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 from pyproject.toml and re-run poetry lock --no-update which should only change the content-hash at the very end of the lock file. This should only really happen in situations where doing a full poetry lock is broken.
  4. 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.