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