diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9b8f40c73e..bf7286d1ce 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -90,7 +90,23 @@ jobs: - name: Build Docs run: | pip install tox-uv - tox -e docs-py310 -- -r + tox -e docs-py310 + + linkcheck: + name: "Link Check" + runs-on: ubuntu-latest + needs: [lint] + continue-on-error: true + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + with: + persist-credentials: false + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 + with: {python-version: "3.10"} + - name: Check external links + run: | + pip install tox-uv + tox -e linkcheck typecheck: needs: [lint] diff --git a/baybe/recommenders/meta/base.py b/baybe/recommenders/meta/base.py index 21b0758843..d44a56bd75 100644 --- a/baybe/recommenders/meta/base.py +++ b/baybe/recommenders/meta/base.py @@ -52,7 +52,8 @@ def get_non_meta_recommender( ) -> RecommenderProtocol: """Follow the meta recommender chain to the selected non-meta recommender. - Recursively calls :meth:`MetaRecommender.select_recommender` until a + Recursively calls + :meth:`~baybe.recommenders.meta.base.MetaRecommender.select_recommender` until a non-meta recommender is encountered, which is then returned. Effectively, this extracts the recommender responsible for generating the recommendations for the specified context. diff --git a/baybe/surrogates/gaussian_process/core.py b/baybe/surrogates/gaussian_process/core.py index 81d7f00c26..dba9ba1cec 100644 --- a/baybe/surrogates/gaussian_process/core.py +++ b/baybe/surrogates/gaussian_process/core.py @@ -306,7 +306,7 @@ def posterior_mean_function( * **Eagerly:** By calling the method and passing the returned module to a GP. * **Lazily:** By passing the bound method itself, without eagerly calling it. This works because the method signature complies with - :class:`~.components.mean.MeanFactoryProtocol`, i.e., the new GP will use it + :obj:`~.components.mean.MeanFactoryProtocol`, i.e., the new GP will use it as a factory and call it automatically at fit time. If the mean-providing GP has not been fitted at call time, its prior mean module diff --git a/baybe/transformations/base.py b/baybe/transformations/base.py index 4b84620980..2af5963eae 100644 --- a/baybe/transformations/base.py +++ b/baybe/transformations/base.py @@ -43,10 +43,11 @@ def get_codomain(self, interval: Interval | None = None, /) -> Interval: In accordance with the mathematical definition of a function's `codomain `_, we define the codomain of a given :class:`~baybe.utils.interval.Interval` under a certain (assumed continuous) - :class:`~Transformation` to be an :class:`~baybe.utils.interval.Interval` - guaranteed to contain all possible outcomes when the :class:`~Transformation` is - applied to all points in the input :class:`~baybe.utils.interval.Interval`. In - cases where the image cannot exactly be computed, it is often still possible to + :class:`~baybe.transformations.base.Transformation` to be an + :class:`~baybe.utils.interval.Interval` guaranteed to contain all possible + outcomes when the :class:`~baybe.transformations.base.Transformation` is applied + to all points in the input :class:`~baybe.utils.interval.Interval`. In cases + where the image cannot exactly be computed, it is often still possible to compute a codomain. The codomain always contains the image, but might be larger. """ @@ -56,10 +57,10 @@ def get_image(self, interval: Interval | None = None, /) -> Interval: In accordance with the mathematical definition of a function's `image `_, we define the image of a given :class:`~baybe.utils.interval.Interval` under a certain (assumed - continuous) :class:`~Transformation` to be the smallest - :class:`~baybe.utils.interval.Interval` containing all possible outcomes when - the :class:`~Transformation` is applied to all points in the input - :class:`~baybe.utils.interval.Interval`. + continuous) :class:`~baybe.transformations.base.Transformation` to be the + smallest :class:`~baybe.utils.interval.Interval` containing all possible + outcomes when the :class:`~baybe.transformations.base.Transformation` is applied + to all points in the input :class:`~baybe.utils.interval.Interval`. """ # By default, it is assumed that the exact image of an interval cannot be # computed but only the codomain is available (see :meth:`get_codomain`). diff --git a/docs/api_reference.md b/docs/api_reference.md new file mode 100644 index 0000000000..25d4f200ad --- /dev/null +++ b/docs/api_reference.md @@ -0,0 +1,17 @@ +--- +orphan: true +--- + + + +```{eval-rst} +.. autosummary:: + :toctree: _autosummary + :template: custom-module-template.rst + :recursive: + + baybe +``` diff --git a/docs/components/transformations.md b/docs/components/transformations.md index 5e349cc971..59c3fc0e81 100644 --- a/docs/components/transformations.md +++ b/docs/components/transformations.md @@ -399,7 +399,7 @@ t = CustomTransformation(torch.sin) ``` ````{admonition} Automatic Wrapping -:note: +:class: note When embedding custom transformations into another context, wrapping the `torch` callable into a {class}`~baybe.transformations.basic.CustomTransformation` happens diff --git a/docs/conf.py b/docs/conf.py index aa0078b88a..175668b69e 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -139,7 +139,6 @@ templates_path = ["templates"] # Tell sphinx which files should be excluded exclude_patterns = ["sdk", "AGENTS.md", "CLAUDE.md", "**/AGENTS.md", "**/CLAUDE.md"] -autodoc_exclude_modules = ["baybe.utils.clustering_algorithms.third_party.kmedoids"] # Enable markdown # Note that we do not need additional configuration here. @@ -151,27 +150,59 @@ # Here, we define regex expressions for errors produced by nitpick that we want to # ignore. nitpick_ignore_regex = [ - # Ignore everything that does not include baybe - (r"py:.*", r"^(?!.*baybe).*"), - # Ignore errors that are from inherited classes we cannot control + ##### External package references ##### + # Qualified references to external packages whose internal module paths cannot be + # resolved via intersphinx (e.g. pandas.core.frame.DataFrame vs pandas.DataFrame). + ( + r"py:.*", + r"(pandas|numpy|torch|botorch|gpytorch|scipy|sklearn|pathlib|polars|attr|joblib|matplotlib|skfp|rdkit|shap|xyzpy|typing)[\._].*", + ), # noqa: E501 + ##### Inherited torch.nn.Module docstring references ##### + # Unqualified names from inherited external docstrings (torch, botorch, sklearn) + # that cannot be resolved outside their original documentation context. + (r"py:class", r"^(Tensor|Module|Parameter|Dropout|BatchNorm)$"), + (r"py:class", r"^(Posterior|MetadataRequest|Ignored)$"), + (r"py:attr", r"^(persistent|grad_input|grad_output|requires_grad)$"), + (r"py:attr", r"^(device|dtype|dst_type|non_blocking)$"), + (r"py:func", r"^(register_module_forward_hook|register_module_forward_pre_hook)$"), + (r"py:func", r"^(register_module_full_backward_hook)$"), + (r"py:func", r"^(register_module_full_backward_pre_hook|load_state_dict)$"), + (r"py:meth", r"^nn\.Module\.load_state_dict$"), + ##### Inherited sklearn/scipy docstring artifacts ##### + # sklearn docstrings use informal type descriptions that Sphinx parses as refs. + (r"py:class", r"^(optional|shape|shape=|n_samples|n_features|n_query)$"), + (r"py:class", r"^(n_features_new|n_outputs|n_indexed|n_clusters)$"), + (r"py:class", r"^(array-like|ndarray|ndarray array|string)$"), + (r"py:class", r"^(estimator instance|sparse matrix\})$"), + (r"py:class", r"^(\{array-like|default=.*|\{\"default\")$"), + (r"py:class", r"^(dtype=np\.int64|if metric == 'precomputed')$"), + ##### Type aliases in TYPE_CHECKING blocks ##### + # These exist only at type-checking time and cannot be resolved by Sphinx. + (r"py:class", r"^(GPComponent|TensorCallable|ConvertibleToFloat)$"), + (r"py:class", r"^(GPyTorchKernel|GPyTorchLikelihood|GPyTorchMean|GPyTorchModel)$"), + (r"py:class", r"^(pd\.DataFrame|pl\.Expr)$"), + (r"py:class", r"^(TypeAliasForwardRef|P)$"), + (r"py:class", r"^\"(pandas|polars)\"\}?$"), + ##### BayBE-specific suppressions ##### + # Inherited classes we cannot control (r"py:.*", r".*DTypeFloatONNX.*"), - # Ignore the functions that we manually delete from in child classes + # Serialization functions manually deleted from child classes (r"py:.*", r".*from_dict.*"), (r"py:.*", r".*from_json.*"), (r"py:.*", r".*to_dict.*"), (r"py:.*", r".*to_json.*"), (r"py:.*", r".*_T.*"), - # Ignore files for which no __init__ is available at all + # Classes for which no __init__ is available at all (r"py:.*", "baybe.constraints.conditions.Condition.__init__"), (r"py:.*", "baybe.serialization.mixin.SerialMixin.__init__"), (r"DeprecationWarning:", ""), - # Ignore the generics/aliases + # Generics/aliases (r"py:class", "baybe.utils.basic._C"), (r"py:class", "baybe.utils.basic._T"), (r"py:class", "baybe.utils.basic._U"), (r"py:class", "baybe.surrogates.composite._SurrogateGetter"), (r"ref:obj", "baybe.surrogates.base.ModelContext"), - # Ignore custom class properties + # Custom class properties (r"py:obj", "baybe.settings._AdoptedRandomSeed.*"), (r"py:obj", "baybe.acquisition.acqfs.*.supports_batching"), (r"py:obj", "baybe.acquisition.acqfs.*.supports_pending_experiments"), @@ -208,8 +239,15 @@ ] -# Ignore the warnings that are given by autosectionlabel -suppress_warnings = ["autosectionlabel.*"] +# Ignore certain warning categories +suppress_warnings = [ + "autosectionlabel.*", + # Forward reference and guarded import warnings from sphinx-autodoc-typehints. + # These are unavoidable since heavy deps (torch, botorch, gpytorch) are lazy-loaded + # and only available in TYPE_CHECKING blocks at runtime. + "sphinx_autodoc_typehints.forward_reference", + "sphinx_autodoc_typehints.guarded_import", +] # -- Options for HTML output ------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output diff --git a/docs/index.md b/docs/index.md index 2e94cc9a3b..f43b044add 100644 --- a/docs/index.md +++ b/docs/index.md @@ -23,16 +23,6 @@ FAQ :relative-docs: docs/ ``` -```{eval-rst} -.. autosummary:: - :toctree: _autosummary - :template: custom-module-template.rst - :recursive: - :hidden: - - baybe -``` - ```{toctree} :maxdepth: 2 :titlesonly: diff --git a/docs/scripts/build_documentation.py b/docs/scripts/build_documentation.py index 756797be50..013b5ad191 100644 --- a/docs/scripts/build_documentation.py +++ b/docs/scripts/build_documentation.py @@ -6,7 +6,6 @@ from subprocess import check_call, run from build_examples import build_examples -from check_links import check_links from utils import adjust_pictures parser = argparse.ArgumentParser() @@ -16,16 +15,10 @@ help="Re-run the examples.", action="store_true", ) -parser.add_argument( - "-l", - "--no_linkcheck", - help="Do not check the links.", - action="store_true", -) parser.add_argument( "-r", "--full-rebuild", - help="Perform a full rebuild, independent of `-e` and `-l` flags.", + help="Perform a full rebuild, independent of `-e` flag.", action="store_true", ) parser.add_argument( @@ -44,7 +37,6 @@ # Parse input arguments args = parser.parse_args() RUN_EXAMPLES = args.run_examples -LINKCHECK = not args.no_linkcheck FULL_REBUILD = args.full_rebuild INCLUDE_WARNINGS = args.include_warnings FORCE = args.force @@ -52,16 +44,15 @@ def build_documentation( run_examples: bool = False, - verify_links: bool = False, full_rebuild: bool = False, force: bool = False, ) -> None: """Build the documentation. A full build of the documentation consists of converting the examples into jupyter - notebooks, executing them, transforming them into markdown files, as well as - checking all links and performing the actual ``sphinx-build``. Such a full build can - be triggered using the ``full_rebuild`` flag. + notebooks, executing them, transforming them into markdown files, and performing + the actual ``sphinx-build``. Such a full build can be triggered using the + ``full_rebuild`` flag. If this flag is not set, this function tries to re-use as much of potentially existing structures like already built examples as possible. This behavior can be changed by using the other flags. @@ -70,18 +61,14 @@ def build_documentation( run_examples: Fully recalculate the examples. If this is ``False`` and no folder containing an already built set of examples is found, dummy files replicating the structure of the examples are created. - verify_links: Check both internal and external links. full_rebuild: Perform a full rebuild of the documentation, including a - recalculation of the examples and checking the links. Note that this option - ignores the choices for ``run_examples`` and ``check_links`` if set to - ``True. + recalculation of the examples. force: Force-build the steps, ignoring any errors or warnings. """ examples_directory = pathlib.Path("docs/examples") examples_exist = examples_directory.is_dir() rerun_examples = run_examples or full_rebuild - perform_linkcheck = verify_links or full_rebuild if rerun_examples: build_examples( @@ -98,9 +85,6 @@ def build_documentation( remove_dir=examples_exist, ) - if perform_linkcheck: - check_links() - # Directory where the documentation is build. build_dir = pathlib.Path("docs/build") @@ -128,11 +112,8 @@ def build_documentation( if not INCLUDE_WARNINGS: os.environ["PYTHONWARNINGS"] = "ignore" - print(f"{LINKCHECK=}") - build_documentation( run_examples=RUN_EXAMPLES, - verify_links=LINKCHECK, full_rebuild=FULL_REBUILD, force=FORCE, ) diff --git a/docs/scripts/check_links.py b/docs/scripts/check_links.py index a2e3b415ac..35114948ce 100644 --- a/docs/scripts/check_links.py +++ b/docs/scripts/check_links.py @@ -1,10 +1,10 @@ -"""Utility for checking the links of the documentation.""" +"""Utility for checking the external links of the documentation.""" from subprocess import check_call def check_links() -> None: - """Check whether the links of the documentation are valid.""" + """Check whether the external links of the documentation are valid.""" link_call = [ "sphinx-build", "-b", @@ -14,3 +14,7 @@ def check_links() -> None: ] check_call(link_call) + + +if __name__ == "__main__": + check_links() diff --git a/docs/templates/custom-module-template.rst b/docs/templates/custom-module-template.rst index 329973b4b4..becaa9a8ec 100644 --- a/docs/templates/custom-module-template.rst +++ b/docs/templates/custom-module-template.rst @@ -61,7 +61,7 @@ :template: custom-module-template.rst :recursive: {% for item in modules %} -{% if not item in ("baybe.objectives.deprecation", "baybe.recommenders.pure.bayesian.sequential_greedy") %} +{% if not item in ("kmedoids",) %} {{ item }} {%- endif %} {%- endfor %} diff --git a/tox.ini b/tox.ini index 89a820cb60..8dca87833f 100644 --- a/tox.ini +++ b/tox.ini @@ -102,10 +102,19 @@ commands = uv run --locked --extra docs docs/scripts/build_documentation.py {posargs} [testenv:docs-quickbuild] -description = Force-build documentation, ignoring links and examples +description = Force-build documentation, ignoring examples skip_install = True setenv = SMOKE_TEST = true commands = python --version - uv run --locked --extra docs docs/scripts/build_documentation.py -f -l + uv run --locked --extra docs docs/scripts/build_documentation.py -f + +[testenv:linkcheck] +description = Check external links in the documentation +skip_install = True +setenv = + SMOKE_TEST = true +commands = + python --version + uv run --locked --extra docs docs/scripts/check_links.py