Skip to content

Commit

Permalink
Add Python docs (#35)
Browse files Browse the repository at this point in the history
* Add first version of python docs (local build)

* Try to reuse steps, test build docs

* Can't reuse job contant

* Add dummy step

* Make CI look organized

* Make mypy ignore docs

* Try publishing Python docs

* Can't deploy docs: interferes with julia docs

* Try again

* try something

* Change dir names

* Typo

* meh

* Ignore py docs build in main gitignore

* Get around gitignore

* Try this

* Push main docs to /python

* Correct docs CI url
  • Loading branch information
BSchilperoort authored Nov 19, 2024
1 parent 30a326e commit f8d247f
Show file tree
Hide file tree
Showing 12 changed files with 411 additions and 6 deletions.
32 changes: 31 additions & 1 deletion .github/workflows/python.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ on:
- "python/pyproject.toml"
- .github/workflows/python.yml
types: [opened, synchronize, reopened]
workflow_call:

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
Expand All @@ -33,7 +34,6 @@ jobs:
lint-format:
name: formatting and type checking 🔍️
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4
Expand Down Expand Up @@ -119,3 +119,33 @@ jobs:
run: pip install build
- name: Build package
run: python3 -m build
docs:
name: build docs 📖
runs-on: ubuntu-latest
env:
BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
permissions:
contents: write
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: "3.13"
cache: "pip"
- name: Install docs requirements
run: pip install -r docs/requirements.txt
- name: Build docs
run: make -C docs/ html
- name: Move files
run: mv docs/_build/html pydocs
- name: Deploy docs
uses: peaceiris/actions-gh-pages@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: python/pydocs
destination_dir:
${{ env.BRANCH_NAME == 'main' && 'python/' || format('python/branch/{0}', env.BRANCH_NAME) }}
- name: Display CI docs URL
run: echo https://www.ewatercycle.org/remotebmi/${{ env.BRANCH_NAME == 'main' && 'python/' || format('python/branch/{0}', env.BRANCH_NAME) }}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ __pycache__
/venv
python/.venv/
python/heat.toml
python/docs/_build
julia/example/Project.toml
julia/example/heat.toml
openapi-generator-cli.jar
Expand Down
5 changes: 5 additions & 0 deletions python/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,8 @@ and the client can connect to it with the following code.
> client.get_component_name()
leakybucket
```

## Documentation

In a Python environment, go to the docs directory, do `pip install -r requirements.txt`
and then `make html`.
20 changes: 20 additions & 0 deletions python/docs/Makefile
Original file line number Diff line number Diff line change
@@ -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 = .
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)
197 changes: 197 additions & 0 deletions python/docs/conf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
# remotebmi documentation build configuration file
#
# This file is execfile()d with the current directory set to its
# containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
from pathlib import Path

import tomli

# -- 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.napoleon",
"myst_nb",
"sphinx.ext.intersphinx",
"sphinx_copybutton",
"autoapi.extension",
]

# Add any paths that contain templates here, relative to this directory.
templates_path = ["_templates"]

# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
#
source_suffix = [".rst"]

# The master toctree document.
master_doc = "index"

# General information about the project.
project = "remotebmi"
copyright = "2024, Netherlands eScience Center" # noqa: A001
author = "Stefan Verhoeven"


# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
# The full version, including alpha/beta/rc tags.
def get_version():
"""Get the version from pyproject.toml."""
# Not using importlib as we do not want to install
# package during readthdocs build, to keep build quick
project = Path(__file__).parent / "../pyproject.toml"
with project.open("rb") as f:
pyproject = tomli.load(f)
return pyproject["project"]["version"]


version = get_version()
release = version

# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = "en"

# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This patterns also effect to html_static_path and html_extra_path
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", "**.ipynb_checkpoints"]

# The name of the Pygments (syntax highlighting) style to use.
pygments_style = "sphinx"

# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = False

# -- 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"
# TODO move logo to root of docs folder, also some notebooks will need to be updated
html_logo = "examples/logo.png"

# 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"]

# Custom sidebar templates, must be a dictionary that maps document names
# to template names.
#
# This is required for the alabaster theme
# refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars
html_sidebars = {
"**": [
"relations.html", # needs 'show_related': True theme option to display
"searchbox.html",
]
}


# -- Options for HTMLHelp output ------------------------------------------

# Output file base name for HTML help builder.
htmlhelp_basename = "remotebmi_doc"


# -- Options for LaTeX output ---------------------------------------------

# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
(
master_doc,
"remotebmi.tex",
"remotebmi Documentation",
"Stefan Verhoeven",
"manual",
),
]


# -- Options for manual page output ---------------------------------------

# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [(master_doc, "remotebmi", "remotebmi Documentation", [author], 1)]


# -- Options for Texinfo output -------------------------------------------

# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
(
master_doc,
"remotebmi",
"remotebmi Documentation",
author,
"remotebmi",
"Python utilities to gather input files for running a hydrology model",
"Miscellaneous",
),
]

# Turned off nitpicky as not all warnings are fixable
# To find reference target not found warnings turn this on
nitpicky = False

autodoc_mock_imports = [
"cf_units", # Causes many errors in docs build.
]
# Prevent alphabetic sorting of (@data)class attributes/methods
autodoc_member_order = "bysource"

autoapi_dirs = ["../src"]
autoapi_python_class_content = "both"
autoapi_options = ["members", "undoc-members", "imported-members", "show-inheritance"]
# If you get build errors pointing to audoapi/**/*.rst files,
# to debug set `autoapi_keep_files` to True
autoapi_keep_files = False

myst_heading_anchors = 3
nb_execution_mode = "off"
nb_output_stderr = "remove"

intersphinx_mapping = {
"cf_units": ("https://cf-units.readthedocs.io/en/latest/", None),
"esmvalcore": ("https://docs.esmvaltool.org/projects/ESMValCore/en/latest/", None),
"esmvaltool": ("https://docs.esmvaltool.org/en/latest/", None),
"grpc4bmi": ("https://grpc4bmi.readthedocs.io/en/latest/", None),
"iris": ("https://scitools-iris.readthedocs.io/en/latest/", None),
"lime": ("https://lime-ml.readthedocs.io/en/latest/", None),
"basic_modeling_interface": ("https://bmi.readthedocs.io/en/latest/", None),
"matplotlib": ("https://matplotlib.org/stable/", None),
"numpy": ("https://numpy.org/doc/stable/", None),
"pandas": ("https://pandas.pydata.org/pandas-docs/dev", None),
"python": ("https://docs.python.org/3/", None),
"scipy": ("https://docs.scipy.org/doc/scipy/", None),
"seaborn": ("https://seaborn.pydata.org/", None),
"sklearn": ("https://scikit-learn.org/stable", None),
"xarray": ("https://docs.xarray.dev/en/stable/", None),
"pydantic": ("https://docs.pydantic.dev/latest/", None),
}

# Dont copy line number, >>> and ... from code blocks
copybutton_exclude = ".linenos, .gp"
4 changes: 4 additions & 0 deletions python/docs/examples/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
ewatercycle.yaml
# Exclude intermediate Marrmot files
BMI_testcase_m01_BuffaloRiver_TN_USA.mat
marrmott_*_*
Binary file added python/docs/examples/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
108 changes: 108 additions & 0 deletions python/docs/index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
################
remotebmi Python
################

Welcome to ``remotebmi``'s python documentation!

The `Basic Model Interface (BMI) <https://bmi.readthedocs.io/en/stable/>`_
is a standard interface for models.
The interface is available in different languages and a
`language agnostic version in SIDL. <https://github.com/csdms/bmi/blob/stable/bmi.sidl>`_

To have a consumer of the model and the provider of the model seperated you
can use `grpc4bmi <https://grpc4bmi.readthedocs.io/>`_, but this only works on
languages that have a grpc implementation.
This repo replaced the gRPC protocol with an REST API.
The `REST API specification <https://github.com/eWaterCycle/remotebmi/blob/main/openapi.yaml>`_
is in the `OpenAPI <https://swagger.io/specification/>`_ format.

Installation
============

.. code-block:: shell
pip install remotebmi
How to use
==========

There are two parts to remotebmi: the server (which hosts the model) and the
consumer (who wants to interact with the model).

The server is available for different languages (Julia, R, and Python). Any client
can connect with any server.

Python consumer
---------------

.. code-block:: python
from remotebmi.client.client import RemoteBmiClient
model = RemoteBmiClient('http://localhost:50051')
# Now you can use the BMI methods on model
# for example
model.initialize('config.file')
model.update()
model.get_value('var_name')
A client can also start a `Apptainer <https://apptainer.org/>`_ container
containing the model and the server:

.. code-block:: python
from remotebmi.client.apptainer import BmiClientApptainer
model = BmiClientApptainer('my_model.sif', work_dir='/tmp')
The client picks a random port and expects the container to run the BMI web server
on that port. The port is passed to the container using the ``BMI_PORT`` environment
variable.
A client can also start a `Docker <https://docs.docker.com/engine/>`_ container
containing the model and the server.

.. code-block:: python
from remotebmi.client.docker import BmiClientDocker
model = BmiClientDocker('ewatercycle/wflowjl:0.7.3', work_dir='/tmp')
The BMI web server inside the Docker container should be running on port 50051.
If the port is different, you can pass the port as the ``image_port`` argument
to the ``BmiClientDocker`` constructor.

Python server
-------------

Given you have a model class called ``MyModel`` in a package ``mypackage``
then the web service can be started with the following command.

.. code-block:: shell
BMI_MODULE=mypackage BMI_CLASS=MyModel run-bmi-server
For example `leakybucket <https://github.com/eWaterCycle/leakybucket-bmi>`_:

.. code-block:: shell
pip install leakybucket
BMI_MODULE=leakybucket.leakybucket_bmi BMI_CLASS=LeakyBucketBmi run-bmi-server
and the Python client can connect to it with the following code.

.. code-block:: python
>>> from remotebmi.client.client import RemoteBmiClient
>>> client = RemoteBmiClient('http://localhost:50051')
>>> client.get_component_name()
leakybucket
.. _Docker: https://docs.docker.com/engine/

.. toctree::
:maxdepth: 3
:hidden:

self
autoapi/index
Loading

0 comments on commit f8d247f

Please sign in to comment.