First principles¶
hydrolib-core is structured around the input and output files (I/O) of the DHYDRO suite.
Inheritance/file handling¶
Model setups often consist of different files, many implicitly linked to each other. HYDROLIB makes these links explicit, by recursively loading all child configuration files.
For example, when parsing a DIMR file, one could happen upon a reference to an MDU file, which contain references to other files, such as cross sections, etc.
>>> dimr = DIMR(filepath=Path(test_data_dir / "dimr_config.xml"))
You can see this tree structure if you call show_tree
.
>>> dimr.show_tree()
DIMR represented by dimr_config.xml.
RRComponent
FMComponent
∟ FMModel represented by FlowFM.mdu.
∟ Geometry
∟ StructureModel represented by structures.ini.
∟ CrossDefModel represented by crsdef.ini.
∟ CrossLocModel represented by crsloc.ini.
∟ FrictionModel represented by roughness-Main.ini.
∟ FrictionModel represented by roughness-Sewer1.ini.
∟ FrictionModel represented by roughness-Sewer2.ini.
∟ ExternalForcing
∟ ExtModel represented by FlowFM_bnd.ext.
∟ Boundary
∟ ForcingModel represented by FlowFM_boundaryconditions1d.bc.
∟ Boundary
∟ ForcingModel represented by FlowFM_boundaryconditions1d.bc.
∟ Boundary
∟ ForcingModel represented by FlowFM_boundaryconditions1d.bc.
Program flow while recursively creating FileModels¶
In short:¶
- The root folder is stored in the global variable
context_dir
, so that referenced files can be found. - Parsed data from files will be cached in the class variable
FileModel._file_models_cache
, so that duplicate references do not lead to duplicate parsing and multiple object instances. For all duplicate references, the same cachedFileModel
instance is used. - Pydantic first calls the
FileModel.validate
and then the initializer (FileModel.__new__
andFileModel.__init__
).
In detail:¶
=> User initializes a new root FileModel from file:
1) FileModel.__new__(cls, filepath: str/Path)
- Returns the result of the default __new__
function
2) FileModel.__init__(self, filepath: str/Path)
- self
holds the FileModel instance that was returned in step 1.
- filepath
is assumed to hold the absolute file path to the root file.
- Updates the global variable context_dir
with the parent directory of filepath
.
- Caches this FileModel instance (self
) in the static class variable FileModel._file_models_cache
with the filepath
.
- Loads the data (dict
) from the file.
- Initialize self
with the data.
- => Pydantic tries to convert the data to objects, a.o. sub FileModels:
3) FileModel.validate(value: str)
- value
holds a relative or absolute file path to the referenced file.
- If value
is not an absolute file path, resolves the absolute path by using the directory stored in the context_dir
, previously set in step 2.
- Calls super().validate(value)
with the absolute file path stored in value
.
- => Pydantic create a new FileModel with the data:
4) FileModel.__new__(cls, filepath: str)
- filepath
holds the absolute file path that was resolved in step 3.
- If FileModel._file_models_cache
already contains a FileModel instance with this filepath
, returns the cached instance,
- Otherwise, returns the result of the default __new__
function.
5) FileModel.__init__(self, filepath: str)
- self
holds the FileModel instance that was returned in step 4.
- filepath
holds the absolute file path that was resolved in step 3.
- If FileModel._file_models_cache
already contains a FileModel instance with this filepath
, returns immediately: no initialization is done.
- Else, caches this FileModel instance (self
) in the static class variable FileModel._file_models_cache
with the filepath
.
- Loads the data (dict
) from the file.
- Initialize self
with the data.
- => Pydantic tries to convert the data to objects, a.o. sub FileModels:
6) Repeat steps 3-5
Parsing and serializing INIBasedModels
¶
Parsing an INI file should be case-insensitive. To achieve this, the parsable field names of each INIBasedModel
should be equal to the expected key in the file in lower case.
Some property values are explicitly made case-insensitive for parsing as well. This applies to enum values and values that represent a specific type of INIBasedModel
, such as the type property of a structure. To support this, custom validators are placed to compare the given value with the available known values. Structures are initialized based on the value in the type
field. The value of this field of each subclass of a Structure
is compared to the input and the subclass with the corresponding type is initialized.
The serialization of an INIBasedModel
to an INI file should respect certain casing rules (overriding the casing used by the user):
- Property keys need to be "lowerCamelCase"
- Section headers need to be "UpperCamelCase"
To achieve this, each serializable field in a INIBasedModel
has an alias. This alias will be written as property key to file. Each INIBasedModel
that represents an INI section, has a field _header
. The default value of this field will be written to file.
Enum values and the values that represent a specific type of INIBasedModel
will be serialized to file by how they are defined.