Repository navigation
tutorials 16 python utilities
lclang includes small Python APIs that remain useful when an application does not evaluate LCL source or use the lclang command-line framework. They keep the same typed, async-first, explicit-ownership approach without forcing a larger integration.
- how to read the live process environment without snapshotting or mutation;
- how to create and close an isolated file logger outside
lclang.cli; - how to call the reviewed iterable, text, data, and JSON helpers from Python;
- how to reuse business-day calendars in an ordinary async application;
- which package owns each downstream utility surface.
Import env from lclang.utils. Attribute access is convenient for valid
Python and LCL identifiers; get also accepts names containing punctuation.
Every access consults os.environ at that moment.
import os
from unittest.mock import patch
from lclang.utils import Environment, env
with patch.dict(os.environ, {"LCLANG_REGION": "blue"}, clear=False):
assert isinstance(env, Environment)
assert env.LCLANG_REGION == "blue"
os.environ["LCLANG_REGION"] = "green"
assert env.LCLANG_REGION == "green"
assert env.get("deployment-region", "local") == "local"
assert "LCLANG_REGION" in env.field_names()The singleton is a live read-only view, not a Frame proxy. Assigning to or
deleting os.environ remains the host application's responsibility. In an LCL
Frame, the canonical env binding adds scoped configuration overrides above
the same live fallback without mutating the process environment.
A process entry point owns one handler scope. Application loggers only bind source names and prefixes; the scope owns the background writer and its sinks.
import asyncio
from pathlib import Path
from tempfile import TemporaryDirectory
from lclang.logger import LoggerHandlerConfig, use_logger, use_logger_handler
async def main() -> None:
with TemporaryDirectory(prefix="lclang-logger-tutorial-") as directory:
config = LoggerHandlerConfig(
console={"enabled": False},
file={"worker": {"directory": directory}},
)
async with use_logger_handler(config) as runtime:
logger = await use_logger(name="example.worker", prefix="[WORKER]")
logger.info("processed %d records", 3)
paths = list(Path(directory).glob("*.log"))
assert len(paths) == 1
text = paths[0].read_text(encoding="utf-8")
assert text.startswith("log file: ")
assert "[WORKER] processed 3 records" in text
assert runtime.metrics.records_written == 1
asyncio.run(main())The producer enqueues the record without file I/O. Scope exit drains it before returning, so reading the segment afterwards observes the completed message. The exclusive filename contains the process ID, UTC creation timestamp, and sequence. Closed segments keep that path permanently. Console output can run alongside any number of files. The logger reference explains templates, rotation, filtering, cancellation, and worker initialization.
The functions behind LCL's iter, text, data, and json namespaces are
also direct exports from lclang.stdlib. The iterable and join helpers accept
synchronous or asynchronous iteration.
import asyncio
from lclang.stdlib import collect, first, join, json_decode, json_encode, lookup, merge
async def labels():
for label in ("blue", "green"):
yield label
async def main() -> None:
assert await collect(labels()) == ["blue", "green"]
assert await first([], "missing") == "missing"
assert await join(" / ", labels()) == "blue / green"
settings = merge({"retries": 2, "region": "blue"}, {"region": "green"})
assert lookup(settings, "region") == "green"
assert lookup(settings, "timeout", 30) == 30
payload = json_encode({"regions": ["blue", "green"]})
assert payload == '{"regions":["blue","green"]}'
assert json_decode(payload) == {"regions": ["blue", "green"]}
asyncio.run(main())merge is a shallow, left-to-right operation returning a read-only mapping.
The JSON helpers reject nonstandard NaN and infinity rather than emitting
implementation-specific data. Other public exports include lines, the
fixed-point recursive helper, and the manifest APIs used to assemble a
reviewed standard preset.
Calendars live under lclang.utils.calendar and do not depend on parsing LCL.
This example turns a weekend report date into the next weekday and then moves
one further business day.
import asyncio
from datetime import date
from lclang.utils.calendar import ALL_DAYS, ALL_WEEKDAYS
async def main() -> None:
report_day = ALL_DAYS.map_this_or_next(ALL_WEEKDAYS)
settlement_day = report_day.shift_n_days(1, ALL_WEEKDAYS)
saturday = date(2024, 1, 6)
assert await report_day.map_date(saturday) == date(2024, 1, 8)
assert await settlement_day.map_date(saturday) == date(2024, 1, 9)
asyncio.run(main())The first mapping adjusts Saturday to Monday. The chained shift begins from that mapped value and reaches Tuesday. For calendar algebra, sparse policies, strict JSON loading, and managed caches, continue with the business-day calendar chapter.
Use safe_repr when a value's display code must not break a diagnostic. Masking
skips rendering entirely, and a canonical renderer can choose the displayed form.
from lclang.utils import safe_repr
assert safe_repr(42) == "42"
assert safe_repr("private", masked=True) == "*masked*"
assert safe_repr("line", renderer=lambda value: "a\n" + value) == "a\\nline"
assert len(safe_repr("x" * 300)) == 200
assert safe_repr("x" * 300, max_length=None) == repr("x" * 300)The default limit counts characters after line-break escaping and includes the
truncation marker. None preserves the full escaped representation. A failed
renderer produces a stable failure description; the helper adds no type label.
Downstream code can adopt only the layer it needs:
-
lclang.utilsprovides the live environment;lclang.loggerowns process logging. -
lclang.stdlibprovides reviewed pure-data helpers and preset assembly. -
lclang.utils.calendarprovides date policy and managed calendar loading. -
lclang.workflowprovides typed task trees and nested execution status. -
lclang.cliprovides command discovery, binding, auditing, and process exit behavior.
The latter two are larger application frameworks, covered by the
tree-workflow and
command-line application chapters. None is
required merely to use env, create a logger, transform reviewed data, or
apply a calendar.
For the complete stable names and contracts, see the utilities reference. Keep imports at the owning public package instead of reaching into implementation modules; that leaves each application explicit about the subsystem and lifecycle it has adopted.
Previous: Case Study: Energy Settlement Workflow | Return to the series introduction
Published from ac045a6487d6. Documentation is maintained and tested in the source repository.