I'm not going to try to do anything about it yet, but I've figured out a few things about the prototype task runner file that I was so annoyed about the length of. First off, the length is mostly due to the fact that it has helper functions that I really want in a library instead of the config files.
The bigger realization I had is that the interfaces get a lot simpler if I wrap everything in a generator function. Basically, the template that everything is kind of fitting into is that there are helper functions that return a list of generators and possibly some extra value. If I relax the idea of "returning multiple generators" (which then get evaluated in sequence), then I can rework these helper functions into generators that expect to be yielded from.
This would allow me to do some... somewhat questionable things.
Decomposing the various registry modifications into the smallest units of work made it easier to reason about when they should be idempotent. By implementing idempotence at the lowest level, I can bubble it up higher without actually doing anything at any intermediate level. This means, and I have automated tests for this, that playing back the same sequence of modifications, which before was often an error, is now a no-op. So, this means I don't need to worry about "repeating" modifications, as long as the original modifications are executed in order. I mean, from a correctness perspective. From a performance perspective, I am... not quite sure what I'm setting myself up for.
As far as what this allows me to do, this means that, supposing I make a helper class for representing virtual environments, I can turn the helper method to retrieve the path to a binary into a generator that establishes the link to the pip upgrade command that installed the binary.
I'm realizing now that I probably want to have some kind of wrapper around the generator to divert exceptions from the for-loop into the generator, so the traceback can bring together the code that hit the error and the code that caused the error to be hit. This would probably be a bit squirrelly to call, but I only have to call it once. Or I could not bother, and say that continuing after an exception is undefined behavior. That's probably a better use of my time for now.
I don't want to actually touch any of this code for now, but talking through this has given me some good ideas. And some maybe-good ideas.
I don't think I have anything else to talk about right now, so I'm going to send this.