Standard and Workflow Utilities¶
Invoking Python callbacks¶
await lclang.utils.invoke(callback, *args, **kwargs) calls a synchronous or
asynchronous callable exactly once, then resolves any returned awaitables using
the shared utility resolver. Its type signature retains the
callback's parameters and final return type. Ordinary values retain identity.
Native failures become LclUtilityError with the original exception in
__cause__; native groups become LclErrorGroup with their members retained.
Existing LCL errors and control signals propagate unchanged. The helper creates
no thread, background task, or synchronous event-loop boundary.
Builtin values¶
The canonical runtime hierarchy exposes these value types:
bool, bytes, dict, float, frozenset, int, list, set, str, and
tuple.
SnowflakeGenerator is also available as an explicit stateful utility constructor;
see Snowflake IDs below.
It also exposes these functions:
abs, all, any, bin, chr, divmod, enumerate, filter, format,
hex, isinstance, len, map, max, min, oct, ord, parse_ymd,
pow, range, recursive, repr, reversed, round, slice, sorted,
sum, to_ymd, and zip.
parse_ymd converts strict eight-character YYYYMMDD strings to dates;
to_ymd returns that string format. Integer inputs are rejected. recursive(builder) returns an eager fixed-point
function suitable for recursive LCL programs.
Python callers can import parse_ymd and to_ymd from lclang.utils; neither
helper loads the LCL interpreter.
Standard namespaces¶
STANDARD_PRESET supplies read-only namespaces assembled from reviewed
manifests. Canonical Frames merge those bindings into LCL_BUILTINS:
iter.collectanditer.firstconsume synchronous or asynchronous iterables.text.joinandtext.linesprovide deterministic text operations.data.lookupanddata.mergework with immutable mapping snapshots.json.encodeandjson.decodeprovide strict JSON conversion.
The iter, text, data and json helpers perform only their documented
operations. The separately exposed env utility reads the live process
environment. Calendar file loading can access the filesystem when explicitly
configured by the application. These capabilities follow the trusted-code model.
Canonical Frames also expose the reviewed calendars namespace and explicit
calendar-manager construction helpers. Filesystem access remains opt-in through
use_file_system_hardcoded_calendar_loader; see the
business day calendar reference.
Python integrations may import STANDARD_MANIFESTS, STANDARD_PRESET,
StdlibEntry, StdlibManifest, StdlibNamespace, RecursiveFunction,
assemble_stdlib, collect, first, join, lines, lookup, merge,
json_encode, json_decode, and recursive from lclang.lang.stdlib.
Snowflake IDs¶
from lclang.utils import SnowflakeGenerator exposes
SnowflakeGenerator(worker_id, *, epoch_ms=1704067200000). The same constructor
is a canonical LCL builtin. Retain one instance and call generator.next_id()
to obtain a nonnegative Python integer that fits in a signed 64-bit column.
Construction and generation are synchronous, with no I/O or owned async resources.
The layout is (elapsed_ms << 22) | (worker_id << 12) | sequence: 41 bits of
elapsed UTC Unix milliseconds, 10 worker bits, and 12 sequence bits. The default
epoch is 2024-01-01 00:00:00 UTC. The clock is read with integer time.time_ns()
conversion, never floating-point seconds. The first sequence in each millisecond
is zero; IDs strictly increase within an instance. Worker zero at the epoch can
produce ID zero. The maximum elapsed time is 2**41 - 1 milliseconds, about
69.7 years after the configured epoch; at most 4096 IDs fit in each millisecond
per worker. These are identifiers, not secrets or cryptographic random values.
Both arguments must be integers excluding bool (LclValidationError otherwise).
worker_id must be in 0..1023 and epoch_ms must be nonnegative (LclValidationError
otherwise). The read-only properties worker_id and epoch_ms expose the
validated configuration. Each instance serializes clock reads and state updates
with a thread lock, so Python threads and tasks in different event loops can
share it. It never sleeps or spins waiting for the clock.
next_id() raises LclValidationError when the clock precedes the epoch,
LclStateError when it moves behind the last successful millisecond, and
OverflowError when the sequence or 41-bit timestamp is exhausted. Failures
leave the last successful timestamp and sequence unchanged. After sequence
exhaustion, the caller may retry when the clock advances; after rollback, it
must wait until the clock catches up (and advances if that sequence was full).
Timestamp exhaustion requires a planned migration to a new ID domain.
Uniqueness across instances is the application's responsibility: use the same epoch throughout an ID domain and assign distinct worker IDs to concurrent generators/processes. No worker discovery, coordination, or persistent state is provided. Do not copy a generator into a forked child or recreate it per ID. Reusing a worker ID after restart is safe only once wall time is strictly beyond every timestamp previously emitted by that worker. Changing the epoch does not preserve uniqueness with old IDs.
In LCL, define ids: SnowflakeGenerator(worker_id) once and use
request_id: ids.next_id(). A named result is a cached Frame snapshot: repeated
frame.get("request_id") returns the same ID, while explicit recalculation
generates another. Do not recalculate the ids definition: that resets its state.
For multiple Frames using the same worker, construct the generator in Python
and share it through a preset instead of constructing one in each Frame.
See the executable Python and LCL examples.
Environment and logging utilities¶
from lclang.utils import env returns the singleton live Environment view.
env.NAME and env.get(name, default=None) consult os.environ at each call;
missing names return the default and reads never snapshot or mutate the process
environment. The canonical LCL env utility adds scoped Frame overrides on top
of this view.
Python env.field_names() returns sorted Unicode identifier names, including
language keywords. The Frame adapter removes names reserved by LCL from its
attribute listing; env.get(name) still supports those names explicitly.
Process logging lives in lclang.logger. Its two async entry points are
use_logger_handler(config) and use_logger(name=None, prefix="", emit_level=0).
The handler scope owns console and named file output and restores stdlib logging
on exit. CLI and Workflow applications resolve the same configuration from LCL
and command-line overrides; see the logger reference.
Workflow status¶
lclang.workflow exports:
ExecutionStatus: the status value used by tasks and steps.ExecutionTaskType: task classification.ExecutionStatusTree: an ordered public record of a task subtree.ExecutionStatusManager: creates and coordinates nested task cursors.ExecutionStatusStep: a scoped step whose description and status may be updated atomically.
Task and step context managers finalize successful scopes automatically, record
failures, and allow exceptions to propagate. Parent aggregation is
deterministic, subtree operations are locked, and snapshots preserve child
order. FAILURE_COVERED records an exception handled by an explicit workflow
context without disguising it as success. See the workflow reference
for task definitions, mappings, execution, static rendering, and CLI conversion.
Safe representations¶
lclang.utils.safe_repr(value, *, max_length=200, renderer=None, masked=False)
returns one physical line without a type prefix or task-local masking state.
It uses repr or the supplied renderer(value), escapes CR and LF, and converts
renderer exceptions (including BaseException) or non-string results to
<repr failed: ExceptionType>. The length budget includes ...<truncated>;
small budgets retain its prefix, zero returns empty text, and None disables
truncation. A negative budget raises LclValidationError; non-integers, including bool,
raise LclValidationError. With masked=True, the result is *masked* and no renderer
runs; the masking marker is independent of the validated budget.
Multiline representations¶
See also dataclass flattening for creating dotted preset keys.
make_repr_lines(instances, key_column_length=None, sort_keys=False,
masked_keys=None) accepts one dataclass instance or string-keyed dictionary,
or an iterable of those records. It retains declaration, insertion and record
order, including duplicate names; sorting by name is stable when requested.
The key width is the maximum of all name lengths and the supplied minimum.
Dataclass classes, unsupported records and non-string keys raise LclValidationError.
Widths must be nonnegative integers (not bool), or None. Empty inputs yield
no lines. Masked names are checked before getters or dictionary reads and
render as *masked*; other values use safe_repr. Neither nested values nor
masks are recursively expanded or copied.
make_multi_log_lines(title, lines, line_indent=" ") indents every body
line, including empty lines and embedded newlines, while leaving the title
unchanged. An empty body produces only the title. Both helpers are synchronous
and available from lclang.utils and lclang.utils.value_representation.
from lclang.utils import make_multi_log_lines, make_repr_lines
lines = make_repr_lines({"count": 3, "token": "secret"}, masked_keys={"token"})
message = make_multi_log_lines("scan completed", lines)
assert message == "scan completed\n count: 3\n token: *masked*"
Each dictionary field contributes one line. The two names have equal width; the token value is never read or represented. Joining the lines creates one message suitable for a single logger call.
Dataclass flattening¶
flatten_to_dict(instance, prefix="", nested=True) always expands the root
dataclass's direct fields into a new dictionary. Values remain shared by
reference. nested=False keeps nested records as leaves; nested=True expands
every nested dataclass instance; a set[str] expands only its relative paths
and the ancestors needed to reach them. Set paths exclude the prefix. An empty
set is equivalent to False. Containers and dataclass class objects are leaves.
Empty nested records also remain leaves so an explicitly present empty value
does not disappear. Empty root records produce an empty dictionary.
Invalid root/control types raise LclValidationError. Invalid, unknown or non-record
selected paths and cycles on expanded paths raise LclValidationError. Reusing an
acyclic record on different branches is supported. The helper never calls a
constructor or default factory, performs no masking or box conversion, and
does not deep-copy values. It uses dataclasses.fields, including init=False
instance fields but excluding ClassVar and InitVar. When creating CLI presets,
omit computed init=False fields because they are not configurable inputs.
from dataclasses import dataclass
from lclang.utils import flatten_to_dict
@dataclass
class Dialect:
delimiter: str = ","
@dataclass
class Options:
dialect: Dialect
headers: list[str]
options = Options(Dialect(), ["name"])
flat = flatten_to_dict(options, "csv", {"dialect"})
assert flat == {"csv.dialect.delimiter": ",", "csv.headers": ["name"]}
assert flat["csv.headers"] is options.headers
assert flatten_to_dict(options, nested=False)["dialect"] is options.dialect
The selected dialect becomes a dotted leaf. The headers list remains the same
object, and shallow expansion preserves the dialect instance too. Pass flat
to workflow.to_cli(..., preset=flat) when individual CLI overrides are needed.
See errors and diagnostic codes for exception fields, specific
codes, cause chains, and executable troubleshooting examples. Ordinary library
validation uses LclValidationError; native callback and operation failures
retain their original exception in __cause__.
Awaitable resolution¶
Import resolve_awaitable from lclang.utils to unwrap a value that may contain
several layers of awaitables. Immediate values retain their identity. The helper
awaits each layer in the current task; it creates no task, thread or event loop.
Failures and cancellation propagate unchanged. The caller owns any resources
acquired by the awaited operations.
import asyncio
from lclang.utils import resolve_awaitable
async def main():
async def inner():
return 42
async def outer():
return inner()
value = []
assert await resolve_awaitable(value) is value
assert await resolve_awaitable(outer()) == 42
asyncio.run(main())
The outer coroutine returns another coroutine, which resolves to 42. The
ordinary list passes through unchanged. Library boundaries that add diagnostic
codes use a separate error adapter; the utility itself preserves the exception.
Binding masks¶
split_masked_name, normalize_masked_mapping and normalize_masked_names
operate on string keys without parsing LCL. A single trailing ! marks a key as
masked. Normalization returns a new dictionary and a frozenset of exact names;
values remain shared. A collision such as token and token!, a malformed
marker, or an explicit mask without a matching key raises LclValidationError.
These helpers record masking metadata. Apply that metadata when rendering values.
from lclang.utils import (
MASKED_VALUE,
normalize_masked_mapping,
normalize_masked_names,
safe_repr,
split_masked_name,
)
secret = ["token-value"]
values, masks = normalize_masked_mapping({"token!": secret, "count": 3})
assert values == {"token": ["token-value"], "count": 3}
assert values["token"] is secret
assert masks == frozenset({"token"})
assert split_masked_name("count") == ("count", False)
assert normalize_masked_names(["token", "token"]) == masks
assert safe_repr(values["token"], masked="token" in masks) == MASKED_VALUE
token! becomes token, while its payload retains identity. The mask makes
safe_repr return *masked* without inspecting the payload.
Default declarations¶
DefaultBinding, DefaultOmission and NO_DEFAULT describe defaults for Python
integrations. NO_DEFAULT distinguishes omission from an explicit None.
A declaration holds either a value or a no-argument factory. Construction never
calls the factory. The consumer decides when to call it and whether to await its
result; supplying both forms raises LclValidationError.
from lclang.utils import DefaultBinding, DefaultOmission, NO_DEFAULT
omitted = DefaultBinding()
null = DefaultBinding(value=None)
fresh = DefaultBinding(factory=list)
assert omitted.value is NO_DEFAULT is DefaultOmission.ABSENT
assert null.value is None
assert fresh.factory is not None
first = fresh.factory()
second = fresh.factory()
assert first == second == []
assert first is not second
The literal null remains an explicit default. Calling the factory twice creates independent lists, so consumers can avoid sharing mutable defaults.
Scoped proxy markers¶
ScopedProxyFactory and ScopedProxyValue are marker base classes for utilities
whose consumers bind a value to a scope. They contain no language or execution
logic. scoped_proxy_factory(value) returns a factory instance unchanged, or
None for any other object. A consumer defines its own binding and lookup rules;
the LCL runtime supplies its Frame adapter separately.
from dataclasses import dataclass
from lclang.utils import ScopedProxyFactory, ScopedProxyValue, scoped_proxy_factory
@dataclass(frozen=True)
class ScopedLabel(ScopedProxyValue):
label: str
class LabelFactory(ScopedProxyFactory):
def bind(self, scope, path):
return ScopedLabel(f"{scope}:{'.'.join(path)}")
factory = LabelFactory()
selected = scoped_proxy_factory(factory)
assert selected is factory
assert scoped_proxy_factory("ordinary") is None
assert factory.bind("request-7", ("service", "label")).label == "request-7:service.label"
The application selects a factory and binds its label to one request. The marker does not cache the value or own the scope; the application controls both.