github m3trik/pythontk v0.9.1
pythontk v0.9.1

3 hours ago
  • 2026-08-01 — HelpMixin misclassified every INHERITED descriptor: properties reported as "attribute", static/classmethods as plain "method". _get_member_type looked the raw descriptor up in klass.__dict__ alone, so for anything reached through the MRO the lookup simply missed and the member fell through to the callable() test — a property object isn't callable, hence "attribute". This repo composes public classes from private capability mixins (PackageManager(_PkgVersionCheck, _PkgVersionUtils, _PackageManagerHelperMixin, …)), so most descriptors are inherited: PackageManager.installed_ver / .latest_ver / .new_version_available all reported as attributes, and .update_version / .update_requirements as methods rather than staticmethods. It now resolves the descriptor from whichever class in inspect.getmro(klass) defines it. Surfaced by the API-registry drift gate once the static walker learned to resolve the same mixins (see m3trik's CHANGELOG); help() output across the ecosystem was wrong the same way. test_help_mixin.py +3 (inherited kinds, own kinds unregressed, kinds through a subclass) → 71.

  • 2026-08-01 — StrUtils.collapse_delimiter_runs — collapse the separator residue left when tokens are removed from a delimited name. 'a__tok__tokB' with tok stripped yields 'a____B'; this collapses it to 'a_B' (leading delimiter preserved as a legality prefix, trailing runs stripped by default, strip_trailing=False to keep them). Backs the mayatk/blendertk Naming.rename residue fix (production scene had shape names with 83 consecutive underscores left by stripping accumulated scratch tokens). test_str.py +4.

  • 2026-08-01 — Optimize now reports file size, and assess became a real dry run. The optimizer's result line read (4096x4096, 24bit (8x3)) — no file size, which is the whole point of optimizing.

    • MapOptimizer.format_result renders the new one-liner: 4096x4096 -> 2048x2048, 24bit (8x3), 12.00 MB -> 3.00 MB (-75%). The source size is now read before the archive/overwrite step (that move used to destroy the "from" half), and an unchanged dimension pair collapses instead of printing a no-op arrow.
    • MapOptimizer.assess(..., predict_size=True, output_type=…, output_profile=…) returns a predicted block — {width, height, mode, bit_depth, ext, path, size_bytes}. path comes from the same resolve_texture_filename call optimize_map makes, so a dry run names the file the real run would write instead of re-deriving the map-type suffix by hand. size_bytes is measured by encoding through ImgUtils.save_image (the same writer, with the profile's bit depth and compression) into a scratch file that's immediately discarded: compression ratios can't be derived from dimensions and mode, so a real encode is the only honest answer to "how much smaller?". Opt-in — bulk report callers assessing every texture in a scene don't pay for it, and the caller's pre-loaded image is passed through uncopied (apply rebinds rather than mutating, the invariant optimize_map already relies on).
    • MapOptimizer.project replays a plan's op params to the resulting size/mode with no pixel work; depalettize and force_pot ops now carry their result in params so this is a replay, not a second copy of plan's decision logic.
    • FileUtils.format_bytes / format_bytes_delta — the byte-rendering that was private to MatReport._fmt_size_auto, promoted to the general layer now that the optimizer's reporting needs it too. _fmt_size_auto is a thin alias.

Don't miss a new pythontk release

NewReleases is sending notifications on new releases.