""" Python versioning with requirements.txt syntax ============================================== # Copied from project iscompatible with support packaging Version # from packaging.version import Version :mod:`iscompatible` gives you the power of the pip requirements.txt syntax for everyday python packages, modules, classes or arbitrary functions. The requirements.txt syntax allows you to specify inexact matches between a set of requirements and a version. For example, let's assume that the single package foo-5.6.1 exists on disk. The following requirements are all compatible with foo-5.6.1. =========== ================================================= Requirement Description =========== ================================================= foo any version of foo foo>=5 any version of foo, above or equal to 5 foo>=5.6 any version of foo, above or equal to 5.6 foo==5.6.1 exact match foo>5 foo-5 or greater, including minor and patch foo>5, <5.7 foo-5 or greater, but less than foo-5.7 foo>0, <5.7 any foo version less than foo-5.7 =========== ================================================= Example: >>> iscompatible("foo>=5", (5, 6, 1)) True >>> iscompatible("foo>=5.6.1, <5.7", (5, 0, 0)) False >>> MyPlugin = type("MyPlugin", (), {'version': (5, 6, 1)}) >>> iscompatible("foo==5.6.1", MyPlugin.version) True References ^^^^^^^^^^ - `The requirements file-format`_ .. _The requirements file-format: https://pip.readthedocs.org/en/1.1/requirements.html#the-requirements-file-format .. _VCS: https://pip.readthedocs.org/en/1.1/requirements.html#version-control .. _extras: http://peak.telecommunity.com/DevCenter/setuptools#declaring-extras-optional-features-with-their-own-dependencies """ version_info = (0, 1, 1) __version__ = "%s.%s.%s" % version_info import operator import re from packaging.version import Version def iscompatible(requirements, version): """Return whether or not `requirements` is compatible with `version` Arguments: requirements (str): Requirement to compare, e.g. foo==1.0.1 version (tuple): Version to compare against, e.g. (1, 0, 1) Example: >>> iscompatible("foo<=1", (0, 9, 0)) True >>> iscompatible("foo>=1, <1.3", (1, 2, 0)) True >>> iscompatible("foo>=0.9.9", (1, 0, 0)) True >>> iscompatible("foo>=1.1, <2.1", (2, 0, 0)) True >>> iscompatible("foo==1.0.0", (1, 0, 0)) True >>> iscompatible("foo==1.0.0", (1, 0, 1)) False """ results = list() for operator_string, requirement_string in parse_requirements( requirements ): operator = operators[operator_string] requirement_version = Version(requirement_string) # base_version keep only 3 and less value from semversion required = string_to_tuple(requirement_version.base_version) result = operator(version.release, required) results.append(result) return all(results) def parse_requirements(line): """Return list of tuples with (operator, version) from `line` .. note:: This is a minimal re-implementation of pkg_utils.parse_requirements and doesn't include support for `VCS`_ or `extras`_. Example: >>> parse_requirements("foo==1.0.0") [('==', '1.0.0')] >>> parse_requirements("foo>=1.1.0") [('>=', '1.1.0')] >>> parse_requirements("foo>=1.1.0, <1.2") [('>=', '1.1.0'), ('<', '1.2')] """ LINE_END = re.compile(r"\s*(#.*)?$") DISTRO = re.compile(r"\s*((\w|[-.])+)") VERSION = re.compile(r"\s*(<=?|>=?|==|!=)\s*((\w|[-.])+)") COMMA = re.compile(r"\s*,") match = DISTRO.match(line) p = match.end() specs = list() while not LINE_END.match(line, p): match = VERSION.match(line, p) if not match: raise ValueError("Expected version spec in", line, "at", line[p:]) specs.append(match.group(*(1, 2))) p = match.end() match = COMMA.match(line, p) if match: p = match.end() # Skip comma elif not LINE_END.match(line, p): raise ValueError( "Expected ',' or end-of-list in", line, "at", line[p:] ) return specs def string_to_tuple(version): """Convert version as string to tuple Example: >>> string_to_tuple("1.0.0") (1, 0, 0) >>> string_to_tuple("2.5") (2, 5) """ return tuple(map(int, version.split("."))) operators = { "<": operator.lt, "<=": operator.le, "==": operator.eq, "!=": operator.ne, ">=": operator.ge, ">": operator.gt, }