Skip to content

pythontk v0.8.88

Latest

Choose a tag to compare

@m3trik m3trik released this 17 Jul 04:10
  • 2026-07-16 — CoreUtils.listify(threading=True) built a ThreadPoolExecutor even for a scalar call — sustained, the churn aborted the interpreter. Found while running tentacle's suite: the full run died mid-way with Fatal Python error: Aborted (no traceback, no summary — just a native exit code), and faulthandler put the main thread inside Thread.start(), reached from ptk.format_path(ui_file, "name") under uitk's .ui loader. The decorator wraps a scalar argument as [arg] and then mapped that one item through a pool: nothing to parallelize, a worker thread spun up and torn down per call, on pure-CPU helpers callers hit per path/string (format_path, truncate, math_utils/img_utils/str_utils — nine functions carry the flag). It presented as a full-suite-only crash because it needs the churn to accumulate; each test module passed alone, which is why it read as flakiness. Fixed by gating the pool on the already-computed is_single_item flag, so scalars run inline and genuine batches still fan out — no parallelism is lost, since mapping one item never had any to begin with; format_path(one_path) measures 388 µs → 7 µs per call (~55×). Gated on the flag rather than len(arg_list) because a real iterable may be lazy (generator/zip/map): executor.map accepts those, len raises. Behaviour is otherwise unchanged (executor.map and the list comprehension both preserve order). Tentacle's full suite now completes (449 tests, 0 failures) where it previously aborted; guarded by test_core.py, which pins both halves — scalars stay on the calling thread, batches still leave it.

  • 2026-07-16 — New TempArtifacts (root: ptk.TempArtifacts) — the single home for the "stage a temp payload for another process" pattern; HandoffBridge._make_payload_path now delegates to it. An audit found ~14 independent temp-payload sites across the bridges with four divergent lifecycle patterns — including the ScriptLaunch payloads, which were never deleted (unique tag per send → %TEMP% accumulation forever). The class allocates prefix-scoped <prefix>_<tag><ext> paths and names the only three lifetimes that are sound for inter-process payloads: scoped (delete on success, keep + log on exception — a blocking convert), session (atexit removal — a launched app reads during this process's life), and detached (no deterministic delete exists, so allocation garbage-collects stale same-prefix files instead — age-gated ≥ max_age_days, never a fresh payload another app may still be reading). Also: register() adopts side artifacts into the lifecycle, and on_cleanup(paths) fires just before removal (exceptions logged, never propagated). _make_payload_path keeps its exact path shape (same prefix_tag scheme, now monotonic per instance — Windows' time_ns is too coarse to be unique back-to-back) and gains the stale sweep for free; all four push bridges inherit the fix with zero call-site changes. Pre-push review refinements: the first-allocation stale sweep now runs for every policy, not just detached — scoped's keep-on-failure and session's hard-crash leftovers had no other reclamation path (the exact accumulation this class exists to end); the keep-on-failure message logs at warning (the default level — info made the documented "keep + log" contract silent); and path()'s default extension is a neutral .tmp instead of the bridge-flavored .fbx (_make_payload_path keeps its own .fbx default at the bridge layer, where it belongs). New test/test_temp_artifacts.py (22) pins policies, CM keep-on-failure, callback, sweep conservatism (age gate, prefix scope, all-policy leftover GC), and the root export.

  • 2026-07-16 — New run_script_to_artifact / ScriptRunResult (core_utils/script_run.py) — the blocking run-script-collect-artifact counterpart of ScriptLaunchDeliverer. The deliverer renders a script and launches a detached app (fire-and-forget push); pull-direction bridges need the opposite: write the rendered script, run the app attached via AppLauncher.run, wait, and hand back a verified artifact. Success is judged by the artifact (exists + non-empty) — the exit code is advisory only, because DCC standalone interpreters (mayapy) are known to crash in teardown after the real work succeeded (observed live during verification: mayapy fatal-errored post-save and the artifact was perfect). On failure it raises RuntimeError embedding the exit code + output tail, keeps the temp script (path carried on the exception) for debugging, and removes it on success — the scoped-TempArtifacts contract, which it composes. ScriptRunResult carries artifact/returncode/output (combined stdout+stderr — DCC warnings are diagnostic gold)/duration/script_path. First consumer: blendertk's MayaSceneImport (import a .ma/.mb via headless mayapy → FBX). Two hardenings from the post-ship critique pass: a pre-existing artifact is removed before the run (TDD'd red-first — a stale leftover from a prior run would otherwise fake success for a failed script, the exact hole a judged-by-artifact contract can't afford), and the child runs with the new AppLauncher.run(hide_window=True) — Windows pops a console window for a console-subsystem child (mayapy) when the parent is a GUI app (live Blender), and a captured-output child has no use for one (additive param, default off, POSIX no-op). Pre-push review refinement: subprocess.TimeoutExpired now carries script_path too — the docstring promised "script kept" on timeout but only the RuntimeError path said where, leaving timeout debuggers to dig through %TEMP% by hand. New test/test_script_run.py (11) drives it with sys.executable as the app — success, artifact-missing/empty/stale failure, stderr capture, kept-script, timeout (incl. the carried path), custom launch_args; the failure tests sweep their kept scripts in teardown via a test-owned script_prefix.

  • 2026-07-16 — New StrUtils.strip_ansi (root: ptk.strip_ansi): remove ANSI/VT100 escape sequences from text. Any process that tees a stream inherits its isatty(), and CPython emits colored tracebacks whenever stderr is a TTY — so a tee that faithfully delegates isatty starts receiving escape bytes it never asked for. Consumers that render text without interpreting VT100 (a Qt view, a log file) then show literal [35m garbage. Scope is CSI (ESC [ … final-byte, covering SGR color) plus the ESC Fe range; a bare ESC that opens nothing recognizable is left alone rather than swallowing the next character. Pattern compiled at module scope (ANSI_ESCAPE_RE) — callers run it per console write. Consumed by uitk.ScriptOutput (at the widget, for every host) and blendertk's Script Output capture (at ingest, so the transcript buffer's char cap counts characters a reader will actually see).