Registration Dependencies

In registration, we have seen that cinnamon pairs Configuration to Component via RegistrationKey. Moreover, Configuration can be nested to indirectly define nested Component (see configuration).

We are only left to the question of how should we use these APIs to work with cinnamon.

Code Organization

Ideally, registration functions (either class methods or ad-hoc functions) can be written anywhere: it is up to the Registry to find these functions and run them to populate itself with RegistrationKey and associated configuration and component information.

Nonetheless, cinnamon is designed to check only registration functions written in files located in configurations folder. This choice is meant to avoid unwanted code executions when checking python files.

Therefore, we recommend organizing your code as follows

project_folder
    configurations
        folder containing ``Configuration`` scripts

    components
        folder containing ``Component`` scripts

We also recommend using the same filename for <Configuration, Component> paired scripts for readability purposes.

For instance, if we define a data loader component, our code organization will be

project_folder
    configurations
        data_loader.py

    components
        data_loader.py

where

components/data_loader.py
class DataLoader(Component):

    def load(...):
        ...
configurations/data_loader.py
class DataLoaderConfig(Configuration):

    @classmethod
    @register_method(name='loader', tags={'default'}, namespace='testing', component_class=DataLoader)
    def default(cls):
        config = super(cls).default()

        config.add(name='folder_name', type_hint=str, value='my_custom_folder')

        return config

Note

Defining a components folder is not mandatory, but it improves readability by allowing users to quickly pair components and configurations.

Resolving dependencies

Registering and nesting Configuration can quickly lead to a dependency problem. Furthermore, the addition of Configuration variants may further exacerbate the issue.

To avoid users manually ordering registrations to avoid conflicts, cinnamon dynamically builds a dependency graphs, independently of the registration order.

For instance, consider the following nesting dependency between two configurations:

class ParentConfig(cinnamon.configuration.Configuration):

    @classmethod
    def default(
            cls
    ):
        config = super(cls).default()

        config.add(name='param_1', value=True)
        config.add(name='param_2', value=False)
        config.add(name='child',
                   value=RegistrationKey(name='test', tags={'nested'}, namespace='testing'))
        return config


class NestedChild(Configuration):

    @classmethod
    def default(
            cls
    ):
        config = super().default()

        config.add(name='x', value=42)

        return config

The following registration functions produce the same dependency graph.

@register
def custom_registration():
    Registry.register_configuration(config_class=ParentConfig,
                                    name='test',
                                    tags={'parent'},
                                    namespace='testing',
                                    )
    Registry.register_configuration(config_class=NestedChild,
                                    name='test',
                                    tags={'nested'},
                                    namespace='testing',
                                    )

@register
def custom_registration():
    Registry.register_configuration(config_class=NestedChild,
                                    name='test',
                                    tags={'nested'},
                                    namespace='testing',
                                    )
    Registry.register_configuration(config_class=ParentConfig,
                                    name='test',
                                    tags={'parent'},
                                    namespace='testing',
                                    )

Note

The same reasoning applies for class method registrations (i.e., via register_method decorator).

This code organization is meant to simplify registration burden while keeping high readability.

Behind the curtains, the Registry is issued to look for all @register and @register_method decorators located in configurations folder to automatically execute them.

This action is handled by Registry.setup() method.

Registry.setup(directory=Path('.'))

Issues the Registry to look for all configurations folder(s) under the current working directory.

Note

The Registry search for registrations also accounts for nested configurations folders in a given directory.

External dependencies

Cinnamon is a community project. This means that you are the main contributor.

In many situations, you may need to import other’s work: external configurations and components.

Cinnamon supports loading registration function calls that are external to your project’s configurations folder. Moreover, you can also build your Configuration and Component with dependencies on external ones.

For instance, suppose that a DataLoaderConfig variant has a external dependency.

class DataLoaderConfig(Configuration):

    @classmethod
    def default(cls):
        config = super(cls).get_default()

        config.add(name='folder_name', type_hint=str)

        return config

    @classmethod
    @register_method(name='loader', tags={'external'}, namespace='testing')
    def external_variant(cls):
        config = cls.default()

        config.add(name='processor', value=RegistrationKey(name='processor', namespace='external'))

        return config

In this case, to avoid incurring in errors, we need to inform the Registry where RegistrationKey(name='processor', namespace='external') has been declared.

We do so, by specifying the main external directory when issuing Registry.setup().

Registry.setup(directory=Path('.'), external_directories=[Path('path/to/external/directory')])