Coding 2022-03-14

By Max Woerner Chase

It's a little earlier in the day, and I'm having trouble concentrating on what I want to concentrate on, so I'm going to try to lay out what MOTR does and see if I can find a better way, or if it actually does just all work.

(By the way, some of this stuff is slated to be potentially renamed. I'm going to go with the names currently in the code for now.)

So, let's start near the end. The end goal of running the motrfile is the creation of an object of type motr.core.registry.Registry. The registry contains several mappings:

These fields are not modified directly. Instead, the class defines a require() method that takes an argument that is an instance of one of the types in the Requirement union:

There are some undocumented requirements about ordering in order to avoid cycles, but one of the really important parts of this is that it should be the case that, if new_registry == old_registry.require(requirement) (without any error), then new_registry == new_registry.require(requirement). This allows me to play kind of fast and loose with requirements at the higher layers.

The next type that matters is motr._api.requirements.requirements.Requirements, which is just an alias for typing.Generator[motr.core.registry.Registry, None, T_co], where T_co is just typing.TypeVar("T_co", covariant=True). This type is used as a return type all over the high levels of code.

The motr._api.requirements package also has some modules that provide helpful wrappers around the Requirement classes, in the form of tiny helper functions that coordinate the creation of instances of those classes. I'm going to skip over them unless I realize that they're somehow important.

The next level up is the modules under motr._api.actions. One of these modules, io, defines more of the types used all over the place in the higher levels. Basically, wrappers around generic values, but marking them to be either an input or output of their associated action. The other modules are:

Then, there's the motr._api.build module, which just defines a function to convert a Requirements object into a Registry.

I believe this is the point after which I ended up building a whole lot of stuff that isn't actually used yet.

I don't remember what order any of this is in, let's see...

The next place to look is into cli_types.

The easy ones:

Then things start getting a little more elaborate. I'm gong to try to focus on the stuff that probably won't need to change, if I do need to change something.

Here are the remaining modules:

So, what I'm basically looking for to handle the changes is to have a single Invocation object that can have something passed to its invoke() method that results in regular changes to the paths and names of some of the outputs it defines. I think that looks like...

invoke takes an Objects argument that gets somehow injected into the Dynamic[_arguments.Command] that it returns. (Another possibility is that Invocation objects have an Objects mapping on them...) Meanwhile, FlexOut should be somehow incorporating the values from the injected Objects into the generated paths and the output names.

FlexOut makes sense as a target than the slightly-lower-level modules, because the lower-level modules don't use Dynamics, and those are the most straightforward candidates for changing the data around like this.

Anyway, I'm going to need some time to consider some of this, so I'm going to wrap up for now, and try to deal with the clocks changing.

Good night.