Utilities¶
Public API for kstlib.utils, a collection of utility helpers shared across kstlib modules. These functions
handle common tasks like dictionary merging, value formatting, lazy initialization, secure file deletion,
and email validation.
Tip
These utilities are building blocks used internally by other kstlib modules but are also available for direct use in your applications.
Quick overview¶
Dictionary helpers:
deep_merge(base, override)- Recursively merge dictionaries with override semantics
Formatting:
format_bytes(size)- Human-readable byte sizes (e.g., “1.5 GB”)format_count(n)- Human-readable counts with SI suffixesformat_duration(seconds)- Duration as “Xh Ym Zs”format_time_delta(delta)- Format timedelta objectsformat_timestamp(epoch, fmt, tz)- Config-driven epoch to datetime stringparse_size_string(s)- Parse “10MB” to bytes
Lazy initialization:
lazy_factory(factory)- Decorator for lazy singleton initialization
Secure deletion:
secure_delete(path)- Securely delete files with configurable overwrite methodsSecureDeleteMethod- Enum of deletion strategies (zeros, random, DoD, Gutmann)SecureDeleteReport- Result object with deletion details
Text processing:
replace_placeholders(template, values)- Simple{key}placeholder substitution
Validators:
parse_email_address(s)- Parse email string toEmailAddressobjectnormalize_address_list(addresses)- Normalize list of email addressesEmailAddress- Named tuple for parsed email componentsValidationError- Raised on invalid input
Dictionary Helpers¶
deep_merge¶
- kstlib.utils.deep_merge(base: 'dict[str, Any]', updates: 'Mapping[str, Any]', *, deep_copy: 'bool' = False, _depth: 'int' = 0) 'dict[str, Any]' -> dict[str, Any][source]
Recursively merge updates into base dictionary (in place).
- Parameters:
- Returns:
The modified base dictionary (for chaining).
- Raises:
RecursionError – If nesting exceeds MAX_MERGE_DEPTH.
- Return type:
Examples
>>> base = {"a": {"x": 1}, "b": 2} >>> deep_merge(base, {"a": {"y": 2}, "c": 3}) {'a': {'x': 1, 'y': 2}, 'b': 2, 'c': 3}
Formatting Functions¶
format_bytes¶
- kstlib.utils.format_bytes(size: 'float', binary: 'bool' = True) 'str' -> str[source]
Format a byte count as a human-readable string.
- Parameters:
- Returns:
Human-readable size string (e.g., “25.0 MiB” or “25.0 MB”).
- Return type:
Examples
>>> format_bytes(25 * 1024 * 1024) '25.0 MiB' >>> format_bytes(25 * 1000 * 1000, binary=False) '25.0 MB'
format_count¶
format_duration¶
- kstlib.utils.format_duration(seconds: 'float') 'str' -> str[source]
Format a duration in seconds as a human-readable string.
- Parameters:
seconds (float) – Duration in seconds.
- Returns:
Human-readable duration (e.g., “5 minutes”, “2 hours”).
- Return type:
Examples
>>> format_duration(300) '5 minutes' >>> format_duration(3661) 'an hour'
format_time_delta¶
- kstlib.utils.format_time_delta(dt: 'datetime', other: 'datetime | None' = None) 'str' -> str[source]
Format a datetime as a relative time string.
- Parameters:
dt (datetime) – Target datetime.
other (datetime | None) – Reference datetime (defaults to now).
- Returns:
Relative time string (e.g., “2 hours ago”, “in 3 days”).
- Return type:
Examples
>>> from datetime import datetime, timedelta >>> past = datetime.now() - timedelta(hours=2) >>> format_time_delta(past) '2 hours ago'
format_timestamp¶
- kstlib.utils.format_timestamp(epoch: 'float | str | None', fmt: 'str | None' = None, tz: 'str | None' = None) 'str' -> str[source]
Format an epoch timestamp as a human-readable datetime string.
Converts Unix epoch timestamps to formatted datetime strings using pendulum for timezone-aware formatting. Configuration can be loaded from kstlib.conf.yml or provided explicitly.
- Parameters:
epoch (float | str | None) – Unix timestamp (seconds since 1970-01-01 UTC). Accepts int, float, or string representation. Returns “(invalid)” if None or unparseable.
fmt (str | None) – Datetime format string (pendulum tokens). If None, uses config value or “YYYY-MM-DD HH:mm:ss”.
tz (str | None) – Timezone for display (“local”, “UTC”, or IANA name). If None, uses config value or “local”.
- Returns:
Formatted datetime string, or “(invalid)” on error.
- Return type:
Examples
>>> format_timestamp(1706234567, tz="UTC") '2024-01-26 02:02:47' >>> format_timestamp(1706234567, fmt="DD/MM/YYYY", tz="UTC") '26/01/2024' >>> format_timestamp(None) '(invalid)'
parse_size_string¶
- kstlib.utils.parse_size_string(value: 'str | float') 'int' -> int[source]
Parse a human-readable size string into bytes.
Accepts raw integers, floats, or strings with optional units. Supported units: B, K, KB, KiB, M, MB, MiB, G, GB, GiB, T, TB, TiB.
- Parameters:
value (str | float) – Size as int, float, or string with optional unit suffix.
- Returns:
Size in bytes as an integer.
- Raises:
ValueError – If the string format is invalid or the unit is unknown.
- Return type:
Examples
>>> parse_size_string(1024) 1024 >>> parse_size_string("25M") 26214400 >>> parse_size_string("100 MiB") 104857600 >>> parse_size_string("1.5GB") 1610612736
Lazy Initialization¶
lazy_factory¶
- kstlib.utils.lazy_factory(module_path: 'str', class_name: 'str') 'Callable[[Callable[..., T]], Callable[..., T]]' -> Callable[[Callable[..., T]], Callable[..., T]][source]
Defer import of a class until the factory function is first called.
Defers the import of the specified class until the factory is actually called, reducing startup time when the factory is registered but not used.
- Parameters:
- Returns:
A decorator that wraps the factory function with lazy import behavior.
- Return type:
Callable[[Callable[…, T]], Callable[…, T]]
Example
>>> @lazy_factory("kstlib.secrets.providers.sops", "SOPSProvider") ... def _sops_factory(**kwargs): ... ... # Body is ignored, class is instantiated automatically >>> >>> # SOPSProvider is only imported when _sops_factory() is called >>> provider = _sops_factory(path="secrets.sops.yml")
Secure Deletion¶
secure_delete¶
- kstlib.utils.secure_delete(target: 'Path | str', *, passes: 'int' = 3, method: 'SecureDeleteMethod | str' = <SecureDeleteMethod.AUTO: 'auto'>, chunk_size: 'int' = 1048576, zero_last_pass: 'bool' = True) 'SecureDeleteReport' -> SecureDeleteReport[source]
Securely remove
targetfrom disk.- Parameters:
passes (int) – Number of overwrite passes to perform when relying on the built-in overwrite implementation. Values lower than
1raiseValueError.method (SecureDeleteMethod | str) – Preferred strategy.
autoattempts to use a platform shred command and falls back to overwriting when none is available or when the command fails.commandforces the usage of a system command and reports an error if it is not available.overwriteforces the Python overwrite implementation.chunk_size (int) – Size, in bytes, of the chunks written during the overwrite loop. Defaults to 1 MiB.
zero_last_pass (bool) – If
True, the final overwrite pass writes zeros instead of random data.
- Returns:
A
SecureDeleteReportdescribing the outcome.- Raises:
ValueError – If
passesis lower than1or iftargetdoes not reference a regular file.- Return type:
SecureDeleteReport
Example
Securely remove a cleartext file once it is no longer needed:
>>> from pathlib import Path >>> from kstlib.utils.secure_delete import secure_delete, SecureDeleteMethod >>> path = Path("secret.txt") >>> _ = path.write_text("classified") >>> report = secure_delete(path, method=SecureDeleteMethod.OVERWRITE, passes=1) >>> report.success True
SecureDeleteMethod¶
SecureDeleteReport¶
- class kstlib.utils.SecureDeleteReport(success, method, passes, command=None, message=None)[source]
Bases:
objectSummary result produced by
secure_delete().- success: bool
- method: SecureDeleteMethod
- passes: int
- __init__(self, success: 'bool', method: 'SecureDeleteMethod', passes: 'int', command: 'Sequence[str] | None' = None, message: 'str | None' = None) None -> None
Text Processing¶
replace_placeholders¶
- kstlib.utils.replace_placeholders(template: 'str', values: 'Mapping[str, Any] | None' = None, /, **kwargs: 'Any') 'str' -> str[source]
Replace
{{ placeholder }}tokens within template.- Parameters:
- Returns:
Rendered template with matching placeholders substituted by their string representation. Missing placeholders are left untouched to simplify incremental rendering.
- Return type:
Examples
>>> replace_placeholders("Hello {{ name }}!", name="Ada") 'Hello Ada!'
Validators¶
parse_email_address¶
- kstlib.utils.parse_email_address(value: 'str') 'EmailAddress' -> EmailAddress[source]
Parse value into a validated
EmailAddress.- Parameters:
value (str) – Raw email string, optionally containing a display name.
- Returns:
A normalized
EmailAddressinstance.- Raises:
ValidationError – If the string does not contain a valid address.
- Return type:
EmailAddress
Examples
>>> parse_email_address("Ada Lovelace <ADA@example.COM>").formatted 'Ada Lovelace <ada@example.com>' >>> parse_email_address("foo@bar") Traceback (most recent call last): ... kstlib.utils.validators.ValidationError: Invalid email address: 'foo@bar'
normalize_address_list¶
- kstlib.utils.normalize_address_list(values: 'Iterable[str]') 'list[EmailAddress]' -> list[EmailAddress][source]
Validate and normalize a sequence of email addresses.
Examples
>>> normalize_address_list([ ... "Ada Lovelace <ada@example.com>", ... "grace@example.net", ... ]) [EmailAddress(name='Ada Lovelace', address='ada@example.com'), EmailAddress(name='', address='grace@example.net')]
EmailAddress¶
ValidationError¶
- class kstlib.utils.ValidationError[source]
Bases:
KstlibError,ValueErrorRaised when user supplied values fail validation.
HTTP Tracing¶
HTTPTraceLogger¶
- class kstlib.utils.HTTPTraceLogger(logger, *, trace_level=5, sensitive_keys=None, pretty_print=True, max_body_length=2000)[source]
Bases:
objectReusable HTTP trace logger with sensitive data redaction.
This class provides httpx event hooks for logging HTTP requests and responses at TRACE level with automatic redaction of sensitive data.
- Parameters:
logger (logging.Logger) – Logger instance to use for trace output.
trace_level (int) – Logging level for trace messages (default: 5 for TRACE).
sensitive_keys (frozenset[str] | None) – Set of keys to redact in request bodies.
pretty_print (bool) – Whether to pretty-print JSON responses.
max_body_length (int) – Maximum response body length before truncation.
Examples
>>> import logging >>> import httpx >>> from kstlib.utils.http_trace import HTTPTraceLogger >>> tracer = HTTPTraceLogger(logging.getLogger(__name__)) >>> client = httpx.Client( ... event_hooks={ ... "request": [tracer.on_request], ... "response": [tracer.on_response], ... } ... )
- __init__(self, logger: 'logging.Logger', *, trace_level: 'int' = 5, sensitive_keys: 'frozenset[str] | None' = None, pretty_print: 'bool' = True, max_body_length: 'int' = 2000) 'None' -> None[source]
Initialize the HTTP trace logger.
- configure(self, *, pretty_print: 'bool | None' = None, max_body_length: 'int | None' = None) 'None' -> None[source]
Update trace configuration at runtime.
- on_request(self, request: 'httpx.Request') 'None' -> None[source]
Httpx event hook for outgoing requests (TRACE logging).
Redacts sensitive data in request body and Authorization headers.
- Parameters:
request (httpx.Request) – The outgoing HTTP request.
- on_response(self, response: 'httpx.Response') 'None' -> None[source]
Httpx event hook for incoming responses (TRACE logging).
Optionally pretty-prints JSON and truncates long bodies.
- Parameters:
response (httpx.Response) – The incoming HTTP response.