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.
- omlish/__about__.py +7 -0
- omlish/__init__.py +0 -0
- omlish/argparse.py +223 -0
- omlish/asyncs/__init__.py +17 -0
- omlish/asyncs/anyio.py +23 -0
- omlish/asyncs/asyncio.py +19 -0
- omlish/asyncs/asyncs.py +76 -0
- omlish/asyncs/futures.py +179 -0
- omlish/asyncs/trio.py +11 -0
- omlish/c3.py +173 -0
- omlish/cached.py +9 -0
- omlish/check.py +231 -0
- omlish/collections/__init__.py +63 -0
- omlish/collections/_abc.py +156 -0
- omlish/collections/_io_abc.py +78 -0
- omlish/collections/cache/__init__.py +11 -0
- omlish/collections/cache/descriptor.py +188 -0
- omlish/collections/cache/impl.py +485 -0
- omlish/collections/cache/types.py +37 -0
- omlish/collections/coerce.py +337 -0
- omlish/collections/frozen.py +148 -0
- omlish/collections/identity.py +106 -0
- omlish/collections/indexed.py +75 -0
- omlish/collections/mappings.py +127 -0
- omlish/collections/ordered.py +81 -0
- omlish/collections/persistent.py +36 -0
- omlish/collections/skiplist.py +193 -0
- omlish/collections/sorted.py +126 -0
- omlish/collections/treap.py +228 -0
- omlish/collections/treapmap.py +144 -0
- omlish/collections/unmodifiable.py +174 -0
- omlish/collections/utils.py +110 -0
- omlish/configs/__init__.py +0 -0
- omlish/configs/flattening.py +147 -0
- omlish/configs/props.py +64 -0
- omlish/dataclasses/__init__.py +83 -0
- omlish/dataclasses/impl/__init__.py +6 -0
- omlish/dataclasses/impl/api.py +260 -0
- omlish/dataclasses/impl/as_.py +76 -0
- omlish/dataclasses/impl/exceptions.py +2 -0
- omlish/dataclasses/impl/fields.py +148 -0
- omlish/dataclasses/impl/frozen.py +55 -0
- omlish/dataclasses/impl/hashing.py +85 -0
- omlish/dataclasses/impl/init.py +173 -0
- omlish/dataclasses/impl/internals.py +118 -0
- omlish/dataclasses/impl/main.py +150 -0
- omlish/dataclasses/impl/metaclass.py +126 -0
- omlish/dataclasses/impl/metadata.py +74 -0
- omlish/dataclasses/impl/order.py +47 -0
- omlish/dataclasses/impl/params.py +150 -0
- omlish/dataclasses/impl/processing.py +16 -0
- omlish/dataclasses/impl/reflect.py +173 -0
- omlish/dataclasses/impl/replace.py +40 -0
- omlish/dataclasses/impl/repr.py +34 -0
- omlish/dataclasses/impl/simple.py +92 -0
- omlish/dataclasses/impl/slots.py +80 -0
- omlish/dataclasses/impl/utils.py +167 -0
- omlish/defs.py +193 -0
- omlish/dispatch/__init__.py +3 -0
- omlish/dispatch/dispatch.py +137 -0
- omlish/dispatch/functions.py +52 -0
- omlish/dispatch/methods.py +162 -0
- omlish/docker.py +149 -0
- omlish/dynamic.py +220 -0
- omlish/graphs/__init__.py +0 -0
- omlish/graphs/dot/__init__.py +19 -0
- omlish/graphs/dot/items.py +162 -0
- omlish/graphs/dot/rendering.py +147 -0
- omlish/graphs/dot/utils.py +30 -0
- omlish/graphs/trees.py +249 -0
- omlish/http/__init__.py +0 -0
- omlish/http/consts.py +20 -0
- omlish/http/wsgi.py +34 -0
- omlish/inject/__init__.py +85 -0
- omlish/inject/binder.py +12 -0
- omlish/inject/bindings.py +49 -0
- omlish/inject/eagers.py +21 -0
- omlish/inject/elements.py +43 -0
- omlish/inject/exceptions.py +49 -0
- omlish/inject/impl/__init__.py +0 -0
- omlish/inject/impl/bindings.py +19 -0
- omlish/inject/impl/elements.py +154 -0
- omlish/inject/impl/injector.py +182 -0
- omlish/inject/impl/inspect.py +98 -0
- omlish/inject/impl/private.py +109 -0
- omlish/inject/impl/providers.py +132 -0
- omlish/inject/impl/scopes.py +198 -0
- omlish/inject/injector.py +40 -0
- omlish/inject/inspect.py +14 -0
- omlish/inject/keys.py +43 -0
- omlish/inject/managed.py +24 -0
- omlish/inject/overrides.py +18 -0
- omlish/inject/private.py +29 -0
- omlish/inject/providers.py +111 -0
- omlish/inject/proxy.py +48 -0
- omlish/inject/scopes.py +84 -0
- omlish/inject/types.py +21 -0
- omlish/iterators.py +184 -0
- omlish/json.py +194 -0
- omlish/lang/__init__.py +112 -0
- omlish/lang/cached.py +267 -0
- omlish/lang/classes/__init__.py +24 -0
- omlish/lang/classes/abstract.py +74 -0
- omlish/lang/classes/restrict.py +137 -0
- omlish/lang/classes/simple.py +120 -0
- omlish/lang/classes/test/__init__.py +0 -0
- omlish/lang/classes/test/test_abstract.py +89 -0
- omlish/lang/classes/test/test_restrict.py +71 -0
- omlish/lang/classes/test/test_simple.py +58 -0
- omlish/lang/classes/test/test_virtual.py +72 -0
- omlish/lang/classes/virtual.py +130 -0
- omlish/lang/clsdct.py +67 -0
- omlish/lang/cmp.py +63 -0
- omlish/lang/contextmanagers.py +249 -0
- omlish/lang/datetimes.py +67 -0
- omlish/lang/descriptors.py +52 -0
- omlish/lang/functions.py +126 -0
- omlish/lang/imports.py +153 -0
- omlish/lang/iterables.py +54 -0
- omlish/lang/maybes.py +136 -0
- omlish/lang/objects.py +103 -0
- omlish/lang/resolving.py +50 -0
- omlish/lang/strings.py +128 -0
- omlish/lang/typing.py +92 -0
- omlish/libc.py +532 -0
- omlish/logs/__init__.py +9 -0
- omlish/logs/_abc.py +247 -0
- omlish/logs/configs.py +62 -0
- omlish/logs/filters.py +9 -0
- omlish/logs/formatters.py +67 -0
- omlish/logs/utils.py +20 -0
- omlish/marshal/__init__.py +52 -0
- omlish/marshal/any.py +25 -0
- omlish/marshal/base.py +201 -0
- omlish/marshal/base64.py +25 -0
- omlish/marshal/dataclasses.py +115 -0
- omlish/marshal/datetimes.py +90 -0
- omlish/marshal/enums.py +43 -0
- omlish/marshal/exceptions.py +7 -0
- omlish/marshal/factories.py +129 -0
- omlish/marshal/global_.py +33 -0
- omlish/marshal/iterables.py +57 -0
- omlish/marshal/mappings.py +66 -0
- omlish/marshal/naming.py +17 -0
- omlish/marshal/objects.py +106 -0
- omlish/marshal/optionals.py +49 -0
- omlish/marshal/polymorphism.py +147 -0
- omlish/marshal/primitives.py +43 -0
- omlish/marshal/registries.py +57 -0
- omlish/marshal/standard.py +80 -0
- omlish/marshal/utils.py +23 -0
- omlish/marshal/uuids.py +29 -0
- omlish/marshal/values.py +30 -0
- omlish/math.py +184 -0
- omlish/os.py +32 -0
- omlish/reflect.py +359 -0
- omlish/replserver/__init__.py +5 -0
- omlish/replserver/__main__.py +4 -0
- omlish/replserver/console.py +247 -0
- omlish/replserver/server.py +146 -0
- omlish/runmodule.py +28 -0
- omlish/stats.py +342 -0
- omlish/term.py +222 -0
- omlish/testing/__init__.py +7 -0
- omlish/testing/pydevd.py +225 -0
- omlish/testing/pytest/__init__.py +8 -0
- omlish/testing/pytest/helpers.py +35 -0
- omlish/testing/pytest/inject/__init__.py +1 -0
- omlish/testing/pytest/inject/harness.py +159 -0
- omlish/testing/pytest/plugins/__init__.py +20 -0
- omlish/testing/pytest/plugins/_registry.py +6 -0
- omlish/testing/pytest/plugins/logging.py +13 -0
- omlish/testing/pytest/plugins/pycharm.py +54 -0
- omlish/testing/pytest/plugins/repeat.py +19 -0
- omlish/testing/pytest/plugins/skips.py +32 -0
- omlish/testing/pytest/plugins/spacing.py +19 -0
- omlish/testing/pytest/plugins/switches.py +70 -0
- omlish/testing/testing.py +102 -0
- omlish/text/__init__.py +0 -0
- omlish/text/delimit.py +171 -0
- omlish/text/indent.py +50 -0
- omlish/text/parts.py +265 -0
- omlish-0.0.0.dev1.dist-info/LICENSE +21 -0
- omlish-0.0.0.dev1.dist-info/METADATA +17 -0
- omlish-0.0.0.dev1.dist-info/RECORD +187 -0
- omlish-0.0.0.dev1.dist-info/WHEEL +5 -0
- 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
|