versions.py 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. # Copyright 2020 The HuggingFace Team. All rights reserved.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. """
  15. Utilities for working with package versions
  16. """
  17. import importlib.metadata
  18. import operator
  19. import re
  20. import sys
  21. from packaging import version
  22. ops = {
  23. "<": operator.lt,
  24. "<=": operator.le,
  25. "==": operator.eq,
  26. "!=": operator.ne,
  27. ">=": operator.ge,
  28. ">": operator.gt,
  29. }
  30. def _compare_versions(op, got_ver, want_ver, requirement, pkg, hint):
  31. if got_ver is None or want_ver is None:
  32. raise ValueError(
  33. f"Unable to compare versions for {requirement}: need={want_ver} found={got_ver}. This is unusual. Consider"
  34. f" reinstalling {pkg}."
  35. )
  36. if not ops[op](version.parse(got_ver), version.parse(want_ver)):
  37. raise ImportError(
  38. f"{requirement} is required for a normal functioning of this module, but found {pkg}=={got_ver}.{hint}"
  39. )
  40. def require_version(requirement: str, hint: str | None = None) -> None:
  41. """
  42. Perform a runtime check of the dependency versions, using the exact same syntax used by pip.
  43. The installed module version comes from the *site-packages* dir via *importlib.metadata*.
  44. Args:
  45. requirement (`str`): pip style definition, e.g., "tokenizers==0.9.4", "tqdm>=4.27", "numpy"
  46. hint (`str`, *optional*): what suggestion to print in case of requirements not being met
  47. Example:
  48. ```python
  49. require_version("pandas>1.1.2")
  50. require_version("numpy>1.18.5", "this is important to have for whatever reason")
  51. ```"""
  52. hint = f"\n{hint}" if hint is not None else ""
  53. # non-versioned check
  54. if re.match(r"^[\w_\-\d]+$", requirement):
  55. pkg, op, want_ver = requirement, None, None
  56. else:
  57. match = re.findall(r"^([^!=<>\s]+)([\s!=<>]{1,2}.+)", requirement)
  58. if not match:
  59. raise ValueError(
  60. "requirement needs to be in the pip package format, .e.g., package_a==1.23, or package_b>=1.23, but"
  61. f" got {requirement}"
  62. )
  63. pkg, want_full = match[0]
  64. want_range = want_full.split(",") # there could be multiple requirements
  65. wanted = {}
  66. for w in want_range:
  67. match = re.findall(r"^([\s!=<>]{1,2})(.+)", w)
  68. if not match:
  69. raise ValueError(
  70. "requirement needs to be in the pip package format, .e.g., package_a==1.23, or package_b>=1.23,"
  71. f" but got {requirement}"
  72. )
  73. op, want_ver = match[0]
  74. wanted[op] = want_ver
  75. if op not in ops:
  76. raise ValueError(f"{requirement}: need one of {list(ops.keys())}, but got {op}")
  77. # special case
  78. if pkg == "python":
  79. got_ver = ".".join([str(x) for x in sys.version_info[:3]])
  80. for op, want_ver in wanted.items():
  81. _compare_versions(op, got_ver, want_ver, requirement, pkg, hint)
  82. return
  83. # check if any version is installed
  84. try:
  85. got_ver = importlib.metadata.version(pkg)
  86. except importlib.metadata.PackageNotFoundError:
  87. raise importlib.metadata.PackageNotFoundError(
  88. f"The '{requirement}' distribution was not found and is required by this application. {hint}"
  89. )
  90. # check that the right version is installed if version number or a range was provided
  91. if want_ver is not None:
  92. for op, want_ver in wanted.items():
  93. _compare_versions(op, got_ver, want_ver, requirement, pkg, hint)
  94. def require_version_core(requirement):
  95. """require_version wrapper which emits a core-specific hint on failure"""
  96. hint = "Try: `pip install transformers -U` or `pip install -e '.[dev]'` if you're working with git main"
  97. return require_version(requirement, hint)