Skip to content

docs: add guidance on dependency version range #586

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Apr 23, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 94 additions & 0 deletions doc/source/how-to/packaging.rst
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,100 @@ Examples of PyAnsys projects that have these optional dependencies are:
- `PyAnsys Geometry targets <https://github.com/ansys/pyansys-geometry/blob/e6d8210f9d79718d607a2f4b2e8ead33babcbfca/pyproject.toml#L44-L58>`_
- `PyACP targets <https://github.com/ansys/pyacp/blob/f4d8c1779cd451b1fc8ef649cc3b2cd5799ff11a/pyproject.toml#L89-L110>`_

Dependency version range
------------------------

.. note::

This guidance applies only to PyAnsys *library* projects. For projects
which deliver an *application* or *dedicated workflow*, it is
recommended to fully pinning all (direct and transitive) dependencies.

When specifying dependencies in a project, it is generally recommended to avoid
setting upper version limits unless it is absolutely necessary. The reason for
that is because arbitrarily restricting a dependency to be lower than a
certain version (for example `numpy<2.0`) can prevent your project from working
with newer and perfectly compatible versions, and often causes more problems
than it solves. Such restrictions limit forward compatibility, block users from
upgrading dependencies, and increase the risk of version conflicts.

This issue is even more critical in the context of the PyAnsys `metapackage`_
which install many PyAnsys projects. In this setup, having strict upper bounds
on versions can easily result in unsatisfiable dependency constraints across
the ecosystem. For instance, if a package declares a dependency on `numpy<2.0`
despite being compatible with later versions, and another package requires
`numpy>=2.0.0` to leverage a new feature, it becomes impossible to install
both packages simultaneously. This occurs even though no actual incompatibility
exists between them, and it can lead to frustration for users and maintainers
as it prevents otherwise compatible packages from being used together seamlessly.

It is better to define only a minimum version (`>=`) and rely on Continuous
Integration (CI) to detect real breakages as dependencies evolve. If a future
version does introduce a breaking change, you can then add an upper bound with
a clear explanation. For example:

.. code-block:: toml

[project]
dependencies = [
"numpy<2.0", # breaking changes in Python and C APIs'.
]

Setting a lower bound (`>=`) is considered good practice for multiple reasons.
First, it documents the oldest version of a dependency that your project
explicitly supports. It is often the oldest version that is compatible with
the Python versions you support. For example, if your project supports
Python versions from `3.11` to `3.13`, you need to ensure that all dependencies
are compatible with at least Python `3.11`. This is important for users who may
be using older versions of Python and want to ensure compatibility with your
project. In other cases, the lower bound is related to the version where
certain key features your code relies on were first introduced. For instance,
if your code uses an API or behavior that only appeared in version `1.3`,
setting `>=1.3` communicates both a technical requirement and an implicit
contract to your users and contributors.

This helps avoiding unexpected breakages when someone installs your project
in an environment with older versions of dependencies. Rather than encountering
obscure runtime errors or missing features, the version constraint prevents
your project to be installed. It also helps to maintain clarity for long-term
maintenance and simplifies debugging.

Below is an example of a dependency specification that follows these guidelines:

.. tab-set::

.. tab-item:: flit

.. code-block:: toml

[project]
dependencies = [
"matplotlib>=3.5.2",
"numpy>=1.20.0",
]

.. tab-item:: poetry

.. code-block:: toml

[tool.poetry.dependencies]
matplotlib = ">=3.5.2"
numpy = ">=1.20.0"

.. tab-item:: setuptools

.. code-block:: python

from setuptools import setup

setup(
...,
install_requires=[
"matplotlib >= 3.5.2",
"numpy >= 1.20.0",
],
)

Dependabot
----------

Expand Down