edri.utility package¶
Subpackages¶
Submodules¶
edri.utility.cache module¶
- class edri.utility.cache.Cache[source]¶
Bases:
object- seed(key: str, when: datetime) None[source]¶
Set the last-change timestamp for
keyexplicitly.Use from
Manager.after_start()to pre-seed a cache key so its first ETag reflects a known baseline instead of process start time. A naivewhenis assumed to be in the cache timezone, keeping it comparable with the aware timestampsrenew()produces.
edri.utility.coercion module¶
Process-agnostic value coercion.
Turns a raw incoming value into the type declared by a dataclass field's
annotation, running any Injection
validators/transformers along the way. This logic is intentionally free of any
request/transport state so it can be reused outside the API Listener — for
example by the Broker when coercing WebSocket subscription filter values to the
target event field's type.
The behaviour lives on TypeCoercer. The API handlers inherit it (so an
http_handler can still override TypeCoercer.convert_type()), while
non-handler components instantiate it directly.
- class edri.utility.coercion.TypeCoercer[source]¶
Bases:
objectConverts raw values to a declared annotation type.
Stateless — every method depends only on its arguments, so a single shared instance is safe to reuse across calls and threads.
- convert_type(value: Any, annotation: type) Any[source]¶
Validates and converts input values to the specified annotation type,
- supporting:
basic types
Optional / Union / |
list / tuple / dict with type args
Literal
list-like subclasses (e.g. ListValidation[int])
Injection of validation classes (e.g. Injection((ListValidation[int],), {...}))
Nullable injection via inject(...) | None
edri.utility.function module¶
- edri.utility.function.camel2snake(title: str) str[source]¶
Converts a camelCase or PascalCase string to a snake_case string.
- Parameters:
title (str) -- The camelCase or PascalCase string to be converted.
- Returns:
The converted snake_case string.
- Return type:
str
- Examples:
>>> camel2snake("camelCaseExample") 'camel_case_example' >>> camel2snake("CamelCaseExample") 'camel_case_example'
- edri.utility.function.inject(*args: Callable[[...], Any], **kwargs: Any) Injection[source]¶
Creates an Injection for validator callables and their parameters.
This function is typically used to declaratively define dependency injections by specifying a class and any keyword arguments to pass to its constructor.
- Parameters:
*args -- Validator classes or functions called in order.
**kwargs -- Keyword arguments passed to validators whose signatures accept them.
- Returns:
An Injection object wrapping the class and its parameters.
- Return type:
Injection[T]
Example
inject(MyService, config=..., db=...)
Use
| Noneto make the injection optional:inject(StringValidator, minimum_length=2) | None
- edri.utility.function.snake2camel(title: str, /, *, exceptions: tuple[str, ...] | None = None) str[source]¶
Converts a snake_case string to a camelCase string, with optional exceptions where the conversion should not be applied.
- Parameters:
title (str) -- The snake_case string to be converted.
exceptions (Optional[Tuple[str, ...]]) -- A tuple of strings that, when matched at the beginning of the title, prevent conversion and return the title as is. Defaults to None.
- Returns:
The converted camelCase string if no exception is matched; otherwise, the original string.
- Return type:
str
- Raises:
KeyError -- If a non-trailing URL parameter is missing and subsequent placeholders are present.
- Examples:
>>> snake2camel("snake_case_example") 'snakeCaseExample' >>> snake2camel("id_example", exceptions=("id_",)) 'id_example'
edri.utility.json_encoder module¶
- class edri.utility.json_encoder.CustomJSONEncoder(*, skipkeys: bool = False, ensure_ascii: bool = True, check_circular: bool = True, allow_nan: bool = True, sort_keys: bool = False, indent: int | str | None = None, separators: tuple[str, str] | None = None, default: Callable[[Any], Any] | None = None, context: dict[str, Any] | None = None)[source]¶
Bases:
JSONEncoderCustom JSON encoder that extends Python's built-in JSONEncoder to handle additional Python types and custom serialization logic.
Supported types and custom behaviors: - Objects with a to_json() method: Serialized using that method. - Datetime-like objects with isoformat(): Serialized to ISO 8601 format. - Path objects: Serialized as POSIX-style strings. - bytes and bytearray: Encoded as hexadecimal strings. - Enum members: Serialized using their .value. - UUID instances: Serialized as hexadecimal strings. - Exception instances: Serialized as a dictionary with type, message, and args. - Dataclass instances: Converted to dictionaries via asdict.
- Parameters:
skipkeys (bool) -- Whether to skip keys not of a basic type (str, int, float, bool, None).
ensure_ascii (bool) -- If True, escapes all non-ASCII characters in output.
check_circular (bool) -- If True, checks for circular references and raises an error if found.
allow_nan (bool) -- If True, allows NaN and Infinity values (JavaScript-compatible).
sort_keys (bool) -- If True, output dictionary keys are sorted.
indent (int or None) -- If specified, pretty-prints output with given number of spaces.
separators (tuple or None) -- Tuple of (item_separator, key_separator) for customizing output.
default (callable or None) -- A fallback function for unsupported objects.
context (dict or None) -- Optional context dictionary for passing extra encoding parameters.
- default(data: Any) Any[source]¶
Serializes unsupported data types into JSON-compatible formats.
Custom serialization behavior: - If object has to_json() method: Calls and returns its result. - If object has isoformat() method (e.g., datetime): Uses its ISO 8601 representation. - If object is a Path: Returns its POSIX-style path string. - If object is bytes or bytearray: Returns a hex-encoded string. - If object is an Enum: Returns its .value. - If object is a UUID: Returns its hexadecimal string. - If object is an Exception: Returns a dict with type, message, and args. - If object is a dataclass: Converts to a dictionary via asdict().
- Parameters:
data (Any) -- The object to be serialized.
- Returns:
A JSON-serializable object.
- Return type:
Any
edri.utility.normalized_default_dict module¶
- class edri.utility.normalized_default_dict.NormalizedDefaultDict(default_factory: Callable[[], VT] | None = None, /, *args: Any, normalization: Callable[[Any], str] | None = None, **kwargs: VT)[source]¶
Bases:
defaultdict[str,VT],GenericA
defaultdictvariant that normalizes keys before dictionary operations.NormalizedDefaultDictbehaves likecollections.defaultdict, but normalizes keys consistently before storing, retrieving, checking membership, deleting, and updating values. This is useful when keys may come from user input, external APIs, configuration files, event names, headers, or other sources where the same logical key may appear in different forms.By default, keys must be strings and are normalized using
str.lower(). A custom normalization function can be supplied via thenormalizationkeyword argument.Unlike a plain
defaultdict, lookup operations such asd["KEY"]andd["key"]refer to the same stored value when the default normalization is used.- Parameters:
default_factory (Callable[[], VT] | None) -- A callable used to create default values for missing keys, exactly like
collections.defaultdict. IfNone, missing keys raiseKeyError.args (Any) -- Optional initial data passed as a mapping or iterable of key-value pairs. All keys from the initial data are normalized before being stored.
normalization (Callable[[Any], str] | None) -- A callable that receives the original lookup key and returns its normalized string representation. If omitted, the default normalization requires a string key and returns
key.lower().kwargs (VT) -- Additional initial key-value pairs. Keyword names are normalized before being stored.
- Raises:
TypeError -- If
normalizationis not callable and notNone.TypeError -- If the default normalization is used with a non-string key.
KeyError -- If a key is missing and
default_factoryisNone.
- Example:
>>> d = NormalizedDefaultDict(set) >>> d["EVENT_TYPE"].add("connection") >>> d["event_type"] {'connection'} >>> "EVENT_TYPE" in d True >>> "event_type" in d True
- Example:
>>> d = NormalizedDefaultDict( ... list, ... normalization=lambda key: str(key).strip().lower(), ... ) >>> d[" User-ID "].append(123) >>> d["user-id"] [123]
- Note:
The normalized key is what is actually stored internally. For example, with the default normalization, assigning
d["FOO"] = valuestores the key as"foo".- Note:
The
get()method does not create a default value for missing keys. This matches standarddictbehavior and avoids triggeringcollections.defaultdict.__missing__().- Warning:
The normalization function should be deterministic and should preferably be idempotent. In other words, normalizing the same key multiple times should return the same result.
- copy() Self[source]¶
Return a shallow copy of this normalized defaultdict.
The returned object preserves the same
default_factoryand normalization function. Stored keys are already normalized, so they are copied directly without re-normalization.- Returns:
Shallow copy of this instance.
- get(key: Any, default: None = None, /) VT | None[source]¶
- get(key: Any, default: VT, /) VT
- get(key: Any, default: _T, /) VT | _T
Return the value for
keyif present, otherwise returndefault.The key is normalized before lookup. Unlike
__getitem__(), this method does not call__missing__()and therefore does not create a default value as a side effect.- Parameters:
key -- Key to normalize before lookup.
default -- Value returned when the normalized key is not present.
- Returns:
Stored value or
default.
- pop(key: Any) VT[source]¶
- pop(key: Any, default: VT) VT
- pop(key: Any, default: _T) VT | _T
Remove and return the value associated with the normalized form of
key.If
defaultis provided and the normalized key is missing,defaultis returned instead of raisingKeyError.- Parameters:
key -- Key to normalize before removal.
default -- Optional fallback value returned when the key is missing.
- Returns:
Removed value, or
defaultif provided and the key is missing.- Raises:
KeyError -- If the normalized key is missing and no
defaultis provided.
- setdefault(key: Any) VT | None[source]¶
- setdefault(key: Any, default: VT) VT
Return the value for
key, insertingdefaultif the key is missing.The key is normalized before lookup or insertion.
- Parameters:
key -- Key to normalize before lookup or insertion.
default -- Value to store if the normalized key is missing.
- Returns:
Existing value if present, otherwise
default.
- update(*args: Any, **kwargs: VT) None[source]¶
Update the dictionary with normalized keys.
Accepts the same forms as
dict.update(): either one mapping, one iterable of key-value pairs, keyword arguments, or a combination of one positional source and keyword arguments.- Parameters:
args -- Optional mapping or iterable of key-value pairs.
kwargs -- Additional key-value pairs.
- Raises:
TypeError -- If more than one positional argument is provided.
edri.utility.queue module¶
edri.utility.storage module¶
- class edri.utility.storage.Storage(choices: LiteralString = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', length: int = 16)[source]¶
Bases:
dict[str,T],GenericA generic storage class that extends a dictionary to hold items of any type, identified by unique string keys. It provides functionality to automatically generate unique keys for items if not provided.
- Type Parameters:
T: The type of the items to be stored in the dictionary.
- Examples:
>>> storage = Storage[int]() >>> key1 = storage.append(100) >>> key2 = storage.append(200, key="custom_key") >>> storage[key1] 100 >>> storage["custom_key"] 200
- Note:
The Storage class ensures that all keys are unique. If a key is not provided, it automatically generates a unique key composed of random letters and digits.
The default key length is 16 characters, but this can be customized during initialization.
- append(item: T, key: str | None = None) str[source]¶
Adds an item to the storage with an automatically generated or specified key.
If the key already exists in the storage, a KeyError is raised to prevent overwriting existing items.
- Parameters:
item (T) -- The item to be stored.
key (str, optional) -- The key under which to store the item. If None, a unique key is generated automatically.
- Returns:
The key used to store the item in the dictionary.
- Return type:
str
- Raises:
KeyError -- If the specified key already exists in the storage.
- Examples:
>>> storage = Storage[str]() >>> generated_key = storage.append("apple") >>> storage[generated_key] 'apple' >>> custom_key = storage.append("banana", key="fruit_1") >>> storage["fruit_1"] 'banana' >>> storage.append("cherry", key="fruit_1") Traceback (most recent call last): ... KeyError: 'Key already exists: fruit_1'
edri.utility.transformation module¶
- class edri.utility.transformation.StringTransformer(value: Any, transform: Callable[[str], str] | None = None, lower: bool = False, upper: bool = False)[source]¶
Bases:
strA class that extends str and allows transformation on initialization. The transformation can be predefined (like lowercasing) or custom using a lambda function. The transformation settings are applied during object initialization.
- Parameters:
transform (callable, optional) -- A function that takes a string and returns a transformed version.
lower (bool, optional) -- If True, the string will be converted to lowercase.
upper (bool, optional) -- If True, the string will be converted to uppercase.
Example
>>> st = StringTransformer("Hello", lower=True) >>> print(st) hello
>>> st = StringTransformer("Hello", transform=lambda x: x[::-1]) >>> print(st) olleH
edri.utility.validation module¶
- class edri.utility.validation.DateTimeValidator(value: str | datetime, /, *, minimum_datetime: datetime | None = None, maximum_datetime: datetime | None = None)[source]¶
Bases:
datetimeA datetime type that performs validation on initialization.
This class validates the datetime against optional constraints: - Minimum allowed datetime. - Maximum allowed datetime.
Like the other validators, it accepts a single raw value (an ISO
stror an existingdatetime) so it can be used throughinject(...).- Parameters:
value (str | datetime) -- ISO datetime string (e.g.
"2024-03-28T15:45") or a datetime.minimum_datetime (datetime, optional) -- The earliest valid datetime.
maximum_datetime (datetime, optional) -- The latest valid datetime.
- Raises:
ValueError -- If the value cannot be parsed or is outside the allowed bounds.
Example
>>> DateTimeValidator("2024-03-28T15:45", minimum_datetime=datetime(2024, 3, 1)) DateTimeValidator(2024, 3, 28, 15, 45)
- class edri.utility.validation.DateValidator(value: str | date, /, *, minimum_date: date | None = None, maximum_date: date | None = None)[source]¶
Bases:
dateA date type that performs validation on initialization.
This class validates the date against optional constraints: - Minimum allowed date. - Maximum allowed date.
Like the other validators, it accepts a single raw value (an ISO
stror an existingdate/datetime) so it can be used throughinject(...).- Parameters:
- Raises:
ValueError -- If the value cannot be parsed or is outside the allowed bounds.
Example
>>> DateValidator("2024-03-28", minimum_date=date(2024, 1, 1)) DateValidator(2024, 3, 28)
- class edri.utility.validation.FloatValidator(value: Any, minimum: float | None = None, maximum: float | None = None)[source]¶
Bases:
floatA float type that performs validation on initialization.
This class validates the float against optional constraints: - Minimum and maximum allowed values.
- Parameters:
data (float) -- The input float to validate.
minimum (float, optional) -- Minimum allowed value.
maximum (float, optional) -- Maximum allowed value.
- Raises:
ValueError -- If the value is less than minimum or greater than maximum.
Example
>>> FloatValidator(3.14, minimum=1.0, maximum=5.0) 3.14
- class edri.utility.validation.IntegerValidator(value: Any, /, *, minimum: int | None = None, maximum: int | None = None)[source]¶
Bases:
intAn integer type that performs validation on initialization.
This class validates the integer against optional constraints: - Minimum and maximum allowed values.
- Parameters:
data (int) -- The input integer to validate.
minimum (int, optional) -- Minimum allowed value.
maximum (int, optional) -- Maximum allowed value.
- Raises:
ValueError -- If the value is less than minimum or greater than maximum.
Example
>>> IntegerValidator(5, minimum=1, maximum=10) 5
- class edri.utility.validation.ListValidator(iterable: Iterable[Any] = (), /, *, minimum_length: int | None = None, maximum_length: int | None = None, generics: list[Any] | None = None)[source]¶
Bases:
list[Any]A list type that performs validation on initialization.
This class validates the list against optional constraints: - Minimum allowed length. - Maximum allowed length.
- Parameters:
iterable (Iterable, optional) -- Values to initialize the list with.
minimum_length (int, optional) -- The smallest allowed list length.
maximum_length (int, optional) -- The largest allowed list length.
- Raises:
ValueError -- If the list length is outside the allowed bounds.
Example
>>> ListValidator([1, 2, 3], minimum_length=2) [1, 2, 3] >>> ListValidator([1, 2, 3], maximum_length=2) Traceback (most recent call last): ... ValueError: List length '3' is greater than maximum allowed '2'
- class edri.utility.validation.StringValidator(value: Any, /, *, maximum_length: Any | None = None, minimum_length: int | None = None, regex: Pattern[str] | None = None)[source]¶
Bases:
strA string type that performs validation on initialization.
This class validates the string against optional constraints: - A regular expression pattern. - Minimum and maximum allowed lengths. - Maximum length derived from a column-like object exposing
.type.length.- Parameters:
value (str) -- The input string to validate.
maximum_length (int, optional) -- Maximum allowed length of the string, or a column-like object with a string type length.
minimum_length (int, optional) -- Minimum required length of the string.
regex (Pattern, optional) -- A compiled regex pattern the string must match.
- Raises:
ValueError -- If the string does not match the regex, or its length is outside the allowed bounds.
Example
>>> StringValidator("hello", minimum_length=3, maximum_length=10) 'hello'
- class edri.utility.validation.TimeValidator(value: str | time, /, *, minimum_time: time | None = None, maximum_time: time | None = None)[source]¶
Bases:
timeA time type that performs validation on initialization.
This class validates the time against optional constraints: - Minimum allowed time. - Maximum allowed time.
Like the other validators, it accepts a single raw value (an ISO
stror an existingtime) so it can be used throughinject(...).- Parameters:
value (str | time) -- ISO time string (e.g.
"12:30") or a time.minimum_time (time, optional) -- The earliest valid time.
maximum_time (time, optional) -- The latest valid time.
- Raises:
ValueError -- If the value cannot be parsed or is outside the allowed bounds.
Example
>>> TimeValidator("12:30", maximum_time=time(20, 0)) TimeValidator(12, 30)