|
1 | 1 | from collections import OrderedDict |
2 | 2 | from collections.abc import Awaitable, Callable |
3 | | -from contextlib import suppress |
4 | 3 | from dataclasses import dataclass, field |
| 4 | +from typing import ClassVar |
| 5 | + |
| 6 | + |
| 7 | +class NoExternalValue: |
| 8 | + _instance: "ClassVar[NoExternalValue | None]" = None |
| 9 | + |
| 10 | + def __new__(cls) -> "NoExternalValue": |
| 11 | + if NoExternalValue._instance is not None: |
| 12 | + return NoExternalValue._instance |
| 13 | + |
| 14 | + instance = super().__new__(cls) |
| 15 | + NoExternalValue._instance = instance |
| 16 | + |
| 17 | + return instance |
| 18 | + |
| 19 | + |
| 20 | +type ExternalValue[ValueT] = ValueT | NoExternalValue |
5 | 21 |
|
6 | 22 |
|
7 | 23 | @dataclass(frozen=True, unsafe_hash=False) |
8 | 24 | class LazyMap[KeyT, ValueT]: |
9 | | - _computed_map_max_len: int |
10 | | - _external_value: Callable[[KeyT], Awaitable[ValueT | None]] |
11 | | - _computed_map: OrderedDict[KeyT, ValueT] = field( |
| 25 | + _cache_map_max_len: int |
| 26 | + _external_value: Callable[[KeyT], Awaitable[ExternalValue[ValueT]]] |
| 27 | + |
| 28 | + _cache_map: OrderedDict[KeyT, ExternalValue[ValueT]] = field( |
12 | 29 | init=False, default_factory=OrderedDict |
13 | 30 | ) |
14 | 31 |
|
15 | | - async def __getitem__(self, key: KeyT) -> ValueT | None: |
16 | | - with suppress(KeyError): |
17 | | - return self._computed_map[key] |
| 32 | + def cache_map(self) -> OrderedDict[KeyT, ExternalValue[ValueT]]: |
| 33 | + return OrderedDict(self._cache_map) |
18 | 34 |
|
19 | | - value = await self._external_value(key) |
| 35 | + async def __getitem__(self, key: KeyT) -> ValueT: |
| 36 | + if key in self._cache_map: |
| 37 | + return self._output(self._cache_map[key]) |
20 | 38 |
|
21 | | - if value is None: |
22 | | - return None |
| 39 | + value = await self._external_value(key) |
| 40 | + self._cache_map[key] = value |
23 | 41 |
|
24 | | - self._computed_map[key] = value |
| 42 | + if len(self._cache_map) > self._cache_map_max_len: |
| 43 | + self._cache_map.pop(next(iter(self._cache_map))) |
25 | 44 |
|
26 | | - if len(self._computed_map) > self._computed_map_max_len: |
27 | | - self._computed_map.pop(next(iter(self._computed_map))) |
28 | | - |
29 | | - return value |
| 45 | + return self._output(value) |
30 | 46 |
|
31 | 47 | def __setitem__(self, key: KeyT, value: ValueT) -> None: |
32 | | - self._computed_map[key] = value |
| 48 | + self._cache_map[key] = value |
| 49 | + |
| 50 | + def _output(self, value: ExternalValue[ValueT]) -> ValueT: |
| 51 | + if isinstance(value, NoExternalValue): |
| 52 | + raise KeyError |
33 | 53 |
|
34 | | - def __delitem__(self, key: KeyT) -> None: |
35 | | - del self._computed_map[key] |
| 54 | + return value |
0 commit comments