Version
feu.version ¶
Contain functions to manage package versions.
feu.version.PackageBounds
dataclass
¶
Lower and upper version bounds for a package dependency.
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
The canonical package name as declared in the dependency specifier. |
lower |
str | None
|
The lower bound version string (from |
upper |
str | None
|
The upper bound version string (from |
section |
str
|
The |
feu.version.compare_version ¶
compare_version(
package: str, op: Callable, version: str
) -> bool
Compare a package version to a given version.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
package
|
str
|
The package to check. |
required |
op
|
Callable
|
The comparison operator. |
required |
version
|
str
|
The version to compare with. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
The comparison status. |
Example
>>> import operator
>>> from feu.version import compare_version
>>> compare_version("pytest", op=operator.ge, version="7.3.0")
True
feu.version.fetch_latest_major_versions ¶
fetch_latest_major_versions(
package: str,
lower: str | None = None,
upper: str | None = None,
include_lower_bound: bool = False,
) -> tuple[str, ...]
Get the latest version for each major version for a given package.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
package
|
str
|
The package name. |
required |
lower
|
str | None
|
The lower version bound (inclusive).
If |
None
|
upper
|
str | None
|
The upper version bound (exclusive).
If |
None
|
include_lower_bound
|
bool
|
If |
False
|
Returns:
| Type | Description |
|---|---|
tuple[str, ...]
|
A tuple containing the latest version for each major version,
sorted by major version number. If |
Example
>>> from feu.version import fetch_latest_major_versions
>>> versions = fetch_latest_major_versions("requests", lower="2.0") # doctest: +SKIP
feu.version.fetch_latest_major_versions_map ¶
fetch_latest_major_versions_map(
packages: Sequence[PackageBounds],
include_lower_bound: bool = False,
) -> dict[str, list[str]]
Fetch the latest major versions for a sequence of packages.
For each PackageBounds in packages, calls
fetch_latest_major_versions with the package name and lower bound,
and collects the results into a dictionary.
If a package appears more than once in packages (e.g. because it
was found in multiple sections), the last entry wins.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
packages
|
Sequence[PackageBounds]
|
A sequence of |
required |
include_lower_bound
|
bool
|
If |
False
|
Returns:
| Type | Description |
|---|---|
dict[str, list[str]]
|
A dictionary mapping each package name to the list of latest major
version strings returned by |
Example
>>> from feu.version import fetch_latest_major_versions_map, read_pyproject_dependencies
>>> bounds = read_pyproject_dependencies("pyproject.toml") # doctest: +SKIP
>>> versions = fetch_latest_major_versions_map(bounds) # doctest: +SKIP
feu.version.fetch_latest_minor_versions ¶
fetch_latest_minor_versions(
package: str,
lower: str | None = None,
upper: str | None = None,
include_lower_bound: bool = False,
) -> tuple[str, ...]
Get the latest version for each minor version for a given package.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
package
|
str
|
The package name. |
required |
lower
|
str | None
|
The lower version bound (inclusive).
If |
None
|
upper
|
str | None
|
The upper version bound (exclusive).
If |
None
|
include_lower_bound
|
bool
|
If |
False
|
Returns:
| Type | Description |
|---|---|
tuple[str, ...]
|
A tuple containing the latest version for each minor version,
sorted by minor version number. If |
Example
>>> from feu.version import fetch_latest_minor_versions
>>> versions = fetch_latest_minor_versions("requests", lower="2.28") # doctest: +SKIP
feu.version.fetch_latest_minor_versions_map ¶
fetch_latest_minor_versions_map(
packages: Sequence[PackageBounds],
include_lower_bound: bool = False,
) -> dict[str, list[str]]
Fetch the latest minor versions for a sequence of packages.
For each PackageBounds in packages, calls
fetch_latest_minor_versions with the package name and lower bound,
and collects the results into a dictionary.
If a package appears more than once in packages (e.g. because it
was found in multiple sections), the last entry wins.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
packages
|
Sequence[PackageBounds]
|
A sequence of |
required |
include_lower_bound
|
bool
|
If |
False
|
Returns:
| Type | Description |
|---|---|
dict[str, list[str]]
|
A dictionary mapping each package name to the list of latest minor
version strings returned by |
Example
>>> from feu.version import fetch_latest_minor_versions_map, read_pyproject_dependencies
>>> bounds = read_pyproject_dependencies("pyproject.toml") # doctest: +SKIP
>>> versions = fetch_latest_minor_versions_map(bounds) # doctest: +SKIP
>>> versions_with_lower = fetch_latest_minor_versions_map(
... bounds, include_lower_bound=True
... ) # doctest: +SKIP
feu.version.fetch_latest_stable_version ¶
fetch_latest_stable_version(package: str) -> str
Get the latest stable valid versions for a given package.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
package
|
str
|
The package name. |
required |
Returns:
| Type | Description |
|---|---|
str
|
The latest stable valid versions. |
Example
>>> from feu.version import fetch_latest_stable_version
>>> version = fetch_latest_stable_version("requests") # doctest: +SKIP
feu.version.fetch_latest_version ¶
fetch_latest_version(package: str) -> str
Get the latest valid versions for a given package.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
package
|
str
|
The package name. |
required |
Returns:
| Type | Description |
|---|---|
str
|
The latest valid versions. |
Example
>>> from feu.version import fetch_latest_version
>>> version = fetch_latest_version("requests") # doctest: +SKIP
feu.version.fetch_pypi_versions
cached
¶
fetch_pypi_versions(
package: str, reverse: bool = False
) -> tuple[str, ...]
Get the package versions available on PyPI.
The package versions are read from PyPI.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
package
|
str
|
The package name. |
required |
reverse
|
bool
|
If |
False
|
Returns:
| Type | Description |
|---|---|
tuple[str, ...]
|
A list containing the sorted version strings. |
Example
>>> from feu.version import fetch_pypi_versions
>>> versions = fetch_pypi_versions("requests") # doctest: +SKIP
feu.version.fetch_sampled_latest_minor_versions ¶
fetch_sampled_latest_minor_versions(
package: str,
lower: str | None = None,
upper: str | None = None,
n: int = 1,
include_lower_bound: bool = False,
) -> tuple[str, ...]
Get a sampled subset of the latest minor versions for a given package.
Fetches the latest version for each minor release, then returns every
n-th version plus the most recent one, deduplicated and sorted.
This is useful for generating a representative but not exhaustive set
of versions to test against.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
package
|
str
|
The package name. |
required |
lower
|
str | None
|
The lower version bound (inclusive).
If |
None
|
upper
|
str | None
|
The upper version bound (exclusive).
If |
None
|
n
|
int
|
The sampling stride. Every |
1
|
include_lower_bound
|
bool
|
If |
False
|
Returns:
| Type | Description |
|---|---|
tuple[str, ...]
|
A sorted tuple of sampled version strings. |
Example
>>> from feu.version import fetch_sampled_latest_minor_versions
>>> versions = fetch_sampled_latest_minor_versions(
... "requests", lower="2.28", n=2
... ) # doctest: +SKIP
feu.version.fetch_versions ¶
fetch_versions(
package: str,
lower: str | None = None,
upper: str | None = None,
) -> tuple[str, ...]
Get the valid versions for a given package.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
package
|
str
|
The package name. |
required |
lower
|
str | None
|
The lower version bound (inclusive).
If |
None
|
upper
|
str | None
|
The upper version bound (exclusive). If None, no upper limit is applied. |
None
|
Returns:
| Type | Description |
|---|---|
tuple[str, ...]
|
A tuple containing the valid versions. |
Example
>>> from feu.version import fetch_versions
>>> versions = fetch_versions("requests") # doctest: +SKIP
feu.version.filter_every_n_versions ¶
filter_every_n_versions(
versions: Sequence[str], n: int
) -> list[str]
Filter a list of version strings, keeping only every n-th version using 0-based indexing.
This function preserves the original order of the input list and returns
a new list containing only the versions at positions that are multiples of
n (using 0-based indexing). For example, if n = 2, the function keeps
the 0th, 2nd, 4th, ... versions from the list.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
versions
|
Sequence[str]
|
A list of version strings. |
required |
n
|
int
|
The interval for selecting versions. Must be >= 1. |
required |
Returns:
| Type | Description |
|---|---|
list[str]
|
A new list containing only every n-th version in |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Example
>>> from feu.version import filter_every_n_versions
>>> versions = filter_every_n_versions(["1.0", "1.1", "1.2", "1.3", "1.5", "1.6"], n=2)
>>> versions
['1.0', '1.2', '1.5']
>>> versions = filter_every_n_versions(["1.0", "1.1", "1.2", "1.3", "1.5", "1.6"], n=1)
>>> versions
['1.0', '1.1', '1.2', '1.3', '1.5', '1.6']
feu.version.filter_last_n_versions ¶
filter_last_n_versions(
versions: Sequence[str], n: int
) -> list[str]
Return only the last n versions from a list of version strings.
This function preserves the original ordering of the final n elements.
If n is greater than the number of versions available, the entire list
is returned.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
versions
|
Sequence[str]
|
A list of version strings. |
required |
n
|
int
|
Number of versions to keep from the end of the list. Must be > 0. |
required |
Returns:
| Type | Description |
|---|---|
list[str]
|
A new list containing only the last n versions, in order. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Example
>>> from feu.version import filter_last_n_versions
>>> versions = filter_last_n_versions(["1.0", "1.1", "1.2", "1.3"], n=2)
>>> versions
['1.2', '1.3']
>>> versions = filter_last_n_versions(["1.0", "1.1", "1.2", "1.3"], n=5)
>>> versions
['1.0', '1.1', '1.2', '1.3']
feu.version.filter_range_versions ¶
filter_range_versions(
versions: Sequence[str],
lower: str | None = None,
upper: str | None = None,
) -> list[str]
Filter a list of version strings to include only versions within optional bounds.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
versions
|
Sequence[str]
|
A list of version strings. |
required |
lower
|
str | None
|
The lower version bound (inclusive).
If |
None
|
upper
|
str | None
|
The upper version bound (exclusive). If None, no upper limit is applied. |
None
|
Returns:
| Type | Description |
|---|---|
list[str]
|
A list of version strings that fall within the specified bounds. |
Example
>>> from feu.version import filter_range_versions
>>> versions = filter_range_versions(
... ["1.0.0", "1.2.0", "1.3.0", "2.0.0"], lower="1.1.0", upper="2.0.0"
... )
>>> versions
['1.2.0', '1.3.0']
>>> versions = filter_range_versions(["0.9.0", "1.0.0", "1.1.0"], lower="1.0.0")
>>> versions
['1.0.0', '1.1.0']
feu.version.filter_stable_versions ¶
filter_stable_versions(
versions: Sequence[str],
) -> list[str]
Filter out pre-release, post-release, and dev-release versions from a list of version strings.
A stable version is defined as
- Not a pre-release (e.g., alpha
a, betab, release candidaterc) - Not a post-release (e.g.,
1.0.0.post1) - Not a development release (e.g.,
1.0.0.dev1)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
versions
|
Sequence[str]
|
A list of version strings. |
required |
Returns:
| Type | Description |
|---|---|
list[str]
|
A list containing only stable version strings. |
Example
>>> from feu.version import filter_stable_versions
>>> versions = filter_stable_versions(
... ["1.0.0", "1.0.0a1", "2.0.0", "2.0.0.dev1", "3.0.0.post1"]
... )
>>> versions
['1.0.0', '2.0.0']
feu.version.filter_valid_versions ¶
filter_valid_versions(versions: Sequence[str]) -> list[str]
Filter out invalid version strings based on PEP 440.
A valid version is one that can be parsed by packaging.version.Version.
Invalid versions include strings that don't conform to semantic versioning rules.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
versions
|
Sequence[str]
|
A list of version strings. |
required |
Returns:
| Type | Description |
|---|---|
list[str]
|
A list containing only valid version strings. |
Example
>>> from feu.version import filter_valid_versions
>>> versions = filter_valid_versions(
... [
... "1.0.0",
... "1.0.0a1",
... "2.0.0.post1",
... "not-a-version",
... "",
... "2",
... "3.0",
... "v1.0.0",
... "1.0.0.0.0",
... "4.0.0.dev1",
... ]
... )
>>> versions
['1.0.0', '1.0.0a1', '2.0.0.post1', '2', '3.0', 'v1.0.0', '1.0.0.0.0', '4.0.0.dev1']
feu.version.get_package_bounds ¶
get_package_bounds(
packages: Sequence[PackageBounds], name: str
) -> PackageBounds
Return the first PackageBounds matching a given package name.
The name comparison is case-insensitive and treats hyphens and underscores as equivalent, following PEP 508 normalisation rules.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
packages
|
Sequence[PackageBounds]
|
A sequence of |
required |
name
|
str
|
The package name to look up. |
required |
Returns:
| Type | Description |
|---|---|
PackageBounds
|
The first |
Raises:
| Type | Description |
|---|---|
ValueError
|
If no entry matching |
Example
>>> from feu.version import PackageBounds, get_package_bounds
>>> packages = [
... PackageBounds(
... name="numpy", lower="1.21", upper="2.0", section="project.dependencies"
... ),
... PackageBounds(
... name="torch", lower="2.0", upper=None, section="project.dependencies"
... ),
... ]
>>> get_package_bounds(packages, "numpy")
PackageBounds(name='numpy', lower='1.21', upper='2.0', section='project.dependencies')
feu.version.get_package_version ¶
get_package_version(package: str) -> Version | None
Get the package version.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
package
|
str
|
The package name. |
required |
Returns:
| Type | Description |
|---|---|
Version | None
|
The package version. |
Example
>>> from feu.version import get_package_version
>>> get_package_version("pytest")
<Version('...')>
feu.version.get_python_major_minor
cached
¶
get_python_major_minor() -> str
Get the MAJOR.MINOR version of the current python.
Returns:
| Type | Description |
|---|---|
str
|
The MAJOR.MINOR version of the current python. |
Example
>>> from feu.version import get_python_major_minor
>>> get_python_major_minor() # doctest: +SKIP
feu.version.latest_major_versions ¶
latest_major_versions(versions: Sequence[str]) -> list[str]
Return the latest version for each major version in a list of semantic versions.
This function takes a list of semantic version strings (e.g. "1.0.0", "1.2.1", "2.0.0"), groups them by their major version number, and returns only the latest version from each major group (based on minor and patch numbers).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
versions
|
Sequence[str]
|
A list of version strings in semantic version format. |
required |
Returns:
| Type | Description |
|---|---|
list[str]
|
A list containing the latest version for each major version, sorted by major version number. |
Example
>>> from feu.version import latest_major_versions
>>> versions = latest_major_versions(["1.0.0", "1.1.0", "1.2.0", "1.2.1", "2.0.0"])
>>> versions
['1.2.1', '2.0.0']
feu.version.latest_minor_versions ¶
latest_minor_versions(versions: Sequence[str]) -> list[str]
Return the latest version for each minor version in a list of semantic versions.
This function takes a list of semantic version strings (e.g. "1.0.0", "1.0.1", "1.1.0", "2.0.0"), groups them by their major and minor version numbers, and returns only the latest version from each minor group (based on the patch number).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
versions
|
Sequence[str]
|
A list of version strings in semantic version format. |
required |
Returns:
| Type | Description |
|---|---|
list[str]
|
A list containing the latest version for each minor version, sorted by major and minor version numbers. |
Example
>>> from feu.version import latest_major_versions
>>> versions = latest_minor_versions(["1.0.0", "1.0.1", "1.1.0", "1.1.2", "2.0.0", "2.0.3"])
>>> versions
['1.0.1', '1.1.2', '2.0.3']
feu.version.latest_version ¶
latest_version(versions: Sequence[str]) -> str
Return the latest version string in a list of version identifiers.
This function compares version strings according to the PEP 440
specification using :class:packaging.version.Version. It supports
standard releases, pre-releases (alpha, beta, release candidates),
development releases, post releases, and epoch-based versions.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
versions
|
Sequence[str]
|
A list of version strings to compare. |
required |
Returns:
| Type | Description |
|---|---|
str
|
The highest (latest) version in the list based on PEP 440 ordering. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Example
>>> import operator
>>> from feu.version import latest_version
>>> latest_version(["1.0.0", "1.0.1rc1", "1.0.1"])
'1.0.1'
>>> latest_version(["1.2.0", "2.0.0a1"])
'2.0.0a1'
feu.version.normalize_package_name ¶
normalize_package_name(name: str) -> str
Normalize a package name per PEP 508.
Converts the name to lowercase and replaces hyphens with underscores,
so that "scikit-learn", "scikit_learn", and "Scikit_Learn"
all compare as equal.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
The package name to normalize. |
required |
Returns:
| Type | Description |
|---|---|
str
|
The normalized package name. |
Example
>>> from feu.version import normalize_package_name
>>> normalize_package_name("Scikit-Learn")
'scikit_learn'
feu.version.partition_package_bounds ¶
partition_package_bounds(
packages: Sequence[PackageBounds], names: Sequence[str]
) -> tuple[list[PackageBounds], list[PackageBounds]]
Split a sequence of PackageBounds into matched and unmatched
by name.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
packages
|
Sequence[PackageBounds]
|
A sequence of |
required |
names
|
Sequence[str]
|
The package names to match against. |
required |
Returns:
| Type | Description |
|---|---|
list[PackageBounds]
|
A tuple of two lists, both in the order they appear in |
list[PackageBounds]
|
|
tuple[list[PackageBounds], list[PackageBounds]]
|
|
Example
>>> from feu.version import PackageBounds, partition_package_bounds
>>> packages = [
... PackageBounds(
... name="numpy", lower="1.21", upper="2.0", section="project.dependencies"
... ),
... PackageBounds(
... name="torch", lower="2.0", upper=None, section="project.dependencies"
... ),
... ]
>>> matched, unmatched = partition_package_bounds(packages, ["numpy"])
>>> matched
[PackageBounds(name='numpy', lower='1.21', upper='2.0', section='project.dependencies')]
>>> unmatched
[PackageBounds(name='torch', lower='2.0', upper=None, section='project.dependencies')]
feu.version.read_pyproject_dependencies ¶
read_pyproject_dependencies(
path: str | Path,
) -> list[PackageBounds]
Read a pyproject.toml file and return the bounds for all
packages defined in [project.dependencies].
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str | Path
|
Path to the |
required |
Returns:
| Type | Description |
|---|---|
list[PackageBounds]
|
A list of |
list[PackageBounds]
|
|
list[PackageBounds]
|
is absent or empty. |
Raises:
| Type | Description |
|---|---|
FileNotFoundError
|
If the file does not exist. |
TOMLDecodeError
|
If the file is not valid TOML. |
Example
bounds = read_pyproject_dependencies("pyproject.toml")
for b in bounds:
print(b.name, b.lower, b.upper)
feu.version.read_pyproject_optional_dependencies ¶
read_pyproject_optional_dependencies(
path: str | Path,
) -> list[PackageBounds]
Read a pyproject.toml file and return the bounds for all
packages defined in [project.optional-dependencies].
All groups under [project.optional-dependencies] are included. The
section field of each returned PackageBounds identifies the group,
e.g. 'project.optional-dependencies.dev'.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str | Path
|
Path to the |
required |
Returns:
| Type | Description |
|---|---|
list[PackageBounds]
|
A list of |
list[PackageBounds]
|
optional-dependency groups, in the order they appear in the file. |
list[PackageBounds]
|
Returns an empty list if the section is absent or empty. |
Raises:
| Type | Description |
|---|---|
FileNotFoundError
|
If the file does not exist. |
TOMLDecodeError
|
If the file is not valid TOML. |
Example
bounds = read_pyproject_optional_dependencies("pyproject.toml")
for b in bounds:
print(b.name, b.section, b.lower, b.upper)
feu.version.read_pyproject_package_bounds ¶
read_pyproject_package_bounds(
path: str | Path, package: str
) -> list[PackageBounds]
Read a pyproject.toml file and return the version bounds for
a package.
Searches the following standard sections:
[project.dependencies][project.optional-dependencies.*][dependency-groups.*]
The package name comparison is case-insensitive and treats hyphens and underscores as equivalent, following PEP 508 normalisation rules.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str | Path
|
Path to the |
required |
package
|
str
|
The name of the package to look up. |
required |
Returns:
| Type | Description |
|---|---|
list[PackageBounds]
|
A list of |
list[PackageBounds]
|
package across all sections. Returns an empty list if the package |
list[PackageBounds]
|
is not found. |
Raises:
| Type | Description |
|---|---|
FileNotFoundError
|
If the file does not exist. |
TOMLDecodeError
|
If the file is not valid TOML. |
Example
bounds = find_package_bounds("pyproject.toml", "numpy")
for b in bounds:
print(b.section, b.lower, b.upper)
feu.version.sort_versions ¶
sort_versions(
versions: Sequence[str], reverse: bool = False
) -> list[str]
Sort a list of version strings in ascending or descending order.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
versions
|
Sequence[str]
|
A list of version strings. |
required |
reverse
|
bool
|
If |
False
|
Returns:
| Type | Description |
|---|---|
list[str]
|
A new list of version strings sorted according to semantic version order. |
Example
>>> import operator
>>> from feu.version import sort_versions
>>> sort_versions(["1.0.0", "1.2.0", "1.1.0"])
['1.0.0', '1.1.0', '1.2.0']
>>> sort_versions(["1.0.0", "1.2.0", "1.1.0"], reverse=True)
['1.2.0', '1.1.0', '1.0.0']
feu.version.unique_versions ¶
unique_versions(versions: Sequence[str]) -> list[str]
Return a list of unique versions while preserving order.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
versions
|
Sequence[str]
|
A list of version strings. |
required |
Returns:
| Type | Description |
|---|---|
list[str]
|
A list containing only unique version strings, preserving the original order of first occurrence. |
Example
>>> from feu.version import unique_versions, sort_versions
>>> versions = sort_versions(unique_versions(["1.0.0", "1.0.1", "1.0.0", "1.2.0"]))
>>> versions
['1.0.0', '1.0.1', '1.2.0']