schmonz.com is a Fediverse instance that uses the ActivityPub protocol. In other words, users at this host can communicate with people that use software like Mastodon, Pleroma, Friendica, etc. all around the world.

This server runs the snac software and there is no automatic sign-up process.

Search results for tag #python

[?]Python Software Foundation » 🌐
@ThePSF@fosstodon.org

📺 Watch PSF Executive Director @baconandcoconut close out with a fireside chat on stewarding Rust and Python: AI's impact, security, governance, and leading global communities. youtu.be/z5_tEecNkRs?si=EFL6hA

    #refactoring boosted

    [?]Hack a Day (unofficial) » 🤖 🌐
    @hackaday@www.urbanmind.net

    Split one decision table only after observed outputs are frozen. A model diff is not a safety net. Tests must pin allow, deny, ask, and audit order first.

    A messy access helper hides three contracts in one function. Callers depend on the return token and the audit list. They also depend on whether the input map changes.

    Move the decision table too early and those contracts drift. Style cleanup is not the same as a safe split. Freeze those rows first, then move exactly one function.

    Name the failure before you edit


    The helper below is a proposed example, not a production trace. It mixes normalization, a module audit list, and a branching decision. Empty role and missing role take different paths.

    That difference is the bug you must not clean up by accident. Read the function as a contract list, not as style debt. Four outcomes matter more than the nested branches.

    AUDIT =

    []def grant_access(user, action, resource):
    role = user.get("role")
    if role is None:
    AUDIT.append(("missing", action, resource))
    return "ask"
    role = str(role).strip().lower()
    if role == "":
    AUDIT.append(("blank", action, resource))
    return "deny"
    if action == "read" and role in {"guest", "member", "admin"}:
    AUDIT.append(("allow", action, resource))
    return "allow"
    if action == "write" and role == "admin":
    user["elevated"] = True
    AUDIT.append(("allow", action, resource))
    return "allow"
    AUDIT.append(("deny", action, resource))
    return "deny"

    The return token is the first contract callers already observe. The audit tuple order is the second contract. The input map mutates only on admin write, and that is the third.

    Step 1: Freeze the rows you already observe


    Do not rename helpers or extract a pure function yet. Record the rows that current callers already see. A later edit is safe only when every recorded row stays green.

    1. Reset the module audit list inside every test setup.
    2. Call grant_access once with a single input row.
    3. Assert the return token and the full audit list.
    4. Assert whether the input map gained an elevated key.
    5. Keep each observed case in its own test function.

    These five steps are the gate for the later edit. One red row means you changed behavior, not structure. Fix the recording before you touch the helper.

    Step 2: Lock a visible case table


    Use a table so the pins stay easy to review. The table is the artifact for this refactor. It is not a benchmark and it has no production metric.

    role

    action

    return

    audit head

    elevated

    missing

    read

    ask

    missing

    no

    blank

    read

    deny

    blank

    no

    guest

    read

    allow

    allow

    no

    guest

    write

    deny

    deny

    no

    admin

    write

    allow

    allow

    yes

    member

    write

    deny

    deny

    no


    Missing role is not the same as a blank role. Guest read is not the same as guest write. Admin write mutates the map, and member write does not.

    Those three distinctions are the contract you must hold. A prettier branch that collapses them is a behavior change. Leave that policy question outside this structural split.

    Step 3: Turn the table into tests


    The tests below are a proposed harness for this example. Run them against the messy function before any split. They should pass before you trust a generated diff.

    import unittest

    class GrantContractTest(unittest.TestCase):
    def setUp(self):
    AUDIT.clear()

    def test_missing_role_asks(self):
    user = {}
    token = grant_access(user, "read", "doc")
    self.assertEqual(token, "ask")
    self.assertEqual(AUDIT, [("missing", "read", "doc")])
    self.assertNotIn("elevated", user)

    def test_blank_role_denies(self):
    user = {"role": " "}
    token = grant_access(user, "read", "doc")
    self.assertEqual(token, "deny")
    self.assertEqual(AUDIT, [("blank", "read", "doc")])
    self.assertNotIn("elevated", user)

    def test_guest_read_allows(self):
    user = {"role": "Guest"}
    token = grant_access(user, "read", "doc")
    self.assertEqual(token, "allow")
    self.assertEqual(AUDIT, [("allow", "read", "doc")])
    self.assertNotIn("elevated", user)

    def test_guest_write_denies(self):
    user = {"role": "guest"}
    token = grant_access(user, "write", "doc")
    self.assertEqual(token, "deny")
    self.assertEqual(AUDIT, [("deny", "write", "doc")])
    self.assertNotIn("elevated", user)

    def test_admin_write_mutates(self):
    user = {"role": "Admin"}
    token = grant_access(user, "write", "doc")
    self.assertEqual(token, "allow")
    self.assertEqual(AUDIT, [("allow", "write", "doc")])
    self.assertTrue(user["elevated"])

    def test_member_write_denies(self):
    user = {"role": "member"}
    token = grant_access(user, "write", "doc")
    self.assertEqual(token, "deny")
    self.assertEqual(AUDIT, [("deny", "write", "doc")])
    self.assertNotIn("elevated", user)

    if __name__ == "__main__":
    unittest.main()

    Add both functions to one module before you run this file. Do not parameterize yet if a failure becomes harder to read. A failed test name should name the exact row.

    Run the harness with one local command before you edit. Expect six passing tests before you open any diff. If a test fails now, the pin is wrong, so fix the pin.

    python -m unittest grant_contract.py -v

    Step 4: Allow only the smallest split


    The safe change moves the branch logic into decide. grant_access still appends the audit tuple in order. grant_access still sets elevated only on admin write.

    decide returns a token and a reason string only. It must not touch AUDIT or the user map. That boundary is the whole point of this split.

    def decide(role, action):
    if role is None:
    return "ask", "missing"
    role = str(role).strip().lower()
    if role == "":
    return "deny", "blank"
    if action == "read" and role in {"guest", "member", "admin"}:
    return "allow", "allow"
    if action == "write" and role == "admin":
    return "allow", "allow"
    return "deny", "deny"

    def grant_access(user, action, resource):
    token, reason = decide(user.get("role"), action)
    if token == "allow" and action == "write":
    user["elevated"] = True
    AUDIT.append((reason, action, resource))
    return token

    That split stays a proposal until the same tests pass. Do not also rename AUDIT in this same diff. Do not also stop mutating the user map yet.

    Those edits are later changes with their own pins. Batching them hides which edit broke a row. Keep this round limited to one function move.

    Step 5: Reject diffs that do two jobs


    Hold a written reject list next to the tests. Use it when a generated diff looks tidy but wide. Width is a risk even when the suite is green.

    1. Reject a diff that deletes the blank-role branch.
    2. Reject a diff that treats missing role as deny.
    3. Reject a diff that drops the elevated write.
    4. Reject a diff that reorders audit tuple fields.
    5. Reject a diff that adds network, clock, or file calls.

    A free coding model can draft the split for you. It cannot waive the reject list or the tests. You still run the unittest command and read the diff.

    Where a free model may draft the split


    Disclosure: This article was prepared as part of MonkeyCode's product outreach. MonkeyCode free model access can draft the decide split. Use it only after the six characterization tests exist.

    The free server option can run unittest away from your dirty tree. Both claims here are availability options supplied for this draft. This article does not state quotas, model names, or hardware.

    It also does not state duration or benchmark numbers. Use the model only as a diff proposer in this loop. Paste the frozen table and the reject list into the prompt.

    Ask for one function move and no token changes. Apply the patch in the isolated run, not on caller code. If that run is red, discard the diff without debate.

    Do not ask the model for a broader rewrite next. Tighten the prompt with the one failing row only. Repeat that attempt once, then stop if it is still red.

    Edit the split by hand after two red runs. A useful prompt stays short, closed, and specific. It names the frozen contracts and forbids extra behavior.

    Tests already pin the return token, the audit tuples, and elevated.
    Move only the branch logic into decide(role, action).
    Keep the audit append and the elevated write inside grant_access.
    Do not merge a missing role with a blank role.
    Return one unified diff and then stop writing.

    Use a decision matrix after the run


    Count the diff shape before you praise the result. A green suite can still hide a second change. Accept only the row that matches the smallest split.

    Diff shape

    Suite

    Decision

    decide added, wrapper behavior unchanged

    green

    accept

    missing role merged into blank role

    any

    reject

    elevated write removed

    red

    reject

    audit field order swapped

    red

    reject

    extra log or clock call added

    green

    reject

    rename bundled with the split

    green

    reject this round


    Six table rows must pass before any structural split. One function moves in the first accepted diff. Two red model runs is the hard stop line.

    Zero new side effects may appear in that first diff. Those counts are gates, not performance results from a lab. They do not measure a product or a model score.

    Limitations you should keep visible


    Characterization tests pin observed behavior, including old bugs. If blank role should later become ask, write a new test. Do not hide that policy fix inside the structural split.

    The harness does not cover concurrent callers at all. AUDIT is a shared list, so threads can interleave tuples. This split does not make that shared list thread safe.

    The example ignores resource ownership and external stores. A real helper may call a database or a cache. Pin that call before you move it into decide.

    Generated diffs can pass tests and still be unclear. Unclear code remains a review defect after a green run. Passing tests are necessary here, but they are not sufficient.

    Role matching here is exact after strip and lower. It does not handle aliases, nested groups, or inherited roles. Do not treat this sample as an access-control design.

    Who should not use this path


    Skip this path if you have no caller you can execute. A table you invented from memory is not characterization. It is a guess, and a guess cannot gate a split.

    Skip this path if behavior must change in this same patch. Write a red test that states the new rule first. Do not hide that fix inside a rename or a move.

    Skip the model step if you cannot read a unified diff. An isolated run does not replace your own review. A green run can still drop a branch the tests forgot.

    Skip the free server step when tests need secrets or private rows. Do not upload credentials just to try a generated draft. Use local fixtures with fake roles and fake resource names.

    Close the loop before you merge


    Re-run the six tests after you apply the split. Confirm elevated still flips only on an admin write. Confirm missing and blank still log different audit heads.

    Review the diff stat before you merge the branch. Two changed definitions are the expected diff shape. Ten files changed means this round is no longer small.

    git diff --stat
    python -m unittest grant_contract.py -v

    If those rows stay green, merge that one split only. Queue the next change behind a new written pin. Do not batch a rename, a policy fix, and a log change.

    A free model can draft the next pinned split after that pin exists. MonkeyCode's free model access and free server option can host that trial. Keep the tests on fixtures, and keep the reject list in the prompt.
    Keep Audit Order Fixed Before You Extract Decide

      #refactoring boosted

      [?]Hack a Day (unofficial) » 🤖 🌐
      @hackaday@www.urbanmind.net

      Picture a billing worker that skipped invoice generation after a weekend deploy because a blank region string replaced a valid environment value. In that scenario, the on-call engineer expected missing keys and blank values to behave the same way under overlay. They did not, and the same function treated a JSON null as an explicit delete of that key. File reads, environment scans, and CLI parsing lived in one module, so a broad cleanup looked useful and unsafe.

      The notes below describe a characterization-first path for that class of settings merger, using a labeled composite module. Nothing in this draft reports a private employer incident, a measured outage length, or any customer count. The tests pin today's behavior before anyone moves code, including behavior that a later change may deliberately reject. Product assistance appears only after that contract exists, and the outreach disclosure sits beside the first mention.

      What must stay stable during the extract


      A safe extract preserves outputs for every input class you can name, even when those outputs look wrong. The merger currently walks sources in file, environment, then CLI order, and later writes replace earlier writes. A null value deletes the key, while an empty string, zero, and an empty list remain stored values. Moving I/O, renaming keys, and fixing null handling in the same diff would hide which edit changed behavior.

      Decision table before any code move


      Lock the contract in a table before you touch the module, because prose arguments drift during review. Each row names three source states and the exact merged result you observed, not the result you prefer. You should record absent keys, empty strings, JSON null, numeric zero, and empty collections as distinct states. If two reviewers disagree on a row, rerun the current function and paste the observed output into the table.

      Case

      File value

      Env value

      CLI value

      Observed merge

      later value wins

      region=us

      region=eu

      absent

      region=eu

      empty string wins

      region=us

      region=empty string

      absent

      region=empty string

      null deletes

      region=us

      region=null

      absent

      region absent

      zero is kept

      retries=3

      retries=0

      absent

      retries=0

      empty list kept

      tags=[a]

      tags=[]

      absent

      tags=[]

      cli null deletes

      region=us

      region=eu

      region=null

      region absent

      delete then set

      region=null

      region=eu

      absent

      region=eu

      Composite module under test


      Label this module as an unexecuted teaching example until you paste it into a scratch repository and run it. It intentionally keeps the awkward rules so the tests describe the code you have, not the code you want. Do not import production secrets, live endpoints, or customer fixtures into this small characterization harness at all. The function accepts plain dictionaries so characterization does not depend on disk layout or the process environment.

      Messy function to pin


      # scratch/overlay.py
      # Labeled composite. Run it before you treat any row as evidence.

      def overlay_settings(file_cfg, env_cfg, cli_cfg):
      merged = {}
      for source in (file_cfg, env_cfg, cli_cfg):
      for key, value in source.items():
      if value is None:
      merged.pop(key, None)
      else:
      merged[key] = value
      return merged

      Characterization tests that refuse silent drift


      These tests call the current function and compare full dictionaries, because a partial assert can miss deleted keys. A missing key and a stored null are different outcomes, so the expected object must not contain a placeholder. Keep one assertion per case name, and let that case name match the corresponding decision table row. If a test fails on the first run, fix the expectation to match observed output before you edit production code.

      # tests/test_overlay_characterize.py
      import pytest

      from overlay import overlay_settings

      CASES = [
      ("later_value_wins", {"region": "us"}, {"region": "eu"}, {}, {"region": "eu"}),
      ("empty_string_wins", {"region": "us"}, {"region": ""}, {}, {"region": ""}),
      ("null_deletes", {"region": "us"}, {"region": None}, {}, {}),
      ("zero_is_kept", {"retries": 3}, {"retries": 0}, {}, {"retries": 0}),
      ("empty_list_kept", {"tags": ["a"]}, {"tags": []}, {}, {"tags": []}),
      (
      "cli_null_deletes",
      {"region": "us"},
      {"region": "eu"},
      {"region": None},
      {},
      ),
      ("delete_then_set", {"region": None}, {"region": "eu"}, {}, {"region": "eu"}),
      ]

      @pytest.mark.parametrize(
      "name,file_cfg,env_cfg,cli_cfg,expected",
      CASES,
      ids=[row[0] for row in CASES],
      )
      def test_overlay_characterizes_current_contract(
      name, file_cfg, env_cfg, cli_cfg, expected
      ):
      del name # parametrize ids already label the failure
      assert overlay_settings(file_cfg, env_cfg, cli_cfg) == expected

      Commands for a local proof


      Run the suite from a clean shell so inherited variables do not masquerade as deliberate environment input. The sample function does not read the process environment, but the next extract might, and the habit should start now. Save the command output with the commit that introduces the tests, because a later green run is not the original evidence. If your laptop shell exports REGION or RETRIES, the clean-server rerun described later is the stricter check.

      python -m venv .venv
      . .venv/bin/activate
      python -m pip install pytest
      env -u REGION -u RETRIES python -m pytest -q tests/test_overlay_characterize.py

      python -c 'from overlay import overlay_settings; print(overlay_settings({"region": "us"}, {"region": None}, {}))'
      python -c 'from overlay import overlay_settings; print(overlay_settings({"region": "us"}, {"region": ""}, {}))'

      git diff --stat -- overlay.py tests/test_overlay_characterize.py
      git diff -- overlay.py

      Smallest safe change after the pin


      The smallest safe change extracts the loop body into a pure helper and leaves file, environment, and CLI loading untouched. Callers still pass three dictionaries in the same order, and null still deletes while empty strings still overwrite. That boundary is one function move plus an import update, which a reviewer can check against the unchanged test file. A second change may later redefine null, but only after the table marks those rows as intended breaks.

      def apply_source(merged, source):
      for key, value in source.items():
      if value is None:
      merged.pop(key, None)
      else:
      merged[key] = value
      return merged

      def overlay_settings(file_cfg, env_cfg, cli_cfg):
      merged = {}
      for source in (file_cfg, env_cfg, cli_cfg):
      apply_source(merged, source)
      return merged

      What not to smuggle into the move


      Adding deepcopy, sorting keys, or coercing empty strings to null would change observable results for some rows. Shared mutable values remain part of the current contract until a test proves callers rely on isolation. Sorting keys would change JSON dump order if a later caller serializes the dict without an explicit sort. Leave those ideas in a follow-up list, and keep their code out of this characterization-backed diff.

      Review checklist before merge


      1. Confirm every decision-table row has a passing test whose expected value came from a run, not from a design discussion.
      2. Confirm the diff does not add file reads, environment reads, or argument parsing inside the new helper.
      3. Confirm falsy values other than null, including zero and empty collections, still survive a later source write.
      4. Confirm a planned behavior change is absent from this commit, or is listed as an explicit failing-test follow-up.


      Where free model access and a free server fit


      Once the table and tests exist, MonkeyCode's free model access can propose extra rows that your first pass missed. Disclosure: This article was prepared as part of MonkeyCode's product outreach. The operator supplied two availability claims for this draft: free model access, and a free server option for running work away from a laptop. This article does not name models, token quotas, hardware sizes, time limits, or benchmark scores, because those details were not verified here.

      How to use the assistant without letting it rewrite the contract


      Ask for candidate rows only, and require each proposal to cite a source cell and an expected dictionary. Reject any suggestion that corrects null into a stored value, unless you opened a separate behavior-change task. Paste accepted rows into the test file yourself, then run the suite, because an unrun suggestion is not evidence. Treat the open-source project as a helper for drafting and execution, not as a source of production truth.

      Why a free server rerun matters for this bug


      Environment leaks are part of the original failure mode, so a second run on a machine you did not customize is useful. A free server option lets you repeat the same pytest command outside a shell that already exports billing variables. Record the image or setup notes you can actually verify, and do not assume the remote box matches production libc, locale, or timezone.

      If the remote result differs from the laptop result, stop the extract and reconcile the inputs before you trust either green log. If the characterization table is already green locally, one remote rerun is enough of a second opinion before you merge the helper. Keep that remote log next to the test file so a reviewer can see which command produced the green result.

      Limitations and who should not use this path


      This path assumes you can execute the current function on synthetic dictionaries and read stable, comparable outputs. Skip it when the merger calls a live network, a paid vendor, or a database you cannot stub without changing behavior. Also skip it when product policy already demands a null-semantics change in the same release, because characterization-then-extract would delay a required break. Free model access can invent plausible rows that never occur in your payloads, so every accepted row still needs a local run.

      A free server option is a cleaner shell, not a production replica, and unverified hardware claims would only add false confidence. Do not use the remote run as evidence if you cannot record which command you executed and which test file you copied. Teams without permission to upload even synthetic fixtures should keep the suite on an approved internal runner instead. If you cannot explain absent, empty, and null to a reviewer in one table, you are not ready to extract the helper.

      What this workflow deliberately leaves undone


      The extract does not choose a new precedence policy, and it does not migrate callers to a typed settings object. It also does not prove thread safety, file encoding, or comment-preserving YAML edits, because those risks sat outside the pinned contract. A later change can replace null-delete with store-null, but it should flip specific table rows and show the red tests first. Until that change exists, the honest description of the system is the table you ran, not the overlay you wish you had.
      Pin Absent, Empty, and Null Before You Extract a Settings Overlay

        #agile boosted

        [?]Alvin Ashcraft's Morning Dew » 🌐
        @alvinashcraft.com@web.brid.gy

        Dew Drop - October 7, 2026 (#4765)

        Top Links
        Bringing Native AOT to the Aspire dashboard (James Newton-King)
        Building a 2D Game with WinUI and Win2D (Morten Nielsen)
        The results of the 2026 Stack Overflow Developer Survey are here! (Ryan Donovan)
        Introducing WinDbg MCP: Debug with natural language, grounded in evi…

        d@nny disc@ boosted

        [?]Jesus Michał von Gentoo 🏔 (he) » 🌐
        @mgorny@social.treehouse.systems

        If I were to judge demand for kinds of software based on package releases last month, I'd say we have a very quickly rising demand for:

        • a (redundant) package providing virtual environments (23 releases in a month)
        • a package providing test environments (16 releases)
        • a package providing lockfile support (15 releases)
        • a package providing support for special platform directories (XDG, etc.) (12 releases)

        Or maybe it's just . I don't know, they're not admitting to it in their very verbose commit messages.

          #agile boosted

          [?]Alvin Ashcraft's Morning Dew » 🌐
          @alvinashcraft.com@web.brid.gy

          Dew Drop - October 5, 2026 (#4763)

          Top Links
          Build expressive voice experiences with new MAI models in Microsoft Foundry (Naomi Moneypenny)
          Credential Explorer - A modern WinUI 3 metadata-only explorer for Windows credentials and Credential Locker (Scott Hanselman)
          Meet the A2A CLI: discover, message, and manage a…

          Amélie boosted

          [?]Hydrooos 🌍🏞️🌱🔍💧💧🐟🪱🦠 » 🌐
          @hydrooos@piaille.fr

          Bonjour,
          Si jamais vous ou si vous avez dans votre réseau un·e personne maitrisant le qui serait intéréséée pour une mission de presta courte (de 3j environs) sur un projet déjà avancé de données environnementales liées à l'énergie et dispo rapidement faites signe !
          (j'ai pas plus de détails mais je fais la mise en relation)
          🔄 appréciés

          (enfin pas moi)

            dch :flantifa: :flan_hacker: boosted

            [?]0mp at FreeBSD [he/him] » 🌐
            @mpts@mastodon.social

            I've published a patch adding a option to a port on .

            reviews.freebsd.org/D60369

            Feedback and testing welcome.

            Also, if you know how to make DTrace work with , I want to talk with you.

              Glyph boosted

              [?]Trey Hunner 🐍 » 🌐
              @treyhunner@mastodon.social

              Python Tip #276 (of 365):

              Make self positional-only when accepting "**kwargs".

              This class accepts arbitrary keyword arguments... except for self:

              >>> class Namespace:
              ... def __init__(self, **kwargs):
              ... for name, value in kwargs.items():
              ... setattr(self, name, value)
              ...
              >>> space = Namespace(other=2, self=1)
              Traceback (most recent call last):
              ...
              TypeError: Namespace.__init__() got multiple values for argument 'self'

              🧵 (1/3)

                #agile boosted

                [?]Alvin Ashcraft's Morning Dew » 🌐
                @alvinashcraft.com@web.brid.gy

                Dew Drop - October 2, 2026 (#4762)

                Top Links
                Bringing rich terminal experiences to Aspire (Mitch Denny)
                Semantic Design in Uno Themes - Part 1 (Steve Bilogan)
                A New Agentic Experience: JetBrains Air in IDEs – EAP Now Open (Dominique Rolink)
                Data API builder 2.1.5: JSON and Vector Data Type Support, and More (Carlo…

                #netbsd boosted

                [?]sinza :netbsd: :openbsd: :freebsd: :omya_linux: [e/em/eirs or vi/vim/vis] » 🌐
                @sinza@oldbytes.space

                Hi! I'm looking for a job or gig! I live in the US (Central Indiana), and would much much rather work remotely.

                I have system administration experience with , , and professionally. I strongly prefer the family of OSes and am willing to work with and professionally and have played with them off the job. I have done this for about three years.

                I also know graphic design. My preferred programs are and , but I can use or if need be. I have done this for a decade.

                I have skills in development, especially web development, and have professionally developed with , , and . I also am trying to familiarize myself with for legacy maintenance. I also have experience working with Microsoft databases, as well as played with other major SQL servers. I've also played with many other languages. I'm also willing to familiarize myself with a tech stack on the fly. As implied, I am willing to touch legacy code.

                I have also designed websites, and am quite familiar with and . I also got to play nicely as well.

                I am a jack of all trades, with my strongest skills I feel be in system administration and graphic design.

                LLMs and generative AI are something I prefer to avoid where possible. I have experience with working under tight deadlines with a micromanaging boss before recent hype around LLMs, though I'd rather avoid such things if possible.

                  [?]mumpkin » 🌐
                  @mitrouille@mastodon.gamedev.place

                  Hello ! I'm developing a new image file format, it's originally a joke project but I want to push it as far as possible.

                  However, I currently need 4 palettes of 31 colors and I know nothing about color theory, can someone provide me some advice about palette creation ?

                  codeberg.org/UWU-format

                  A cat UwU

                  Alt...A cat UwU

                    #agile boosted

                    [?]Alvin Ashcraft 🐿️ » 🌐
                    @alvinashcraft@hachyderm.io

                    [?]halcy​ :icosahedron: [He/Him] » 🌐
                    @halcy@icosahedron.website

                    new post: QR codes that can be read multiple different ways!

                    Including a \\ world first // (?) four different payloads in a single QR code!

                    halcy.de/blog/2026/10/01/qr-co

                      [?]David Lord :python: » 🌐
                      @davidism@mas.to

                      RE: mas.to/@davidism/1172923990498

                      The beginning of the month, when every junk producer's token limit resets, and we get a whole new wave of junk. Turning off PRs helped a lot, but they just started opening "test issue, checking if my API access is broken", or commenting or emailing their patches instead. I really wish I knew how to escape this without turning off community interaction entirely.

                      Greg Wilson boosted

                      [?]David Lord :python: » 🌐
                      @davidism@mas.to

                      I've turned off pull requests from non-members for Flask, Werkzeug, Jinja, and Click this week, and have enjoyed not waking up to a fresh wave of AI junk every morning. I think I'm going to update our contributing guide to say "introduce yourself and how you use the library in chat first if you want to contribute pull requests". Sad that we had to go here, but aside from conference sprints we weren't really getting outside contribution anyway (which is also sad, but that's been the case a lot longer than AI has been around).

                          #refactoring boosted

                          [?]Hack a Day (unofficial) » 🤖 🌐
                          @hackaday@www.urbanmind.net

                          I've been bitten by the same bug more times than I'd like to admit. I change a "small" helper in a...
                          How I find what breaks before I change a Python function

                            [?]Glyph » 🌐
                            @glyph@mastodon.social

                            3.14.8 is out, you should upgrade

                              #agile boosted

                              [?]Alvin Ashcraft's Morning Dew » 🌐
                              @alvinashcraft.com@web.brid.gy

                              Dew Drop - September 28, 2026 (#4758)

                              Top Links
                              AG-UI Protocol now has a first-class .NET SDK (Daniel Roth)
                              Syncfusion Releases First Set of Open-Source Blazor Controls (Syncfusion Team)
                              GitHub Copilot app for Beginners: How to build custom workflows with canvases (Kayla Cinnamon)
                              Introducing the new Copilot with Hom…

                              [?]Leanpub » 🌐
                              @leanpub@mastodon.social

                              Build Your Own Coding Agent by J. Owen is on sale on Leanpub! Its suggested price is $34.99; get it for $15.99 with this coupon: leanpub.com/build-your-own-cod

                                [?]Mark Dominus » 🌐
                                @mjd@mathstodon.xyz

                                I'm looking for work, please boost!

                                I'm a software engineer with 35 years of experience. I've worked across an unusually wide range of domains: mobile game backends, privacy-preserving data platforms, high-throughput COVID testing infrastructure, email and account systems, e-payment processing, job marketplace systems, and bioinformatics. I pick up new domains quickly and have a history of doing it repeatedly. I understand how to turn business needs into engineering requirements.

                                I prefer to work remotely but I am open to an in-office job in the Philadelphia area or especially in Philadelphia itself. I've often worked remotely since the 1990s and can operate with minimal supervision. Several of my favorite projects were self-directed: I identified the need, built the thing, and shipped it. My preferred time zone is America/New_York, but I'm flexible.

                                Some of the many, many technologies I've used are: Python, Perl, TypeScript/JavaScript, Haskell, Go, C, Java. Postgres, MySQL, SQLite. Flask, SQLAlchemy. AWS (Lambda, S3, RDS, SQS, EC2). Docker, Git. Github and Gitlab.

                                I've also repeatedly picked up new languages and stacks as needed: Haskell for differential privacy research, TypeScript for a 24/7 AWS Lambda system, Flask for my most recent employer. I've become productive with new systems over and over, and I can do it quickly.

                                I'm a published author (Higher-Order Perl, Morgan Kaufmann), longtime blogger, and conference speaker with a reputation for making complex ideas clear.

                                I am a U.S. citizen.

                                My résumé is at https://
                                plover.com/~mjd/cv/Mark%20Jason%20Dominus.pdf

                                mjd@pobox.com

                                Thanks!



                                  #agile boosted

                                  [?]Alvin Ashcraft's Morning Dew » 🌐
                                  @alvinashcraft.com@web.brid.gy

                                  Dew Drop - September 23, 2026 (#4755)

                                  Top Links
                                  Creating a memory dump in C# and Today I will… debug a production crash (Aaron Powell)
                                  GPT-6 Astra, Sol, and Luna for production AI agents in Microsoft Foundry (Naomi Moneypenny)
                                  Developer Deep Dive | Build native Windows apps faster with agents (Nikola Metulev)
                                  Foundry…

                                  #refactoring boosted

                                  [?]Hack a Day (unofficial) » 🤖 🌐
                                  @hackaday@www.urbanmind.net

                                  Extract one mutator only after tests pin object identity. Value equality alone hides alias bugs in shared containers. A copied list can match every item and still break a later caller.

                                  That constraint fits a messy module with shared lists and dicts. The smallest safe change is one leaf extract with stable ids. Wider splits wait until those identity contracts stay green.

                                  Why value checks miss the bug


                                  Many helpers mutate a list that another function still holds. One path sorts that list during a report build. A later path expects the caller's original order to remain.

                                  A value assertion can pass on the returned rows alone. The caller then reads the same object and sees a new order. The failure is an identity change, not a wrong aggregate.

                                  Scope of this pin


                                  This note covers in-place container identity and nothing else. It does not cover clocks, argv arrays, env diffs, or stream bytes. Those other pins answer different risks during an extract.

                                  Pin three facts at each public entry point you still call. Capture object ids for each mutable argument before the call. Capture whether those same ids still match after return.

                                  Also capture the set of changed mapping keys. Capture whether the return value is a new object. Leave file paths and process status codes out of this harness.

                                  Decision table for one leaf


                                  Use the table below before any code move. A leaf may be extracted only when its row says pass. A fail row means you keep that code in place.

                                  Observed leaf behavior

                                  Identity result

                                  Smallest safe move

                                  Reads inputs and returns a new list

                                  Input ids unchanged

                                  Extract that leaf alone

                                  Sorts a caller list in place

                                  Same id, new order

                                  Keep it and pin the order

                                  Copies a dict, then updates the copy

                                  Input id unchanged

                                  Extract and assert a new id

                                  Writes through a nested dict alias

                                  Nested id changed

                                  Do not extract until alias is named

                                  Rebinds a local name only

                                  Caller id unchanged

                                  Extract; the rebind stays local

                                  Replaces one list element object

                                  Same list id, new item id

                                  Extract only if item ids are pinned


                                  The table is a review proposal, not a measured benchmark. It is not a product score and not a quota claim. Your repository rows may differ after the first local run.

                                  Step 1: Name one candidate leaf


                                  Pick the smallest function that touches one container. Prefer a leaf with no further calls into the same module. Reject a candidate that opens files or starts processes.

                                  Write the current name and line range in a short note. State each mutable argument on one following line. Stop if you cannot name a single leaf yet.

                                  Step 2: Record ids around the entry point


                                  Wrap the public function with a thin local probe. Store the id of each mutable argument before the call. Store the id of each argument again after return.

                                  Compare those pairs inside the test, not in production logs. Fail the test when a pinned id changes unexpectedly. Fail it when a new key appears in a watched dict.

                                  Step 3: Add a local probe module


                                  Keep the probe outside the messy production module. Pass the entry point in as a plain callable. Do not import the probe from production code paths.

                                  The sample below is an unexecuted local proposal. Run it only inside a disposable checkout you can delete. Adapt every name to your module before you trust a result.

                                  """Proposal harness for container identity. Not executed in this article."""

                                  def snapshot(args):
                                  rows =
                                  [] for arg in args:
                                  if isinstance(arg, dict):
                                  keys = tuple(sorted(repr(key) for key in arg))
                                  rows.append(("dict", id(arg), keys))
                                  elif isinstance(arg, list):
                                  rows.append(("list", id(arg), len(arg)))
                                  else:
                                  rows.append(("other", id(arg), type(arg).__name__))
                                  return tuple(rows)

                                  def changed_keys(before, after):
                                  lost = set(before) - set(after)
                                  gained = set(after) - set(before)
                                  shared = before.keys() & after.keys()
                                  edited = {key for key in shared if before[key] != after[key]}
                                  names = (repr(key) for key in lost | gained | edited)
                                  return tuple(sorted(names))

                                  def pin_call(func, args, dict_index):
                                  before = snapshot(args)
                                  watched = args
                                  [dict_index] keys_before = dict(watched)
                                  result = func(*args)
                                  after = snapshot(args)
                                  same_ids = all(before[i][1] == after[i][1] for i in range(len(args)))
                                  return {
                                  "same_ids": same_ids,
                                  "changed_keys": changed_keys(keys_before, watched),
                                  "result_id": id(result),
                                  "result_is_arg": any(result is arg for arg in args),
                                  }

                                  Step 4: Score the leaf with fixed rules


                                  Treat the probe output as data, not as a hunch. Allow an extract only when the same ids flag stays true. Require the result object to differ from every input object.

                                  If changed keys are non-empty, keep the mutator in place. Name that mutation in the test before any move. Extract only after the test expects those exact keys.

                                  The return shape below is a schema example, not a captured run. Use it to name fields before you write assertions. Replace the placeholder id when you run the probe locally.

                                  # Schema example only. Not a captured run from any repository.
                                  {
                                  "same_ids": True,
                                  "changed_keys": ("status",),
                                  "result_id": 0,
                                  "result_is_arg": False,
                                  }

                                  Step 5: Apply the smallest edit


                                  Move one passing leaf into a new function body. Keep the old name as a one-line wrapper call. Do not rename callers in that same change.

                                  Preserve argument order and every existing default value. Do not clean up nearby branches in the same patch. A second edit hides which line broke object identity.

                                  Step 6: Re-run the same probe


                                  Run the probe on the wrapper after the move. Compare the same ids flag, changed keys, and result aliasing. Accept the change only when those three fields match.

                                  If any field flips, revert the extract immediately. Add a tighter pin for the field that moved. Retry with a smaller leaf, or stop the split.

                                  Commands to run locally


                                  Use the test runner your repository already trusts. The commands below are a pattern, not a timed result. They do not claim a pass rate or a duration.

                                  python -m pytest tests/test_identity_pin.py -q --tb=short
                                  python -m compileall -q src

                                  Review the failure list before you edit production code. A red identity pin is a hard stop sign. Do not silence it with a broader value assertion.

                                  Where a draft assistant can sit


                                  Disclosure: This article was prepared as part of MonkeyCode's product outreach. MonkeyCode provides free model access and a free server option. Those two availability facts are the only product claims here.

                                  Model names, quotas, hardware, and duration stay out of scope. A free model can draft probe comments and empty table rows. Keep that draft off the repository when a free server is available.

                                  Keep the repository and the test run on your machine. Do not let the draft choose which leaf to extract. Alias rules are easy for a generated draft to miss.

                                  The score function remains the only merge gate. If a second table draft would help, use the free server option there. Paste probe output only, and do not paste secrets or private paths.

                                  Merge nothing until the local re-run matches every pin. A generated row is a suggestion, not a passing characterization. Your local probe output remains the record you keep.

                                  Limits of the probe


                                  An object id is meaningful only during that object's life. A collected object can have its id reused later. Compare ids inside one call, not across separate process runs.

                                  The probe misses mutations that happen inside C extensions. It misses memory changes made through ctypes views. It misses list edits that keep length and equal values.

                                  Nested containers need their own separate identity snapshots. A shallow key check ignores inner list order. Add a nested walk only for the leaf you plan to move.

                                  This method assumes a single thread during the probe. Another thread can mutate a container between the two snapshots. Do not use these pins to bless a concurrent extract.

                                  Who should not use this


                                  Skip the method when every function already returns new objects. You would spend time pinning a contract you do not break. A smaller diff review is enough in that pure module.

                                  Skip it when you cannot call the entry point in a test. A probe that never runs is not evidence of safety. Build a caller harness first, then return to identity pins.

                                  Skip it when fresh objects are the intended contract. Caches, pools, and factories mint new ids on purpose. Pinning stable ids there would freeze the wrong rule.

                                  Skip it for permission checks and other security boundaries. Identity pins do not prove authorization behavior at all. Use dedicated tests for those sensitive paths instead.

                                  Close


                                  Start from the identity conclusion, not from a broad rewrite. Pin object ids, changed keys, and result aliasing first. Extract one passing leaf, then re-pin those same fields.

                                  Leave every other cleanup for a later, separate change. The decision table tells you when to stop moving code. A green value test is not permission to move a mutator.
                                  Freeze Object Identity Before One Mutator Extract

                                    #refactoring boosted

                                    [?]Hack a Day (unofficial) » 🤖 🌐
                                    @hackaday@www.urbanmind.net

                                    A pricing function still folds region rules, bulk surcharges, and coupon stacking into one nested block. A teammate asks an assistant to tidy that module before a tax change lands next week. The first generated patch rewrites four helpers, renames two exceptions, and flips a surcharge for twelve-item carts. Review then spends more time reconstructing prior behavior than evaluating the one extract that was actually needed.

                                    This walkthrough treats that failure as a process problem rather than a taste debate about clean code. The useful unit of work is one nested decision on one hot path, recorded before any symbol moves. After the outcomes are frozen, the only permitted edit is a single predicate extract that preserves those outcomes. The fixture below is labeled as an unexecuted example; adapt the recorder to your language and runner.

                                    Why broad cleanup patches fail on nested pricing logic


                                    Messy pricing code is usually a decision tree that hides inside mutation, logging, and ad-hoc rounding. Assistants trained to improve readability optimize for local style, not for the sparse matrix of inputs that production actually hits. A four-hundred-line rewrite can look coherent in diff view while changing only one compound condition that finance already depends on.

                                    Three failure patterns show up repeatedly in review, even when the generated code is syntactically nicer:

                                    • Silent branch collapse. Two region checks get merged, and a cart that used to skip surcharge now pays it.
                                    • Exception reshaping. A ValueError becomes a custom type, and an upstream retry path stops matching.
                                    • Rounding drift. Intermediate round(..., 2) calls move, so a twelve-item cart differs by one cent.

                                    None of those failures are visible if the first test you add is an assertion against the new design. Characterization has to lock the old outcomes first, before any helper is renamed or moved. Only then does a one-predicate extract become a reviewable change rather than a behavior lottery.

                                    Freeze the nested decision, not the surrounding module


                                    Pick the hottest path through the function, not the whole file, before anyone starts renaming symbols. In this fixture, that path is whether a cart receives a bulk surcharge and which reason code is attached. Coupon stacking and tax remain inside the messy function on purpose, because moving them would expand the blast radius past a single commit.

                                    Define a record as a triple: canonical input, outcome tuple, and a stable hash of that pair. The hash is a review signal, not a cryptographic control, and a mismatch should stop the extract immediately. If two consecutive runs disagree, the path is still too noisy to touch.

                                    A compact recorder for one hot path


                                    The module under test is intentionally awkward. It mutates a dict, appends a log line, and buries the surcharge predicate among unrelated branches.

                                    # pricing.py — unexecuted fixture, not production code
                                    from typing import Any

                                    def price_order(cart: dict[str, Any]) -> dict[str, Any]:
                                    items = cart.get("items") or
                                    [] region = (cart.get("region") or "US").upper()
                                    coupon = cart.get("coupon")
                                    subtotal = sum(
                                    float(i.get("unit_cents", 0)) * int(i.get("qty", 0)) for i in items
                                    )
                                    log = list(cart.get("_log") or [])

                                    surcharge = 0.0
                                    reason = "none"
                                    count = sum(int(i.get("qty", 0)) for i in items)

                                    if region in {"EU", "UK"} and coupon == "VATZERO":
                                    reason = "vat_exempt"
                                    elif count >= 12 and region != "EU":
                                    surcharge = round(subtotal * 0.04, 2)
                                    reason = "bulk_surcharge"
                                    log.append(f"bulk:{count}:{region}")
                                    elif count >= 12 and region == "EU":
                                    reason = "eu_bulk_skipped"
                                    log.append(f"skip_eu:{count}")
                                    else:
                                    log.append(f"std:{count}:{region}")

                                    if coupon == "SAVE10" and reason != "vat_exempt":
                                    subtotal = round(subtotal * 0.9, 2)

                                    cart["subtotal"] = subtotal
                                    cart["surcharge"] = surcharge
                                    cart["reason"] = reason
                                    cart["_log"] = log
                                    cart["total"] = round(subtotal + surcharge, 2)
                                    return cart

                                    The recorder ignores style and stores only the decision surface planned for extract: item count, region, coupon, reason, surcharge, and total.

                                    # characterize_price_order.py — unexecuted fixture
                                    import hashlib
                                    import json
                                    from copy import deepcopy

                                    from pricing import price_order

                                    CASES = [
                                    {"items": [{"unit_cents": 199, "qty": 12}], "region": "US", "coupon": None},
                                    {"items": [{"unit_cents": 199, "qty": 12}], "region": "EU", "coupon": None},
                                    {"items": [{"unit_cents": 199, "qty": 11}], "region": "US", "coupon": None},
                                    {"items": [{"unit_cents": 500, "qty": 12}], "region": "UK", "coupon": "VATZERO"},
                                    {"items": [{"unit_cents": 199, "qty": 12}], "region": "US", "coupon": "SAVE10"},
                                    {"items": [{"unit_cents": 50, "qty": 0}], "region": "US", "coupon": None},
                                    {"items": [{"unit_cents": 199, "qty": 12}], "region": "eu", "coupon": None},
                                    ]

                                    def outcome(cart):
                                    result = price_order(deepcopy(cart))
                                    return {
                                    "count": sum(int(i.get("qty", 0)) for i in cart.get("items") or []),
                                    "region": (cart.get("region") or "US").upper(),
                                    "coupon": cart.get("coupon"),
                                    "reason": result["reason"],
                                    "surcharge": result["surcharge"],
                                    "total": result["total"],
                                    }

                                    def ledger_hash(rows):
                                    blob = json.dumps(rows, sort_keys=True, separators=(",", ":")).encode()
                                    return hashlib.sha256(blob).hexdigest()[:16]

                                    if __name__ == "__main__":
                                    rows = [outcome(c) for c in CASES]
                                    print(json.dumps(rows, indent=2, sort_keys=True))
                                    print("ledger", ledger_hash(rows))

                                    Commands that keep the freeze honest


                                    Run the recorder twice before anyone edits pricing.py, and treat a hash mismatch as a stop sign. Store the printed ledger in the review notes, or as a checked-in JSON snapshot if that habit already exists. The second run must match the first hash; if it does not, shrink the recorded surface before extracting anything.

                                    python characterize_price_order.py | tee /tmp/ledger1.txt
                                    python characterize_price_order.py | tee /tmp/ledger2.txt
                                    diff -u /tmp/ledger1.txt /tmp/ledger2.txt
                                    python -m pytest tests/test_price_order_characterization.py -q
                                    git add characterize_price_order.py tests/test_price_order_characterization.py
                                    git commit -m "Characterize bulk-surcharge decision before any extract"

                                    A minimal pytest pin is enough for CI. It should fail on reason-code drift, surcharge drift, or total drift, and it should ignore log-line wording.

                                    # tests/test_price_order_characterization.py — unexecuted fixture
                                    from characterize_price_order import CASES, ledger_hash, outcome

                                    # Captured from the first honest run of characterize_price_order.py
                                    PINNED_HASH = "replace_me_after_first_run"

                                    def test_bulk_surcharge_decision_is_frozen():
                                    rows = [outcome(c) for c in CASES]
                                    assert ledger_hash(rows) == PINNED_HASH

                                    Replace PINNED_HASH with the value from the first run, then keep that commit separate from the extract commit. Mixing the pin and the extract in one diff reintroduces the original review problem, because reviewers cannot tell a captured baseline from a behavior change.

                                    A decision table the extract must not violate


                                    The table is the contract for the extract commit. If an assistant proposes a prettier predicate that disagrees with any row, the extract is rejected, regardless of naming quality.

                                    qty

                                    region

                                    coupon

                                    reason

                                    surcharge rule

                                    12

                                    US

                                    none

                                    bulk_surcharge

                                    4% of pre-coupon subtotal

                                    12

                                    EU

                                    none

                                    eu_bulk_skipped

                                    0

                                    11

                                    US

                                    none

                                    none

                                    0

                                    12

                                    UK

                                    VATZERO

                                    vat_exempt

                                    0

                                    12

                                    US

                                    SAVE10

                                    bulk_surcharge

                                    4% first; coupon then cuts subtotal

                                    0

                                    US

                                    none

                                    none

                                    0

                                    12

                                    eu

                                    none

                                    eu_bulk_skipped

                                    0, because region is uppercased


                                    The SAVE10 row is the interesting collision in this fixture. The current function applies bulk surcharge against the pre-coupon subtotal, then discounts the subtotal. A cleanup that computes surcharge after the coupon looks cleaner and is wrong relative to today's ledger. Characterization exists to make that disagreement boring and automatic, instead of a late finance incident.

                                    The smallest safe change: one predicate, one commit


                                    After the hash is pinned, the only allowed production edit is extracting the condition that decides bulk_surcharge versus eu_bulk_skipped. Coupon handling, logging, and totals stay in price_order during this commit. VAT exemption stays inline as well, because it is a different decision and deserves a later extract.

                                    def bulk_surcharge_reason(count: int, region: str) -> str | None:
                                    """Return a bulk-related reason, or None when the bulk branch does not apply."""
                                    if count < 12:
                                    return None
                                    if region == "EU":
                                    return "eu_bulk_skipped"
                                    return "bulk_surcharge"

                                    Wire it in with the smallest possible splice. Do not reorder coupon math in the same commit, even if the new order reads more linearly.

                                    bulk_reason = bulk_surcharge_reason(count, region)
                                    if region in {"EU", "UK"} and coupon == "VATZERO":
                                    reason = "vat_exempt"
                                    elif bulk_reason == "bulk_surcharge":
                                    surcharge = round(subtotal * 0.04, 2)
                                    reason = bulk_reason
                                    log.append(f"bulk:{count}:{region}")
                                    elif bulk_reason == "eu_bulk_skipped":
                                    reason = bulk_reason
                                    log.append(f"skip_eu:{count}")
                                    else:
                                    log.append(f"std:{count}:{region}")

                                    Then re-run the recorder and the pinned test before opening the review. If PINNED_HASH still matches, the extract changed structure without changing the nested decision. If it does not match, revert and shrink the splice; do not fix forward by editing cases.

                                    A patch-size budget you can enforce in review


                                    Use this checklist on the extract commit only, not on the earlier characterization commit:

                                    1. Production diff stays under roughly forty lines, including the new function.
                                    2. Exactly one new symbol is introduced, and no existing exception type is renamed.
                                    3. Coupon, tax, and logging branches are untouched except for the call-site splice.
                                    4. Characterization hash is unchanged; any hash delta blocks merge.
                                    5. No formatter-only edits in unrelated functions ride along in the same commit.

                                    git diff --stat HEAD~1
                                    git diff -U0 HEAD~1 -- pricing.py
                                    git diff --name-only HEAD~1

                                    If an assistant or a colleague cannot stay inside that budget, split the work instead of raising the budget. The next extract might be is_vat_exempt(region, coupon), and it gets its own pin if those cases are not already covered. Sequential extracts are slower to write and much cheaper to review than one impressive cleanup.

                                    Where a coding assistant belongs in this sequence


                                    Assistants are useful after the ledger exists, because the task becomes proposing a predicate that preserves these rows, not making the file look clean. They are much less useful as the first author of a module-wide rewrite, which is how the twelve-item surcharge usually flips. Feed the model the decision table and the current function, then reject any patch that also reformats coupon math.

                                    Disclosure: This article was prepared as part of MonkeyCode's product outreach. If the recorder needs a scratch environment instead of a laptop checkout, MonkeyCode's free model access and free server option can draft extra cases and rerun the hash loop without pointing the assistant at production secrets. Accept only a predicate-sized patch that keeps PINNED_HASH stable, and keep the same pytest pin in CI regardless of which editor wrote the function.

                                    The method still holds if pytest runs on a workstation and the predicate is written by hand. The assistant is optional infrastructure around a frozen decision, not a substitute for the freeze.

                                    Limitations


                                    • Characterization records what the function does today, including bugs that product may later want to change on purpose.
                                    • A seven-row table does not cover concurrent carts, currency conversion, or coupons that expire mid-request.
                                    • Hashing JSON rows will churn if logs, timestamps, or unordered keys are included without sort_keys.
                                    • Extracting a predicate does not improve observability; existing logs still carry production incidents.
                                    • Assistants can memorize the table and still reorder surcharge math unless the pin is enforced in CI.


                                    Who should skip this approach


                                    Skip it if the change is an intentional price-policy update rather than a structure-only extract. Skip it if the pipeline cannot run even a single-file pytest target on every patch. Skip it for cryptographic, access-control, or tax-engine code that needs a formal spec, not a snapshot of yesterday's behavior. Skip it when the hot path is not identifiable, because freezing a random nested if teaches the team the wrong boundary.

                                    The durable habit is small and slightly boring. Record one nested decision until its hash is dull, then move one predicate, then stop. The cleanup still happens; it happens as a sequence of reviewable extracts instead of one impressive diff that finance cannot reconstruct.
                                    Record One Nested Decision, Then Extract a Single Predicate

                                      [?]Robert Kingett » 🌐
                                      @WeirdWriter@caneandable.social

                                      My Git experimentation for beta reader reviewing thoughts. [SENSITIVE CONTENT]

                                      As I continue to use my Git workflow for beta readers, I am realizing Git just isn't for fiction prose and everything I am doing is tricks and hacks to work with it.

                                      I ended up hiring someone to build me a python script that is interactive.

                                      The common frustration I ran into was,

                                      let's say Jason edited a paragraph in Chapter 1.

                                      Jane edited the same paragraph but a different section.

                                      I wanted Git to smartly present these as changes and have me reject, accept, and or set them aside for later.

                                      It couldn't do that with Git. I'd eventually have to open files and compare myself, which defeats the reasoning. I was contorting Git to the point it stopped making sense to hack and contort.

                                      I gave up and just use Git as glorified Cloud storage and for tracking my own changes to my own MD files.

                                      My trial and errors to turn Git into more of a track changes system that was screen reader friendly wasn't cutting it, so I just decided to pay someone money to make me a non-vibed Python script that includes multiple things such as Critic Markup support, Pandoc's Track Changes support when I convert DOCX to MD using --track-changes=all,

                                      and other goodies that compare comments, compares insertions, deletions, and far more inside of text files and MD files.

                                      I *could* use Google Docs and Track changes, but I like working with local text files, and I have more than five beta readers.

                                        #agile boosted

                                        [?]Alvin Ashcraft's Morning Dew » 🌐
                                        @alvinashcraft.com@web.brid.gy

                                        Dew Drop - September 22, 2026 (#4754)

                                        Top Links
                                        Introducing XAML.io v0.9: Build .NET Apps From a Prompt, in Your Browser (XAML.io Team)
                                        Single Day Tickets Now Available for TechBash 2026 (TechBash Team)
                                        New in Edge for developers – Create better components and make your site agent-ready (Patrick Brosset)
                                        7 Document A…

                                        [?]SeaGL 2026: Oct 23rd and 24th » 🌐
                                        @SeaGL@mastodon.social

                                        #refactoring boosted

                                        [?]Hack a Day (unofficial) » 🤖 🌐
                                        @hackaday@www.urbanmind.net

                                        A tangled pricing function still mixes tax rules, discounts, and rounding in one seven-hundred-line Python module. An agent then opens a pull request that rewrites helpers, renames locals, and claims the cleanup is behavior-preserving. The existing tests remain green because they only assert a final integer total for two happy-path invoices. This walkthrough freezes one entry point as a contract tape, then allows only the smallest extract that keeps that tape identical.

                                        The method is intentionally narrow. It does not certify the whole service, and it does not bless a large rewrite just because unit tests still pass.

                                        Why green tests still miss the extract


                                        Messy modules usually have tests that pin outcomes, not contracts. An agent can change control flow, drop a rare branch, or replace None with {} while those outcome tests stay green. Reviewers then debate naming while the silent behavior change hides in a helper that used to skip missing keys. A useful freeze therefore records more than the final number.

                                        Record four things for a single entry point, not for the entire package:

                                        • argument type trees and sorted mapping keys
                                        • return type trees, including None versus empty containers
                                        • exception class names on known failure inputs
                                        • a short digest of the canonical JSON result

                                        That combination is a contract tape. It is cheaper than a full-suite dump and stricter than one assertion on a total. The tape is the gate; the extract is allowed only after the gate is red-green on the current tree.

                                        The lab fixture


                                        The example below is a proposed fixture, not a production service. Treat every snippet as unexecuted sample code for this walkthrough.

                                        # messy_pricing.py — proposed lab fixture
                                        from decimal import Decimal, ROUND_HALF_UP

                                        def price_invoice(payload):
                                        items = payload.get("items") or
                                        [] subtotal = Decimal("0")
                                        for item in items:
                                        qty = Decimal(str(item.get("qty") or 0))
                                        unit = Decimal(str(item.get("unit") or 0))
                                        subtotal += qty * unit
                                        discount = Decimal(str(payload.get("discount") or 0))
                                        if payload.get("kind") == "wholesale" and subtotal > 100:
                                        discount += Decimal("5")
                                        taxable = subtotal - discount
                                        if taxable < 0:
                                        taxable = Decimal("0")
                                        rate = Decimal("0.08") if payload.get("region") == "west" else Decimal("0.06")
                                        tax = (taxable * rate).quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
                                        total = (taxable + tax).quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
                                        return {
                                        "subtotal": str(subtotal),
                                        "discount": str(discount),
                                        "tax": str(tax),
                                        "total": str(total),
                                        "flags": payload.get("flags") or {},
                                        }

                                        The function looks small, yet it mixes defaults, regional tax, wholesale extras, and stringified decimals. An agent can split it into several helpers and still keep total stable on the two cases a sparse test file already covers. The missing risk is a changed flags default, a dropped wholesale bonus, or a different empty-items path.

                                        Build the contract tape


                                        The recorder walks JSON-like values, stores a shape tree, and stores a short digest. Run it against one function only. Do not start by taping every private helper, because that freeze would block the extract you actually want.

                                        # contract_tape.py — proposed walkthrough code
                                        from __future__ import annotations

                                        import hashlib
                                        import json
                                        from pathlib import Path
                                        from typing import Any

                                        TAPE_DIR = Path("tapes")

                                        def shape_of(value: Any) -> Any:
                                        if value is None:
                                        return {"kind": "none"}
                                        if isinstance(value, bool):
                                        return {"kind": "bool"}
                                        if isinstance(value, int) and not isinstance(value, bool):
                                        return {"kind": "int"}
                                        if isinstance(value, float):
                                        return {"kind": "float"}
                                        if isinstance(value, str):
                                        return {"kind": "str", "len": len(value)}
                                        if isinstance(value, (list, tuple)):
                                        return {
                                        "kind": "list",
                                        "len": len(value),
                                        "items": [shape_of(v) for v in list(value)[:8]],
                                        }
                                        if isinstance(value, dict):
                                        keys = sorted(value.keys(), key=lambda k: str(k))
                                        return {
                                        "kind": "dict",
                                        "keys": [str(k) for k in keys],
                                        "fields": {str(k): shape_of(value[k]) for k in keys},
                                        }
                                        return {"kind": type(value).__name__}

                                        def canonical(value: Any) -> Any:
                                        if isinstance(value, dict):
                                        return {str(k): canonical(value[k]) for k in sorted(value, key=lambda x: str(x))}
                                        if isinstance(value, (list, tuple)):
                                        return [canonical(v) for v in value]
                                        return value

                                        def digest(value: Any) -> str:
                                        blob = json.dumps(canonical(value), separators=(",", ":"), ensure_ascii=True)
                                        return hashlib.sha256(blob.encode("utf-8")).hexdigest()[:16]

                                        def record_call(fn, payload):
                                        row = {"input_shape": shape_of(payload)}
                                        try:
                                        result = fn(payload)
                                        except Exception as exc:
                                        row["exception"] = type(exc).__name__
                                        row["result_shape"] = None
                                        row["digest"] = None
                                        return row
                                        row["exception"] = None
                                        row["result_shape"] = shape_of(result)
                                        row["digest"] = digest(result)
                                        return row

                                        Seed the tape with cases that miss the happy path, not only the two invoices already in unit tests. Wholesale bonus, missing items, and a discount that drives taxables below zero are the usual silent diffs.

                                        # record_tape.py — proposed walkthrough code
                                        import json
                                        import sys
                                        from pathlib import Path

                                        from contract_tape import TAPE_DIR, record_call
                                        from messy_pricing import price_invoice

                                        CASES = [
                                        {
                                        "name": "retail_east_empty_flags",
                                        "payload": {
                                        "items": [{"qty": 2, "unit": "10.00"}],
                                        "discount": "1.00",
                                        "region": "east",
                                        },
                                        },
                                        {
                                        "name": "wholesale_west_bonus",
                                        "payload": {
                                        "items": [{"qty": 12, "unit": "9.50"}],
                                        "kind": "wholesale",
                                        "region": "west",
                                        "flags": {"rush": True},
                                        },
                                        },
                                        {
                                        "name": "negative_after_discount",
                                        "payload": {"items": [{"qty": 1, "unit": "3"}], "discount": "9.00"},
                                        },
                                        {
                                        "name": "missing_items",
                                        "payload": {"region": "west"},
                                        },
                                        ]

                                        def build_tape():
                                        return {
                                        "entry": "price_invoice",
                                        "cases": [
                                        {"name": case["name"], **record_call(price_invoice, case["payload"])}
                                        for case in CASES
                                        ],
                                        }

                                        def main(mode: str) -> int:
                                        TAPE_DIR.mkdir(exist_ok=True)
                                        path = TAPE_DIR / "price_invoice.json"
                                        fresh = build_tape()
                                        if mode == "--write":
                                        path.write_text(json.dumps(fresh, indent=2, sort_keys=True) + "\n")
                                        print(f"wrote {path}")
                                        return 0
                                        if not path.exists():
                                        print("missing tape; run with --write first", file=sys.stderr)
                                        return 2
                                        pinned = json.loads(path.read_text())
                                        if pinned != fresh:
                                        print("contract tape drift")
                                        print(json.dumps({"pinned": pinned, "fresh": fresh}, indent=2))
                                        return 1
                                        print("contract tape matched")
                                        return 0

                                        if __name__ == "__main__":
                                        raise SystemExit(main(sys.argv[1] if len(sys.argv) > 1 else "--check"))

                                        Commands stay boring on purpose. Write once from the known-messy tree, then check after every extract. If the check is not in CI yet, run it locally before you even open the diff.

                                        python record_tape.py --write
                                        python record_tape.py --check
                                        git add tapes/price_invoice.json contract_tape.py record_tape.py
                                        git commit -m "Pin price_invoice contract tape before extract"

                                        A matched tape means the entry point still accepts the same shapes and still emits the same canonical result. It does not mean the internals are pretty, and it does not mean every caller is covered.

                                        Allow only the smallest safe change


                                        After the tape is pinned, the next move is one extract, not a module rewrite. The candidate in this fixture is the discount block, because it is a closed rule with one extra wholesale branch. Keep price_invoice as the public entry so callers do not move in the same commit.

                                        def apply_discount(subtotal, payload):
                                        discount = Decimal(str(payload.get("discount") or 0))
                                        if payload.get("kind") == "wholesale" and subtotal > 100:
                                        discount += Decimal("5")
                                        return discount

                                        That is the whole change budget for the first patch. Do not rename flags, do not switch Decimal to float, and do not introduce a pricing class in the same diff. If an agent returns a four-file cleanup, reject it before reading the prose in the pull request.

                                        Use a diff gate so the budget is mechanical. The script below is proposed local tooling, not a required platform hook.

                                        # extract_gate.py — proposed walkthrough code
                                        import subprocess
                                        import sys

                                        MAX_FILES = 2
                                        MAX_NET_LINES = 40

                                        def main() -> int:
                                        raw = subprocess.check_output(
                                        ["git", "diff", "--numstat", "HEAD"],
                                        text=True,
                                        ).strip()
                                        if not raw:
                                        print("no unstaged diff against HEAD")
                                        return 0
                                        files = 0
                                        net = 0
                                        for line in raw.splitlines():
                                        added, deleted, path = line.split("\t", 2)
                                        if path.startswith("tapes/"):
                                        continue
                                        files += 1
                                        if added != "-" and deleted != "-":
                                        net += abs(int(added) - int(deleted)) + min(int(added), int(deleted))
                                        if files > MAX_FILES or net > MAX_NET_LINES:
                                        print(f"extract budget exceeded: files={files} net_lines~={net}")
                                        return 1
                                        print(f"extract budget ok: files={files} net_lines~={net}")
                                        return 0

                                        if __name__ == "__main__":
                                        raise SystemExit(main())

                                        Accept or reject the agent patch

                                        Observation

                                        Decision

                                        Next step

                                        Tape matches and the diff touches one helper plus the entry function

                                        Accept

                                        Commit, then pick the next closed block

                                        Tape matches but three unrelated files moved

                                        Reject

                                        Ask for a single-function extract

                                        Tape drifts on missing_items only

                                        Reject

                                        Restore the empty-items default before any rename

                                        Tape drifts on digest but not on shapes

                                        Reject

                                        A value changed; do not treat it as a style cleanup

                                        Tests pass while the tape is missing

                                        Reject

                                        The suite is too coarse to review an agent rewrite


                                        The table is the review script. It keeps the discussion on contracts and budgets instead of on whether the generated names look tidy.

                                        Where a disposable coding environment fits


                                        Once the tape and the gate exist, an agent is useful only as a proposer of the next one-function extract. It should not be the source of truth for behavior. Disclosure: This article was prepared as part of MonkeyCode's product outreach. MonkeyCode's free model access and free server option can host that record-and-check loop when you want a scratch machine, without turning the tape into a marketing demo.

                                        Keep the workflow local-first either way:

                                        1. Write the tape from the messy tree.
                                        2. Ask for one extract that preserves price_invoice.
                                        3. Run python record_tape.py --check and python extract_gate.py.
                                        4. Reject any patch that fails either command, even when unit tests pass.

                                        The product mention is optional. The tape still works if you run the same commands on a laptop and ignore every coding agent.

                                        Limitations, and who should skip this


                                        The tape hashes canonical JSON of return values, so unordered sets, timestamps, and randomly allocated identifiers will thrash the digest. Do not use this recorder on those outputs without first stripping volatile fields. Shape trees also stop at eight list items, which is enough for invoice lines in this fixture and too weak for bulk imports.

                                        Skip the method when the entry point is a long-lived process, a GUI loop, or a network client with live clocks. Skip it when you do not own the module, because pinning a tape is still a behavior freeze and can conflict with an active feature branch. Skip it when the real bug is numeric policy, such as rounding mode, because a digest match can still hide a business change if your cases never hit that branch.

                                        The approach also fails closed on purpose. A missing tape is a failed gate, not a reason to trust a large agent rewrite. If the team cannot name four cases that miss the happy path, the extract is not the current problem; the missing cases are.

                                        What this walkthrough actually settles


                                        A messy module becomes safer to touch when one entry point has a replayable contract, not when an agent restyles the file. The smallest safe change is then a single closed helper, reviewed against a tape and a diff budget. If those two checks pass, you earned the next extract. If they fail, the rewrite was a story about cleanliness, not a proof about behavior.
                                        Tape One Entry Point Before You Extract Anything From a Messy Module

                                          [?]Jeff Triplett » 🌐
                                          @webology@mastodon.social

                                          Hey devs,

                                          Remember when Pillow took over PIL's space?

                                          Do we have that for the requests + httpx + httpx2 space yet?

                                          I have hit my point where having all three installed spark no joy for me.

                                          I don't even care who wins or loses this package off, but I do not need all three installed, and I'll keep my personal preference to myself.

                                          Thid party packages are very opinioned which is why I have all three. I do not want or need all three.

                                          What's the solution?

                                            Greg Wilson boosted

                                            [?]David Lord :python: » 🌐
                                            @davidism@mas.to

                                            I've turned off pull requests from non-members for Flask, Werkzeug, Jinja, and Click this week, and have enjoyed not waking up to a fresh wave of AI junk every morning. I think I'm going to update our contributing guide to say "introduce yourself and how you use the library in chat first if you want to contribute pull requests". Sad that we had to go here, but aside from conference sprints we weren't really getting outside contribution anyway (which is also sad, but that's been the case a lot longer than AI has been around).

                                              #refactoring boosted

                                              [?]Hack a Day (unofficial) » 🤖 🌐
                                              @hackaday@www.urbanmind.net

                                              Pin the query-string contract before any helper extract. Extract one encoder only after those bytes...
                                              Pin urlencode doseq and quote_via Before One Query Extract

                                                Glyph boosted

                                                [?]Seth Larson » 🌐
                                                @sethmlarson@mastodon.social

                                                RE: social.rust-lang.org/@rust/117

                                                The lovely folks from @rust gave me a shoulder-tap in one of our mutual security discussion channels about these ongoing attacks:

                                                blog.rust-lang.org/2026/09/17/

                                                There's no particular reason the attackers wouldn't do the same to maintainers of Python projects. Please remain vigilant and send anything strange to security@python.org so we can alert others if needed.

                                                  #refactoring boosted

                                                  [?]Hack a Day (unofficial) » 🤖 🌐
                                                  @hackaday@www.urbanmind.net

                                                  I have a small agent that handles one piece of routine work at a time. It looks
                                                  at what needs doing, picks the thing most worth doing, shows me the plan, and
                                                  does it if I say yes. Underneath, it is glue: it drives a few command-line
                                                  tools, calls a model, reshapes a lot of JSON, and prints a readable summary.

                                                  It was 2150 lines of bash across seven files. It is now Python.

                                                  So the answer looks like yes. I don't think it is, and why I don't is most of
                                                  the reason I'm writing this down.

                                                  The argument for staying was sound. Its inputs weren't.


                                                  I had been through this question before and decided to stay — carefully enough
                                                  that the reasoning became a section of the project README titled Why this is
                                                  still bash
                                                  . Nine tenths of the program is subprocess orchestration, which is
                                                  bash's home ground. Rewriting 2000 lines with no test coverage is the standard
                                                  way to lose behavior silently. And the bug ledger said the expensive bugs were
                                                  design errors that any language would have permitted.

                                                  I still think all of that is true.

                                                  What moved was a requirement. I had been treating runs with no build step as
                                                  hard, which made "python3 is already installed" the load-bearing argument. Then
                                                  the requirement got clarified: no build step isn't a rule, it just shouldn't be
                                                  complicated to start.

                                                  That one sentence killed my best argument. So I measured what was actually at
                                                  stake — 89ms for Python plus every standard library module it needs, against
                                                  3ms for bash. Eighty-six milliseconds, in a program that waits thirty to a
                                                  hundred seconds on a model.

                                                  My reasoning was valid; its inputs weren't, and I had spent almost no effort
                                                  checking them.
                                                  That ratio was backwards, and I don't think that's unusual.

                                                  What decided it was 162 calls to jq


                                                  Every list length, every filter, forking a process to handle data that should
                                                  have been sitting in memory.

                                                  The cost was not performance. 162 forks are nothing next to a minute of model
                                                  latency. The cost was expressiveness. Every structure in the program either
                                                  fit in a one-line jq expression or got split into three pieces. What I wrote
                                                  was never the structure I wanted; it was the structure jq could state on one
                                                  line. That cost is invisible in any single line of code and shows up in the
                                                  designs you never consider.

                                                  I had three lists: the files a change actually touched, the files the agent
                                                  itself had written, and the files I had approved in advance. I needed two
                                                  differences between them.

                                                  later="$(jq -nc --argjson a "$actual" --argjson w "$written" \
                                                  'if $w == null then [] else ($a - $w) end')"
                                                  extra="$(jq -nr --argjson w "$written" --argjson p "$planned" \
                                                  '($w - $p) | join(", ")')"

                                                  later = [] if written is None else [f for f in actual if f not in written]
                                                  extra = [f for f in written if f not in planned]

                                                  The second one is barely shorter. It is what I would have written on the first
                                                  attempt; the first took me several tries to get right.

                                                  There was also a run that died on line 567: 1: command not found, which I
                                                  never located. It went away when that section was rewritten for unrelated
                                                  reasons. A bug you can't find after the fact doesn't just go unfixed — it
                                                  tells you the next one of its kind will too.

                                                  The line count did not go down


                                                  The port took a day.

                                                  bash 2150 lines
                                                  Python 2258 lines ← up 108

                                                  of which:
                                                  code 1278 ← down 40%
                                                  comments 980

                                                  Most people expect the opposite, so it's worth being blunt about. The code
                                                  shrank by forty percent; the difference is comments and docstrings — the notes
                                                  recording which specific incident each safety check exists to prevent, which
                                                  were exactly what I'd been afraid of losing.

                                                  Evaluate this rewrite by total line count and it accomplished nothing.

                                                  What made it cheap: two seams built for other reasons


                                                  This is the part I actually wanted to write down.

                                                  The prompts are files, not strings. Every prompt lives in its own Markdown
                                                  file and the code fills {{placeholder}} holes in it. I did that for unrelated
                                                  reasons: prompts get edited constantly, they want to be read as prose, and a
                                                  stray $ or backtick has to stay inert instead of being eaten by the shell.

                                                  The result was that the migration did not touch one word of any prompt. I
                                                  checked modification times afterward to be sure. Everything that determines
                                                  this program's behavior — the criteria I've tuned over and over, the order
                                                  judgments get made in — lives in those files. Changing languages only replaced
                                                  the glue that assembles them.

                                                  Each kind of task is a separate executable that speaks JSON. Verb on argv,
                                                  JSON on stdin, JSON on stdout. I built it that way because bash has no modules,
                                                  and putting them behind a process boundary beat having them scribble on each
                                                  other's variables. Pure coping.

                                                  The result: there was no big-bang rewrite to choose. The orchestrator could
                                                  be Python while the task types were still bash, or the reverse. I moved one
                                                  file at a time and ran each one on its own afterward. (That boundary is
                                                  probably also why this never hit the wall bash projects hit — the largest bash
                                                  agent I know of reached 4700 lines as a single file assembled by cat src/*.sh,
                                                  and what broke was module structure, not correctness.)

                                                  Neither seam was built with portability in mind. Both were built to solve
                                                  something annoying at the time.

                                                  Whether a rewrite will be cheap is decided before you start it.

                                                  I kept the process boundary afterward, by the way. Folding the task types into
                                                  Python imports would save a JSON round trip, and it is the best structural
                                                  decision in the project.

                                                  The real risk of Python is not the build step


                                                  With a shebang and standard library only, it is invoked exactly the way it was
                                                  before. No virtualenv, no install.

                                                  The risk is that Python invites dependencies, and bash's poverty was itself
                                                  a form of protection. requests when urllib is right there; a schema
                                                  validator when the model CLI already enforces the schema; an argument parser
                                                  for 25 lines of parsing; a formatting library for a display layer that exists.
                                                  Each has a plausible case, and after all four "quick to start" is gone.

                                                  So there is one rule in the README now: standard library only, and say why it
                                                  can't be done with it before adding anything.
                                                  The whole program needs six
                                                  modules.

                                                  What I did not verify


                                                  I exercised every path after the port, including a full dry run of the
                                                  expensive one — isolated checkout, model writes the code, formatter, vet,
                                                  build, tests, commit — stopping short of pushing. Fifteen tests on the pure
                                                  functions pass.

                                                  The two lines that push a branch and open a change request never ran,
                                                  because running them means actually opening one. And the safety checks inside
                                                  the task types I translated by hand, one at a time. I believe I got them right,
                                                  and this project still has no test that can prove it.

                                                  I don't think bash was the wrong choice. It carried this to 2150 lines, and for
                                                  all of that time I was changing judgment logic rather than fighting the
                                                  language. Its problem was never that it couldn't do the job. Its problem was
                                                  that its expressiveness had started deciding my designs.

                                                  Was bash the wrong language for my agent?

                                                    [?]Python Software Foundation » 🌐
                                                    @ThePSF@fosstodon.org

                                                    The PSF is pleased to announce the results of the inaugural election for the Python Packaging Council! Sending a big thank you to the candidates and our community for participating in this long awaited election. We're excited to see the council start its work!
                                                    pyfound.blogspot.com/2026/09/a

                                                      #agile boosted

                                                      [?]Alvin Ashcraft's Morning Dew » 🌐
                                                      @alvinashcraft.com@web.brid.gy

                                                      Dew Drop - September 16, 2026 (#4752)

                                                      Top Links
                                                      Aspire 13.5.4 (Aspire Team)
                                                      Performance Improvements in .NET 11 (Stephen Toub)
                                                      Intelligent Terminal 0.2.2572: Faster, Smoother, and More Reliable (Hamza Usmani)
                                                      Share your .NET story with the community (Luis Quintanilla)
                                                      Rider and ReSharper 2026.2.2 Are Out! (Alexander …

                                                      [?]Leanpub » 🌐
                                                      @leanpub@mastodon.social

                                                      100 LLM Autopsies by Hatem M. is on sale on Leanpub! Its suggested price is $32.00; get it for $19.84 with this coupon: leanpub.com/llm-autopsies/c/Le

                                                        [?]Leanpub » 🌐
                                                        @leanpub@mastodon.social

                                                        Discrete Mathematics for Computer Science by Marie Brodsky, Alexander Golovnev, Alexander S. Kulikov, Vladimir Podolskii, and Alexander Shen is the featured book 📖 on Leanpub!

                                                        This book supplements the DM for CS Specialization at Coursera and contains many interactive puzzles, autograded quizzes, and code snippets. They are intended to help you to discover important ideas in discrete mathematics on your own.

                                                        Link: leanpub.com/discrete-math

                                                          #refactoring boosted

                                                          [?]Hack a Day (unofficial) » 🤖 🌐
                                                          @hackaday@www.urbanmind.net

                                                          Messy repos hide json.dumps flags in dozens of call sites. A later helper extract then changes wire bytes without a failing test. Pin those bytes first, then extract one serializer function.

                                                          Reviewers rarely catch ensure_ascii flipping from True to False. sort_keys and separators also rewrite objects that look equal in Python. default handlers change datetime and Decimal encoding on the first deploy.

                                                          This workflow freezes dumps() outputs as UTF-8 bytes. It then allows one function extract and nothing else. Parsed dict equality is not a pin and must not gate the change.

                                                          The failure mode in a tangled module


                                                          Inline dumps() calls look harmless until a client parses key order. Some gateways hash the raw body and reject reordered objects. Some logs treat escaped Unicode as a new event class.

                                                          A typical messy module mixes three dumps dialects in one file. One call sorts keys for cache stability. Another omits spaces for a compact queue payload. A third ships ensure_ascii=True for an old HTTP stack.

                                                          Extracting to_json(data) without a byte pin merges those dialects. The merge often lands as a silent default of json.dumps. Downstream tests still pass because they decode JSON and compare Python objects.

                                                          What the pin must freeze


                                                          A useful pin records exact UTF-8 bytes, not parsed dicts. It also records the exception type dumps() raises on bad values. It records whether default= was present at each call site.

                                                          Capture these fields for every representative payload. Skip fields that the call site never observed in production traffic.

                                                          Field

                                                          Why it drifts

                                                          Pin as

                                                          sort_keys

                                                          Dict order is not JSON order

                                                          bytes

                                                          ensure_ascii

                                                          é versus \\u00e9

                                                          bytes

                                                          separators

                                                          Compact versus spaced bodies

                                                          bytes

                                                          default handler

                                                          datetime, Decimal, set

                                                          bytes or error type

                                                          allow_nan

                                                          NaN becomes non-JSON text

                                                          bytes or ValueError

                                                          skipkeys

                                                          Non-str keys vanish or raise

                                                          bytes or TypeError


                                                          Do not pin pretty-print indent unless a call site uses it. Do not pin Python dict equality after json.loads. Do not pin wall-clock timestamps inside payloads.

                                                          Artifact: a dumps pin harness


                                                          The harness below is a local example, not a measured production run. Place it next to the messy module. Keep fixtures in a committed directory so diffs stay reviewable.

                                                          # pin_json_bytes.py
                                                          from __future__ import annotations

                                                          import json
                                                          from dataclasses import dataclass
                                                          from datetime import datetime, timezone
                                                          from decimal import Decimal
                                                          from pathlib import Path
                                                          from typing import Any, Callable

                                                          FIXTURE_DIR = Path(__file__).parent / "json_pins"

                                                          @dataclass(frozen=True)
                                                          class DumpCase:
                                                          name: str
                                                          payload: Any
                                                          dumps_kwargs: dict[str, Any]

                                                          def _default(value: Any) -> Any:
                                                          if isinstance(value, datetime):
                                                          return value.isoformat()
                                                          if isinstance(value, Decimal):
                                                          return str(value)
                                                          raise TypeError(f"unpinned type: {type(value)!r}")

                                                          CASES = [
                                                          DumpCase(
                                                          "cache_key_sorted",
                                                          {"b": 1, "a": 2},
                                                          {"sort_keys": True, "separators": (",", ":")},
                                                          ),
                                                          DumpCase(
                                                          "queue_compact_ascii",
                                                          {"title": "café", "ok": True},
                                                          {"ensure_ascii": True, "separators": (",", ":")},
                                                          ),
                                                          DumpCase(
                                                          "audit_spaced",
                                                          {"n": Decimal("1.50"), "at": datetime(2026, 9, 16, tzinfo=timezone.utc)},
                                                          {"ensure_ascii": False, "default": _default},
                                                          ),
                                                          ]

                                                          def dump_bytes(case: DumpCase) -> bytes:
                                                          text = json.dumps(case.payload, **case.dumps_kwargs)
                                                          return text.encode("utf-8")

                                                          def write_pins() -> None:
                                                          FIXTURE_DIR.mkdir(exist_ok=True)
                                                          for case in CASES:
                                                          path = FIXTURE_DIR / f"{case.name}.json.bin"
                                                          path.write_bytes(dump_bytes(case))

                                                          def assert_pins(dumps_fn: Callable[..., str]) -> None:
                                                          for case in CASES:
                                                          path = FIXTURE_DIR / f"{case.name}.json.bin"
                                                          expected = path.read_bytes()
                                                          kwargs = dict(case.dumps_kwargs)
                                                          got = dumps_fn(case.payload, **kwargs).encode("utf-8")
                                                          if got != expected:
                                                          raise AssertionError(
                                                          f"{case.name}: pin drift {got!r} != {expected!r}"
                                                          )

                                                          Record pins once from the current call sites. Commit the .json.bin files as binary fixtures. Later extracts must match those bytes with no whitespace drift.

                                                          # test_json_pins.py
                                                          import json
                                                          from pin_json_bytes import assert_pins, write_pins

                                                          def test_write_pins_is_manual_only() -> None:
                                                          # Run write_pins() from a shell when capturing, not in CI.
                                                          assert callable(write_pins)

                                                          def test_current_dumps_matches_committed_pins() -> None:
                                                          assert_pins(json.dumps)

                                                          Capture command for the first pin set:

                                                          python -c "from pin_json_bytes import write_pins; write_pins()"
                                                          pytest test_json_pins.py -q
                                                          xxd json_pins/queue_compact_ascii.json.bin | head

                                                          The xxd check exists to catch UTF-8 versus escaped ASCII by eye. Do not trust a terminal print of the decoded object. Two payloads can loads() equal and still differ in bytes.

                                                          Numbered workflow


                                                          Follow the steps in order. Stop when a step fails. Do not extract during inventory.

                                                          1. Inventory dumps() call sites


                                                          Search the messy module with a single pattern. Record kwargs, not only the function name. Note any wrapper that already calls dumps().

                                                          rg -n "json\.dumps\(|dumps\(" -g "*.py" app/

                                                          Group sites that share identical kwargs into one candidate extract. Leave mixed-kwargs sites out of the first extract. Mixed kwargs are a second change and a second pin set.

                                                          2. Build one payload per dialect


                                                          Pick the smallest object that still trips each flag. Include Unicode, Decimal, datetime, True, and empty dict. Exclude live secrets and customer records from fixtures.

                                                          Name each case after the caller, not after the flag. Caller names survive later file moves. Flag names hide which product path broke.

                                                          3. Commit binary pins before any edit


                                                          Run write_pins() on the current tree only. Commit fixtures in the same branch as the tests. Do not regenerate pins after the extract lands.

                                                          If a pin file changes in git, the extract is too large. Revert the helper and split the dialect instead. Byte drift is a failed gate, not a fixture update.

                                                          4. Prove the pin fails on a known dialect mix


                                                          Break one flag on purpose before the real extract. This is a labeled probe, not a production patch.

                                                          # labeled probe: expect test_current_dumps_matches_committed_pins to fail
                                                          import json
                                                          from pin_json_bytes import assert_pins

                                                          def mixed_dumps(payload, **kwargs):
                                                          kwargs.pop("sort_keys", None)
                                                          kwargs["ensure_ascii"] = True
                                                          return json.dumps(payload, **kwargs)

                                                          assert_pins(mixed_dumps)

                                                          The probe must fail on cache_key_sorted or queue_compact_ascii. If it stays green, the pin is comparing decoded objects. Fix the harness before touching production code.

                                                          5. Extract one serializer for one dialect


                                                          Move a single kwargs set into one function. Keep the function in the same module for the first patch. Do not rename keys inside payloads during this step.

                                                          def dumps_cache_key(payload: dict) -> str:
                                                          return json.dumps(payload, sort_keys=True, separators=(",", ":"))

                                                          Point only matching call sites at dumps_cache_key. Leave queue and audit sites on raw json.dumps. Re-run pytest and the xxd spot check.

                                                          6. Diff the branch against the pin set


                                                          The allowed diff is the new function plus call-site swaps. Fixture files must stay binary-identical. Test files may grow assertions but must not rewrite pins.

                                                          git diff --stat
                                                          git diff -- json_pins/
                                                          pytest test_json_pins.py -q

                                                          A non-empty diff under json_pins/ means the extract changed bytes. Restore the helper and reduce the move. Do not refresh pins to match the new helper.

                                                          Where a free remote runner fits


                                                          Laptop Python builds can hide dumps() drift across versions. A second runtime is useful after the pin suite exists. It is not a substitute for committed fixtures.

                                                          Disclosure: This article was prepared as part of MonkeyCode's product outreach. MonkeyCode offers free model access and a free server option, which can draft the one-dialect helper after pins are green and run the same pytest suite off the laptop. Skip both if local pytest already isolates dumps() bytes.

                                                          Do not ask a model to regenerate pin files. Do not ask a model to merge dialects in one patch. Feed only the green pin tests and the single-kwargs extract goal.

                                                          Limits of a byte pin


                                                          Byte pins do not prove the JSON schema is correct. They only prove this extract did not change encodings. Schema drift needs a separate contract test.

                                                          They also fail on intentional pretty-print changes. If a human-readable admin dump must gain indent=2, that is a new dialect. Give it a new case name and a new extract.

                                                          Floating time fields will thrash binary fixtures. Freeze clocks in payloads before recording pins. Naive datetime objects are a dialect, not an accident to ignore.

                                                          Python version gaps can change nothing except implementation details. json.dumps output for these flags is stable on current CPython for the cases above. Still rerun pins when the runtime changes.

                                                          Who should not use this approach


                                                          Do not use byte pins for streaming JSON lines with timestamps. Do not use them when the payload includes unordered set iteration. Do not use them as a substitute for an HTTP contract test.

                                                          Skip this extract if every dumps() site already shares one kwargs dict. Skip it if the module ships only debug logs and no wire format. Skip it if legal review forbids committed payload shapes.

                                                          Teams without pytest or another byte-level runner should not extract yet. Install the runner and record pins first. An untested helper extract is still a dialect merge.

                                                          Close


                                                          Wire clients consume bytes, not Python dicts. Pin dumps() bytes for one dialect, then extract that dialect only. Leave every other json.dumps call untouched until its own pin exists.
                                                          Pin JSON Bytes and Default Handlers Before One Serializer Extract

                                                            #refactoring boosted

                                                            [?]Hack a Day (unofficial) » 🤖 🌐
                                                            @hackaday@www.urbanmind.net

                                                            A characterization test that cannot fail is decoration, not a safety net.

                                                            Most messy-repo refactors fail the same way. You record golden values, they all pass, and you feel safe. Then you extract a function and ship a silent behavior change. The goldens never noticed, because they were never able to notice.

                                                            Here is a workflow that fixes that. Record behavior first. Then prove each recorded case can fail. Only then make the smallest safe change.

                                                            The rule: a golden you have not falsified is a guess


                                                            Golden values freeze what the code does today, including its bugs. That is the point. But a passing test proves nothing on its own.

                                                            A test only earns trust when you can make it fail on purpose. Without that step, your suite may be asserting on an empty result, a swallowed exception, or a stub that never runs.

                                                            So the gate is simple. Every characterization case must survive one deliberate, minimal mutation of the code under test.

                                                            Step 1: Inject the seams before you capture anything


                                                            Messy functions hide their collaborators. Time, randomness, network calls, and global writers make goldens flaky.

                                                            Do not refactor yet. Patch those seams in the harness instead. Notice the patch is temporary and lives in test code.

                                                            # golden_capture.py
                                                            import json
                                                            from app.legacy import settle_order # the messy 90-line function under test
                                                            import app.legacy as legacy

                                                            CASES = [
                                                            {"id": "empty_cart", "args": [[], "US"]},
                                                            {"id": "one_item", "args": [[{"sku": "A1", "cents": 500, "qty": 1}], "US"]},
                                                            {"id": "qty_zero", "args": [[{"sku": "A1", "cents": 500, "qty": 0}], "US"]},
                                                            {"id": "unknown_region", "args": [[{"sku": "A1", "cents": 500, "qty": 2}], "ZZ"]},
                                                            ]

                                                            LEDGER =

                                                            []def spy(name):
                                                            def wrap(*a, **kw):
                                                            LEDGER.append((name, [repr(x) for x in a], tuple(sorted(kw.items()))))
                                                            return 0
                                                            return wrap

                                                            def snapshot(case):
                                                            LEDGER.clear()
                                                            legacy.charge_card = spy("charge_card")
                                                            legacy.send_receipt = spy("send_receipt")
                                                            try:
                                                            return {"status": "returned", "value": settle_order(*case["args"])}
                                                            except Exception as exc:
                                                            return {"status": "raised", "type": type(exc).__name__, "msg": str(exc)}
                                                            finally:
                                                            pass

                                                            def full_snapshot(case):
                                                            out = snapshot(case)
                                                            out["calls"] = [{"fn": n, "args": a, "kwargs": dict(k)} for n, a, k in LEDGER]
                                                            return out

                                                            The call ledger matters more than the return value here. Extracting a writer often preserves the result and reorders the side effects.

                                                            Step 2: Capture the corpus into a file you can diff


                                                            Write goldens to disk. Commit them. A reviewable diff beats a magic assertion.

                                                            if __name__ == "__main__":
                                                            goldens = {c["id"]: full_snapshot(c) for c in CASES}
                                                            with open("goldens.json", "w") as fh:
                                                            json.dump(goldens, fh, indent=2, sort_keys=True)
                                                            print(f"recorded {len(goldens)} cases")

                                                            Run it once against the untouched file. Inspect the JSON by hand. Delete any case whose recorded behavior looks like an artifact of your harness.

                                                            Step 3: Replay the goldens as a test


                                                            Keep the replay boring. One parametrized test, exact equality, no fuzzy matching.

                                                            # tests/test_goldens.py
                                                            import json
                                                            import pytest
                                                            from golden_capture import full_snapshot, CASES

                                                            GOLDENS = json.load(open("goldens.json"))

                                                            @pytest.mark.parametrize("case", CASES, ids=lambda c: c["id"])
                                                            def test_behavior_is_frozen(case):
                                                            assert full_snapshot(case) == GOLDENS[case["id"]]

                                                            At this point every test passes. That is expected and meaningless. Step 4 is the one people skip.

                                                            Step 4: Mutation gate — prove the suite can fail


                                                            Mutate the target file in a scratch copy. If the suite still passes, that golden is untested for that branch.

                                                            # mutation_gate.py
                                                            import pathlib, re, shutil, subprocess, sys, tempfile

                                                            MUTANTS = [
                                                            (r"if qty <= 0:", "if qty < 0:"),
                                                            (r"total = 0\b", "total = 1"),
                                                            (r"return total", "return total + 1"),
                                                            ]

                                                            def run_gate(target="app/legacy.py"):
                                                            src = pathlib.Path(target).read_text()
                                                            survivors =
                                                            [] for pattern, repl in MUTANTS:
                                                            mutated, hits = re.subn(pattern, repl, src)
                                                            if hits == 0:
                                                            continue
                                                            with tempfile.TemporaryDirectory() as td:
                                                            copy = pathlib.Path(td) / "repo"
                                                            shutil.copytree(".", copy, ignore=shutil.ignore_patterns(".git", ".venv", "__pycache__"))
                                                            (copy / target).write_text(mutated)
                                                            r = subprocess.run(
                                                            [sys.executable, "-m", "pytest", "tests/test_goldens.py", "-q", "-x"],
                                                            cwd=copy, capture_output=True, text=True,
                                                            )
                                                            if r.returncode == 0:
                                                            survivors.append(pattern)
                                                            return survivors

                                                            if __name__ == "__main__":
                                                            survivors = run_gate()
                                                            print("survivors:", survivors)
                                                            sys.exit(1 if survivors else 0)

                                                            A survivor means one of two things. Either no case exercises that branch, or your spy layer hides the effect. Add a case, not a comment.

                                                            Treat the counts in that output as illustrative. The real number depends on your function.

                                                            Where a free model actually helps


                                                            The expensive part is enumerating branches, not writing asserts. This is the narrow job I hand to a model: read the messy function and propose input classes I forgot.

                                                            Disclosure: This article was prepared as part of MonkeyCode's product outreach.

                                                            I use MonkeyCode's free model access and free server option, as described by the operator, to draft candidate cases and a first-pass mutation list. The model's output is raw material only. Every case still has to pass capture, replay, and the mutation gate before it earns a line in the corpus. The gate is the anti-hallucination step, since a model cannot verify its own tests.

                                                            Step 5: Make the smallest safe change


                                                            The gate is green when every mutant dies. Now touch production code once.

                                                            Extract one function. Change nothing else. No renames, no formatting, no logging tweaks in the same commit.

                                                            python mutation_gate.py && pytest -q && git diff --stat

                                                            If the golden diff is empty and the gate still fails every mutant, you have a real safety net. If a golden changes, stop and explain why before continuing.

                                                            Decision table: characterize, or walk away

                                                            Situation

                                                            Characterize first?

                                                            Why

                                                            Function you touch weekly

                                                            Yes, full corpus plus gate

                                                            The investment pays back fast

                                                            One-off script, deleted next sprint

                                                            No

                                                            Goldens outlive their value

                                                            Heavy nondeterminism, no injectable seam

                                                            Patch seams first, then yes

                                                            Flaky goldens teach nothing

                                                            Function with 40+ branches, no tests

                                                            Yes, but time-box it

                                                            Gate tells you when coverage is enough

                                                            Behavior you are about to delete

                                                            No, add deletion tests after

                                                            Freezing a bug is counterproductive

                                                            Limitations and who should skip this


                                                            Goldens freeze existing bugs, not correct behavior. That is intentional, and it is also a trap if you never revisit them.

                                                            Seam patching gets fragile. If collaborators are imported deeply, the harness grows faster than the refactor.

                                                            The mutation gate needs a fast test run. On a suite that takes minutes per case, this loop becomes unusable.

                                                            Skip this approach for prototypes, generated code, or any file scheduled for replacement. Also skip it if your team will not review the golden JSON in pull requests. Unreviewed goldens turn into noise nobody trusts.

                                                            Run the capture, run the gate, then make one small change. Ship the diff you can explain line by line. If you want a place to draft those first-pass cases, the free model access and free server option in MonkeyCode are a reasonable starting point.
                                                            Make Each Characterization Test Fail Once Before You Refactor

                                                              [?]Anna Liberty [she/her] » 🌐
                                                              @liberty@mathstodon.xyz

                                                              I switched from Python to Guile as my go-to scripting language a few months ago and I've been enjoying it.

                                                              Pros:
                                                              - Guile is a Scheme
                                                              - Guile is slop-free
                                                              - Guile is faster than Python
                                                              - Guile can easily be embedded in other programs
                                                              - Guile easily compiles to WASM with Hoot

                                                              Cons:
                                                              - Guile's syntax can be less readable
                                                              - Guile's ecosystem is smaller
                                                              - Guile is less documented

                                                                #refactoring boosted

                                                                [?]Hack a Day (unofficial) » 🤖 🌐
                                                                @hackaday@www.urbanmind.net

                                                                Characterization Tests First, Then the Smallest Safe Change


                                                                Stop refactoring. Start recording. The smallest safe change wins only if you can prove nothing else moved.

                                                                That is the whole method. You freeze the hidden inputs, snapshot the current output, then change one line. The snapshot decides whether you were safe.

                                                                This article walks through a runnable loop on a small legacy stand-in module. Swap in your real module and the steps stay the same.

                                                                1. Turn the ticket into a behavior, not a design


                                                                The ticket here is vague: unknown PLAN breaks invoice generation. Do not translate that into an architecture plan yet.

                                                                Translate it into one observable statement. "Unknown plan raises KeyError before any row is processed."

                                                                Now the statement is testable. It also tells you the bug lives in one lookup, not in the whole loop.

                                                                2. Freeze the hidden globals before you assert anything


                                                                Legacy modules read two hidden inputs constantly: the clock and the environment. Both change between your laptop and CI.

                                                                Here is the stand-in under test. It is deliberately small and deliberately dirty.

                                                                # billing/cycle.py
                                                                import datetime
                                                                import os

                                                                RATES = {"standard": 1.0, "pro": 0.8, "legacy": 1.25}

                                                                def invoice_lines(rows):
                                                                today = datetime.date.today()
                                                                plan = os.environ.get("PLAN", "standard")
                                                                rate = RATES
                                                                [plan] out =
                                                                [] for row in rows:
                                                                days = (today - row["start"]).days
                                                                if days < 0:
                                                                days = 0
                                                                amount = round(row["units"] * rate * days, 2)
                                                                out.append({"id": row["id"], "days": days, "amount": amount})
                                                                return out

                                                                Two hidden inputs sit in four lines. datetime.date.today() reads the machine clock. os.environ.get reads the process environment.

                                                                Freeze both by patching the names inside the module under test. Never patch the stdlib module globally.

                                                                # tests/test_cycle_char.py
                                                                import datetime
                                                                import json
                                                                import os
                                                                import pathlib
                                                                import types

                                                                import pytest

                                                                from billing import cycle

                                                                GOLDEN = pathlib.Path(__file__).parent / "golden" / "invoice_lines.json"

                                                                class FixedDate(datetime.date):
                                                                @classmethod
                                                                def today(cls):
                                                                return cls(2026, 3, 1)

                                                                @pytest.fixture
                                                                def frozen(monkeypatch):
                                                                monkeypatch.setenv("PLAN", "pro")
                                                                monkeypatch.setattr(cycle, "datetime", types.SimpleNamespace(date=FixedDate))

                                                                This works because the module calls datetime.date.today() by attribute access. A module that does from datetime import date needs a different patch point.

                                                                That detail matters. Note it, or you will fight a passing test that proves nothing.

                                                                3. Record a golden, then assert against it


                                                                Write the rows once, run them, and save the output. The recorder is the source of truth, not your memory of the old behavior.

                                                                ROWS = [
                                                                {"id": "a1", "start": datetime.date(2026, 2, 1), "units": 3},
                                                                {"id": "a2", "start": datetime.date(2026, 3, 4), "units": 2},
                                                                {"id": "a3", "start": datetime.date(2026, 3, 1), "units": 0},
                                                                ]

                                                                def test_invoice_lines_matches_golden(frozen):
                                                                got = cycle.invoice_lines(ROWS)
                                                                if os.environ.get("RECORD_GOLDEN") == "1":
                                                                GOLDEN.parent.mkdir(parents=True, exist_ok=True)
                                                                GOLDEN.write_text(json.dumps(got, indent=2))
                                                                assert got == json.loads(GOLDEN.read_text())

                                                                Record once with RECORD_GOLDEN=1 python -m pytest tests/test_cycle_char.py -q. The first run always passes, and that is expected.

                                                                Read the recorded file by hand before you commit it. A snapshot is not a truth claim.

                                                                [
                                                                {"id": "a1", "days": 28, "amount": 67.2},
                                                                {"id": "a2", "days": 0, "amount": 0.0},
                                                                {"id": "a3", "days": 0, "amount": 0.0}
                                                                ]

                                                                Pin the error path too. Empty input still hits the lookup, so the exception fires before the loop.

                                                                def test_unknown_plan_raises_keyerror(monkeypatch, frozen):
                                                                monkeypatch.setenv("PLAN", "platinum")
                                                                with pytest.raises(KeyError):
                                                                cycle.invoice_lines([])

                                                                4. Prove the harness bites before you trust it


                                                                A test that cannot fail is decoration. Break a copy of the code and confirm the suite goes red.

                                                                rsync -a --exclude .git ./ /tmp/char-mut/
                                                                sed -i 's/if days < 0:/if days < -1:/' /tmp/char-mut/billing/cycle.py
                                                                cd /tmp/char-mut && python -m pytest tests/test_cycle_char.py -q; echo "exit=$?"

                                                                Expect exit=1. The clamped future date now leaks a negative days value, and the golden catches it.

                                                                If the suite still passes, your cases do not cover the branch. Fix that before touching the real module.

                                                                5. Take the smallest change, then edit the pin on purpose


                                                                Smallest here is one expression. Replace the strict lookup with a guarded one.

                                                                - rate = RATES
                                                                [plan]+ rate = RATES.get(plan, RATES["standard"])

                                                                This changes observable behavior, so the pinned error test must change with it. Edit it deliberately, in the same commit.

                                                                -def test_unknown_plan_raises_keyerror(monkeypatch, frozen):
                                                                - monkeypatch.setenv("PLAN", "platinum")
                                                                - with pytest.raises(KeyError):
                                                                - cycle.invoice_lines([])
                                                                +def test_unknown_plan_falls_back_to_standard(monkeypatch, frozen):
                                                                + monkeypatch.setenv("PLAN", "platinum")
                                                                + assert cycle.invoice_lines([]) ==

                                                                []Now the diff shows one intended behavior edit. Everything else stays green, which is the only proof you have.

                                                                Use Python 3.11 or newer here. Python 3.9 is already past its end-of-life date, so new test tooling should not target it.

                                                                6. Use a change ladder instead of judgment calls


                                                                Rank candidate changes by blast radius. Take rank 0 first, then climb one rung per commit.

                                                                Rank

                                                                Change

                                                                Lines touched

                                                                Gate to pass

                                                                0

                                                                Rename a local variable

                                                                1

                                                                goldens unchanged

                                                                1

                                                                Add a guard clause

                                                                1-3

                                                                goldens unchanged

                                                                2

                                                                Extract one pure helper

                                                                5-15

                                                                goldens unchanged, call order identical

                                                                3

                                                                Change observable behavior

                                                                1-5

                                                                exactly one pinned test edited on purpose

                                                                4

                                                                Move the module

                                                                many

                                                                old import path still pinned

                                                                5

                                                                Rewrite the module

                                                                all

                                                                stop, split into ranks 0-4


                                                                Rank 3 is the one people skip. They mix a behavior fix with a structure change and lose the ability to review either.

                                                                7. Know when to stop the loop


                                                                Set stop rules before you start. Seam hunting expands without limits otherwise.

                                                                • Stop if freezing the hidden globals takes more than about twenty minutes.
                                                                • Stop if the golden output contains wall-clock timestamps or process IDs you cannot stub.
                                                                • Stop if behavior depends on network calls, random seeds, or thread interleaving you cannot reproduce.
                                                                • Stop if you cannot run the code at all. Characterization without execution is fiction.

                                                                Each stop is a signal to shrink scope, not to lower standards.

                                                                8. Where model assistance fits, and where it does not


                                                                Drafting the edge-case rows in step 3 is the slow part. That is the part worth delegating.

                                                                I use MonkeyCode's free model access to propose candidate input rows and hidden-global guesses. Disclosure: This article was prepared as part of MonkeyCode's product outreach. The free server option lets me run the mutation check on a repo copy without provisioning a machine of my own.

                                                                The model proposes. The recorder decides. Never let a model write the expected output from memory.

                                                                Free access and a free server option are the only two availability claims I make. I have not measured throughput or uptime, and a free tier is not an SLA.

                                                                9. Limitations and who should not use this


                                                                Characterization tests pin current behavior, including bugs. They do not prove correctness and they do not replace intent-based tests.

                                                                Exact JSON equality is brittle with floats. Compare amounts with pytest.approx when your golden is hand-edited.

                                                                Clock patching only works when the module reads time by attribute access. Import styles, C extensions, and third-party clients may refuse the patch.

                                                                Skip this method if you are writing greenfield code, deleting the module next sprint, or have no way to execute it. Skip it if your team cannot review a behavior diff honestly.

                                                                If you run this loop on a repo copy, start with the recorder and keep it in charge of the truth.
                                                                Characterization Tests First, Then the Smallest Safe Change

                                                                  [?]Trey Hunner 🐍 » 🌐
                                                                  @treyhunner@mastodon.social

                                                                  “If you find subprocess.run to be a little too verbose, you might consider writing a custom wrapper function or two for your own use cases.”

                                                                  Read more 👉 pym.dev/running-subprocesses-i

                                                                    Back to top - More...