1- """
2- Caching support.
3- """
1+ """Caching support."""
42
53import collections
64from importlib import metadata
75import logging
86import time
7+ import typing as t
98
10- logger = logging .getLogger ("uwhoisd" )
9+ logger = logging .getLogger (__name__ )
10+
11+
12+ class Cache (t .Protocol ):
13+ """A WHOIS cache protocol."""
14+
15+ def get (self , key : str ) -> t .Optional [str ]:
16+ """Retrieve a value from the cache.
17+
18+ Args:
19+ key: The cache key to look up.
20+
21+ Returns:
22+ The cached value, or `None` if not found.
23+ """
24+
25+ def set (self , key : str , value : str ) -> None :
26+ """Store a value in the cache.
27+
28+ Args:
29+ key: The cache key to store the value under.
30+ value: The value to store.
31+ """
1132
1233
1334class UnknownCacheError (Exception ):
14- """
15- The supplied cache type name cannot be found.
16- """
35+ """The supplied cache type name cannot be found."""
1736
1837
19- def get_cache (cfg ):
20- """
21- Attempt to load the configured cache.
38+ def get_cache (cfg : dict [str , str ]) -> t .Optional [Cache ]:
39+ """Attempt to load the configured cache.
40+
41+ Args:
42+ cfg: The cache configuration. Must contain a "type" key giving the
43+ cache type name. Any other keys are passed as parameters to the
44+ cache constructor.
45+
46+ Returns: The cache object, or `None` if caching is disabled.
2247 """
2348 cache_name = cfg .pop ("type" , "null" )
2449 if cache_name == "null" :
@@ -33,14 +58,23 @@ def get_cache(cfg):
3358 raise UnknownCacheError (cache_name )
3459
3560
36- def wrap_whois (cache , whois_func ):
37- """
38- Wrap a WHOIS query function with a cache.
61+ def wrap_whois (
62+ cache : t .Optional [Cache ],
63+ whois_func : t .Callable [[str ], t .Awaitable [str ]],
64+ ) -> t .Callable [[str ], t .Awaitable [str ]]:
65+ """Wrap a WHOIS query function with a cache.
66+
67+ Args:
68+ cache: The cache to use, or `None` to disable caching.
69+ whois_func: The WHOIS query function to wrap.
70+
71+ Returns:
72+ The wrapped WHOIS query function.
3973 """
4074 if cache is None :
4175 return whois_func
4276
43- async def wrapped (query ) :
77+ async def wrapped (query : str ) -> str :
4478 response = cache .get (query )
4579 if response is None :
4680 response = await whois_func (query )
@@ -60,40 +94,41 @@ class LFU:
6094 2-tuples consisting of a counter giving the number of times this item
6195 occurs on the eviction queue and the value.
6296
97+ Args:
98+ max_size: Maximum number of entries the cache can contain.
99+ max_age: Maximum number of seconds to consider an entry live.
63100 """
64101
65102 # I may end up reimplementing an LRU cache if it turns out that's more apt,
66103 # but I haven't went that route as an LRU cache is somewhat more awkward
67104 # and involved to implement correctly.
68105
69- __slots__ = ("cache" , "max_age" , "max_size" , "queue" )
106+ __slots__ = (
107+ "cache" ,
108+ "max_age" ,
109+ "max_size" ,
110+ "queue" ,
111+ )
70112
71113 clock = staticmethod (time .time )
72114
73- def __init__ (self , max_size = 256 , max_age = 300 ):
74- """
75- Create a new LFU cache.
76-
77- :param max_size int: Maximum number of entries the cache can contain.
78- :param max_age int: Maximum number of seconds to consider an entry
79- live.
80- """
115+ def __init__ (self , max_size : int = 256 , max_age : int = 300 ) -> None :
81116 super ().__init__ ()
82- self .cache = {}
83- self .queue = collections .deque ()
117+ self .cache : dict [ str , tuple [ int , str ]] = {}
118+ self .queue : t . Deque [ tuple [ int , str ]] = collections .deque ()
84119 self .max_size = int (max_size )
85120 self .max_age = int (max_age )
86121
87- def evict_one (self ):
88- """
89- Remove the item at the head of the eviction cache.
90- """
122+ def evict_one (self ) -> None :
123+ """Remove the item at the head of the eviction cache."""
91124 _ , key = self .queue .popleft ()
92125 self .attempt_eviction (key )
93126
94- def attempt_eviction (self , key ):
95- """
96- Attempt to remove the named item from the cache.
127+ def attempt_eviction (self , key : str ) -> None :
128+ """Attempt to remove the named item from the cache.
129+
130+ Args:
131+ key: The cache key to evict.
97132 """
98133 counter , value = self .cache [key ]
99134 counter -= 1
@@ -102,10 +137,8 @@ def attempt_eviction(self, key):
102137 else :
103138 self .cache [key ] = (counter , value )
104139
105- def evict_expired (self ):
106- """
107- Evict any items older than the maximum age from the cache.
108- """
140+ def evict_expired (self ) -> None :
141+ """Evict any items older than the maximum age from the cache."""
109142 cutoff = self .clock () - self .max_age
110143 while len (self .queue ) > 0 :
111144 ts , key = self .queue .popleft ()
@@ -114,11 +147,14 @@ def evict_expired(self):
114147 break
115148 self .attempt_eviction (key )
116149
117- def get (self , key ):
118- """
119- Pull a value from the cache corresponding to the key.
150+ def get (self , key : str ) -> t .Optional [str ]:
151+ """Pull a value from the cache corresponding to the key.
120152
121- If no value exists, `None` is returned.
153+ Args:
154+ key: The cache key to look up.
155+
156+ Returns:
157+ The cached value, or `None` if not found.
122158 """
123159 self .evict_expired ()
124160 if key not in self .cache :
@@ -128,9 +164,12 @@ def get(self, key):
128164 self .set (key , value )
129165 return value
130166
131- def set (self , key , value ):
132- """
133- Add `value` to the cache, to be referenced by `key`.
167+ def set (self , key : str , value : str ) -> None :
168+ """Add `value` to the cache, to be referenced by `key`.
169+
170+ Args:
171+ key: The cache key to store the value under.
172+ value: The value to store.
134173 """
135174 if len (self .queue ) == self .max_size :
136175 self .evict_one ()
@@ -139,4 +178,4 @@ def set(self, key, value):
139178 else :
140179 counter = 0
141180 self .cache [key ] = (counter + 1 , value )
142- self .queue .append ((self .clock (), key ))
181+ self .queue .append ((int ( self .clock () ), key ))
0 commit comments