diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 49d4c20..5a6dd57 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -95,24 +95,7 @@ jobs: - name: Install Python dependencies run: | python -m pip install --upgrade pip - # TODO add autodoc test - - - package: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - name: Set up Python ${{ env.X_PYTHON_VERSION }} - uses: actions/setup-python@v2 - with: - python-version: ${{ env.X_PYTHON_VERSION }} - - name: Install dependencies + pip install -r docs/docs-requirements.txt + - name: Check documentation for errors run: | - python -m pip install --upgrade pip - pip install -q build - - name: Create source and wheel dist - run: | - ls - cd ./sdk - python -m build + SPHINXOPTS="-a -E -n -W --keep-going" make -C docs html diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000..95a49c1 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,16 @@ +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +version: 2 + +build: + os: ubuntu-lts-latest + tools: + python: "3.9" + +sphinx: + configuration: docs/source/conf.py + +python: + install: + - requirements: docs/docs-requirements.txt diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..d0c3cbf --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/docs-requirements.txt b/docs/docs-requirements.txt new file mode 100644 index 0000000..26245de --- /dev/null +++ b/docs/docs-requirements.txt @@ -0,0 +1,23 @@ +# This requirement list is only used by ReadTheDocs to compile the documentation on their side. +# For now the pyproject.toml is unfortunately not an option to specify required dependencies as well as self uploading +# compiled documentations. Until this changes, this file is necessary and should be kept up to date. + +################################################ +# Basic requirements of basyx-python-framework # +################################################ +# These are technically redundant, as they are already kept in the pyproject.toml. +# Please updated these entries here when updating the projects dependencies. + +aas-core3.0~=1.0.4 + + +################################################# +# Additional requirements for building the docs # +################################################# + +sphinx~=7.1.2 +sphinx-rtd-theme~=2.0 +sphinx-argparse~=0.4.0 +sphinx_autodoc_typehints~=2.0.0 +toml~=0.10.2 +urllib3>=1.26,<2.0 diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..dc1312a --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.https://www.sphinx-doc.org/ + exit /b 1 +) + +if "%1" == "" goto help + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/docs/source/_static/_static/custom.css b/docs/source/_static/_static/custom.css new file mode 100644 index 0000000..0b7b0ba --- /dev/null +++ b/docs/source/_static/_static/custom.css @@ -0,0 +1,7 @@ +/* Fix white-space wrapping in tables. + * See https://github.com/readthedocs/sphinx_rtd_theme/issues/1505 + * This is included via html_static_path and html_style in conf.py + */ +.wy-table-responsive table td { + white-space: normal; +} diff --git a/docs/source/client/index.rst b/docs/source/client/index.rst new file mode 100644 index 0000000..ba358f8 --- /dev/null +++ b/docs/source/client/index.rst @@ -0,0 +1,5 @@ +.. basyx-python-framework documentation sub file, regarding the client module + +client +====== +.. FUTURE: automodule:: sdk.basyx. \ No newline at end of file diff --git a/docs/source/conf.py b/docs/source/conf.py new file mode 100644 index 0000000..54448dd --- /dev/null +++ b/docs/source/conf.py @@ -0,0 +1,100 @@ +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +import os +import sys +import datetime +import toml + +# Load pyproject.toml +with open("../../sdk/pyproject.toml", "r") as f: + pyproject_data = toml.load(f) + +# Extract the version +release = pyproject_data["project"]["version"] + +# Add the root directory of the project to sys.path +sys.path.insert(0, os.path.abspath('../..')) + +# -- Project information ----------------------------------------------------- + +project = 'Eclipse BaSyx Python Framework' +project_copyright = str(datetime.datetime.now().year) + ', the Eclipse BaSyx Authors' +author = 'The Eclipse BaSyx Authors' + +# The full version, including alpha/beta/rc tags +release = "none" + + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. + +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.coverage', + 'sphinx.ext.intersphinx', + 'sphinx_rtd_theme', + 'sphinxarg.ext', + 'sphinx_autodoc_typehints' # Allow TypeVars to be compiled properly. +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = [] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = [] + +# Don't prepend the name of the current module to all classes. +add_module_names = False + +# Include all public documented and undocumented members by default. +autodoc_default_options = { + 'members': True, + 'undoc-members': True +} + +# Mapping for correctly linking other module documentations. +intersphinx_mapping = { + 'python': ('https://docs.python.org/3', None), + 'aas_core3': ('https://aas-core30-python.readthedocs.io/en/latest/', None) +} + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'sphinx_rtd_theme' + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Fix white-space wrapping in tables. css files specified here will be applied on top of the selected theme. +# See https://github.com/readthedocs/sphinx_rtd_theme/issues/1505 +# Once fixed, this can be removed and '_static' can be removed from html_static_path. +html_css_files = ["custom.css"] + +# Configuration of the 'Edit on GitHub' button at the top right. +html_context = { + 'display_github': True, + 'github_user': 'eclipse-basyx', + 'github_repo': 'basyx-python-framework', + 'github_version': 'docs', + 'conf_py_path': '/docs/source/' +} diff --git a/docs/source/index.rst b/docs/source/index.rst new file mode 100644 index 0000000..9bffa25 --- /dev/null +++ b/docs/source/index.rst @@ -0,0 +1,21 @@ +.. basyx-python-framework documentation master file, created by + sphinx-quickstart on Fri Aug 16 23:03:59 2024 + +Welcome to the Eclipse-BaSyx Python Framework's documentation! +============================================================== + +.. toctree:: + :numbered: + :maxdepth: 2 + :caption: Contents: + + sdk/index + client/index + server/index + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` \ No newline at end of file diff --git a/docs/source/sdk/index.rst b/docs/source/sdk/index.rst new file mode 100644 index 0000000..d80ad6f --- /dev/null +++ b/docs/source/sdk/index.rst @@ -0,0 +1,6 @@ +.. basyx-python-framework documentation sub file, regarding the sdk module + +sdk +=== + +.. automodule:: sdk.basyx.object_store \ No newline at end of file diff --git a/docs/source/server/index.rst b/docs/source/server/index.rst new file mode 100644 index 0000000..5814390 --- /dev/null +++ b/docs/source/server/index.rst @@ -0,0 +1,5 @@ +.. basyx-python-framework documentation sub file, regarding the server module + +server +====== +.. FUTURE: automodule:: sdk.basyx. \ No newline at end of file diff --git a/sdk/basyx/object_store.py b/sdk/basyx/object_store.py index 73bc68d..ea64c87 100644 --- a/sdk/basyx/object_store.py +++ b/sdk/basyx/object_store.py @@ -6,7 +6,8 @@ # SPDX-License-Identifier: MIT """ This module implements Registries for the AAS, in order to enable resolving global -`Identifiers`; and mapping `Identifiers` to `Identifiable` objects. +:any:`Identifier ` and mapping +:any:`Identifiers ` to :class:`~aas_core3.types.Identifiable` objects. """ import abc @@ -14,13 +15,16 @@ from aas_core3.types import Identifiable, Referable, Class +# We define types for :class:`aas_core3.types.Identifier` and :class:`aas_core3.types.Referable` for easier referencing. _IdentifiableType = TypeVar('_IdentifiableType', bound=Identifiable) +_ReferableType = TypeVar('_ReferableType', bound=Referable) class AbstractObjectProvider(metaclass=abc.ABCMeta): """ - Abstract baseclass for all objects, that allow to retrieve `Identifiable` objects - (resp. proxy objects for remote `Identifiable` objects) by their `Identifier`. + Abstract baseclass for all objects, that allows to retrieve :class:`~aas_core3.types.Identifiable` objects + (resp. proxy objects for remote :class:`~aas_core3.types.Identifiable` objects) by their + :any:`Identifier ` This includes local object stores, database clients and AAS API clients. """ @@ -28,25 +32,30 @@ class AbstractObjectProvider(metaclass=abc.ABCMeta): @abc.abstractmethod def get_identifiable(self, identifier: str) -> Identifiable: """ - Find an `Identifiable` by its `Identifier` + Find an :class:`~aas_core3.types.Identifiable` by its + :any:`Identifier ` This may include looking up the object's endpoint in a registry and fetching it from an HTTP server or a database. - :param identifier: `Identifier` of the object to return - :return: The `Identifiable` object (or a proxy object for a remote `Identifiable` object) - :raises KeyError: If no such `Identifiable` can be found + :param identifier: :any:`Identifier ` of the object to return + :return: The :class:`~aas_core3.types.Identifiable` object (or a proxy object for a remote + :class:`~aas_core3.types.Identifiable` object) + :raises KeyError: If no such :class:`~aas_core3.types.Identifiable` can be found """ pass def get(self, identifier: str, default: Optional[Identifiable] = None) -> Optional[Identifiable]: """ - Find an object in this set by its `Identifier`, with fallback parameter + Find an object in this set by its :any:`Identifier `, + with fallback parameter - :param identifier: `Identifier` of the object to return - :param default: An object to be returned, if no object with the given `Identifier` is found - :return: The `Identifiable` object with the given `Identifier` in the provider. Otherwise, the ``default`` - object or None, if none is given. + :param identifier: :any:`Identifier ` of the object to return + :param default: An object to be returned, if no object with the given + :any:`Identifier ` is found + :return: The :class:`~aas_core3.types.Identifiable` object with the given + :any:`Identifier ` in the provider. + Otherwise, the ``default`` object or None, if none is given. """ try: return self.get_identifiable(identifier) @@ -57,11 +66,11 @@ def get(self, identifier: str, default: Optional[Identifiable] = None) -> Option class AbstractObjectStore(AbstractObjectProvider, MutableSet[_IdentifiableType], Generic[_IdentifiableType], metaclass=abc.ABCMeta): """ - Abstract baseclass of for container-like objects for storage of `Identifiable` objects. + Abstract baseclass of for container-like objects for storage of :class:`~aas_core3.types.Identifiable` objects. ObjectStores are special ObjectProvides that – in addition to retrieving objects by - `Identifier` – allow to add and delete objects (i.e. behave like a Python set). - This includes local object stores (like :class:`~.DictObjectStore`) and database `Backends`. + :any:`Identifier ` – allow to add and delete objects (i.e. behave like a + Python set). The AbstractObjectStore inherits from the :class:`~collections.abc.MutableSet` abstract collections class and therefore implements all the functions of this class. @@ -78,8 +87,8 @@ def update(self, other: Iterable[_IdentifiableType]) -> None: class ObjectStore(AbstractObjectStore[_IdentifiableType], Generic[_IdentifiableType]): """ - A local in-memory object store for `Identifiable` objects, backed by a dict, mapping - `Identifier` → `Identifiable` + A local in-memory object store for :class:`~aas_core3.types.Identifiable` objects, backed by a dict, mapping + :any:`Identifier ` → :class:`~aas_core3.types.Identifiable` """ def __init__(self, objects: Iterable[_IdentifiableType] = ()) -> None: @@ -197,10 +206,10 @@ def __iter__(self) -> Iterator[_IdentifiableType]: class ObjectProviderMultiplexer(AbstractObjectProvider): """ - A multiplexer for Providers of `Identifiable` objects. + A multiplexer for Providers of :class:`~aas_core3.types.Identifiable` objects. - This class combines multiple registries of `Identifiable` objects into a single one - to allow retrieving `Identifiable` objects from different sources. + This class combines multiple registries of :class:`~aas_core3.types.Identifiable` objects into a single one + to allow retrieving :class:`~aas_core3.types.Identifiable` objects from different sources. It implements the :class:`~.AbstractObjectProvider` interface to be used as registry itself. :ivar providers: A list of :class:`AbstractObjectProviders <.AbstractObjectProvider>` to query when looking up an