.. _custom_components:

Custom components
=================

Components are a large part of how HydroMT defines its behavior.
A component is a part of the model architecture that is responsible
for handling a specific part of the data, such as the Grid, Config, or Mesh
but also potentially something like rivers, catchments, pipes or other custom behavior
your plugin might need.


Implementing a component
^^^^^^^^^^^^^^^^^^^^^^^^

Initialisation
--------------

There are generally two types of components you might want to implement:
`ModelComponent` and `SpatialModelComponent`. They are similar but the
`SpatialModelComponent` is meant to hold spacial data. A model MUST have at least one
`SpatialModelComponent` for it's region functionality to function properly.

Components of any kind, should take a reference to the model they are a part of at
initialization so that components can access other components through the model as is
necessary. Typically this is done like so:

.. doctest:: python

    class AwesomeRiverComponent(ModelComponent):
        def __init__(
            self,
            model: Model,
            filename: str = "component/{name}.csv",
        ):
            self._data: Optional[Dict[str, Union[pd.DataFrame, pd.Series]]] = None
            self._filename: str = filename
            super().__init__(model=model)


It is also important that the component has a ``self._filename`` property defined
because the data in components they are lazy loaded by default, meaning that
the data associated with them only get's loaded if necessary. Therefore it is important
to set a property on the component so that it can read from/write to the correct default
file location, without additional input.

``SpatialModelComponent`` s should take some additional information in their
initialisation:

.. code-block:: python

    def __init__(
        self,
        model: Model,
        *,
        region_component: str,
        filename: str = "spatial_component/{name}.nc",
        region_filename: str = "spatial_component/region.geojson",
    ):
        ...

The ``region_filename`` is a similar file location within the model root where region data
should be written if necessary. Note also that spatial components do not necessarily
have their own region. Sometimes they can derive their region from other spacial
components, such as a subgrid deriving its region from the main grid. If this is the
case then set the ``region_component`` to the name of the component from which you wish to
derive the region.

Required attributes
-------------------

Aside form initialisation the components are expected to have some other properties
(`data`, `model`, `data_catalog` and `root`) and functions (`read` and `write`).
Functions annotated with the ``@hydromt_step`` decorator will be available to users of a
workflow yaml. Depending on the context your component may also want to implement the
functions `set` (which is typically not annotaed with the ``@hydromt_step`` decorator
since it cannot be sued in a yaml workflow)`, and `test_equal`. `set` is typically used
by python users to overwrite data in the component after they have done something with
it outside of your component. `test_equal` is purely for testing purposes and should test
whether the component provided (including potential data) is equal to the component it
is being run on. This is very useful for testing that includes data.