omlish 0.0.0.dev1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of omlish might be problematic. Click here for more details.

Files changed (187) hide show
  1. omlish/__about__.py +7 -0
  2. omlish/__init__.py +0 -0
  3. omlish/argparse.py +223 -0
  4. omlish/asyncs/__init__.py +17 -0
  5. omlish/asyncs/anyio.py +23 -0
  6. omlish/asyncs/asyncio.py +19 -0
  7. omlish/asyncs/asyncs.py +76 -0
  8. omlish/asyncs/futures.py +179 -0
  9. omlish/asyncs/trio.py +11 -0
  10. omlish/c3.py +173 -0
  11. omlish/cached.py +9 -0
  12. omlish/check.py +231 -0
  13. omlish/collections/__init__.py +63 -0
  14. omlish/collections/_abc.py +156 -0
  15. omlish/collections/_io_abc.py +78 -0
  16. omlish/collections/cache/__init__.py +11 -0
  17. omlish/collections/cache/descriptor.py +188 -0
  18. omlish/collections/cache/impl.py +485 -0
  19. omlish/collections/cache/types.py +37 -0
  20. omlish/collections/coerce.py +337 -0
  21. omlish/collections/frozen.py +148 -0
  22. omlish/collections/identity.py +106 -0
  23. omlish/collections/indexed.py +75 -0
  24. omlish/collections/mappings.py +127 -0
  25. omlish/collections/ordered.py +81 -0
  26. omlish/collections/persistent.py +36 -0
  27. omlish/collections/skiplist.py +193 -0
  28. omlish/collections/sorted.py +126 -0
  29. omlish/collections/treap.py +228 -0
  30. omlish/collections/treapmap.py +144 -0
  31. omlish/collections/unmodifiable.py +174 -0
  32. omlish/collections/utils.py +110 -0
  33. omlish/configs/__init__.py +0 -0
  34. omlish/configs/flattening.py +147 -0
  35. omlish/configs/props.py +64 -0
  36. omlish/dataclasses/__init__.py +83 -0
  37. omlish/dataclasses/impl/__init__.py +6 -0
  38. omlish/dataclasses/impl/api.py +260 -0
  39. omlish/dataclasses/impl/as_.py +76 -0
  40. omlish/dataclasses/impl/exceptions.py +2 -0
  41. omlish/dataclasses/impl/fields.py +148 -0
  42. omlish/dataclasses/impl/frozen.py +55 -0
  43. omlish/dataclasses/impl/hashing.py +85 -0
  44. omlish/dataclasses/impl/init.py +173 -0
  45. omlish/dataclasses/impl/internals.py +118 -0
  46. omlish/dataclasses/impl/main.py +150 -0
  47. omlish/dataclasses/impl/metaclass.py +126 -0
  48. omlish/dataclasses/impl/metadata.py +74 -0
  49. omlish/dataclasses/impl/order.py +47 -0
  50. omlish/dataclasses/impl/params.py +150 -0
  51. omlish/dataclasses/impl/processing.py +16 -0
  52. omlish/dataclasses/impl/reflect.py +173 -0
  53. omlish/dataclasses/impl/replace.py +40 -0
  54. omlish/dataclasses/impl/repr.py +34 -0
  55. omlish/dataclasses/impl/simple.py +92 -0
  56. omlish/dataclasses/impl/slots.py +80 -0
  57. omlish/dataclasses/impl/utils.py +167 -0
  58. omlish/defs.py +193 -0
  59. omlish/dispatch/__init__.py +3 -0
  60. omlish/dispatch/dispatch.py +137 -0
  61. omlish/dispatch/functions.py +52 -0
  62. omlish/dispatch/methods.py +162 -0
  63. omlish/docker.py +149 -0
  64. omlish/dynamic.py +220 -0
  65. omlish/graphs/__init__.py +0 -0
  66. omlish/graphs/dot/__init__.py +19 -0
  67. omlish/graphs/dot/items.py +162 -0
  68. omlish/graphs/dot/rendering.py +147 -0
  69. omlish/graphs/dot/utils.py +30 -0
  70. omlish/graphs/trees.py +249 -0
  71. omlish/http/__init__.py +0 -0
  72. omlish/http/consts.py +20 -0
  73. omlish/http/wsgi.py +34 -0
  74. omlish/inject/__init__.py +85 -0
  75. omlish/inject/binder.py +12 -0
  76. omlish/inject/bindings.py +49 -0
  77. omlish/inject/eagers.py +21 -0
  78. omlish/inject/elements.py +43 -0
  79. omlish/inject/exceptions.py +49 -0
  80. omlish/inject/impl/__init__.py +0 -0
  81. omlish/inject/impl/bindings.py +19 -0
  82. omlish/inject/impl/elements.py +154 -0
  83. omlish/inject/impl/injector.py +182 -0
  84. omlish/inject/impl/inspect.py +98 -0
  85. omlish/inject/impl/private.py +109 -0
  86. omlish/inject/impl/providers.py +132 -0
  87. omlish/inject/impl/scopes.py +198 -0
  88. omlish/inject/injector.py +40 -0
  89. omlish/inject/inspect.py +14 -0
  90. omlish/inject/keys.py +43 -0
  91. omlish/inject/managed.py +24 -0
  92. omlish/inject/overrides.py +18 -0
  93. omlish/inject/private.py +29 -0
  94. omlish/inject/providers.py +111 -0
  95. omlish/inject/proxy.py +48 -0
  96. omlish/inject/scopes.py +84 -0
  97. omlish/inject/types.py +21 -0
  98. omlish/iterators.py +184 -0
  99. omlish/json.py +194 -0
  100. omlish/lang/__init__.py +112 -0
  101. omlish/lang/cached.py +267 -0
  102. omlish/lang/classes/__init__.py +24 -0
  103. omlish/lang/classes/abstract.py +74 -0
  104. omlish/lang/classes/restrict.py +137 -0
  105. omlish/lang/classes/simple.py +120 -0
  106. omlish/lang/classes/test/__init__.py +0 -0
  107. omlish/lang/classes/test/test_abstract.py +89 -0
  108. omlish/lang/classes/test/test_restrict.py +71 -0
  109. omlish/lang/classes/test/test_simple.py +58 -0
  110. omlish/lang/classes/test/test_virtual.py +72 -0
  111. omlish/lang/classes/virtual.py +130 -0
  112. omlish/lang/clsdct.py +67 -0
  113. omlish/lang/cmp.py +63 -0
  114. omlish/lang/contextmanagers.py +249 -0
  115. omlish/lang/datetimes.py +67 -0
  116. omlish/lang/descriptors.py +52 -0
  117. omlish/lang/functions.py +126 -0
  118. omlish/lang/imports.py +153 -0
  119. omlish/lang/iterables.py +54 -0
  120. omlish/lang/maybes.py +136 -0
  121. omlish/lang/objects.py +103 -0
  122. omlish/lang/resolving.py +50 -0
  123. omlish/lang/strings.py +128 -0
  124. omlish/lang/typing.py +92 -0
  125. omlish/libc.py +532 -0
  126. omlish/logs/__init__.py +9 -0
  127. omlish/logs/_abc.py +247 -0
  128. omlish/logs/configs.py +62 -0
  129. omlish/logs/filters.py +9 -0
  130. omlish/logs/formatters.py +67 -0
  131. omlish/logs/utils.py +20 -0
  132. omlish/marshal/__init__.py +52 -0
  133. omlish/marshal/any.py +25 -0
  134. omlish/marshal/base.py +201 -0
  135. omlish/marshal/base64.py +25 -0
  136. omlish/marshal/dataclasses.py +115 -0
  137. omlish/marshal/datetimes.py +90 -0
  138. omlish/marshal/enums.py +43 -0
  139. omlish/marshal/exceptions.py +7 -0
  140. omlish/marshal/factories.py +129 -0
  141. omlish/marshal/global_.py +33 -0
  142. omlish/marshal/iterables.py +57 -0
  143. omlish/marshal/mappings.py +66 -0
  144. omlish/marshal/naming.py +17 -0
  145. omlish/marshal/objects.py +106 -0
  146. omlish/marshal/optionals.py +49 -0
  147. omlish/marshal/polymorphism.py +147 -0
  148. omlish/marshal/primitives.py +43 -0
  149. omlish/marshal/registries.py +57 -0
  150. omlish/marshal/standard.py +80 -0
  151. omlish/marshal/utils.py +23 -0
  152. omlish/marshal/uuids.py +29 -0
  153. omlish/marshal/values.py +30 -0
  154. omlish/math.py +184 -0
  155. omlish/os.py +32 -0
  156. omlish/reflect.py +359 -0
  157. omlish/replserver/__init__.py +5 -0
  158. omlish/replserver/__main__.py +4 -0
  159. omlish/replserver/console.py +247 -0
  160. omlish/replserver/server.py +146 -0
  161. omlish/runmodule.py +28 -0
  162. omlish/stats.py +342 -0
  163. omlish/term.py +222 -0
  164. omlish/testing/__init__.py +7 -0
  165. omlish/testing/pydevd.py +225 -0
  166. omlish/testing/pytest/__init__.py +8 -0
  167. omlish/testing/pytest/helpers.py +35 -0
  168. omlish/testing/pytest/inject/__init__.py +1 -0
  169. omlish/testing/pytest/inject/harness.py +159 -0
  170. omlish/testing/pytest/plugins/__init__.py +20 -0
  171. omlish/testing/pytest/plugins/_registry.py +6 -0
  172. omlish/testing/pytest/plugins/logging.py +13 -0
  173. omlish/testing/pytest/plugins/pycharm.py +54 -0
  174. omlish/testing/pytest/plugins/repeat.py +19 -0
  175. omlish/testing/pytest/plugins/skips.py +32 -0
  176. omlish/testing/pytest/plugins/spacing.py +19 -0
  177. omlish/testing/pytest/plugins/switches.py +70 -0
  178. omlish/testing/testing.py +102 -0
  179. omlish/text/__init__.py +0 -0
  180. omlish/text/delimit.py +171 -0
  181. omlish/text/indent.py +50 -0
  182. omlish/text/parts.py +265 -0
  183. omlish-0.0.0.dev1.dist-info/LICENSE +21 -0
  184. omlish-0.0.0.dev1.dist-info/METADATA +17 -0
  185. omlish-0.0.0.dev1.dist-info/RECORD +187 -0
  186. omlish-0.0.0.dev1.dist-info/WHEEL +5 -0
  187. omlish-0.0.0.dev1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,11 @@
1
+ from .descriptor import Ignore # noqa
2
+ from .descriptor import Scope # noqa
3
+ from .descriptor import cache # noqa
4
+ from .descriptor import ignore # noqa
5
+ from .impl import LFU # noqa
6
+ from .impl import LRI # noqa
7
+ from .impl import LRU # noqa
8
+ from .impl import new_cache # noqa
9
+ from .types import Cache # noqa
10
+ from .types import Eviction # noqa
11
+ from .types import OverweightException # noqa
@@ -0,0 +1,188 @@
1
+ """
2
+ TODO:
3
+ - try_weak .. allow non-weakrefable keys to just fall through and not cache
4
+ """
5
+ import enum
6
+ import functools
7
+ import typing as ta
8
+ import weakref
9
+
10
+ from .impl import new_cache
11
+ from .types import Cache
12
+
13
+
14
+ C = ta.TypeVar('C', bound=ta.Callable)
15
+ CC: ta.TypeAlias = ta.Callable[[C], C]
16
+
17
+
18
+ class Scope(enum.Enum):
19
+ INSTANCE = 'INSTANCE'
20
+ CLASS = 'CLASS'
21
+ STATIC = 'STATIC'
22
+
23
+
24
+ class _HashedSeq(list):
25
+ __slots__ = ['hash_value']
26
+
27
+ def __init__(
28
+ self,
29
+ tup: ta.Tuple,
30
+ hasher: ta.Callable[[ta.Any], int] = hash
31
+ ) -> None:
32
+ super().__init__()
33
+
34
+ self[:] = tup
35
+ self.hash_value = hasher(tup)
36
+
37
+ def __hash__(self):
38
+ return self.hash_value
39
+
40
+
41
+ def _make_key(
42
+ args: ta.Tuple,
43
+ kwargs: ta.Dict[str, ta.Any],
44
+ typed: bool,
45
+ kwd_mark=(object(),),
46
+ fasttypes=frozenset([int, str, frozenset, type(None)]),
47
+ tuple=tuple,
48
+ type=type,
49
+ len=len
50
+ ) -> ta.Any:
51
+ key = args
52
+ if kwargs:
53
+ key += kwd_mark
54
+ for item in kwargs.items():
55
+ key += item
56
+ if typed:
57
+ key += tuple(type(v) for v in args)
58
+ if kwargs:
59
+ key += tuple(type(v) for v in kwargs.values())
60
+ elif len(key) == 1 and type(key[0]) in fasttypes:
61
+ return key[0]
62
+ return _HashedSeq(key)
63
+
64
+
65
+ class Ignore:
66
+
67
+ def __init__(self, value: ta.Any) -> None:
68
+ super().__init__()
69
+
70
+ self._value = value
71
+
72
+
73
+ def ignore(value: ta.Any) -> ta.Any:
74
+ return Ignore(value)
75
+
76
+
77
+ class _CacheDescriptor:
78
+
79
+ def __init__(
80
+ self,
81
+ fn: ta.Callable,
82
+ scope: Scope,
83
+ typed: bool,
84
+ **kwargs: ta.Any
85
+ ) -> None:
86
+ super().__init__()
87
+
88
+ self._fn = fn
89
+ functools.update_wrapper(self, fn)
90
+ self._scope = scope
91
+ self._typed = typed
92
+ self._kwargs = kwargs
93
+ self.__static: Cache | None = None
94
+ self.__static_built: ta.Callable | None = None
95
+ self._by_class: ta.MutableMapping[ta.Type, Cache] | None = weakref.WeakKeyDictionary() if scope == Scope.CLASS else None # noqa
96
+ self._name = None
97
+ self._unary = kwargs.get('identity_keys', False) or kwargs.get('weak_keys', False)
98
+
99
+ def __set_name__(self, owner, name):
100
+ if self._name is None:
101
+ self._name = name
102
+
103
+ @property
104
+ def _static(self) -> Cache:
105
+ if self.__static is None:
106
+ self.__static = new_cache(**self._kwargs)
107
+ return self.__static
108
+
109
+ def _build(self, fn: ta.Callable, cache: Cache):
110
+ def miss(key, result):
111
+ if isinstance(result, Ignore):
112
+ return result._value
113
+ else:
114
+ cache[key] = result
115
+ return result
116
+
117
+ if self._unary:
118
+ @functools.wraps(fn)
119
+ def inner(key):
120
+ try:
121
+ return cache[key]
122
+ except KeyError:
123
+ pass
124
+ return miss(key, fn(key))
125
+
126
+ else:
127
+ @functools.wraps(fn)
128
+ def inner(*args, **kwargs):
129
+ key = _make_key(args, kwargs, self._typed)
130
+ try:
131
+ return cache[key]
132
+ except KeyError:
133
+ pass
134
+ return miss(key, fn(*args, **kwargs))
135
+
136
+ return inner
137
+
138
+ def __get__(self, instance, owner=None):
139
+ if self._scope == Scope.STATIC:
140
+ cache = self._static
141
+
142
+ elif self._scope == Scope.CLASS:
143
+ if owner is None:
144
+ raise TypeError
145
+ try:
146
+ cache = self._by_class[owner] # type: ignore
147
+ except KeyError:
148
+ cache = self._by_class[owner] = new_cache(**self._kwargs) # type: ignore
149
+
150
+ elif self._scope == Scope.INSTANCE:
151
+ if instance is not None:
152
+ cache = new_cache()
153
+ else:
154
+ @functools.wraps(self._fn)
155
+ def trampoline(this, *args, **kwargs):
156
+ return self.__get__(this, owner)(*args, **kwargs)
157
+ return trampoline
158
+
159
+ else:
160
+ raise TypeError
161
+
162
+ fn = self._build(self._fn.__get__(instance, owner), cache) # noqa
163
+
164
+ if self._scope == Scope.CLASS:
165
+ setattr(owner, self._name, fn) # type: ignore
166
+ elif self._scope == Scope.INSTANCE:
167
+ setattr(instance, self._name, fn) # type: ignore
168
+
169
+ return fn
170
+
171
+ def __call__(self, *args, **kwargs):
172
+ if self.__static_built is None:
173
+ self.__static_built = self._build(self._fn, self._static)
174
+ return self.__static_built(*args, **kwargs)
175
+
176
+
177
+ def cache(
178
+ scope: ta.Union[Scope, str] = Scope.INSTANCE,
179
+ typed: bool = False,
180
+ **kwargs
181
+ ) -> CC:
182
+ if not isinstance(scope, Scope):
183
+ scope = getattr(Scope, scope.upper()) # noqa
184
+
185
+ def inner(fn):
186
+ return _CacheDescriptor(fn, scope, typed, **kwargs) # type: ignore
187
+
188
+ return ta.cast(CC, inner)
@@ -0,0 +1,485 @@
1
+ """
2
+ TODO:
3
+ - dogpile
4
+ - midpoint/arc
5
+ - heapq/etc powered lfu
6
+ - thread scoped?
7
+ - __sizeof__ interop
8
+ - further cythonize hot path?
9
+ """
10
+ import collections
11
+ import functools
12
+ import logging
13
+ import time
14
+ import typing as ta
15
+ import weakref
16
+
17
+ from ... import lang
18
+ from .types import Cache
19
+ from .types import Eviction
20
+ from .types import OverweightException
21
+
22
+
23
+ K = ta.TypeVar('K')
24
+ K2 = ta.TypeVar('K2')
25
+ V = ta.TypeVar('V')
26
+ V2 = ta.TypeVar('V2')
27
+
28
+
29
+ log = logging.getLogger(__name__)
30
+
31
+
32
+ class SKIP(lang.Marker):
33
+ pass
34
+
35
+
36
+ def LRU(cache: 'Cache') -> None:
37
+ cache._kill(cache._root.lru_next) # type: ignore # noqa
38
+
39
+
40
+ def LRI(cache: 'Cache') -> None:
41
+ cache._kill(cache._root.ins_next) # type: ignore # noqa
42
+
43
+
44
+ def LFU(cache: 'Cache') -> None:
45
+ cache._kill(cache._root.lfu_prev) # type: ignore # noqa
46
+
47
+
48
+ class CacheImpl(Cache[K, V]):
49
+ """
50
+ https://google.github.io/guava/releases/16.0/api/docs/com/google/common/cache/CacheBuilder.html
51
+ """
52
+
53
+ try:
54
+ if not ta.TYPE_CHECKING:
55
+ from ..._ext.cy.collections.cache import CacheLink as Link
56
+ else:
57
+ raise ImportError
58
+
59
+ except ImportError:
60
+ class Link:
61
+ __slots__ = [
62
+ 'seq',
63
+ 'ins_prev',
64
+ 'ins_next',
65
+ 'lru_prev',
66
+ 'lru_next',
67
+ 'lfu_prev',
68
+ 'lfu_next',
69
+ 'key',
70
+ 'value',
71
+ 'weight',
72
+ 'written',
73
+ 'accessed',
74
+ 'hits',
75
+ 'unlinked',
76
+ ]
77
+
78
+ seq: int
79
+ ins_prev: 'CacheImpl.Link'
80
+ ins_next: 'CacheImpl.Link'
81
+ lru_prev: 'CacheImpl.Link'
82
+ lru_next: 'CacheImpl.Link'
83
+ lfu_prev: 'CacheImpl.Link'
84
+ lfu_next: 'CacheImpl.Link'
85
+ key: ta.Union[ta.Any, weakref.ref]
86
+ value: ta.Union[ta.Any, weakref.ref]
87
+ weight: float
88
+ written: float
89
+ accessed: float
90
+ hits: int
91
+ unlinked: bool
92
+
93
+ def __repr__(self) -> str:
94
+ return (
95
+ f'Link@{str(self.seq)}('
96
+ f'ins_prev={("@" + str(self.ins_prev.seq)) if self.ins_prev is not None else None}, '
97
+ f'ins_next={("@" + str(self.ins_next.seq)) if self.ins_next is not None else None}, '
98
+ f'lru_prev={("@" + str(self.lru_prev.seq)) if self.lru_prev is not None else None}, '
99
+ f'lru_next={("@" + str(self.lru_next.seq)) if self.lru_next is not None else None}, '
100
+ f'lfu_prev={("@" + str(self.lfu_prev.seq)) if self.lfu_prev is not None else None}, '
101
+ f'lfu_next={("@" + str(self.lfu_next.seq)) if self.lfu_next is not None else None}, '
102
+ f'key={self.key!r}, '
103
+ f'value={self.value!r}, '
104
+ f'weight={self.weight}, '
105
+ f'written={self.written}, '
106
+ f'accessed={self.accessed}, '
107
+ f'hits={self.hits}, '
108
+ f'unlinked={self.unlinked})'
109
+ )
110
+
111
+ _cache: ta.MutableMapping[ta.Any, Link]
112
+
113
+ DEFAULT_MAX_SIZE = 256
114
+
115
+ def __init__(
116
+ self,
117
+ *,
118
+ max_size: int = DEFAULT_MAX_SIZE,
119
+ max_weight: float | None = None,
120
+ identity_keys: bool = False,
121
+ expire_after_access: float | None = None,
122
+ expire_after_write: float | None = None,
123
+ removal_listener: ta.Callable[[ta.Union[K, weakref.ref], ta.Union[V, weakref.ref]], None] | None = None,
124
+ clock: ta.Callable[[], float] | None = None,
125
+ weak_keys: bool = False,
126
+ weak_values: bool = False,
127
+ weigher: ta.Callable[[V], float] = lambda _: 1.,
128
+ lock: lang.DefaultLockable = None,
129
+ raise_overweight: bool = False,
130
+ eviction: Eviction = LRU,
131
+ track_frequency: bool | None = None,
132
+ ) -> None:
133
+ super().__init__()
134
+
135
+ if clock is None:
136
+ if expire_after_access is not None or expire_after_write is not None:
137
+ clock = time.time
138
+ else:
139
+ clock = lambda: 0. # noqa
140
+
141
+ self._max_size = max_size
142
+ self._max_weight = max_weight
143
+ self._identity_keys = identity_keys
144
+ self._expire_after_access = expire_after_access
145
+ self._expire_after_write = expire_after_write
146
+ self._removal_listener = removal_listener
147
+ self._clock = clock
148
+ self._weak_keys = weak_keys
149
+ self._weak_values = weak_values
150
+ self._weigher = weigher
151
+ self._lock = lang.default_lock(lock, True)
152
+ self._raise_overweight = raise_overweight
153
+ self._eviction = eviction
154
+ self._track_frequency = track_frequency if track_frequency is not None else (eviction is LFU)
155
+
156
+ if weak_keys and not identity_keys:
157
+ self._cache = weakref.WeakKeyDictionary()
158
+ else:
159
+ self._cache = {}
160
+
161
+ self._root = CacheImpl.Link()
162
+ self._root.seq = 0
163
+ self._root.ins_next = self._root.ins_prev = self._root
164
+ self._root.lru_next = self._root.lru_prev = self._root
165
+ if self._track_frequency:
166
+ self._root.lfu_next = self._root.lfu_prev = self._root
167
+
168
+ weak_dead: ta.Optional[ta.Deque[CacheImpl.Link]]
169
+ if weak_keys or weak_values:
170
+ weak_dead = collections.deque()
171
+ weak_dead_ref = weakref.ref(weak_dead)
172
+ else:
173
+ weak_dead = None
174
+ weak_dead_ref = None
175
+ self._weak_dead = weak_dead
176
+ self._weak_dead_ref = weak_dead_ref
177
+
178
+ self._seq = 0
179
+ self._size = 0
180
+ self._weight = 0.
181
+ self._hits = 0
182
+ self._misses = 0
183
+ self._max_size_ever = 0
184
+ self._max_weight_ever = 0.
185
+
186
+ def _unlink(self, link: Link) -> None:
187
+ if link is self._root:
188
+ raise TypeError
189
+ if link.unlinked:
190
+ return
191
+
192
+ link.ins_prev.ins_next = link.ins_next
193
+ link.ins_next.ins_prev = link.ins_prev
194
+ link.ins_next = link.ins_prev = link
195
+
196
+ link.lru_prev.lru_next = link.lru_next
197
+ link.lru_next.lru_prev = link.lru_prev
198
+ link.lru_next = link.lru_prev = link
199
+
200
+ if self._track_frequency:
201
+ link.lfu_prev.lfu_next = link.lfu_next
202
+ link.lfu_next.lfu_prev = link.lfu_prev
203
+ link.lfu_next = link.lfu_prev = link
204
+
205
+ if self._removal_listener is not None:
206
+ try:
207
+ self._removal_listener(link.key, link.value)
208
+ except Exception:
209
+ log.exception('Removal listener raised exception')
210
+
211
+ self._size -= 1
212
+ self._weight -= link.weight
213
+ link.key = link.value = None
214
+ link.unlinked = True
215
+
216
+ def _kill(self, link: Link) -> None:
217
+ if link is self._root:
218
+ raise RuntimeError
219
+
220
+ key = link.key
221
+ if self._weak_keys:
222
+ if key is not None:
223
+ key = key()
224
+ if key is None:
225
+ key = SKIP
226
+
227
+ if key is not SKIP:
228
+ cache_key = id(key) if self._identity_keys else key
229
+ cache_link = self._cache.get(cache_key)
230
+ if cache_link is link:
231
+ del self._cache[cache_key]
232
+
233
+ self._unlink(link)
234
+
235
+ def _reap(self) -> None:
236
+ if self._weak_dead is not None:
237
+ while True:
238
+ try:
239
+ link = self._weak_dead.popleft()
240
+ except IndexError:
241
+ break
242
+ self._kill(link)
243
+
244
+ clock = None
245
+
246
+ if self._expire_after_write is not None:
247
+ clock = self._clock()
248
+ deadline = clock - self._expire_after_write
249
+
250
+ while self._root.ins_next is not self._root:
251
+ link = self._root.ins_next
252
+ if link.written > deadline:
253
+ break
254
+ self._kill(link)
255
+
256
+ if self._expire_after_access is not None:
257
+ if clock is None:
258
+ clock = self._clock()
259
+ deadline = clock - self._expire_after_access
260
+
261
+ while self._root.lru_next is not self._root:
262
+ link = self._root.lru_next
263
+ if link.accessed > deadline:
264
+ break
265
+ self._kill(link)
266
+
267
+ def reap(self) -> None:
268
+ with self._lock():
269
+ self._reap()
270
+
271
+ def _get_link(self, key: K) -> tuple[Link, V]:
272
+ cache_key = id(key) if self._identity_keys else key
273
+
274
+ link = self._cache[cache_key]
275
+ if link.unlinked:
276
+ raise Exception
277
+
278
+ def fail():
279
+ try:
280
+ del self._cache[cache_key]
281
+ except KeyError:
282
+ pass
283
+ self._unlink(link)
284
+ raise KeyError(key)
285
+
286
+ if self._identity_keys:
287
+ link_key = link.key
288
+ if self._weak_keys:
289
+ link_key = link_key()
290
+ if link_key is None:
291
+ fail()
292
+ if key is not link_key:
293
+ fail()
294
+
295
+ value = link.value
296
+ if self._weak_values:
297
+ if value is not None:
298
+ value = value()
299
+ if value is None:
300
+ fail()
301
+
302
+ return link, value # type: ignore
303
+
304
+ def __getitem__(self, key: K) -> V:
305
+ with self._lock():
306
+ self._reap()
307
+
308
+ try:
309
+ link, value = self._get_link(key)
310
+ except KeyError:
311
+ self._misses += 1
312
+ raise KeyError(key)
313
+
314
+ if link.lru_next is not self._root:
315
+ link.lru_prev.lru_next = link.lru_next
316
+ link.lru_next.lru_prev = link.lru_prev
317
+
318
+ lru_last = self._root.lru_prev
319
+ lru_last.lru_next = self._root.lru_prev = link
320
+ link.lru_prev = lru_last
321
+ link.lru_next = self._root
322
+
323
+ if self._track_frequency:
324
+ lfu_pos = link.lfu_prev
325
+ while lfu_pos is not self._root and lfu_pos.hits <= link.hits:
326
+ lfu_pos = lfu_pos.lfu_prev
327
+
328
+ if link.lfu_prev is not lfu_pos:
329
+ link.lfu_prev.lfu_next = link.lfu_next
330
+ link.lfu_next.lfu_prev = link.lfu_prev
331
+
332
+ lfu_last = lfu_pos.lfu_prev
333
+ lfu_last.lfu_next = lfu_pos.lfu_prev = link
334
+ link.lfu_prev = lfu_last
335
+ link.lfu_next = lfu_pos
336
+
337
+ link.accessed = self._clock()
338
+ link.hits += 1
339
+ self._hits += 1
340
+ return value
341
+
342
+ @staticmethod
343
+ def _weak_die(dead_ref: weakref.ref, link: Link, key_ref: weakref.ref) -> None:
344
+ dead = dead_ref()
345
+ if dead is not None:
346
+ dead.append(link)
347
+
348
+ @property
349
+ def _full(self) -> bool:
350
+ if self._max_size is not None and self._size >= self._max_size:
351
+ return True
352
+ if self._max_weight is not None and self._weight >= self._max_weight:
353
+ return True
354
+ return False
355
+
356
+ def clear(self) -> None:
357
+ with self._lock():
358
+ self._cache.clear()
359
+ while True:
360
+ link = self._root.ins_prev
361
+ if link is self._root:
362
+ break
363
+ if link.unlinked:
364
+ raise TypeError
365
+ self._unlink(link)
366
+
367
+ def __setitem__(self, key: K, value: V) -> None:
368
+ weight = self._weigher(value)
369
+
370
+ with self._lock():
371
+ self._reap()
372
+
373
+ if self._max_weight is not None and weight > self._max_weight:
374
+ if self._raise_overweight:
375
+ raise OverweightException
376
+ else:
377
+ return
378
+
379
+ try:
380
+ existing_link, existing_value = self._get_link(key)
381
+ except KeyError:
382
+ pass
383
+ else:
384
+ self._unlink(existing_link)
385
+
386
+ while self._full:
387
+ self._eviction(self)
388
+
389
+ link = CacheImpl.Link()
390
+ self._seq += 1
391
+ link.seq = self._seq
392
+ link.key = weakref.ref(key, functools.partial(CacheImpl._weak_die, self._weak_dead_ref, link)) if self._weak_keys else key # noqa
393
+ link.value = weakref.ref(value, functools.partial(CacheImpl._weak_die, self._weak_dead_ref, link)) if self._weak_values else value # noqa
394
+ link.weight = weight
395
+ link.written = link.accessed = self._clock()
396
+ link.hits = 0
397
+ link.unlinked = False
398
+
399
+ ins_last = self._root.ins_prev
400
+ ins_last.ins_next = self._root.ins_prev = link
401
+ link.ins_prev = ins_last
402
+ link.ins_next = self._root
403
+
404
+ lru_last = self._root.lru_prev
405
+ lru_last.lru_next = self._root.lru_prev = link
406
+ link.lru_prev = lru_last
407
+ link.lru_next = self._root
408
+
409
+ if self._track_frequency:
410
+ lfu_last = self._root.lfu_prev
411
+ lfu_last.lfu_next = self._root.lfu_prev = link
412
+ link.lfu_prev = lfu_last
413
+ link.lfu_next = self._root
414
+
415
+ self._weight += weight
416
+ self._size += 1
417
+ self._max_size_ever = max(self._size, self._max_size_ever)
418
+ self._max_weight_ever = max(self._weight, self._max_weight_ever)
419
+
420
+ cache_key = id(key) if self._identity_keys else key
421
+ self._cache[cache_key] = link
422
+
423
+ def __delitem__(self, key: K) -> None:
424
+ with self._lock():
425
+ self._reap()
426
+
427
+ link, value = self._get_link(key)
428
+
429
+ cache_key = id(key) if self._identity_keys else key
430
+ del self._cache[cache_key]
431
+
432
+ self._unlink(link)
433
+
434
+ def __len__(self) -> int:
435
+ with self._lock():
436
+ self._reap()
437
+
438
+ return self._size
439
+
440
+ def __contains__(self, key: K) -> bool: # type: ignore
441
+ with self._lock():
442
+ self._reap()
443
+
444
+ try:
445
+ self._get_link(key)
446
+ except KeyError:
447
+ return False
448
+ else:
449
+ return True
450
+
451
+ def __iter__(self) -> ta.Iterator[K]:
452
+ with self._lock():
453
+ self._reap()
454
+
455
+ link = self._root.ins_prev
456
+ while link is not self._root:
457
+ key = link.key
458
+ if self._weak_keys:
459
+ if key is not None:
460
+ key = key()
461
+ if key is not None:
462
+ yield key
463
+ else:
464
+ yield key # type: ignore
465
+
466
+ next = link.ins_prev
467
+ if next is link:
468
+ raise ValueError
469
+ link = next
470
+
471
+ @property
472
+ def stats(self) -> Cache.Stats:
473
+ with self._lock():
474
+ return Cache.Stats(
475
+ self._seq,
476
+ self._size,
477
+ self._weight,
478
+ self._hits,
479
+ self._misses,
480
+ self._max_size_ever,
481
+ self._max_weight_ever,
482
+ )
483
+
484
+
485
+ new_cache = CacheImpl