You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
2026-07-25 — New StrUtils.resolve_placeholders(text, **kwargs) -> dict: a verbose companion to replace_placeholders for live previews / diagnostics. Where replace_placeholders returns only the substituted string, this returns {result, fields, resolved, unresolved} — the same substituted string, every distinct {token} base name in first-seen order (format specs like {n:03d} and attribute / index access {obj.attr} / {seq[0]} reduce to the base name; positional {} / {0} skipped), the {token: value} map for supplied keys, and the tokens left unresolved. Raises ValueError on a malformed format string, matching replace_placeholders. Powers the ecosystem's new resolved-placeholder tooltips (uitk TooltipFormat.placeholder_preview). Additive; test_str.py +6. Release note: publish pythontk before uitk / mayatk / blendertk — the new tooltip depends on this.
2026-07-25 — SchemaSpec now expresses list-of-nested sub-records via nested=[Spec] (mirroring typing.List[X]). The schema base could nest a single mapping (nested=Spec) but not a list of them — from_dict only recursed when the value was a dict, validate errored "expected a mapping" on a list, and to_dict deep-copied a list of SchemaSpec instances as opaque objects instead of serialising them. Any schema with a repeated sub-record (a rack's bays, a bay's occupants, a table's columns) had to drop to a hand-rolled validate callable plus manual from_dict reconstruction at each call site — the exact per-schema duplication the base exists to remove. A one-element nested=[Spec] now signals a list of that schema, resolved uniformly by new internal _nested_of(meta) -> (schema, is_list): from_dict builds a list of instances, to_dict serialises each element, validate recurses per element with an indexed path (occupants[1].label), skeleton emits a one-element example list (still self-valid), and to_markdown marks the field (list) and renders the element schema's table. Fully additive — bare nested=Spec is unchanged; a malformed multi-element [A, B] raises SchemaError. FieldDoc gained nested_is_list: bool = False. test_schema_spec.py +10 → 36/0. No other public API change; downstream (uitk/mayatk/blendertk/tentacle) unaffected. Release note: publish pythontk before mayatk (its new RackBuilder schema depends on this).
2026-07-20 — New CachedArtifact: the produce-once/reuse-forever lifecycle every headless-conversion pipeline was re-implementing. mayatk's blender_bridge and blendertk's maya_bridge each carried a hand-rolled copy of the same ~40-line block — hash the inputs into a key, serve the cached file if present, else produce into a scoped scratch store and atomically os.replace-promote into the cache slot (plus sidecar promotion and stale-sidecar cleanup). Adding an FBX→native-scene bake stage to both would have made that four copies, so it moved to pythontk beside TempArtifacts (which it composes: "detached" owns the cache, "scoped" owns the scratch). CachedArtifact.key(*parts, files=…) folds each file's path+mtime+size in, so a template fix invalidates stale payloads rather than replaying the old bug on retry; get(key, produce, sidecars=…, use_cache=…) returns (path, hit, scratch). The scratch hop is load-bearing, not ceremony: producing straight into the slot lets a timeout-killed partial write poison every later run and lets two concurrent producers interleave into one file. Both bridges' _cache_key now delegate to it (cache prefixes and on-disk layout unchanged). test_temp_artifacts.py +13 → 34/0.
2026-07-19 — bootstrap_package now derives __all__ automatically from the resolved public surface, so it no longer has to be hand-listed or kept in sync with DEFAULT_INCLUDE (new set_all param, default on). pythontk's root __init__.py carried a 113-name __all__ maintained by hand — and it had already drifted: 20 symbols were registered in DEFAULT_INCLUDE yet missing from __all__ (Git, QcLog/QcGate/GateError, MapFactory/MapRegistry/MapType, OutputSpec/OutputTemplate/OutputTemplates, MatReport, Op, Workspace/WORKSPACE_MARKER/DEFAULT_FILE_RULES/RULE_NICE_NAMES, RpcClient, Call/Result, ImageFormat) — so from pythontk import * and every introspection tool that reads __all__ under-reported the surface. bootstrap_package now populates __all__ after resolver build + namespace-alias creation as the sorted union of any __all__ the module declared before the call and the resolver's class_to_module keys — every module-level class/function/constant and created namespace alias reachable as pkg.Name. Flat method aliases (method_to_module, e.g. ptk.make_iterable) and bare submodule names are intentionally excluded — they still resolve lazily via __getattr__ but aren't advertised, matching the prior convention. The pre-declared list is snapshotted once so a runtime configure()/build_dictionaries() reconfigure re-derives cleanly without accumulating stale names. The derived pythontk __all__ is a strict superset of the old curated list (113 → 133, zero regressions, every name resolves — test_all_names_resolve unchanged and green), and the importable surface is identical (those 20 names were always reachable via __getattr__; __all__ now just reflects reality, so no registry regen). The manual __all__ block is deleted from pythontk/__init__.py. Behavior is opt-out via bootstrap_package(..., set_all=False). Shared-infra change (core_utils/module_resolver.py): every ecosystem package (uitk, mayatk, blendertk, tentacle) that previously shipped no__all__ now gets a clean auto-derived one for free — no from <pkg> import * exists anywhere in the monorepo, so the default is purely additive. Full suite green (2115 passed, 0 failed, 7 skipped); uitk verified (115-name __all__, all resolve).