diff --git a/docs/source/_templates/module_overview.rst b/docs/source/_templates/module_overview.rst index 4978be78e..bd595685f 100644 --- a/docs/source/_templates/module_overview.rst +++ b/docs/source/_templates/module_overview.rst @@ -2,8 +2,10 @@ .. .. SPDX-License-Identifier: MPL-2.0 -{{ fullname }} -{{ "=" * fullname|length }} +{% set parts = fullname.split('.') %} +{% set short_name = parts[-1] if parts|length >= 3 else (parts[1:] | join('.') if parts|length > 1 else fullname) %} +{{ short_name }} +{{ "=" * short_name|length }} .. currentmodule:: {{ fullname }} diff --git a/docs/source/_templates/package_overview.rst b/docs/source/_templates/package_overview.rst index 5cd4cd993..39ac4534f 100644 --- a/docs/source/_templates/package_overview.rst +++ b/docs/source/_templates/package_overview.rst @@ -2,8 +2,10 @@ .. .. SPDX-License-Identifier: MPL-2.0 -{{ fullname }} -{{ "=" * fullname|length }} +{% set parts = fullname.split('.') %} +{% set short_name = parts[-1] if parts|length >= 3 else (parts[1:] | join('.') if parts|length > 1 else fullname) %} +{{ short_name }} +{{ "=" * short_name|length }} .. currentmodule:: {{ fullname }} @@ -12,23 +14,22 @@ :no-inherited-members: {% block package_overview %} -{% if modules or functions or classes or members %} - -{# Custom logic to detect submodules from __all__ #} -{% set submodules = [] %} -{% if fullname == 'openstef_core.datasets' %} -{% set _ = submodules.append('mixins') %} -{% endif %} - -{% if modules or submodules %} +{# Dynamically discover child modules/packages via pkgutil (no hardcoding needed) #} +{% set discovered = discover_submodules(fullname) %} +{# Remove any modules already provided by autosummary to avoid duplicates #} +{% set known = modules | map('replace', fullname ~ '.', '') | list %} +{% set extra_submodules = discovered | reject('in', known) | list %} +{% if modules or extra_submodules or functions or classes or members %} + +{% if modules or extra_submodules %} Submodules ---------- -{% if submodules %} +{% if extra_submodules %} .. autosummary:: :toctree: . :template: module_overview.rst -{% for item in submodules %} +{% for item in extra_submodules %} {{ fullname }}.{{ item }} {%- endfor %} diff --git a/docs/source/api/beam.rst b/docs/source/api/beam.rst new file mode 100644 index 000000000..ee2c30fea --- /dev/null +++ b/docs/source/api/beam.rst @@ -0,0 +1,23 @@ +.. SPDX-FileCopyrightText: 2025 Contributors to the OpenSTEF project +.. +.. SPDX-License-Identifier: MPL-2.0 + +.. _beam-api: + +BEAM Package (:mod:`openstef_beam`) +==================================== + +.. currentmodule:: openstef_beam + +Backtesting, evaluation, analysis and metrics for forecasting models. + +.. autosummary:: + :toctree: generated/ + :template: package_overview.rst + :recursive: + + metrics + backtesting + analysis + evaluation + benchmarking diff --git a/docs/source/api/core.rst b/docs/source/api/core.rst new file mode 100644 index 000000000..39a4190e4 --- /dev/null +++ b/docs/source/api/core.rst @@ -0,0 +1,26 @@ +.. SPDX-FileCopyrightText: 2025 Contributors to the OpenSTEF project +.. +.. SPDX-License-Identifier: MPL-2.0 + +.. _core-api: + +Core Package (:mod:`openstef_core`) +==================================== + +.. currentmodule:: openstef_core + +Core data structures, datasets, and utilities for OpenSTEF. + +.. autosummary:: + :toctree: generated/ + :template: package_overview.rst + :recursive: + + datasets + utils + base_model + exceptions + mixins + testing + transforms + types diff --git a/docs/source/api/index.rst b/docs/source/api/index.rst index 8e57fe44e..edb213275 100644 --- a/docs/source/api/index.rst +++ b/docs/source/api/index.rst @@ -7,7 +7,7 @@ API Reference This is the complete API reference for OpenSTEF. The API is organized into several packages: -.. grid:: 1 1 3 3 +.. grid:: 1 1 2 2 :gutter: 4 :padding: 2 2 0 0 :class-container: sd-text-center @@ -15,73 +15,88 @@ This is the complete API reference for OpenSTEF. The API is organized into sever .. grid-item-card:: :fa:`cube` Core Package :link: core-api :link-type: ref - + Core data structures, datasets, and utilities .. grid-item-card:: :fa:`robot` Models Package :link: models-api :link-type: ref - + Machine learning models and feature engineering .. grid-item-card:: :fa:`chart-line` BEAM Package :link: beam-api :link-type: ref - + Backtesting, evaluation, analysis and metrics for forecasting models -.. _core-api: + .. grid-item-card:: :fa:`layer-group` Meta Package + :link: meta-api + :link-type: ref + + Ensemble forecasting and preset workflows + +.. toctree:: + :maxdepth: 2 + :hidden: + + core + models + beam + meta Core Package (:mod:`openstef_core`) ----------------------------------- .. currentmodule:: openstef_core -**Core Modules:** - .. autosummary:: - :toctree: generated/ - :template: package_overview.rst datasets utils base_model exceptions - -.. _models-api: + mixins + testing + transforms + types Models Package (:mod:`openstef_models`) --------------------------------------- .. currentmodule:: openstef_models -**Model Modules:** - .. autosummary:: - :toctree: generated/ - :template: package_overview.rst - transforms models - pipelines + workflows + presets explainability - exceptions - -.. _beam-api: + mixins + integrations + transforms + utils BEAM Package (:mod:`openstef_beam`) ----------------------------------- .. currentmodule:: openstef_beam -**BEAM Modules:** - .. autosummary:: - :toctree: generated/ - :template: package_overview.rst metrics backtesting analysis evaluation benchmarking + +Meta Package (:mod:`openstef_meta`) +----------------------------------- + +.. currentmodule:: openstef_meta + +.. autosummary:: + + models + presets + utils diff --git a/docs/source/api/meta.rst b/docs/source/api/meta.rst new file mode 100644 index 000000000..be5f06685 --- /dev/null +++ b/docs/source/api/meta.rst @@ -0,0 +1,21 @@ +.. SPDX-FileCopyrightText: 2025 Contributors to the OpenSTEF project +.. +.. SPDX-License-Identifier: MPL-2.0 + +.. _meta-api: + +Meta Package (:mod:`openstef_meta`) +==================================== + +.. currentmodule:: openstef_meta + +Ensemble forecasting and preset workflows. + +.. autosummary:: + :toctree: generated/ + :template: package_overview.rst + :recursive: + + models + presets + utils diff --git a/docs/source/api/models.rst b/docs/source/api/models.rst new file mode 100644 index 000000000..52c0fa099 --- /dev/null +++ b/docs/source/api/models.rst @@ -0,0 +1,26 @@ +.. SPDX-FileCopyrightText: 2025 Contributors to the OpenSTEF project +.. +.. SPDX-License-Identifier: MPL-2.0 + +.. _models-api: + +Models Package (:mod:`openstef_models`) +======================================== + +.. currentmodule:: openstef_models + +Machine learning models and feature engineering for OpenSTEF. + +.. autosummary:: + :toctree: generated/ + :template: package_overview.rst + :recursive: + + models + workflows + presets + explainability + mixins + integrations + transforms + utils diff --git a/docs/source/conf.py b/docs/source/conf.py index c517dcfe9..45fb908ea 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -10,10 +10,13 @@ # -- Project information ----------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information +import importlib +import pkgutil from pathlib import Path import re from typing import Any import warnings +from docutils import nodes as docutils_nodes from sphinx_pyproject import SphinxConfig from sphinx.application import Sphinx import yaml @@ -63,6 +66,33 @@ autosummary_generate_overwrite = True autosummary_imported_members = False +# Suppress benign import_cycle warnings from recursive autosummary (modules are +# listed in both the parent's :recursive: directive and the template's toctree) +suppress_warnings = ["autosummary.import_cycle"] + + +def _discover_submodules(fullname: str) -> list[str]: + """Discover child modules/packages of *fullname* via pkgutil. + + Returns an empty list when *fullname* is not a package (has no ``__path__``) + or when the import fails (e.g. missing optional dependency). + """ + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + try: + mod = importlib.import_module(fullname) + except Exception: # noqa: BLE001 + return [] + pkg_path = getattr(mod, "__path__", None) + if pkg_path is None: + return [] + return sorted(name for _, name, _ispkg in pkgutil.iter_modules(pkg_path)) + + +autosummary_context = { + "discover_submodules": _discover_submodules, +} + # Don't generate separate pages for class members autosummary_mock_imports = [] @@ -243,10 +273,10 @@ def cff_to_bibtex(cff_data: dict[str, Any]) -> str: "search_bar_text": "Search the docs ...", "navigation_with_keys": False, "collapse_navigation": False, - "navigation_depth": 3, # Show more levels for API reference - "show_nav_level": 2, # Show more levels in navbar + "navigation_depth": 4, + "show_nav_level": 2, "navbar_center": ["navbar-nav"], # Use only primary navigation - "show_toc_level": 3, # Show deeper TOC levels in sidebar + "show_toc_level": 2, "navbar_align": "left", "header_links_before_dropdown": 5, "header_dropdown_text": "More", @@ -327,6 +357,58 @@ def rstjinja(app: Sphinx, docname: str, source: list[str]) -> None: source[0] = rendered +def _inject_pydantic_field_descriptions( + app: Sphinx, # noqa: ARG001 + domain: str, + objtype: str, + contentnode: docutils_nodes.Element, +) -> None: + """Inject Pydantic ``Field(description=...)`` text into attribute nodes. + + This runs via ``object-description-transform`` *after* autodoc has rendered + each attribute directive. For Pydantic model fields whose content area is + empty, we look up the ``description`` from ``model_fields`` and insert a + paragraph node so the description appears next to the type annotation. + """ + if domain != "py" or objtype != "attribute": + return + # Only inject into empty content areas + if contentnode.children: + return + # The signature node is the first child of the parent desc node + sig = contentnode.parent[0] if contentnode.parent else None + if sig is None: + return + ids = sig.get("ids", []) + if not ids: + return + # ID looks like "pkg.mod.ClassName.field_name" + full_id = ids[0] + parts = full_id.rsplit(".", 2) + if len(parts) < 3: + return + field_name = parts[-1] + parent_qualname = ".".join(parts[:-1]) + parent_module, _, parent_attr = parent_qualname.rpartition(".") + if not parent_module: + return + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + try: + mod = importlib.import_module(parent_module) + except Exception: # noqa: BLE001 + return + parent_cls = getattr(mod, parent_attr, None) + model_fields = getattr(parent_cls, "model_fields", None) + if model_fields is None or field_name not in model_fields: + return + desc = model_fields[field_name].description + if not desc: + return + para = docutils_nodes.paragraph("", desc) + contentnode.append(para) + + def setup(app: Sphinx) -> None: """Sphinx setup function to make citation data available in templates.""" if citation_cff: @@ -336,3 +418,4 @@ def setup(app: Sphinx) -> None: } app.config.html_context.update(context_update) # type: ignore[attr-defined] app.connect("source-read", rstjinja) + app.connect("object-description-transform", _inject_pydantic_field_descriptions) diff --git a/packages/openstef-models/src/openstef_models/integrations/__init__.py b/packages/openstef-models/src/openstef_models/integrations/__init__.py index 261a35cca..269813961 100644 --- a/packages/openstef-models/src/openstef_models/integrations/__init__.py +++ b/packages/openstef-models/src/openstef_models/integrations/__init__.py @@ -8,3 +8,5 @@ extend OpenSTEF functionality by integrating with external systems such as monitoring tools, databases, cloud storage, and custom processing pipelines. """ + +__all__ = ["joblib", "mlflow", "optuna"] # noqa: F822 # pyright: ignore[reportUnsupportedDunderAll] # Sub-packages with optional deps; not imported to avoid missing-extra errors at import time