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
5from __future__ import annotations
6
7import enum
8import sys
9import types
10import typing
11import warnings
12
13
14# We use a UserWarning subclass, instead of DeprecationWarning, because CPython
15# decided deprecation warnings should be invisible by default.
16class CryptographyDeprecationWarning(UserWarning):
17 pass
18
19
20# Several APIs were deprecated with no specific end-of-life date because of the
21# ubiquity of their use. They should not be removed until we agree on when that
22# cycle ends.
23DeprecatedIn36 = CryptographyDeprecationWarning
24DeprecatedIn37 = CryptographyDeprecationWarning
25DeprecatedIn40 = CryptographyDeprecationWarning
26DeprecatedIn41 = CryptographyDeprecationWarning
27DeprecatedIn42 = CryptographyDeprecationWarning
28DeprecatedIn43 = CryptographyDeprecationWarning
29
30
31def _check_bytes(name: str, value: bytes) -> None:
32 if not isinstance(value, bytes):
33 raise TypeError(f"{name} must be bytes")
34
35
36def _check_byteslike(name: str, value: bytes) -> None:
37 try:
38 memoryview(value)
39 except TypeError:
40 raise TypeError(f"{name} must be bytes-like")
41
42
43def int_to_bytes(integer: int, length: int | None = None) -> bytes:
44 if length == 0:
45 raise ValueError("length argument can't be 0")
46 return integer.to_bytes(
47 length or (integer.bit_length() + 7) // 8 or 1, "big"
48 )
49
50
51class InterfaceNotImplemented(Exception):
52 pass
53
54
55class _DeprecatedValue:
56 def __init__(self, value: object, message: str, warning_class):
57 self.value = value
58 self.message = message
59 self.warning_class = warning_class
60
61
62class _ModuleWithDeprecations(types.ModuleType):
63 def __init__(self, module: types.ModuleType):
64 super().__init__(module.__name__)
65 self.__dict__["_module"] = module
66
67 def __getattr__(self, attr: str) -> object:
68 obj = getattr(self._module, attr)
69 if isinstance(obj, _DeprecatedValue):
70 warnings.warn(obj.message, obj.warning_class, stacklevel=2)
71 obj = obj.value
72 return obj
73
74 def __setattr__(self, attr: str, value: object) -> None:
75 setattr(self._module, attr, value)
76
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
82 delattr(self._module, attr)
83
84 def __dir__(self) -> typing.Sequence[str]:
85 return ["_module", *dir(self._module)]
86
87
88def deprecated(
89 value: object,
90 module_name: str,
91 message: str,
92 warning_class: type[Warning],
93 name: str | None = None,
94) -> _DeprecatedValue:
95 module = sys.modules[module_name]
96 if not isinstance(module, _ModuleWithDeprecations):
97 sys.modules[module_name] = module = _ModuleWithDeprecations(module)
98 dv = _DeprecatedValue(value, message, warning_class)
99 # Maintain backwards compatibility with `name is None` for pyOpenSSL.
100 if name is not None:
101 setattr(module, name, dv)
102 return dv
103
104
105def cached_property(func: typing.Callable) -> property:
106 cached_name = f"_cached_{func}"
107 sentinel = object()
108
109 def inner(instance: object):
110 cache = getattr(instance, cached_name, sentinel)
111 if cache is not sentinel:
112 return cache
113 result = func(instance)
114 setattr(instance, cached_name, result)
115 return result
116
117 return property(inner)
118
119
120# Python 3.10 changed representation of enums. We use well-defined object
121# representation and string representation from Python 3.9.
122class Enum(enum.Enum):
123 def __repr__(self) -> str:
124 return f"<{self.__class__.__name__}.{self._name_}: {self._value_!r}>"
125
126 def __str__(self) -> str:
127 return f"{self.__class__.__name__}.{self._name_}"