utils.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. # This file is dual licensed under the terms of the Apache License, Version
  2. # 2.0, and the BSD License. See the LICENSE file in the root of this repository
  3. # for complete details.
  4. from __future__ import annotations
  5. import enum
  6. import sys
  7. import types
  8. import typing
  9. import warnings
  10. from collections.abc import Sequence
  11. # We use a UserWarning subclass, instead of DeprecationWarning, because CPython
  12. # decided deprecation warnings should be invisible by default.
  13. class CryptographyDeprecationWarning(UserWarning):
  14. pass
  15. # Several APIs were deprecated with no specific end-of-life date because of the
  16. # ubiquity of their use. They should not be removed until we agree on when that
  17. # cycle ends.
  18. DeprecatedIn36 = CryptographyDeprecationWarning
  19. DeprecatedIn40 = CryptographyDeprecationWarning
  20. DeprecatedIn41 = CryptographyDeprecationWarning
  21. DeprecatedIn42 = CryptographyDeprecationWarning
  22. DeprecatedIn43 = CryptographyDeprecationWarning
  23. DeprecatedIn47 = CryptographyDeprecationWarning
  24. DeprecatedIn50 = CryptographyDeprecationWarning
  25. # If you're wondering why we don't use `Buffer`, it's because `Buffer` would
  26. # be more accurately named: Bufferable. It means something which has an
  27. # `__buffer__`. Which means you can't actually treat the result as a buffer
  28. # (and do things like take a `len()`).
  29. Buffer = typing.Union[bytes, bytearray, memoryview]
  30. def _check_bytes(name: str, value: bytes) -> None:
  31. if not isinstance(value, bytes):
  32. raise TypeError(f"{name} must be bytes")
  33. def _check_byteslike(name: str, value: Buffer) -> None:
  34. if isinstance(value, (bytes, bytearray, memoryview)):
  35. return
  36. try:
  37. memoryview(value)
  38. except TypeError:
  39. raise TypeError(f"{name} must be bytes-like")
  40. def int_to_bytes(integer: int, length: int | None = None) -> bytes:
  41. if length == 0:
  42. raise ValueError("length argument can't be 0")
  43. return integer.to_bytes(
  44. length or (integer.bit_length() + 7) // 8 or 1, "big"
  45. )
  46. class InterfaceNotImplemented(Exception):
  47. pass
  48. class _DeprecatedValue:
  49. def __init__(self, value: object, message: str, warning_class):
  50. self.value = value
  51. self.message = message
  52. self.warning_class = warning_class
  53. class _ModuleWithDeprecations(types.ModuleType):
  54. def __init__(self, module: types.ModuleType):
  55. super().__init__(module.__name__)
  56. self.__dict__["_module"] = module
  57. def __getattr__(self, name: str) -> typing.Any:
  58. obj = getattr(self._module, name)
  59. if isinstance(obj, _DeprecatedValue):
  60. warnings.warn(obj.message, obj.warning_class, stacklevel=2)
  61. obj = obj.value
  62. else:
  63. # Cache non-deprecated attributes in our own `__dict__` so that
  64. # subsequent lookups are ordinary module attribute accesses and
  65. # don't pay for this `__getattr__` (which would otherwise defeat
  66. # CPython's LOAD_ATTR module caching for every attribute of the
  67. # module). `__setattr__` and `__delattr__` keep the cache
  68. # coherent.
  69. self.__dict__[name] = obj
  70. return obj
  71. def __setattr__(self, attr: str, value: object) -> None:
  72. if isinstance(value, _DeprecatedValue):
  73. self.__dict__.pop(attr, None)
  74. else:
  75. self.__dict__[attr] = value
  76. setattr(self._module, attr, value)
  77. def __delattr__(self, attr: str) -> None:
  78. obj = getattr(self._module, attr)
  79. if isinstance(obj, _DeprecatedValue):
  80. warnings.warn(obj.message, obj.warning_class, stacklevel=2)
  81. self.__dict__.pop(attr, None)
  82. delattr(self._module, attr)
  83. def __dir__(self) -> Sequence[str]:
  84. return ["_module", *dir(self._module)]
  85. def deprecated(
  86. value: object,
  87. module_name: str,
  88. message: str,
  89. warning_class: type[Warning],
  90. name: str | None = None,
  91. ) -> _DeprecatedValue:
  92. module = sys.modules[module_name]
  93. if not isinstance(module, _ModuleWithDeprecations):
  94. sys.modules[module_name] = module = _ModuleWithDeprecations(module)
  95. dv = _DeprecatedValue(value, message, warning_class)
  96. # Maintain backwards compatibility with `name is None` for pyOpenSSL.
  97. if name is not None:
  98. setattr(module, name, dv)
  99. return dv
  100. # Python 3.10 changed representation of enums. We use well-defined object
  101. # representation and string representation from Python 3.9.
  102. class Enum(enum.Enum):
  103. def __repr__(self) -> str:
  104. return f"<{self.__class__.__name__}.{self._name_}: {self._value_!r}>"
  105. def __str__(self) -> str:
  106. return f"{self.__class__.__name__}.{self._name_}"