_features.py 2.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
  2. import importlib.metadata
  3. import itertools
  4. import string
  5. from typing import Dict, List, Tuple
  6. def _tuple_from_text(version: str) -> Tuple:
  7. text_parts = version.split(".")
  8. int_parts = []
  9. for text_part in text_parts:
  10. digit_prefix = "".join(
  11. itertools.takewhile(lambda x: x in string.digits, text_part)
  12. )
  13. try:
  14. int_parts.append(int(digit_prefix))
  15. except Exception:
  16. break
  17. return tuple(int_parts)
  18. def _version_check(
  19. requirement: str,
  20. ) -> bool:
  21. """Is the requirement fulfilled?
  22. The requirement must be of the form
  23. package>=version
  24. """
  25. package, minimum = requirement.split(">=")
  26. try:
  27. version = importlib.metadata.version(package)
  28. # This shouldn't happen, but it apparently can.
  29. if version is None:
  30. return False
  31. except Exception:
  32. return False
  33. t_version = _tuple_from_text(version)
  34. t_minimum = _tuple_from_text(minimum)
  35. if t_version < t_minimum:
  36. return False
  37. return True
  38. _cache: Dict[str, bool] = {}
  39. def have(feature: str) -> bool:
  40. """Is *feature* available?
  41. This tests if all optional packages needed for the
  42. feature are available and recent enough.
  43. Returns ``True`` if the feature is available,
  44. and ``False`` if it is not or if metadata is
  45. missing.
  46. """
  47. value = _cache.get(feature)
  48. if value is not None:
  49. return value
  50. requirements = _requirements.get(feature)
  51. if requirements is None:
  52. # we make a cache entry here for consistency not performance
  53. _cache[feature] = False
  54. return False
  55. ok = True
  56. for requirement in requirements:
  57. if not _version_check(requirement):
  58. ok = False
  59. break
  60. _cache[feature] = ok
  61. return ok
  62. def force(feature: str, enabled: bool) -> None:
  63. """Force the status of *feature* to be *enabled*.
  64. This method is provided as a workaround for any cases
  65. where importlib.metadata is ineffective, or for testing.
  66. """
  67. _cache[feature] = enabled
  68. _requirements: Dict[str, List[str]] = {
  69. ### BEGIN generated requirements
  70. "dnssec": ["cryptography>=43"],
  71. "doh": ["httpcore>=1.0.0", "httpx>=0.26.0", "h2>=4.1.0"],
  72. "doq": ["aioquic>=1.0.0"],
  73. "idna": ["idna>=3.7"],
  74. "trio": ["trio>=0.23"],
  75. "wmi": ["wmi>=1.5.1"],
  76. ### END generated requirements
  77. }