I'm not going to touch code for now; I'm just going to try to sketch out that FragmentGenerator idea I thought of last night.
First note, it's not really a good name, and I should come up with something else at some point. In any case...
The idea I kind of have right now is that there's some kind of registry-ish object (it can just be a dictionary in the prototype) that contains sequences of values associated with different fields. So, the idea is that the types are like def generate(self, registry: Registry) -> Fragment:
Or... I guess the generator has to explicitly expose the fields it's iterating over, because it has to be possible to compose these generators. That seems to give me an interface where, on the one hand, it passes a registry of sequences, and on the other, it passes a registry of specific values.
But, since we're never going to vary the global registry passed to an overall command, maybe there's some way to move the registry into the construction logic. Then the top-level fragment generator could set up the proper for-loop, and call the sub-generators just with the for-loop variables required by the sub-generators. On the other hand, it's not reasonable to duplicate it as an argument to so many constructors... So, I guess the first idea makes sense. Pass a registry to the top-level FragmentGenerator, get an iterator of Fragments. Within the iterator, convert the registry into a product iterator, and pass all that information into the sub-generators, implicitly, by passing it to a recursive function on the top level. (I'll want to cache the requirements in each sub-generator, because otherwise there's some level of redundant work, I think...) Hm, the top-level should be an independent function.
Okay, I'm too tired to go through the rest of this now. I'll have to make another attempt tomorrow.