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