Skip to content

Conversation

@garethky
Copy link
Contributor

@garethky garethky commented Nov 5, 2025

This change adds a new component registration mechanism that allows modules to register named components with one or more subsystems. The registration happens before printer object creation so no access to Printer is required, limiting possible side effects of registration. This creates a way to do truly dynamic registration without having registries in code, special directories, or files with lists of components.

Modules that want to register a component expose a register_components() function e.g.:

def register_components(subsystem: SubsystemComponentCollection):
    sensors = hx71x.HX71X_SENSOR_TYPES | ads1220.ADS1220_SENSOR_TYPE
    for name, sensor in sensors.items():
        subsystem.register_component("load_cell_sensors", name, sensor)

Modules can get registered components at load_config \ load_config_prefix time with a call to Printer.lookup_components. e.g.

def load_config(config: ConfigWrapper):
    printer: Printer = config.get_printer()
    sensors = printer.lookup_components("load_cell_sensors")

Most of the PR is re-working Printer.load_object so that it can use a cache of loaded modules. The cache is built prior to any printer objects being created. Having the cache allows for all modules to be scanned for a register_components function which is called before the config file is processed.

@garethky garethky force-pushed the pr-subsystem-components branch 5 times, most recently from ffc7b83 to 9b43ceb Compare November 5, 2025 02:15
This change adds a new component registration mechanism that allows modules to register named components with one or more subsystems. The registration happens before printer object creation so no access to Printer is required, limiting possible side effects of registration. This creates a way to do truly dynamic registration without having registries in code, special directories, or files with lists of components. Modules can get registered components at `load_config`\`load_config_prefix` time with a call to `Printer.lookup_components`.

Most of the PR is re-working `Printer.load_object` so that it can use a cache of loaded modules. The cache is built prior to any printer objects being created. Having the cache allows for all modules to be scanned for a `register_components` function which is called before the config file is processed.


Signed-off-by: Gareth Farrington <[email protected]>
@garethky garethky force-pushed the pr-subsystem-components branch from 9b43ceb to d670a74 Compare November 5, 2025 19:42
@garethky garethky marked this pull request as ready for review November 6, 2025 19:19
@garethky garethky mentioned this pull request Nov 10, 2025
m.add_early_printer_objects(self)

@staticmethod
def _list_modules(search_path: str) -> list[Path]:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't the right PR to change this, since you're mostly refactoring existing code here, but I don't think this is the best way of going about finding modules -- it'll miss .pyc, .so, and doesn't work for stuff like a ZipImporter. pkgutil.iter_modules looks like it'd be much more robust.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will look at pkgutil.iter_modules. This is the PR to make that change, lets get it right the first time.

Comment on lines +126 to +128
if subsystem_name not in self._subsystems:
return {}
return self._subsystems[subsystem_name]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if subsystem_name not in self._subsystems:
return {}
return self._subsystems[subsystem_name]
return self._subsystems.get(subsystem_name, {})

But see also my comment about DefaultDict, in which case you can just

Suggested change
if subsystem_name not in self._subsystems:
return {}
return self._subsystems[subsystem_name]
return self._subsystems[subsystem_name]

Comment on lines +139 to +141
if subsystem_name not in self._subsystems:
self._subsystems[subsystem_name] = {}
subsystem = self._subsystems[subsystem_name]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personal preference maybe, but consider collections.DefaultDict for registries like this. It has its problems, though... But it makes these check-and-set patterns a lot more concise.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is very basic use case, so it should work fine.

Comment on lines +17 to +19
ENDSTOP_REST_TIME = 0.001
ENDSTOP_SAMPLE_TIME = 0.000015
ENDSTOP_SAMPLE_COUNT = 4
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know this code well and haven't stepped through all the ins and outs, so this may be a dumb comment, but ... it really looks like these should now no longer be globals, but instead instance variables populated during load_config.

Copy link
Contributor Author

@garethky garethky Nov 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. I was trying to keep the spirit of the existing code but making them local lookups makes more sense.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants