version_requirements.py 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. import sys
  2. from packaging import version as _version
  3. def _check_version(actver, version, cmp_op):
  4. """
  5. Check version string of an active module against a required version.
  6. If dev/prerelease tags result in TypeError for string-number comparison,
  7. it is assumed that the dependency is satisfied.
  8. Users on dev branches are responsible for keeping their own packages up to
  9. date.
  10. """
  11. try:
  12. if cmp_op == '>':
  13. return _version.parse(actver) > _version.parse(version)
  14. elif cmp_op == '>=':
  15. return _version.parse(actver) >= _version.parse(version)
  16. elif cmp_op == '=':
  17. return _version.parse(actver) == _version.parse(version)
  18. elif cmp_op == '<':
  19. return _version.parse(actver) < _version.parse(version)
  20. else:
  21. return False
  22. except TypeError:
  23. return True
  24. def get_module_version(module_name):
  25. """Return module version or None if version can't be retrieved."""
  26. mod = __import__(module_name, fromlist=[module_name.rpartition('.')[-1]])
  27. return getattr(mod, '__version__', getattr(mod, 'VERSION', None))
  28. def is_installed(name, version=None):
  29. """Test if *name* is installed.
  30. Parameters
  31. ----------
  32. name : str
  33. Name of module or "python"
  34. version : str, optional
  35. Version string to test against.
  36. If version is not None, checking version
  37. (must have an attribute named '__version__' or 'VERSION')
  38. Version may start with =, >=, > or < to specify the exact requirement
  39. Returns
  40. -------
  41. out : bool
  42. True if `name` is installed matching the optional version.
  43. """
  44. if name.lower() == 'python':
  45. actver = sys.version[:6]
  46. else:
  47. try:
  48. actver = get_module_version(name)
  49. except ImportError:
  50. return False
  51. if version is None:
  52. return True
  53. else:
  54. # since version_requirements is in the critical import path,
  55. # we lazy import re
  56. import re
  57. match = re.search('[0-9]', version)
  58. assert match is not None, "Invalid version number"
  59. symb = version[: match.start()]
  60. if not symb:
  61. symb = '='
  62. assert symb in ('>=', '>', '=', '<'), f"Invalid version condition '{symb}'"
  63. version = version[match.start() :]
  64. return _check_version(actver, version, symb)
  65. def require(name, version=None):
  66. """Return decorator that forces a requirement for a function or class.
  67. Parameters
  68. ----------
  69. name : str
  70. Name of module or "python".
  71. version : str, optional
  72. Version string to test against.
  73. If version is not None, checking version
  74. (must have an attribute named '__version__' or 'VERSION')
  75. Version may start with =, >=, > or < to specify the exact requirement
  76. Returns
  77. -------
  78. func : function
  79. A decorator that raises an ImportError if a function is run
  80. in the absence of the input dependency.
  81. """
  82. # since version_requirements is in the critical import path, we lazy import
  83. # functools
  84. import functools
  85. def decorator(obj):
  86. @functools.wraps(obj)
  87. def func_wrapped(*args, **kwargs):
  88. if is_installed(name, version):
  89. return obj(*args, **kwargs)
  90. else:
  91. msg = f'"{obj}" in "{obj.__module__}" requires "{name}'
  92. if version is not None:
  93. msg += f" {version}"
  94. raise ImportError(msg + '"')
  95. return func_wrapped
  96. return decorator
  97. def get_module(module_name, version=None):
  98. """Return a module object of name *module_name* if installed.
  99. Parameters
  100. ----------
  101. module_name : str
  102. Name of module.
  103. version : str, optional
  104. Version string to test against.
  105. If version is not None, checking version
  106. (must have an attribute named '__version__' or 'VERSION')
  107. Version may start with =, >=, > or < to specify the exact requirement
  108. Returns
  109. -------
  110. mod : module or None
  111. Module if *module_name* is installed matching the optional version
  112. or None otherwise.
  113. """
  114. if not is_installed(module_name, version):
  115. return None
  116. return __import__(module_name, fromlist=[module_name.rpartition('.')[-1]])