mirror of
https://github.com/macaodha/batdetect2.git
synced 2025-06-29 22:51:58 +02:00
add docs for configs module
This commit is contained in:
parent
5d4d9a5edf
commit
b796e0bc7b
@ -1,23 +1,105 @@
|
|||||||
|
"""Provides base classes and utilities for loading configurations in BatDetect2.
|
||||||
|
|
||||||
|
This module leverages Pydantic for robust configuration handling, ensuring
|
||||||
|
that configuration files (typically YAML) adhere to predefined schemas. It
|
||||||
|
defines a base configuration class (`BaseConfig`) that enforces strict schema
|
||||||
|
validation and a utility function (`load_config`) to load and validate
|
||||||
|
configuration data from files, with optional support for accessing nested
|
||||||
|
configuration sections.
|
||||||
|
"""
|
||||||
|
|
||||||
from typing import Any, Optional, Type, TypeVar
|
from typing import Any, Optional, Type, TypeVar
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
from pydantic import BaseModel, ConfigDict
|
from pydantic import BaseModel, ConfigDict
|
||||||
from soundevent.data import PathLike
|
from soundevent.data import PathLike
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"BaseConfig",
|
||||||
|
"load_config",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class BaseConfig(BaseModel):
|
class BaseConfig(BaseModel):
|
||||||
|
"""Base class for all configuration models in BatDetect2.
|
||||||
|
|
||||||
|
Inherits from Pydantic's `BaseModel` to provide data validation, parsing,
|
||||||
|
and serialization capabilities.
|
||||||
|
|
||||||
|
It sets `extra='forbid'` in its model configuration, meaning that any
|
||||||
|
fields provided in a configuration file that are *not* explicitly defined
|
||||||
|
in the specific configuration schema will raise a validation error. This
|
||||||
|
helps catch typos and ensures configurations strictly adhere to the expected
|
||||||
|
structure.
|
||||||
|
|
||||||
|
Attributes
|
||||||
|
----------
|
||||||
|
model_config : ConfigDict
|
||||||
|
Pydantic model configuration dictionary. Set to forbid extra fields.
|
||||||
|
"""
|
||||||
|
|
||||||
model_config = ConfigDict(extra="forbid")
|
model_config = ConfigDict(extra="forbid")
|
||||||
|
|
||||||
|
|
||||||
T = TypeVar("T", bound=BaseModel)
|
T = TypeVar("T", bound=BaseModel)
|
||||||
|
|
||||||
|
|
||||||
def get_object_field(obj: dict, field: str) -> Any:
|
def get_object_field(obj: dict, current_key: str) -> Any:
|
||||||
if "." not in field:
|
"""Access a potentially nested field within a dictionary using dot notation.
|
||||||
return obj[field]
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
obj : dict
|
||||||
|
The dictionary (or nested dictionaries) to access.
|
||||||
|
field : str
|
||||||
|
The field name to retrieve. Nested fields are specified using dots
|
||||||
|
(e.g., "parent_key.child_key.target_field").
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
Any
|
||||||
|
The value found at the specified field path.
|
||||||
|
|
||||||
|
Raises
|
||||||
|
------
|
||||||
|
KeyError
|
||||||
|
If any part of the field path does not exist in the dictionary
|
||||||
|
structure.
|
||||||
|
TypeError
|
||||||
|
If an intermediate part of the path exists but is not a dictionary,
|
||||||
|
preventing further nesting.
|
||||||
|
|
||||||
|
Examples
|
||||||
|
--------
|
||||||
|
>>> data = {"a": {"b": {"c": 10}}}
|
||||||
|
>>> get_object_field(data, "a.b.c")
|
||||||
|
10
|
||||||
|
>>> get_object_field(data, "a.b")
|
||||||
|
{'c': 10}
|
||||||
|
>>> get_object_field(data, "a")
|
||||||
|
{'b': {'c': 10}}
|
||||||
|
>>> get_object_field(data, "x")
|
||||||
|
Traceback (most recent call last):
|
||||||
|
...
|
||||||
|
KeyError: 'x'
|
||||||
|
>>> get_object_field(data, "a.x.c")
|
||||||
|
Traceback (most recent call last):
|
||||||
|
...
|
||||||
|
KeyError: 'x'
|
||||||
|
"""
|
||||||
|
if "." not in current_key:
|
||||||
|
return obj[current_key]
|
||||||
|
|
||||||
|
current_key, rest = current_key.split(".", 1)
|
||||||
|
subobj = obj[current_key]
|
||||||
|
|
||||||
|
if not isinstance(subobj, dict):
|
||||||
|
raise TypeError(
|
||||||
|
f"Intermediate key '{current_key}' in path '{current_key}' "
|
||||||
|
f"does not lead to a dictionary (found type: {type(subobj)}). "
|
||||||
|
"Cannot access further nested field."
|
||||||
|
)
|
||||||
|
|
||||||
field, rest = field.split(".", 1)
|
|
||||||
subobj = obj[field]
|
|
||||||
return get_object_field(subobj, rest)
|
return get_object_field(subobj, rest)
|
||||||
|
|
||||||
|
|
||||||
@ -26,6 +108,49 @@ def load_config(
|
|||||||
schema: Type[T],
|
schema: Type[T],
|
||||||
field: Optional[str] = None,
|
field: Optional[str] = None,
|
||||||
) -> T:
|
) -> T:
|
||||||
|
"""Load and validate configuration data from a file against a schema.
|
||||||
|
|
||||||
|
Reads a YAML file, optionally extracts a specific section using dot
|
||||||
|
notation, and then validates the resulting data against the provided
|
||||||
|
Pydantic `schema`.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
path : PathLike
|
||||||
|
The path to the configuration file (typically `.yaml`).
|
||||||
|
schema : Type[T]
|
||||||
|
The Pydantic `BaseModel` subclass that defines the expected structure
|
||||||
|
and types for the configuration data.
|
||||||
|
field : str, optional
|
||||||
|
A dot-separated string indicating a nested section within the YAML
|
||||||
|
file to extract before validation. If None (default), the entire
|
||||||
|
file content is validated against the schema.
|
||||||
|
Example: `"training.optimizer"` would extract the `optimizer` section
|
||||||
|
within the `training` section.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
T
|
||||||
|
An instance of the provided `schema`, populated and validated with
|
||||||
|
data from the configuration file.
|
||||||
|
|
||||||
|
Raises
|
||||||
|
------
|
||||||
|
FileNotFoundError
|
||||||
|
If the file specified by `path` does not exist.
|
||||||
|
yaml.YAMLError
|
||||||
|
If the file content is not valid YAML.
|
||||||
|
pydantic.ValidationError
|
||||||
|
If the loaded configuration data (after optionally extracting the
|
||||||
|
`field`) does not conform to the provided `schema` (e.g., missing
|
||||||
|
required fields, incorrect types, extra fields if using BaseConfig).
|
||||||
|
KeyError
|
||||||
|
If `field` is provided and specifies a path where intermediate keys
|
||||||
|
do not exist in the loaded YAML data.
|
||||||
|
TypeError
|
||||||
|
If `field` is provided and specifies a path where an intermediate
|
||||||
|
value is not a dictionary, preventing access to nested fields.
|
||||||
|
"""
|
||||||
with open(path, "r") as file:
|
with open(path, "r") as file:
|
||||||
config = yaml.safe_load(file)
|
config = yaml.safe_load(file)
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ extensions = [
|
|||||||
"sphinx.ext.autodoc",
|
"sphinx.ext.autodoc",
|
||||||
"sphinx.ext.autosummary",
|
"sphinx.ext.autosummary",
|
||||||
"sphinx.ext.intersphinx",
|
"sphinx.ext.intersphinx",
|
||||||
|
"sphinxcontrib.autodoc_pydantic",
|
||||||
"numpydoc",
|
"numpydoc",
|
||||||
"myst_parser",
|
"myst_parser",
|
||||||
"sphinx_autodoc_typehints",
|
"sphinx_autodoc_typehints",
|
||||||
@ -47,3 +48,13 @@ intersphinx_mapping = {
|
|||||||
# -- Options for autodoc ------------------------------------------------------
|
# -- Options for autodoc ------------------------------------------------------
|
||||||
autosummary_generate = True
|
autosummary_generate = True
|
||||||
autosummary_imported_members = True
|
autosummary_imported_members = True
|
||||||
|
|
||||||
|
autodoc_default_options = {
|
||||||
|
"members": True,
|
||||||
|
"undoc-members": False,
|
||||||
|
"private-members": False,
|
||||||
|
"special-members": False,
|
||||||
|
"inherited-members": False,
|
||||||
|
"show-inheritance": True,
|
||||||
|
"module-first": True,
|
||||||
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# Config Reference
|
# Config Reference
|
||||||
|
|
||||||
```{eval-rst}
|
```{eval-rst}
|
||||||
.. automodule:: batdetect2.configs
|
.. automodule:: batdetect2.configs
|
||||||
:members:
|
:members:
|
||||||
:inherited-members: pydantic.BaseModel
|
:inherited-members: pydantic.BaseModel
|
||||||
|
@ -78,6 +78,7 @@ dev-dependencies = [
|
|||||||
"numpydoc>=1.8.0",
|
"numpydoc>=1.8.0",
|
||||||
"sphinx-autodoc-typehints>=2.3.0",
|
"sphinx-autodoc-typehints>=2.3.0",
|
||||||
"sphinx-book-theme>=1.1.4",
|
"sphinx-book-theme>=1.1.4",
|
||||||
|
"autodoc-pydantic>=2.2.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.ruff]
|
[tool.ruff]
|
||||||
|
Loading…
Reference in New Issue
Block a user