# Utility This document describes small helper classes that are used across EDRI. --- ## `Storage` `Storage[T]` is a small in-memory key/value store (`dict[str, T]`) that can **generate unique random keys** for stored items. ### Storage use cases * generating short-lived lookup keys (tickets, temporary IDs) * keeping in-memory objects indexed by a random token ### Key generation * If you do not provide a key, `Storage` generates one. * Keys are generated from a configurable character set (`choices`) and length. * The default character set is ASCII letters + digits. * The default key length is **16**. * Key generation loops until it finds a key that is not currently used. ### Storage API * `append(item, key=None) -> str` * stores `item` * returns the key used * raises `KeyError` if you provide a key that already exists ### Storage example ```python storage = Storage[int]() k1 = storage.append(100) # auto-generated key k2 = storage.append(200, key="job") # explicit key assert storage[k1] == 100 assert storage["job"] == 200 ``` ### Storage gotchas * `Storage` is an in-memory dict. It is not persistent. * If you need cryptographic-strength tokens, consider using a cryptographic RNG (this implementation uses `random.choice`). --- ## `Queue` `Queue[T]` extends Python’s `queue.Queue` by adding `unget(...)`, which inserts an item at the **front** of the queue. ### Queue use cases * re-queueing an item that should be processed again next * implementing simple reprioritization within a FIFO queue ### Queue API * `unget(item, block=True, timeout=None) -> None` * behaves like `put(...)` regarding blocking/full-queue behavior * but inserts at the *front* instead of the end ### Queue example ```python q = Queue[int]() q.put(1) q.put(2) q.unget(0) # will be processed next assert q.get() == 0 assert q.get() == 1 assert q.get() == 2 ``` ### Queue gotchas * If the queue has a `maxsize` and is full, `unget` can block or raise `queue.Full`, consistent with `put`. * `unget` increments `unfinished_tasks`, just like `put`. --- ## `NormalizedDefaultDict` `NormalizedDefaultDict[VT]` is a `defaultdict` variant that **normalizes keys** on both set and get. Keys are always normalized to `str`, so the class takes a single type parameter for the value type (`VT`). ### NormalizedDefaultDict use cases * storing values indexed by case-insensitive keys (e.g. event names, resource names) * preventing accidental duplicates such as `"EVENT"` vs `"event"` ### Normalization * Default normalization is `key.lower()`. * You can pass a custom `normalization=...` function. * If you initialize from a `dict`, the initial keys are normalized too. ### NormalizedDefaultDict example ```python d = NormalizedDefaultDict(set) # default_factory is positional-only d["EVENT_TYPE"].add("client-1") d["event_type"].add("client-2") assert d["Event_Type"] == {"client-1", "client-2"} ``` ### Custom normalization example ```python d = NormalizedDefaultDict( list, # default_factory is positional-only normalization=lambda x: str(x).strip().lower(), ) d[" Hello "].append(1) assert d["hello"] == [1] ``` ### NormalizedDefaultDict gotchas * The default normalization assumes the key supports `.lower()`. * In practice, this is intended for string keys. * If you want to accept non-string keys, provide a normalization function that converts to string first.