.. _migration:

###############
Migrating to v1
###############

As part of the move to v1, several things have been changed that will need to be
adjusted in plugins or applications when moving from a pre-v1 version.
In this document, the changes that will impact downstream users and how to deal with
the changes will be listed. For an overview of all changes please refer to the
changelog.

Model and ModelComponent
========================

New `ModelComponent` class
--------------------------

**Rationale**

Prior to v1, the `Model` class was the only real place where developers could
modify the behavior of Core through either sub-classing it, or using various
`Mixin` classes. All parts of a model were implemented as class properties
forcing every model to use the same terminology. While this was enough for
some users, it was too restrictive for others. For example, the SFINCS
plugin uses multiple grids for its computation, which was not possible in
the setup pre-v1. There was also a lot of code duplication for the use of
several parts of a model such as `maps`, `forcing` and `states`. To offer
users more modularity and flexibility, as well as improve maintainability, we
have decided to move the core to a component based architecture rather than
an inheritance based one.

**Changes required**

For users of HydroMT, the main change is that the `Model` class is now a composition of
`ModelComponent` classes. The core `Model` class does not contain any components by default,
but these can be added by the user upon instantiation or through the yaml configuration file.
Plugins will define their implementation of the `Model` class with the components they need.

For a model which is instantiated with a `GridComponent` component called `grid`, where previously you
would call `model.setup_grid(...)`, you now call `model.grid.create(...)`.
To access the grid component data you call `model.grid.data` instead of `model.grid`.

In the core of HydroMT, the available components are:

+-----------------------------------------------------------+--------------------------+--------------------------------------------------------+
| v0.x                                                      | v1.x                     | Description                                            |
+===========================================================+==========================+========================================================+
| Model.config                                              | ConfigComponent          | Component for managing model configuration             |
+-----------------------------------------------------------+--------------------------+--------------------------------------------------------+
| Model.geoms                                               | GeomsComponent           | Component for managing 1D vector data                  |
+-----------------------------------------------------------+--------------------------+--------------------------------------------------------+
| Model.tables                                              | TablesComponent          | Component for managing non-geospatial data             |
+-----------------------------------------------------------+--------------------------+--------------------------------------------------------+
| -                                                         | DatasetsComponent        | Component for managing non-geospatial data             |
+-----------------------------------------------------------+--------------------------+--------------------------------------------------------+
| Model.maps / Model.forcing / Model.results / Model.states | SpatialDatasetsComponent | Component for managing geospatial data                 |
+-----------------------------------------------------------+--------------------------+--------------------------------------------------------+
| GridModel.grid                                            | GridComponent            | Component for managing regular gridded data            |
+-----------------------------------------------------------+--------------------------+--------------------------------------------------------+
| MeshModel.mesh                                            | MeshComponent            | Component for managing unstructured grids              |
+-----------------------------------------------------------+--------------------------+--------------------------------------------------------+
| VectorModel.vector                                        | VectorComponent          | Component for managing geospatial vector data          |
+-----------------------------------------------------------+--------------------------+--------------------------------------------------------+

Changes to the `yaml` HydroMT configuration file format
-------------------------------------------------------

**Rationale**

Given the changes to the `Model` class and new `ModelComponent` the `yaml` configuration file format has
also been updated. The new format is more flexible and allows configuration of the model object and its
components. Components can be added during instantiation by defining them in the yaml which is useful to
define a model on-the-fly based on existing components. Note that this is however not necessary for plugins
as the available components are defined in their implementation of the `Model` class.

**Changes required**

The first change to the YAML format is that now, at the root of the documents are three keys:

- `modeltype` (optional) details what kind of model is going to be used in the model. This can currently also be provided only through the CLI, but given that YAML files are very model specific we've decided to make this available through the YAML file as well.
- `global` is intended for any configuration for the model object itself, here you may override any default configuration for the components provided by your implementation. Any options mentioned here will be passed to the `Model.__init__` function
- `steps` should contain a list of function calls. In pre-v1 versions this used to be a dictionary, but now it has become a list which removes the necessity for adding numbers to the end of function calls of the same name. You may prefix a component name for the step in a dotted manner, e.g. `<component>.<method>`, to indicate the function should be called on that component instead of the model. In general any step listed here will correspond to a function on either the model or one of its components. Any keys that are listed under a step will be provided to the function call as arguments.

An example of a fictional Wflow YAML file would be:

.. code-block:: yaml

	modeltype: wflow
	global:
		data_libs: deltares_data
		components:
			config:
				filename: wflow_sbm_calibrated.toml
	steps:
		- setup_basemaps:
			region: {'basin': [6.16, 51.84]}
			res: 0.008333
			hydrography_fn: merit_hydro
		- grid.add_data_from_geodataframe:
			vector_data: administrative_areas
			variables: "id_level1"
		- grid.add_data_from_geodataframe:
			vector_data: administrative_areas
			variables: "id_level3"
		- setup_reservoirs:
			reservoirs_fn: hydro_reservoirs
			min_area: 1.0
		- write:
			components:
				- grid
				- config
		- geoms.write:
			filename: geoms/*.gpkg
			driver: GPKG


Model region and geo-spatial components
---------------------------------------

**Rationale**

The model region is a very integral part for the functioning of HydroMT. A users can define a geo-spatial
region for the model by specifying a bounding box, a polygon, or a hydrological (sub)basin. In the previous
version of HydroMT, a model could only have one region and it was "hidden" in the `geoms` property data.
Additionally, there was a lot of logic to handle the different ways of specifying a region through the code.

To simplify this, allow for component-specific regions rather than one single model region,
and consolidate a lot of functionality for easier maintenance, we decided to bring all this functionality
together in the `SpatialModelComponent` class. Some components inherit from this base component in order to
provide a `region`, `crs`, and `bounds` attribute. This class is not directly used by regular users, but
is used by the `GridComponent`, `VectorComponent`, `MeshComponent` and `SpatialDatasetsComponent`.
Note that not all spatial components require their own region, but can also use the region of another
component. The model class itself may still have a `region` property, which points to the region of one of
the components, as defined by the user / plugin developer.

**Changes required**

The command line interface no longer supports a `--region` argument.
Instead, the region should be specified in the yaml file of the relevant component(s).

.. code-block:: yaml

	# Example of specifying the region component via grid.create_from_region
	global:
		region_component: grid
		components:
			grid:
				type: GridComponent
	steps:
		- grid.create_from_region:
			region:
				basin: [6.16, 51.84]

The Model region is no longer part of the `geoms` data. The default path the region is written to is no
longer `/path/to/root/geoms/region.geojson` but is now `/path/to/root/region.geojson`. This behavior can
be modified both from the config file and the python API. Adjust your data and file calls as appropriate.

Another change to mention is that the region methods ``parse_region`` and ``parse_region_value`` are no
longer located in ``workflows.basin_mask`` but in `model.region`. These functions are only relevant
for components that inherit from `SpatialModelComponent`. See `GridComponent` and  `model.processes.grid` on how
to use these functions.

In HydroMT core, we let `GridComponent` inherit from `SpatialModelComponent`. One can call `model.grid.create_from_region`,
which will in turn call `parse_region_x`, based on the kind of region it receives.

+--------------------------+-----------------------------------+
| v0.x                     | v1                                |
+==========================+===================================+
| model.setup_region(dict) | model.<component>.create_region() |
+--------------------------+-----------------------------------+
| model.write_geoms()      | model.<component>.write_region()  |
+--------------------------+-----------------------------------+
| model.read_geoms()       | model.<component>.read_region()   |
+--------------------------+-----------------------------------+
| model.set_region(...)    | -                                 |
+--------------------------+-----------------------------------+
| parse_region             | parse_region_basin                |
|                          | parse_region_geom                 |
|                          | parse_region_bbox                 |
|                          | parse_region_other_model          |
|                          | parse_region_grid                 |
|                          | parse_region_mesh                 |
+--------------------------+-----------------------------------+

Removing support for `ini` and `toml` HydroMT configuration files
-----------------------------------------------------------------

**Rationale**
To keep a consistent experience for our users we believe it is best to offer a single
format for configuring HydroMT, as well as reducing the maintenance burden on our side.
We have decided that YAML suits this use case the best. Therefore we have decided to
deprecate other config formats for configuring HydroMT. Writing model config files
to other formats will still be supported, but HydroMT won't be able to read them
subsequently. From this point on YAML is the only supported format to configure HydroMT.

**Changes required**

Convert any model config files that are still in `ini` or `toml` format to their
equivalent YAML files. This can be done with manually or any converter, or by reading
and writing it through the standard Python interfaces.

Implementing Model Components (for developers)
----------------------------------------------

Here we will describe the specific changes needed to use a `Model` object.
The changes necessary to have core recognize your plugins are described below.
Now a `Model` is made up of several `ModelComponent` classes to which it can delegate work.
While it should still be responsible for workloads that span multiple components
it should delegate work to components whenever possible. For specific changes needed
for appropriate components see their entry in this migration guide, but general
changes will be described here.

Components are objects that the `Model` class can delegate work to. Typically,
they are associated with one object such as a grid, forcing or tables.
To be able to work within a `Model` class properly a `ModelComponent` must implement
the following methods:

- `read`: reading the component and its data from disk.
- `write`: write the component in its current state to disk in the provided root.

Additionally, it is highly recommended to also provide the following methods to ensure
HydroMT can properly handle your objects:

- `set`: add or overwrite data in the component.
- `_initialize`: initializing an empty component.

Finally, you can provide additional functionality by providing the following optional functions:

- `create`: the ability to construct the schematization of the component from the provided arguments.
  e.g. computation units like grid cells, mesh1d or network lines, vector units for lumped model etc.
- `add_data`: the ability to transform and add model data and parameters to the component once the
  schematization is well-defined (i.e. add land-use data to grid or mesh etc.).

Additionally, we encourage some best practices to be aware of when implementing a components:

- Make sure that your component calls `super().__init__(model=model)` in the `__init__` function
  of your component. This will make sure that references such as `self.logger` and `self.root` are
  registered properly so you can access them.
- Your component should take some variation of a `filename` argument in its `__init__` function that
  is either required or provides a default that is not `None`. This should be saved as an attribute
  and be used for reading and writing when the user does not provide a different path as an argument
  to the read or write functions. This allows developers, plugin developers and users alike to both
  provide sensible defaults as well as the opportunity to overwrite them when necessary.

It may additionally implement any necessary functionality. Any implemented functionality should be
available to the user when the plugin is loaded, both from the Python interpreter as well as the
`yaml` file interface. However, to add some validation, functions that are intended to be called from
the yaml interface need to be decorated with the `@hydromt_step` decorator like below.
This decorator can be imported from the root of core.

.. code-block:: python

	@hydromt_step
	def write(self, ...) -> None:
		pass

When implementing a component, you should inherit from the `ModelComponent` class. When you do this,
not only will it provide some additional validation that you have implemented the correct functions,
but your components will also gain access to the following attributes:

+----------------+---------------------------------------------------------------------------------------------------+------------------------------------------+
| Attribute name | Description                                                                                       | Example                                  |
+================+===================================================================================================+==========================================+
| model          | A reference to the model containing the component which can be used to retrieve other components  | self.model.get_component(...)            |
+----------------+---------------------------------------------------------------------------------------------------+------------------------------------------+
| data_catalog   | A reference to the model's data catalog which can be used to retrieve data                        | self.data_catalog.get_rasterdataset(...) |
+----------------+---------------------------------------------------------------------------------------------------+------------------------------------------+
| logger         | A reference to the logger of the model                                                            | self.logger.info(....)                   |
+----------------+---------------------------------------------------------------------------------------------------+------------------------------------------+
| root           | A reference to the model root which can be used for permissions checking and determining IO paths | self.root.path                           |
+----------------+---------------------------------------------------------------------------------------------------+------------------------------------------+

As briefly mentioned in the table above, your component will be able to retrieve other components
in the model through the reference it receives. Note that this makes it impractical if not impossible
to use components outside of the model they are assigned to.

Adding Components to a Model
^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Components can be added to a `Model` object by using the `model.add_component` function. This function
takes the name of the component, and the TYPE (not an instance) of the component as argument. When these
components are added, they are uninitialized (i.e. empty). You can populate them by calling functions such
as `create` or `read` from the yaml interface or any other means through the interactive Python API.

Once a component has been added, any component (or other object or scope that has access to the model class)
can retrieve necessary components by using the `model.get_component` function which takes the name of the
desired component you wish to retrieve. At this point you can do with it as you please.

A developer can defined its own new component either by inheriting from the base `ModelComponent` or from
another one (e.g, `class SubgridComponent(GridComponent)`). The new components can be accessed and discovered
through the `PLUGINS` architecture of HydroMT similar to Model plugins. See the related paragraph for more details.

The `Model.__init__` function can be used to add default components by plugins like so:

.. code-block:: python

	class ExampleModel(Model):
		def __init__(self):
			super().__init__(...)
			self.add_component("grid", GridComponent(self))

	# or

	class ExampleModel(Model):
		def __init__(self):
			super().__init__(..., components={"grid": GridComponent(self}))


If you want to allow your plugin user to modify the root and update or add new component during instantiation
then you can use:

.. code-block:: python

	class ExampleEditModel(Model):
		def __init__(
			self,
			components: Optional[Dict[str, Any]] = None,
			root: Optional[str] = None,
		):
			# Recursively update the components with any defaults that are missing in
			# the components provided by the user.
			components = components or {}
			default_components = {
				"grid": {"type": "GridComponent"},
			}
			components = hydromt.utils.deep_merge.deep_merge(
				default_components, components
			)

			# Now instantiate the Model
			super().__init__(
				root = root,
				components = components,
			)


**SpatialModelComponent**

The region of a `SpatialModelComponent` can either be derived directly from its own component or based on
another referenced component (e.g. a forcing component for which the reference region can be taken from the
grid component). For `SpatialModelComponent` that can derive their own region, it is up to the developer
of the subclass to define how to derive the region from the component `data` by implementing the
`_region_data` property.

The `Model` also contains a property for `region`. That property only works if there is a
`SpatialModelComponent` in the model. If there is only one `SpatialModelComponent`, that component
is automatically detected as the `region`. If there are more than one, the `region_component` can be
specified in the `global` section of the yaml file. If there are no `SpatialModelComponent`s in the model,
the `region` property will error. You can specify this in the configuration as follows:

.. code-block:: yaml

	global:
		region_component: grid
		components:
			grid:
				type: GridComponent  # or any other component that inherits from SpatialModelComponent

The alternative is to specify the region component reference in python, which is useful for plugin developers:

.. code-block:: python

	class ExampleModel(Model):
		def __init__(self):
			super().__init__(region_component="grid", components={"grid": {"type": "GridComponent"}})



**GridComponent**

The `GridMixin` and `GridModel` have been restructured into one `GridComponent` with only
a weak reference to one general `Model` instance. The `set_grid`, `write_grid`,
`read_grid`, and `setup_grid` have been changed to the more generically named `set`,
`write`, `read`, and `create` methods respectively. Also, the `setup_grid_from_*`
methods have been changed to `add_data_from_*`. The functionality of the GridComponent
has not been changed compared to the GridModel.

+------------------------------+-------------------------------------------+
| v0.x                         | v1                                        |
+==============================+===========================================+
| model.set_grid(...)          | model.grid.set(...)                       |
+------------------------------+-------------------------------------------+
| model.read_grid(...)         | model.grid.read(...)                      |
+------------------------------+-------------------------------------------+
| model.write_grid(...)        | model.grid.write(...)                     |
+------------------------------+-------------------------------------------+
| model.setup_grid(...)        | model.grid.create_from_region(...)        |
+------------------------------+-------------------------------------------+
| model.setup_grid_from_*(...) | model.grid.add_data_from_*(...)           |
+------------------------------+-------------------------------------------+

**VectorComponent**

The `VectorMixin` and `VectorModel` have been restructured into one `VectorComponent` with only
a weak reference to one general `Model` instance. The `set_vector`, `write_vector`,
and `read_vector` have been changed to the more generically named `set`,
`write`, and `read` methods respectively. Also, the `setup_vector_from_*`
methods have been changed to `add_data_from_*`. The functionality of the VectorComponent
has not been changed compared to the VectorModel.

+------------------------------+-------------------------------------------+
| v0.x                         | v1                                        |
+==============================+===========================================+
| model.set_vector(...)        | model.vector.set(...)                     |
+------------------------------+-------------------------------------------+
| model.read_vector(...)       | model.vector.read(...)                    |
+------------------------------+-------------------------------------------+
| model.write_vector(...)      | model.vector.write(...)                   |
+------------------------------+-------------------------------------------+

**MeshComponent**

The MeshModel has just like the `GridModel` been replaced with its implementation
of the `ModelComponent`: `MeshComponent`. The restructuring of `MeshModel` follows the same pattern
as the `GridComponent`.

+--------------------------------+-------------------------------------------+
| v0.x                           | v1                                        |
+================================+===========================================+
| model.set_mesh(...)            | model.mesh.set(...)                       |
+--------------------------------+-------------------------------------------+
| model.read_mesh(...)           | model.mesh.read(...)                      |
+--------------------------------+-------------------------------------------+
| model.write_mesh(...)          | model.mesh.write(...)                     |
+--------------------------------+-------------------------------------------+
| model.setup_mesh(...)          | model.mesh.create_2d_from_region(...)     |
+--------------------------------+-------------------------------------------+
| model.setup_mesh2d_from_*(...) | model.mesh.add_2d_data_from_*(...)        |
+--------------------------------+-------------------------------------------+

**TablesComponent**

The previous `Model.tables` is now replaces by a `TablesComponent` that can used to store several
non-geospatial tabular data into a dictionary of pandas DataFrames. The `TablesComponent` for now
only contains the basic methods such as `read`, `write` and `set`.

**GeomsComponent**

The previous `Model.geoms` is now replaced by a `GeomsComponent` that can be used to store several
geospatial geometry based data into a dictionary of geopandas GeoDataFrames. The `GeomsComponent`
for now only contains the basic methods such as `read`, `write` and `set`.

**DatasetsComponent and SpatialDatasetsComponent**

The previous `Model` attributes `forcing`, `states`, `results` and `maps` are now replaced by
a `DatasetsComponent` and a `SpatialDatasetsComponent` that can be used to store several xarray datasets
into a dictionary. If your component should have a region property (in reference to another component),
the component should inherit from `SpatialModelComponent`.

The `DatasetsComponent` for now only contains the basic methods such as `read`, `write` and `set`.
The `SpatialModelComponent` contains additional methods to ``add_raster_data_from`` rasterdataset
and rasterdataset reclassification.

**ConfigComponent**

What was previously called `model.config` as well as some other class variables such as `Model._CONF`
is now located in `ConfigComponent`. Otherwise it still works mostly identically, meaning that it will
parse dotted keys like `a.b.c` into nested dictionaries such as `{'a':{'b':{'c': value}}}`. By default
the data will be read from and written to `<root>/config.yml` which can be overwritten either by providing
different arguments or by sub-classing the component and providing a different default value.

One main change is that the `model.config` used to be created by default from a template file which was
usually located in `Model._DATADIR//Model._NAME//Model._CONF`. To create a config from a template, users
now need to directly call the new `config.create` method, which is similar to how other components work.
Each plugin can still define a default config file template without sub-classing the `ConfigComponent`
by providing a `default_template_filename` when initializing their `ConfigComponent`.

Removed Model attributes
^^^^^^^^^^^^^^^^^^^^^^^^

Below you will find a summary of the functionalities, features, attributes and other things that were
removed from the `Model` class for v1 and how you can access their new equivalents.

- **api**: The `api` property and its associated attributes such as `_API` were previously provided to
  the plugins to enable additional validation. These have been superseded by the component architecture
  and have therefore been removed. Except in the case of equality checking (which will be covered separately
  below) plugins do not need to access any replacement functionality. All the type checking that was
  previously handled by the `api` property is now performed by the component architecture itself. If you use
  components as instructed they will take care of the rest for you.
- **_MAPS/_GEOMS/etc.**: As most aspects are now handled by the components, their model level attributes
  such as `_GEOMS` or `_MAPS` have been removed. The same functionality/ convention can still be used by
  setting these in the components.
- **_CONF** and **config_fn**: For the same reason, defining default config filename from the Model as been
  removed. To update the default config filename for your plugin/model, you can do so by setting the
  `filename` attribute of the `ConfigComponent` as followed. Similarly, if you would like to allow your user
  to easily update the model config file, you can re-add the **config_fn** in your model plugin:

.. code-block:: python

	class MyModel(Model):
	...
	def __init__(self, config_filename: Optional[str] = None):
		...
		# Add the config component
		if config_filename is None:
			config_filename = "my_plugin_default_config.toml"
		config_component = ConfigComponent(self, filename=config_filename)
		self.add_component("config", config_component)

- **_FOLDERS**: Since the components are now responsible for creating their folders when writing, we no
  longer have a `_FOLDERS` attribute and the `Model` will no longer create the folders during model init.
  This was done to provide more flexibility in which folders need to be created and which do not need to be.
  Components should make sure that they create the necessary folders themselves during writing.
- **_CLI_ARGS**: As region and resolution are removed from the command line arguments, this was not needed anymore.
- **deprecated attributes**: all grid related deprecated attributes have been removed (eg dims, coords, res etc.)


DataCatalog
===========

Changes to the data catalog `yaml` file format
----------------------------------------------

With the addition of new classes responsible for different stages of the data
reading phase, see below, the data catalog yaml file is updated accordingly:

.. code-block:: yaml

	mysource:
		data_type: RasterDataset
		uri: meteo/era5_daily/nc_merged/era5_{year}*_daily.nc
		metadata:
			category: meteo
			notes: Extracted from Copernicus Climate Data Store; resampled by Deltares to daily frequency
			crs: 4326
			nodata: -9999
			...
		driver:
			name: netcdf
			filesystem: local
			uri_resolver: convention
			options:
				chunks:
					latitude: 250
					longitude: 240
					time: 30
				combine: by_coords
		data_adapter:
			rename:
				d2m: temp_dew
				msl: press_msl
				...
			unit_add:
				temp: -273.15
				temp_dew: -273.15
				...
			unit_mult:
				kin: 0.000277778
				kout: 0.000277778
				...

Where there are a few changes from the previous versions:

- `path` is renamed to `uri`
- `driver` is it's own class and can be specified:
	- by string, implying default arguments
	- using a YAML object, with a mandatory `name` plus kwargs.
- `uri_resolver` can be specified:
	- by string, implying default arguments
	- using a YAML object, with a mandatory `name` plus kwargs.
- `filesystem` is moved to driver, and can be specified:
	- by string, implying default arguments
	- using a YAML object, with a mandatory `protocol` plus kwargs.
- `unit_add`, `unit_mult`, `rename`, `attrs`, `meta` are moved to `data_adapter`

There is also a script available for migrating your data catalog, available at `scripts/migrate_catalog_to_v1.py`.


Removing dictionary-like features for the DataCatalog
-----------------------------------------------------

**Rationale**

To be able to support different version of the same data set (for example, data sets
that get re-released frequently with updated data) or to be able to take the same data
set from multiple data sources (e.g. local if you have it but AWS if you don't) the
data catalog has undergone some changes. Now since a catalog entry no longer uniquely
identifies one source, (since it can refer to any of the variants mentioned above) it
becomes insufficient to request a data source by string only. Since the dictionary
interface in python makes it impossible to add additional arguments when requesting a
data source, we created a more extensive API for this. In order to make sure users'
code remains working consistently and have a clear upgrade path when adding new
variants we have decided to remove the old dictionary like interface.

**Changes required**

Dictionary like features such as `catalog['source']`, `catalog['source'] = data`,
`source in catalog` etc. should be removed for v1. Equivalent interfaces have been
provided for each operation, so it should be fairly simple. Below is a small table
with their equivalent functions


..table:: Dictionary translation guide for v1
   :widths: auto

+--------------------------+--------------------------------------+
| v0.x                     | v1                                   |
+==========================+======================================+
| if 'name' in catalog:    | if catalog.contains_source('name'):  |
+--------------------------+--------------------------------------+
| catalog['name']          | catalog.get_source('name')           |
+--------------------------+--------------------------------------+
| for x in catalog.keys(): | for x in catalog.get_source_names(): |
+--------------------------+--------------------------------------+
| catalog['name'] = data   | catalog.set_source('name',data)      |
+--------------------------+--------------------------------------+


Split the responsibilities of the `DataAdapter` into separate classes
---------------------------------------------------------------------

The previous version of the `DataAdapter` and its subclasses had a lot of
responsibilities:
- Validate the input from the `DataCatalog` entry.
- Find the right paths to the data based on a naming convention.
- Deserialize/read many different file formats into python objects.
- Merge these different python objects into one that represent that data source in the
model region.
- Homogenize the data based on the data catalog entry and HydroMT conventions.

In v1, this class has been split into three extendable components:

DataSource
^^^^^^^^^^

The `DataSource` is the python representation of a parsed entry in the `DataCatalog`.
The `DataSource` is responsible for validating the `DataCatalog` entry. It also carries
the `DataAdapter` and `DataDriver` (more info below) and serves as an entrypoint to
the data.
Per HydroMT data type (e.g. `RasterDataset`, `GeoDataFrame`), HydroMT has one
`DataSource`, e.g. `RasterDatasetSource`, `GeoDataFrameSource`.

URIResolver
^^^^^^^^^^^

The `URIResolver` takes a single `uri` and the query parameters from the model,
such as the region, or the time range, and returns multiple absolute paths, or `uri`s,
that can be read into a single python representation (e.g. `xarray.Dataset`). This
functionality was previously covered in the `resolve_paths` function. However, there
are more ways than to resolve a single uri, so the `URIResolver` makes this
behavior extendable. Plugins or other code can subclass the Abstract `URIResolver`
class to implement their own conventions for data discovery.
The `URIResolver` is injected into the `Driver` objects and can be used there.

Driver
^^^^^^

The `Driver` class is responsible for deserializing/reading a set of file types, like
a geojson or zarr file, into their python in-memory representations:
`geopandas.DataFrame` or `xarray.Dataset` respectively. To find the relevant files based
on a single `uri` in the `DataCatalog`, a `URIResolver` is used.
The driver has a `read` method. This method accepts a `uri`, a
unique identifier for a single data source. It also accepts different query parameters,
such a the region, time range or zoom level of the query from the model.
This `read` method returns the python representation of the DataSource.
Because the merging of different files from different `DataSource`s can be
non-trivial, the driver is responsible to merge the different python objects coming
from the driver to a single representation. This is then returned from the `read`
method.
Because the query parameters vary per HydroMT data type, the is a different driver
interface per type, e.g. `RasterDatasetDriver`, `GeoDataFrameDriver`.

DataAdapter
^^^^^^^^^^^

The `DataAdapter` now has its previous responsibilities reduced to just homogenizing
the data coming from the `Driver`. This means slicing the data to the right region,
renaming variables, changing units, regridding and more. The `DataAdapter` has a
`transform` method that takes a HydroMT data type and returns this same type. This
method also accepts query parameters based on the data type, so there is a single
`DataAdapter` per HydroMT data type.



Package API
===========

**Rationale**
As HydroMT contains many functions and new classes with v1, the hydromt folder structure
and the import statements have changed.

**Changes required**

The following changes are required in your code:

+--------------------------+--------------------------------------+
| v0.x                     | v1                                   |
+==========================+======================================+
| hydromt.config           | Removed                              |
+--------------------------+--------------------------------------+
| hydromt.log              | Removed (private: hydromt._utils.log)|
+--------------------------+--------------------------------------+
| hydromt.flw              | hydromt.gis.flw                      |
+--------------------------+--------------------------------------+
| hydromt.gis_utils        | hydromt.gis.utils                    |
+--------------------------+--------------------------------------+
| hydromt.raster           | hydromt.gis.raster                   |
+--------------------------+--------------------------------------+
| hydromt.vector           | hydromt.gis.vector                   |
+--------------------------+--------------------------------------+
| hydromt.gis_utils        | hydromt.gis.utils                    |
+--------------------------+--------------------------------------+

Logging
=======

**Rationale**
Previous versions of HydroMT passed the logger around a lot. The Logging module is based on
singleton classes and log propagation using a naming convention. Due to some bugs in the
previous version of the code, the logger passing has been removed and the `"hydromt"` logger
now governs the logging output.

**Changes required**
Remove the `logger` keywords from your HydroMT functions, methods and classes. If you want to
influence HydroMT logging, change the `"hydromt"` logger: `logger = logging.getLogger("hydromt")`.

Plugins
=======

Previously the `Model` class was the only entrypoint for providing core with custom behavior.
Now, there are four:

- `Model`: This class is mostly responsible for dispatching function calls and otherwise
   delegating work to components.
- `ModelComponent`. This class provides more specialized functionalities to do with a single
   part of a model such as a mesh or grid.
- `Driver`. This class provides customizable loading of any data source.
- `PredefinedCatalog`. This class provides a way to define a catalog of data sources that
   can be used in the model.

Each of these parts have entry points at their relevant submodules. For example, see how these
are specified in the `pyproject.toml`

.. code-block:: toml

	[project.entry-points."hydromt.components"]
	core = "hydromt.components"

	[project.entry-points."hydromt.models"]
	core = "hydromt.models"

	[project.entry-points."hydromt.drivers"]
	core = "hydromt.data_catalog.drivers"

To have post v1 core recognize there are a few new requirements:
1. The entrypoint exposes a submodule or script which must specify a `__hydromt_eps__` attribute.
2. All objects listed in the `__hydromt_eps__` attribute will be made available as plugins in the relevant category. These can only be subclasses of the relevant base classes in core for each category (e.g. `ModelComponent` for components)