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.collect and iter.first consume synchronous or asynchronous iterables.
  • text.join and text.lines provide deterministic text operations.
  • data.lookup and data.merge work with immutable mapping snapshots.
  • json.encode and json.decode provide 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.