=1.0,<2.0", that defines a range of valid versions for a Python package. I would like to generate a list" /> =1.0,<2.0", that defines a range of valid versions for a Python package. I would like to generate a list" /> =1.0,<2.0", that defines a range of valid versions for a Python package. I would like to generate a list"/>

How can I generate example versions that match a PEP 440 Version specifier in Python?

704 views Asked by At

I have a PEP 440 Version specifier, such as ">=1.0,<2.0", that defines a range of valid versions for a Python package. I would like to generate a list of example versions that match this specifier.

For example, if the specifier is ">=1.0,<2.0", some valid example versions could be 1.0.1, 1.5.3, or 1.9.9. Invalid example versions could be 0.9.5, 2.0.0, or 3.0.0.

What is the easiest way to generate such example versions in Python (3.10)? Should I use a regular expression, the packaging library, or some other method?

My current approach is to split at commas and remove the comparison operators from the version specifier and then try to create a Version from it.

In the following example I allow invalid versions, because I don't mind them for my use case.

from packaging.version import Version, InvalidVersion

comp_ops = ["===", "~=", "<=", ">=", "!=", "==", "<", ">"]    # order matters
version_spec = ">=1.0,<2.0"
versions = []

for v in version_spec.split(','):
    version = v
    for op in comp_ops:
        version = version.removeprefix(op)
    try:
        versions.append(Version(version))
    except InvalidVersion:
        pass
1

There are 1 answers

0
upe On

Another approach I tried is just repeatedly generating random strings of the form r"[0-9]\.[0-9]\.[0-9]" and parsing versions from it until I get n_samples many valid versions.

import random
from packaging.version import parse, Version
from packaging.specifiers import SpecifierSet

def version_samples(specifier: SpecifierSet, n_versions:int=3) -> list[Version]:
    sample_versions = []
    while len(sample_versions) < n_versions:
        version = ".".join(str(random.randint(0, 9)) for _ in range(3))
        parsed_version = parse(version)
        if specifier.contains(parsed_version):
                sample_versions.append(version)
    return sample_versions

specifier = SpecifierSet(">=1.0,<2.0")
print(version_samples(specifier))        # sample output: ['1.9.9', '1.0.4', '1.8.3']

I guess this approach can be extended for more general PEP 440 public version identifiers ([N!]N(.N)*[{a|b|rc}N][.postN][.devN]). The downside of this approach is that it's guess and check and doesn't (purposely) consider edge cases.