######################################## Requirements and dependencies in pyCIF ######################################## .. role:: bash(code) :language: bash pyCIF automatically links plugins with each other depending on requirements specified in the source of the plugins and on elements of the configuration file. For instance, the observation operator needs to run the numerical model at some point. For this reason, the :bash:`model` plugin that will be run is attached to the :bash:`obsoper` plugin. In the code of :bash:`obsoper`, the model can thus simply be called as follows: .. code-block:: python def obsoper(self): # Running the model self.model.run(**args, **kwargs) Another example is a :bash:`model` plugin that needs information about its corresponding :bash:`domain` plugin: .. code-block:: python def some-model-method(self): # Fetching domain information nlon = self.domain.nlon nlat = self.domain.nlat Types of dependencies ----------------------- There are two main tpes of dependencies in pyCIF: 1. :bash:`plugin A` calls methods from :bash:`plugin B` For that reason, methods must have a standardized format for arguments and outputs, as specified in the corresponding pages of the documentation. 2. :bash:`plugin A` needs data from :bash:`plugin B` Similarly, data format needs to follow a specified standardized format Below is an example graph of dependencies corresponding to the pyCIF configuration file :doc:`here`: .. graphviz:: dependencies.dot :align: center In this example, blue boxes are :bash:`plugins` that are explicitly defined in the configuration file. Red boxes are implicitly deduced from default values as specified in individual :bash:`plugins` (see :doc:`here`). Black arrows stand for method dependencies, while red ones are data dependencies. Thus, for instance, the :bash:`obsoper` plugin is required by the :bash:`mode` plugin while it is not specified in the Yaml file. pyCIF automatically initializes the default dependency: :bash:`obsoper` (standard, std). Definition of dependencies --------------------------- Information about :bash:`requirements` are specified in the :bash:`__init__.py` file of your :bash:`plugin`. To define requirements, one has to include a dictionary called :bash:`requirements` in the :bash:`__init__.py` file. Keys of the dictionary are other :bash:`plugins` needed for the execution of the parent :bash:`plugin`. In the example below, the parent :bash:`plugin` will be attached a :bash:`domain` :bash:`plugin` as attribute, later callable using :bash:`self.domain`. .. code-block:: python requirements = { "domain": { [...] } } .. note:: The possible keys in each key of the :bash:`requirements` dictionary are detailed below. Please note that it is possible to give extra keys that will be used to defined arguments of the corresponding plugin. For instance, in the case above, one can write: .. code-block:: python requirements = { "domain": { "some_extra_key": "grub" } } Then, one will be able to call :bash:`self.domain.some_extra_key` in the internal functions of the corresponding plugin. Moreover, the keys defined in that manner can be used to alter the way the :bash:`domain` will initialize itself. Behaviour with dependencies --------------------------- A given plugin might need to use another plugin in different ways, which will determine how the Yaml configuration should be written and how missing requirements will be dealt with. The expected usage of dependencies is determined through the definition of each corresponding key of the dictionary :bash:`requirements`. The possible arguments to provide to each key of the dictionary are the following: - :bash:`name`/:bash:`version`/:bash:`type`/:bash:`subtype`: name/version/type/subtype of the required plugin; type and subtype are optional if the key corresponds to a type of plugins. For instance, if the key is :bash:`domain`, there is no need to repeat the type. - :bash:`any`: any plugin fitting the required type is fine - :bash:`subplug`: authorizes to search for required plugins not only at the root level of the yaml. - :bash:`preftree` (to be defined if :bash:`subplug` si True): if there are several plugins fitting the requirement in the yaml, choose the one in the preferred tree directory. For instance, if the parent plugin requires a :bash:`domain` and several :bash:`domain` are defined, e.g., at the root level and at some sub-level of another plugin, if one of the two fits :bash:`preftree`, this one is selected; otherwise, an error is returned due to ambiguous choice - :bash:`empty`: an empty plugin is defined according to the :bash:`name`/:bash:`version`/:bash:`type`/:bash:`subtype` if none is available in the Yaml. This can be used when only functions of the required plugin are necessary and no data - :bash:`newplg`: force initializing a new plugin instead of looking for an existing plugin from the yaml In practice, the requirement selection process follows the steps: 1) pyCIF looks in the Yaml for all possible plugins fitting the parameters :bash:`name`/:bash:`version`/:bash:`type`/:bash:`subtype`/:bash:`empty`/:bash:`subplug`. Four possible outcomes: - the only one matching is returned, - an empty plugin is returned if there is no match, - if there are several matches and :bash:`subplug` is True: returns the one matching :bash:`preftree`, - an exception is returned if there are ambiguous choices 2) if there is one match: - if :bash:`any` is True, attaches the fetched one - if :bash:`any` is False, attaches the fetched one if it corresponds to the specified :bash:`name`/:bash:`version`/:bash:`type`/:bash:`subtype` - if :bash:`any` is False and the fetched one does not correspond to the specified :bash:`name`/:bash:`version`/:bash:`type`/:bash:`subtype`, but :bash:`empty` is True, attaches an empty plugin from the default 3) if there is no match: - if :bash:`empty` is True and :bash:`any` is False, attaches an empty plugin from the default - if :bash:`empty` is True and :bash:`any` is True, attaches an empty class plugin, with only the default class attributes .. warning:: The dual options :bash:`subplug`/:bash:`preftree` should be avoided as much as possible as it means that your plugin is critically dependent on external other plugins. There are usually ways to avoid such implementation. Cheat sheet for checking plugins dependencies in yaml files ----------------------------------------------------------- The following list can be obtained by running the following python commands. .. code-block:: python from pycif.utils.classes.baseclass import Plugin Plugin.print_registered(print_rst=True, print_requirement=True) .. include:: plugins/available.rst