Utilities¶
Haiway exposes a small set of utility primitives for common application needs: data normalization, environment loading, logging bootstrap, pagination state, and async producer-consumer coordination.
Most utilities described here are publicly exported from haiway. A few narrower helpers are kept
under submodules, such as format_str in haiway.utils.formatting and AsyncQueueEmpty in
haiway.utils.queue.
Collection Helpers¶
The collection helpers normalize inputs into concrete container types while preserving None.
from haiway import as_dict, as_list, as_map, as_set, as_tuple
values = as_list(range(3)) # [0, 1, 2]
names = as_tuple(["a", "b"]) # ("a", "b")
unique = as_set(["a", "a", "b"]) # {"a", "b"}
mapping = as_dict({"a": 1}) # {"a": 1}
immutable = as_map({"a": 1}) # Map({"a": 1})
Available helpers¶
as_list(iterable)converts any iterable tolist, returning the original list unchanged.as_tuple(iterable)converts any iterable totuple, returning the original tuple unchanged.as_set(collection)converts any iterable toset, returning the original set unchanged.as_dict(mapping)converts any mapping todict, returning the original dict unchanged.as_map(mapping)converts any mapping to Haiway's immutableMap.
All five functions return None when passed None.
Removing MISSING values¶
Use without_missing() when building payloads or state updates from optional values that may carry
Haiway's MISSING sentinel.
from haiway import MISSING, without_missing
payload = without_missing(
{
"name": "alice",
"nickname": MISSING,
"age": 30,
}
)
assert payload == {"name": "alice", "age": 30}
without_missing() only removes values equal to MISSING. It does not remove None, False, or
empty strings.
Environment Helpers¶
The environment helpers provide typed access to os.environ and a minimal .env loader.
from haiway import getenv_bool, getenv_int, getenv_str, load_env
load_env() # reads .env if present
debug = getenv_bool("DEBUG", False)
port = getenv_int("PORT", 8080)
database_url = getenv_str("DATABASE_URL", required=True)
Generic access¶
Use getenv() when you need a custom parser:
If the variable is set but parsing fails, getenv() raises ValueError.
Specialized accessors¶
getenv_bool(key, default=None, required=False)treats only"true","1", and"t"case-insensitively asTrue. Any other present value becomesFalse.getenv_int(...)parses integers and raisesValueErroron invalid input.getenv_float(...)parses floats and raisesValueErroron invalid input.getenv_str(...)returns the raw string value.getenv_base64(key, decoder=..., default=None, required=False)validates base64 input, decodes it to bytes, then applies your decoder function.
Loading .env¶
load_env(path=None, override=True) is intentionally minimal:
- It defaults to
.envin the current working directory. - It ignores lines starting with
#. - It expects
KEY=VALUEper line. - It silently does nothing when the file does not exist.
- It does not implement inline comments, shell expansion, or quoting rules.
That makes it suitable for lightweight app bootstrap, but not a drop-in replacement for more feature-rich env parsers.
Logging Bootstrap¶
setup_logging() configures standard-library logging to write to stdout.
Behavior¶
- Configures the root logger and any explicitly named loggers.
- Uses
INFOby default, orDEBUGwhenDEBUG_LOGGINGis enabled. - Includes timestamps by default and can disable them with
time=False. - Disables previously created loggers by default via
disable_existing_loggers=True.
This helper should normally be called once during application startup.
Pagination Primitives¶
Haiway provides immutable pagination objects for integrations and service layers.
Pagination¶
Pagination is a State carrying:
token: UUID | str | int | Nonelimit: intarguments: Mapping[str, BasicValue]
from haiway import Pagination
pagination = Pagination.of(limit=50, region="eu")
next_page = pagination.with_token("cursor-2").with_arguments(sort="desc")
The with_*() methods always return updated immutable copies. with_arguments() merges new
arguments over existing ones and returns the same instance when no arguments are provided.
Paginated¶
Paginated[Element] stores page items together with the Pagination metadata that produced or
describes the page.
from haiway import Paginated, Pagination
page = Paginated.of(
[1, 2, 3],
pagination=Pagination(limit=3, arguments={}),
)
assert list(page) == [1, 2, 3]
assert page.items == (1, 2, 3)
Paginated behaves like a read-only sequence. Items are stored as an immutable tuple.
Detecting continuation¶
page.has_next_page is intentionally permissive:
- it is
Truewhen a pagination token is present - it is also
Truewhen the page size is greater than or equal topagination.limit
This supports providers that do not return explicit continuation tokens and instead imply "more results may exist" when a page is full.
Async Producer-Consumer Primitives¶
Haiway ships two related async iterators for single-consumer workflows: AsyncQueue and
AsyncStream.
AsyncQueue¶
AsyncQueue is a buffered async iterator.
from haiway import AsyncQueue
queue: AsyncQueue[int] = AsyncQueue()
queue.enqueue(1)
queue.enqueue(2)
queue.finish()
items = [item async for item in queue] # [1, 2]
Use AsyncQueue when producers may outpace the consumer and buffering is acceptable.
Key behavior:
enqueue()immediately delivers to a waiting consumer or appends to an internal buffer.pending_next()returns a buffered item synchronously.pending_next()raisesAsyncQueueEmptyfromhaiway.utils.queuewhen the queue is open but currently empty.finish()stops futureenqueue()calls and ends iteration after buffered items are drained.finish(exception)re-raises that exception on the consumer after buffered items are drained.cancel()is shorthand for finishing withCancelledError.clear()drops only currently buffered items and leaves a waiting consumer intact.
AsyncStream¶
AsyncStream is a flow-controlled async iterator with back-pressure.
from haiway import AsyncStream, ctx
stream: AsyncStream[int] = AsyncStream()
async def producer() -> None:
for i in range(3):
await stream.send(i)
stream.finish()
# `ctx.spawn(producer)` starts `producer()` concurrently while iteration continues below.
ctx.spawn(producer)
items = [item async for item in stream] # [0, 1, 2]
Use AsyncStream when producers should wait for the consumer to accept each item.
Key behavior:
send()suspends until the consumer takes the element if no consumer is currently waiting.finish()ends the stream immediately for future reads.finish(exception)re-raises that exception on the consumer.cancel()is shorthand for finishing withCancelledError.send()to a finished stream is ignored.- Pending producers are released when the stream finishes.
Choosing between them¶
- Use
AsyncQueuefor buffered handoff. - Use
AsyncStreamfor back-pressure and producer-consumer pacing. - Both support exactly one active consumer at a time.
Formatting Values for Diagnostics¶
format_str() recursively formats nested values into a readable string representation for logs,
errors, and observability output. It is available from haiway.utils.formatting.
from haiway.utils.formatting import format_str
formatted = format_str(
{
"user": "alice",
"roles": ["admin", "editor"],
}
)
Formatting rules include:
- strings are quoted
- multiline strings use an indented triple-quoted block
- mappings and sequences are rendered with indentation
- bytes-like values are rendered as
<<<N bytes>>> datetimevalues use ISO 8601UUIDvalues use their canonical string formMISSINGrenders as an empty value and is skipped inside nested structures
This function is primarily useful for human-readable diagnostics rather than stable serialization.