From b8b8b10cf2a08917f36810a517beb493c94e8b98 Mon Sep 17 00:00:00 2001 From: Joshua Fraustro <36318163+jwfraustro@users.noreply.github.com> Date: Tue, 2 Jan 2024 10:26:00 -0500 Subject: [PATCH] ASB-24561: Add Documentation (#13) * add pre-commit * initial docs pages add uws errorSummary snippet / possibly reformat for autodoc added uws snippets and examples add api docs / quickstart use google docstring style minor fixups add readthedocs yaml * add a line in readme for docs * found a dangling quote * wording * use enum in example * final newlines --- .pre-commit-config.yaml | 15 ++ .readthedocs.yml | 15 ++ README.md | 2 + docs/Makefile | 20 ++ docs/make.bat | 35 ++++ docs/source/conf.py | 67 +++++++ docs/source/index.rst | 70 +++++++ docs/source/pages/api/index.rst | 13 ++ docs/source/pages/api/uws_api.rst | 17 ++ docs/source/pages/api/voresource_api.rst | 9 + docs/source/pages/api/vosi_api.rst | 12 ++ docs/source/pages/installation.rst | 29 +++ docs/source/pages/protocols/index.rst | 12 ++ docs/source/pages/protocols/uws.rst | 228 ++++++++++++++++++++++ docs/source/pages/protocols/vosi.rst | 30 +++ docs/source/pages/quickstart.rst | 203 ++++++++++++++++++++ examples/snippets/uws/uws.py | 230 +++++++++++++++++++++++ examples/snippets/vosi/availability.py | 27 +++ pyproject.toml | 7 +- vo_models/uws/models.py | 185 ++++++++++-------- vo_models/uws/types.py | 60 ++++-- vo_models/voresource/types.py | 4 + vo_models/vosi/availability/models.py | 19 +- 23 files changed, 1201 insertions(+), 108 deletions(-) create mode 100644 .pre-commit-config.yaml create mode 100644 .readthedocs.yml create mode 100644 docs/Makefile create mode 100644 docs/make.bat create mode 100644 docs/source/conf.py create mode 100644 docs/source/index.rst create mode 100644 docs/source/pages/api/index.rst create mode 100644 docs/source/pages/api/uws_api.rst create mode 100644 docs/source/pages/api/voresource_api.rst create mode 100644 docs/source/pages/api/vosi_api.rst create mode 100644 docs/source/pages/installation.rst create mode 100644 docs/source/pages/protocols/index.rst create mode 100644 docs/source/pages/protocols/uws.rst create mode 100644 docs/source/pages/protocols/vosi.rst create mode 100644 docs/source/pages/quickstart.rst create mode 100644 examples/snippets/uws/uws.py create mode 100644 examples/snippets/vosi/availability.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..4495c85 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,15 @@ +repos: + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.1.8 + hooks: + - id: ruff + args: [., --no-cache, --select, I, --fix] + - repo: local + hooks: + - id: pylint + name: pylint + entry: pylint + language: system + types: [python] + stages: [push] + files: ^vo_models/.+\.py$ diff --git a/.readthedocs.yml b/.readthedocs.yml new file mode 100644 index 0000000..65779c5 --- /dev/null +++ b/.readthedocs.yml @@ -0,0 +1,15 @@ +version: 2 + +build: + os: ubuntu-22.04 + tools: + python: "3.11" + +python: + install: + - method: pip + path: . + extra_requirements: + - docs + +formats: [] diff --git a/README.md b/README.md index 197e447..1743e39 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,8 @@ The following IVOA protocols are currently supported: - **VOSI (IVOA Support Interfaces) version 1.1** - VOSI Availability +You can read more about using these models in our documentation: https://vo-models.readthedocs.io/ + ## Installation diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..92dd33a --- /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/make.bat b/docs/make.bat new file mode 100644 index 0000000..b344c6a --- /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/conf.py b/docs/source/conf.py new file mode 100644 index 0000000..0e5acb8 --- /dev/null +++ b/docs/source/conf.py @@ -0,0 +1,67 @@ +# Configuration file for the Sphinx documentation builder. +# +# For the full list of built-in configuration values, see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information + +import sys +from pathlib import Path + +import toml + +ROOT_PATH = Path(__file__).parent.parent.parent +CONF_PATH = ROOT_PATH / "pyproject.toml" +sys.path.insert(0, str(ROOT_PATH.absolute())) + +PYPROJECT = toml.load(CONF_PATH)["project"] + +project = PYPROJECT["name"] +release = PYPROJECT["version"] +author = f"{PYPROJECT['authors'][0]['name']} <{PYPROJECT['authors'][0]['email']}>" +copyright = "2023, Joshua Fraustro" + +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration + +extensions = [ + "sphinx_design", + "sphinx_copybutton", + "sphinx.ext.intersphinx", + "sphinx.ext.autosectionlabel", + "sphinx.ext.viewcode", + "sphinx.ext.autodoc", + "sphinx.ext.napoleon", +] + +autodoc_default_options = { + "no-inherited-members": None, + "exclude-members": "model_config, model_fields", +} + +autodoc_typehints = "description" +autodoc_typehints_format = "short" +autodoc_member_order = "bysource" + +autoclass_content = "class" + +intersphinx_mapping = { + "python": ("https://docs.python.org/3", None), + "lxml": ("https://lxml.de/apidoc/", None), +} + +autosectionlabel_prefix_document = True + +html_theme_options = {} +html_title = PYPROJECT["name"] + +templates_path = ["_templates"] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] + + +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output + +html_theme = "furo" +html_static_path = ["_static"] diff --git a/docs/source/index.rst b/docs/source/index.rst new file mode 100644 index 0000000..57f7516 --- /dev/null +++ b/docs/source/index.rst @@ -0,0 +1,70 @@ + +vo-models +========= + +``vo-models`` an open-source project to provide Python models for `IVOA `_ service protocols. + +The project is designed to be used by IVOA members, service implementors, and developers to help facilitate the development of IVOA-compliant services and clients. + +Features +^^^^^^^^ + +- **Pydantic-xml Models:** The project includes Python models for IVOA protocols, using `pydantic-xml `_. Based on `Pydantic `_, these models describe transactions for an IVOA protocol, such as UWS, and feature automatic validation, parsing and serialization of XML data for use with Python clients and web frameworks. + +Using the models in your project for validation and serialization to XML is as simple as: + +.. code-block:: python + + from vo_models.uws import ShortJobDescription + from vo_models.uws.types import ExecutionPhase + + job = ShortJobDescription( + phase=ExecutionPhase.PENDING, # or use "PENDING" + run_id = "run_1", + job_id = "job_1", + creation_time = "2021-01-01T00:00:00Z", + ) + job.to_xml() + +.. code-block:: xml + + + PENDING + run_1 + + +For more information on getting started with ``vo-models``, see :ref:`quickstart`. + +User Guide +^^^^^^^^^^ +.. toctree:: + :maxdepth: 1 + + pages/quickstart + pages/installation + +Supported Protocols +^^^^^^^^^^^^^^^^^^^ +.. toctree:: + :maxdepth: 2 + + pages/protocols/index + +API Documentation +^^^^^^^^^^^^^^^^^ +.. toctree:: + :maxdepth: 1 + + pages/api/index + +Links +^^^^^ + +- `Source Code `_ +- `Pydantic-xml `_ +- `Pydantic `_ + +Indices and tables +^^^^^^^^^^^^^^^^^^ +* :ref:`genindex` +* :ref:`modindex` \ No newline at end of file diff --git a/docs/source/pages/api/index.rst b/docs/source/pages/api/index.rst new file mode 100644 index 0000000..e1a782a --- /dev/null +++ b/docs/source/pages/api/index.rst @@ -0,0 +1,13 @@ +.. _api: + +Developer Documentation +~~~~~~~~~~~~~~~~~~~~~~~ + +This section contains documentation on the package's modules and classes. + +.. toctree:: + :maxdepth: 3 + + uws_api + vosi_api + voresource_api \ No newline at end of file diff --git a/docs/source/pages/api/uws_api.rst b/docs/source/pages/api/uws_api.rst new file mode 100644 index 0000000..b97aa05 --- /dev/null +++ b/docs/source/pages/api/uws_api.rst @@ -0,0 +1,17 @@ +.. _uws_api: + +UWS API +------- + +Models +^^^^^^ + +.. automodule:: vo_models.uws.models + :members: + :no-inherited-members: + :exclude-members: model_config, model_fields, Job + +Simple Types +^^^^^^^^^^^^ +.. automodule:: vo_models.uws.types + :members: \ No newline at end of file diff --git a/docs/source/pages/api/voresource_api.rst b/docs/source/pages/api/voresource_api.rst new file mode 100644 index 0000000..400e449 --- /dev/null +++ b/docs/source/pages/api/voresource_api.rst @@ -0,0 +1,9 @@ +.. _voresource_api: + +VOResource API +-------------- + +Simple Types +^^^^^^^^^^^^ +.. automodule:: vo_models.voresource.types + :members: diff --git a/docs/source/pages/api/vosi_api.rst b/docs/source/pages/api/vosi_api.rst new file mode 100644 index 0000000..19c52b5 --- /dev/null +++ b/docs/source/pages/api/vosi_api.rst @@ -0,0 +1,12 @@ +.. _vosi_api: + +VOSI API +-------- + +Availability +^^^^^^^^^^^^ + +.. automodule:: vo_models.vosi.availability.models + :members: + :no-inherited-members: + :exclude-members: model_config, model_fields, \ No newline at end of file diff --git a/docs/source/pages/installation.rst b/docs/source/pages/installation.rst new file mode 100644 index 0000000..303c3e0 --- /dev/null +++ b/docs/source/pages/installation.rst @@ -0,0 +1,29 @@ +.. _installation: + +Installation +============= + +Install Using pip +------------------ + +To install the ``vo-models`` package, you can use pip: + +.. code-block:: bash + + pip install vo-models + + +Install Using Conda +------------------- + +To install the project using Conda, you can use the provided environment file: + +.. code-block:: bash + + git clone https://github.com/spacetelescope/vo-models.git + cd vo-models + conda env create -f environment.yml + conda activate vo-models + pip install -r requirements.txt + pip install . + diff --git a/docs/source/pages/protocols/index.rst b/docs/source/pages/protocols/index.rst new file mode 100644 index 0000000..2cbe31e --- /dev/null +++ b/docs/source/pages/protocols/index.rst @@ -0,0 +1,12 @@ +.. _protocols: + +Supported Protocols +~~~~~~~~~~~~~~~~~~~ + +The following IVOA protocols are currently supported: + +.. toctree:: + :maxdepth: 3 + + uws + vosi \ No newline at end of file diff --git a/docs/source/pages/protocols/uws.rst b/docs/source/pages/protocols/uws.rst new file mode 100644 index 0000000..27c30f8 --- /dev/null +++ b/docs/source/pages/protocols/uws.rst @@ -0,0 +1,228 @@ +.. _uws: + +UWS (Universal Worker Service) +------------------------------ + +UWS is a protocol for describing and executing jobs on remote services. ``vo-models`` currently provides support for the `UWS 1.1 version `_ of the protocol. + +.. note:: + In the provided examples, the UWS namespace has not been included for brevity. As output by `to_xml()`, the namespace is included in the root element of the XML document. + +Models for UWS are provided in the ``vo_models.uws`` package. Supported models and examples of their usage are: + +Models +^^^^^^ + +ErrorSummary +***************** + +Represents an error summary returned by a UWS service as part of a :ref:`pages/protocols/uws:JobSummary` object. + +.. grid:: 2 + :gutter: 2 + + .. grid-item-card:: Model + + .. literalinclude:: ../../../../examples/snippets/uws/uws.py + :language: python + :start-after: error-summary-model-start + :end-before: error-summary-model-end + + .. grid-item-card:: XML Output + + .. literalinclude:: ../../../../examples/snippets/uws/uws.py + :language: xml + :lines: 2- + :start-after: error-summary-xml-start + :end-before: error-summary-xml-end + +Parameter +********* + +Represents a single parameter of a UWS job. + +.. grid:: 2 + :gutter: 2 + + .. grid-item-card:: Model + + .. literalinclude:: ../../../../examples/snippets/uws/uws.py + :language: python + :start-after: parameter-model-start + :end-before: parameter-model-end + + .. grid-item-card:: XML Output + + .. literalinclude:: ../../../../examples/snippets/uws/uws.py + :language: xml + :lines: 2- + :start-after: parameter-xml-start + :end-before: parameter-xml-end + +Parameters +********** + +Represents a collection of :ref:`pages/protocols/uws:parameter` objects. + +.. attention:: This is a generic class that must be subclassed to represent the parameters of the specific service using the UWS protocol. + + See :ref:`pages/protocols/uws:jobsummary` for an example of how this is used in the context of a UWS job summary response. + +For example, for a TAP service, the parameters could be represented as follows: + +.. grid:: 2 + :gutter: 2 + + .. grid-item-card:: Model + + .. literalinclude:: ../../../../examples/snippets/uws/uws.py + :language: python + :start-after: parameters-model-start + :end-before: parameters-model-end + + .. grid-item-card:: XML Output + + .. literalinclude:: ../../../../examples/snippets/uws/uws.py + :language: xml + :lines: 2- + :start-after: parameters-xml-start + :end-before: parameters-xml-end + +ResultReference +***************** + +Represents a single result reference as returned by a UWS service as part of a :ref:`pages/protocols/uws:Results` object. + +.. grid:: 2 + :gutter: 2 + + .. grid-item-card:: Model + + .. literalinclude:: ../../../../examples/snippets/uws/uws.py + :language: python + :start-after: result-reference-model-start + :end-before: result-reference-model-end + + .. grid-item-card:: XML Output + + .. literalinclude:: ../../../../examples/snippets/uws/uws.py + :language: xml + :lines: 2- + :start-after: result-reference-xml-start + :end-before: result-reference-xml-end + +Results +******** + +Represents a collection of :ref:`pages/protocols/uws:resultreference` objects. + +.. grid:: 2 + :gutter: 2 + + .. grid-item-card:: Model + + .. literalinclude:: ../../../../examples/snippets/uws/uws.py + :language: python + :start-after: results-model-start + :end-before: results-model-end + + .. grid-item-card:: XML Output + + .. literalinclude:: ../../../../examples/snippets/uws/uws.py + :language: xml + :lines: 2- + :start-after: results-xml-start + :end-before: results-xml-end + +ShortJobDescription +******************* + +Represents a UWS ``jobref`` element, returned when fetching the job list from a UWS service. + +.. note:: + Note that the XML tag ```` differs from the model name ``ShortJobDescription``. + + The Python model uses the name of the complexType defined in the UWS schema, but when serialized uses the tag name defined as part of the ```` definition. + + See :ref:`pages/protocols/uws:Jobs` for an example of how this is used in the context of a UWS job list response. + +.. grid:: 2 + :gutter: 2 + + .. grid-item-card:: Model + + .. literalinclude:: ../../../../examples/snippets/uws/uws.py + :language: python + :start-after: short-job-description-model-start + :end-before: short-job-description-model-end + + .. grid-item-card:: XML Output + + .. literalinclude:: ../../../../examples/snippets/uws/uws.py + :language: xml + :lines: 2- + :start-after: short-job-description-xml-start + :end-before: short-job-description-xml-end + +Jobs +**** + +Represents a collection of :ref:`pages/protocols/uws:shortjobdescription` objects, returned at the UWS job list endpoint. + +.. grid:: 2 + :gutter: 2 + + .. grid-item-card:: Model + + .. literalinclude:: ../../../../examples/snippets/uws/uws.py + :language: python + :start-after: jobs-model-start + :end-before: jobs-model-end + + .. grid-item-card:: XML Output + + .. literalinclude:: ../../../../examples/snippets/uws/uws.py + :language: xml + :lines: 2- + :start-after: jobs-xml-start + :end-before: jobs-xml-end + +JobSummary +********** + +A model for the complete representation of a UWS job summary, returned by fetching the job id from a UWS service. + +.. attention:: + + This is a generic class that expects to be provided a subclass of :ref:`pages/protocols/uws:parameters` to represent the parameters of the specific service using the UWS protocol. + + The example below uses the TAPParameters class, shown in the :ref:`pages/protocols/uws:parameters` example above, to represent the parameters of a TAP service. + +.. note:: + In this example, we have included the XML namespace in the output, to show how the namespace is included in the root element of the XML document. +.. grid:: 2 + :gutter: 2 + + .. grid-item-card:: Model + + .. literalinclude:: ../../../../examples/snippets/uws/uws.py + :language: python + :start-after: job-summary-model-start + :end-before: job-summary-model-end + + .. grid-item-card:: XML Output + + .. literalinclude:: ../../../../examples/snippets/uws/uws.py + :language: xml + :lines: 2- + :start-after: job-summary-xml-start + :end-before: job-summary-xml-end + +Simple Types +^^^^^^^^^^^^ + +The following simple types are provided in the ``vo_models.uws`` package for use in UWS models: + +- :py:class:`vo_models.uws.types.ErrorType` +- :py:class:`vo_models.uws.types.ExecutionPhase` +- :py:class:`vo_models.uws.types.UWSVersion` \ No newline at end of file diff --git a/docs/source/pages/protocols/vosi.rst b/docs/source/pages/protocols/vosi.rst new file mode 100644 index 0000000..0e0d402 --- /dev/null +++ b/docs/source/pages/protocols/vosi.rst @@ -0,0 +1,30 @@ +.. _vosi: + +VOSI (VO Support Interface) +-------------------------------------------- + +``vo-models`` supports the following VOSI v1.0 protocols: + +Availability +***************** + +The Availability model is used to represent the response given by a UWS service to a +``GET /availability`` request. + +.. grid:: 2 + :gutter: 2 + + .. grid-item-card:: Model + + .. literalinclude:: ../../../../examples/snippets/vosi/availability.py + :language: python + :start-after: model-start + :end-before: model-end + + .. grid-item-card:: XML Output + + .. literalinclude:: ../../../../examples/snippets/vosi/availability.py + :language: xml + :lines: 2- + :start-after: xml-start + :end-before: xml-end \ No newline at end of file diff --git a/docs/source/pages/quickstart.rst b/docs/source/pages/quickstart.rst new file mode 100644 index 0000000..4263a71 --- /dev/null +++ b/docs/source/pages/quickstart.rst @@ -0,0 +1,203 @@ +.. _quickstart: + +Quickstart +========== + +Basic Usage +----------- + +Working with ``vo-models`` classes is easy. They can be created and modified like any Pydantic model. + +The following example creates a UWS :ref:`pages/protocols/uws:shortjobdescription` model using keyword arguments and updates the phase: + +.. code-block:: python + + from vo_models.uws import ShortJobDescription + + job = ShortJobDescription( + phase="PENDING", + run_id = "run_1", + job_id = "job_1", + creation_time = "2021-01-01T00:00:00Z", + ) + + job.phase = "COMPLETED" + +Deserializing +------------- + +Models can also be created from JSON data or ``dicts``: + +.. code-block:: python + + from vo_models.uws import ShortJobDescription + import json + + data = """ + { + "phase": "PENDING", + "runId": "run_1", + "jobId": "job_1", + "creationTime": "2021-01-01T00:00:00Z" + } + """ + data = json.loads(data) + job = ShortJobDescription(**data) + +or from XML: + +.. code-block:: python + + from vo_models.uws import ShortJobDescription + + xml = """ + + PENDING + ' + """ + + job = ShortJobDescription.from_xml(xml) + +Serializing +----------- + +Models can be serialized to JSON using ``.model_dump_json()`` like any Pydantic model, or to XML using the ``to_xml()`` method of pydantic-xml: + +.. grid:: 2 + :gutter: 2 + + .. grid-item-card:: Model + + .. literalinclude:: ../../../examples/snippets/uws/uws.py + :language: python + :start-after: short-job-description-model-start + :end-before: short-job-description-model-end + + .. grid-item-card:: Document + + .. tab-set:: + + .. tab-item:: XML + + .. code-block:: xml + + + PENDING + 1234567890anon_user + 2023-12-27T16:35:39.628Z + + + .. tab-item:: JSON + + .. code-block:: python + + short_job_description.model_dump_json() + + .. code-block:: json + + {"phase":"PENDING", + "run_id":"1234567890", + "owner_id":"anon_user", + "creation_time":"2023-12-27T16:35:39.628Z", + "job_id":"job_1", + "type":"simple", + "href":null} + +Optional Elements +----------------- + +Some models may have a number of optional elements. By default, ``pydantic-xml`` will include them in the output XML. To exclude them, you can use the ``skip_empty`` argument: + +Without the ``skip_empty`` argument: + +.. code-block:: python + + from vo_models.uws import JobSummary, Parameters + + job_summary = JobSummary[Parameters]( + job_id = "job_1", + phase = "PENDING" + ) + + job_summary.to_xml() + +.. code-block:: xml + + + job_1 + + + PENDING + + + + + 0 + + + +With the ``skip_empty`` argument: + +.. code-block:: python + + job_summary.to_xml(skip_empty=True) + +.. code-block:: xml + + + job_1 + PENDING + 0 + + +Submodels And Namespaces +------------------------- + +Sub-models can also be serialized to XML, and will correctly inherit their parent's namespace: + +.. code-block:: python + + from vo_models.uws import JobSummary, Parameters, Results + + job_summary = JobSummary[Parameters]( + job_id = "job_1", + owner_id = "anon_user", + phase = "COMPLETED", + creation_time = "2023-12-01T12:00:00.000Z", + start_time = "2023-12-01T12:00:00.000Z", + results = Results( + results=[ + ResultReference(id="result1", href="http://example.com/result1"), + ResultReference(id="result2", href="http://example.com/result2"), + ], + ), + ) + + job_summary.results.to_xml() + +.. code-block:: xml + + + + + ' + +For more information on how to use ``pydantic-xml``, see the `pydantic-xml documentation `_. + +For example usage of ``vo-models`` for each protocol, see :ref:`pages/protocols/index:supported protocols`. \ No newline at end of file diff --git a/examples/snippets/uws/uws.py b/examples/snippets/uws/uws.py new file mode 100644 index 0000000..3853c50 --- /dev/null +++ b/examples/snippets/uws/uws.py @@ -0,0 +1,230 @@ +import datetime + +from vo_models.uws import ( + ErrorSummary, + Jobs, + JobSummary, + Parameter, + Parameters, + ResultReference, + Results, + ShortJobDescription, +) + +# [error-summary-model-start] +error_summary = ErrorSummary( + type="fatal", + has_detail=True, + message="The job failed because of a missing parameter.", +) + +error_summary.to_xml() +# [error-summary-model-end] + +# [error-summary-xml-start] +error_summary_xml = """ + +The job failed because of a missing parameter. + +""" # [error-summary-xml-end] + +# [parameter-model-start] +parameter = Parameter( + id="lang", + value="ADQL", + is_post=False, + by_reference=False, +) + +parameter.to_xml() +# [parameter-model-end] + +# [parameter-xml-start] +parameter_xml = """ + + ADQL + +""" # [parameter-xml-end] + + +# [parameters-model-start] +class TAPParameters(Parameters): + lang: Parameter = Parameter(id="lang") + query: Parameter = Parameter(id="query") + maxrec: Parameter = Parameter(id="maxrec") + format: Parameter = Parameter(id="format") + + +parameters = TAPParameters( + lang=Parameter(value="ADQL", id="lang"), + query=Parameter(value="SELECT * FROM ivoa.obscore", id="query"), + maxrec=Parameter(value=10, id="maxrec"), + format=Parameter(value="votable", id="format"), +) +parameters.to_xml() +# [parameters-model-end] + +# [parameters-xml-start] +parameters_xml = """ + + ADQL + SELECT * FROM ivoa.obscore + 10 + votable + +""" # [parameters-xml-end] + +# [result-reference-model-start] +result_reference = ResultReference( + id="result", + href="http://example.com/result", +) + +result_reference.to_xml() +# [result-reference-model-end] + +# [result-reference-xml-start] +result_reference_xml = """ + +""" # [result-reference-xml-end] + +# [results-model-start] +results = Results( + results=[ + ResultReference(id="result1", href="http://example.com/result1"), + ResultReference(id="result2", href="http://example.com/result2"), + ], +) + +results.to_xml() +# [results-model-end] + +# [results-xml-start] +results_xml = """ + + + + +""" # [results-xml-end] + +# [short-job-description-model-start] +short_job_description = ShortJobDescription( + phase="PENDING", + run_id="1234567890", + owner_id="anon_user", + creation_time=datetime.datetime.now(), + job_id="job_1", +) + +short_job_description.to_xml() +# [short-job-description-model-end] + +# [short-job-description-xml-start] +short_job_description_xml = """ + + PENDING + 1234567890 + anon_user + 2023-12-27T12:19:46.889Z + +""" # [short-job-description-xml-end] + +# [jobs-model-start] +jobs = Jobs( + jobref=[ + ShortJobDescription( + phase="PENDING", + run_id="1234567890", + owner_id="anon_user", + job_id="job_1", + ), + ShortJobDescription( + phase="COMPLETED", + run_id="1234567891", + owner_id="anon_user", + job_id="job_2", + ), + ], +) + +jobs.to_xml() +# [jobs-model-end] + +# [jobs-xml-start] +jobs_xml = """ + + + PENDING + 1234567890 + anon_user + + + + COMPLETED + 1234567891 + anon_user + + + +""" # [jobs-xml-end] + +# [job-summary-model-start] +parameters = TAPParameters( + lang=Parameter(value="ADQL", id="lang"), + query=Parameter(value="SELECT * FROM ivoa.obscore", id="query"), + maxrec=Parameter(value=10, id="maxrec"), + format=Parameter(value="votable", id="format"), +) + +job_summary = JobSummary[TAPParameters]( + job_id = "job_1", + owner_id = "anon_user", + phase = "COMPLETED", + creation_time = "2023-12-01T12:00:00.000Z", + start_time = "2023-12-01T12:00:00.000Z", + end_time = datetime.datetime.now(), + parameters = parameters, + results = Results( + results=[ + ResultReference(id="result1", href="http://example.com/result1"), + ResultReference(id="result2", href="http://example.com/result2"), + ], + ), +) + +job_summary.to_xml() +# [job-summary-model-end] + +# [job-summary-xml-start] +job_summary_xml = """ + + job_1 + + anon_user + COMPLETED + + 2023-12-01T12:00:00.000Z + 2023-12-01T12:00:00.000Z + 2023-12-27T12:47:37.196Z + 0 + + + ADQL + SELECT * FROM ivoa.obscore + 10 + votable + + + + + + +""" # [job-summary-xml-end] \ No newline at end of file diff --git a/examples/snippets/vosi/availability.py b/examples/snippets/vosi/availability.py new file mode 100644 index 0000000..8fbe95c --- /dev/null +++ b/examples/snippets/vosi/availability.py @@ -0,0 +1,27 @@ +from vo_models.vosi.availability import Availability + +# [model-start] +availability = Availability( + available=True, + up_since="2023-01-01T00:00:00Z", + down_at="2023-01-02T00:00:00Z", + back_at="2023-01-03T00:00:00Z", + notes=["This service is available for public use."], +) + +availability.to_xml() +# [model-end] + +# [xml-start] +avail_xml = """ + +true +2023-01-01T00:00:00.000Z +2023-01-02T00:00:00.000Z +2023-01-03T00:00:00.000Z + +""" +# [xml-end] \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index e751509..42a1236 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,8 +34,13 @@ keywords = [ [project.optional-dependencies] test = ["pytest", "pytest-cov"] -dev = ["pylint"] +dev = ["pylint", "ruff", "pre-commit"] +docs = ["sphinx", "sphinx_design", "furo", "sphinx-copybutton","toml"] [project.urls] Homepage = "https://github.com/spacetelescope/vo-models" Issues = "https://github.com/spacetelescope/vo-models/issues" + +[tool.ruff] +line-length = 120 +extend-exclude = ["docs/conf.py"] diff --git a/vo_models/uws/models.py b/vo_models/uws/models.py index f29110b..b92adab 100644 --- a/vo_models/uws/models.py +++ b/vo_models/uws/models.py @@ -17,21 +17,22 @@ # pylint: disable=invalid-name ParametersType = TypeVar("ParametersType") + class Parameter(BaseXmlModel, tag="parameter", ns="uws", nsmap=NSMAP): """A UWS Job parameter - Attributes: - byReference (bool): If this attribute is true then the content of the parameter represents a URL to retrieve the - actual parameter value. - It is up to the implementation to decide if a parameter value cannot be returned directly as - the content - the basic rule is that the representation of the parameter must allow the whole - job element to be valid XML. If this cannot be achieved then the parameter value must be - returned by reference. - id (str): The identifier of the parameter. - isPost (bool): Undocumented. - - Content: - value (str): the value of the parameter. + Parameters: + value (str, int, float, bool, bytes): + (content) - the value of the parameter. + by_reference (bool): + (attr) - If this attribute is true then the content of the parameter represents a URL to retrieve the + actual parameter value. + id (str): + (attr) - The identifier of the parameter. + is_post (bool): + (attr) - Undocumented. + + """ value: Optional[str | int | float | bool | bytes] = None # only primitive types are allowed @@ -65,15 +66,18 @@ def __init__(__pydantic_self__, **data) -> None: # pylint: disable=no-self-argu class ErrorSummary(BaseXmlModel, tag="errorSummary", ns="uws", nsmap=NSMAP): - """A short summary of an error - a fuller representation of the - error may be retrieved from /{jobs}/{job-id}/error + """A short summary of an error - Elements: - message (str): a short description of the error. + A fuller representation of the error may be retrieved from /{jobs}/{job-id}/error - Attributes: - type (ErrorType): Characterization of the type of the error - has_detail (bool): If true then there is a more detailed error message available at /{jobs}/{job-id}/error + Parameters: + message (str): + (element) - A short description of the error. + + type (ErrorType): + (attr) - Characterization of the type of the error + has_detail (bool): + (attr) - If true then there is a more detailed error message available at /{jobs}/{job-id}/error """ message: str = element(default="") @@ -85,12 +89,13 @@ class ErrorSummary(BaseXmlModel, tag="errorSummary", ns="uws", nsmap=NSMAP): class ResultReference(BaseXmlModel, tag="result", ns="uws", skip_empty=True, nsmap=NSMAP): """A reference to a UWS result. - Attributes: - id (str): The identifier of the result. - type (XlinkType): The xlink type of the result. - href (str): The link to the result. - size (int): The size of the result in bytes. - mime_type (str): The MIME type of the result. + Parameters: + id (str): (attr) The identifier of the result. + type (XlinkType): (attr) The xlink type of the result. + href (str): (attr) The link to the result. + size (int): (attr) The size of the result in bytes. + mime_type (str): (attr) The MIME type of the result. + any_attrs (dict): (attr) Any other attributes of the result. """ id: str = attr() @@ -108,8 +113,8 @@ class ResultReference(BaseXmlModel, tag="result", ns="uws", skip_empty=True, nsm class Results(BaseXmlModel, tag="results", ns="uws", nsmap=NSMAP): """The element returned for /{jobs}/{job-id}/results - Elements: - result list[ResultReference]: a list of references to UWS results. + Parameters: + results (list[ResultReference]): (element) A list of references to UWS results. """ results: Optional[list[ResultReference]] = element(name="result", default_factory=list) @@ -118,20 +123,24 @@ class Results(BaseXmlModel, tag="results", ns="uws", nsmap=NSMAP): class ShortJobDescription(BaseXmlModel, tag="jobref", ns="uws", nsmap=NSMAP): """A short description of a job. - Elements: - phase (ExecutionPhase): The execution phase - returned at /{jobs}/{job-id}/phase - run_id (str): A client supplied identifier - the UWS system - does nothing other than to return it as part of the - description of the job - owner_id (str): the owner (creator) of the job - this should be - expressed as a string that can be parsed in accordance - with IVOA security standards. - creation_time (datetime): The instant at which the job was created. - - Attributes: - job_id (str): The identifier for the job. - type (XlinkType): The xlink reference type of the job. - href (str): The link to the job. + Parameters: + phase (ExecutionPhase): + (element) - The execution phase - returned at /{jobs}/{job-id}/phase + run_id (str): + (element) - A client supplied identifier - the UWS system does nothing other than to return it as part of + the description of the job + owner_id (str): + (element) - The owner (creator) of the job - this should be expressed as a string that can be parsed in + accordance with IVOA security standards. + creation_time (datetime): + (element) - The instant at which the job was created. + + job_id (str): + (attr) - The identifier for the job. + type (XlinkType): + (attr) - The xlink reference type of the job. + href (str): + (attr) - The link to the job. """ phase: ExecutionPhase = element() @@ -149,14 +158,15 @@ class Jobs(BaseXmlModel, tag="jobs", ns="uws", nsmap=NSMAP): The list presented may be affected by the current security context and may be filtered - Elements: - job list(Job): a list of UWS Jobs. + Parameters: + jobref (Job): (element) a list of UWS Jobs. + + version (UWSVersion): + (attr) - The version of the UWS standard that the server complies with. - Attributes: - version (UWSVersion): The version of the UWS standard that the server complies with. - Note that this attribute is actually required by the 1.1 specification - however remains - optional in the schema for backwards compatibility. - It will be formally required in the next major revision. + Note that this attribute is actually required by the 1.1 specification - however remains + optional in the schema for backwards compatibility. + It will be formally required in the next major revision. """ jobref: Optional[list[ShortJobDescription]] = element(name="jobref", default_factory=list) @@ -167,39 +177,52 @@ class Jobs(BaseXmlModel, tag="jobs", ns="uws", nsmap=NSMAP): class JobSummary(BaseXmlModel, Generic[ParametersType], tag="job", ns="uws", nsmap=NSMAP): """The complete representation of the state of a job - Elements: - job_id (JobIdentifier, str): The identifier for the job. - run_id (str): This is a client supplied identifier - the UWS system does nothing other than to - return it as part of the description of the job - owner_id (str): The owner (creator) of the job - this should be expressed as a string that can be - parsed in accordance with IVOA security standards. If there was no authenticated - job creator then this should be set to NULL. - phase (ExecutionPhase): The execution phase - returned at /{jobs}/{job-id}/phase - quote (datetime): A Quote predicts when the job is likely to complete - returned at - /{jobs}/{job-id}/quote - "don't know" is encoded by setting to the XML null value xsi:nil="true" - creation_time (datetime): The instant at which the job was created. - Note that the version 1.1 of the specification requires that this element - be present. - It is optional only in versions 1.x of the schema for backwards compatibility. - 2.0+ versions of the schema will make this formally mandatory in an XML sense. - start_time (datetime): The instant at which the job started execution. - end_time (datetime): The instant at which the job finished execution. - execution_duration (timedelta): The duration (in seconds) for which the job should be allowed to run - a value of 0 - is intended to mean unlimited - returned at /{jobs}/{job-id}/executionduration - destruction (datetime): The time at which the whole job + records + results will be destroyed. - Returned at /{jobs}/{job-id}/destruction - parameters (Parameters): The parameters to the job (where appropriate) can also be retrieved at - /{jobs}/{job-id}/parameters - results (Results): The results for the job - can also be retrieved at /{jobs}/{job-id}/results - error_summary (ErrorSummary): A short summary of an error - job_info (Any): This is arbitrary information that can be added to the job description by the UWS - implementation. - - Attributes: - version: (UWSVersion) Note that this attribute is actually required by the 1.1 specification - however remains - optional in the schema for backwards compatibility. - It will be formally required in the next major revision. + Parameters: + job_id (JobIdentifier, str): + (element) - The identifier for the job. + run_id (str): + (element) - This is a client supplied identifier - the UWS system does nothing other than to return it + as part of the description of the job + owner_id (str): + (element) - The owner (creator) of the job - this should be expressed as a string that can be + parsed in accordance with IVOA security standards. + + If there was no authenticated job creator then this should be set to NULL. + phase (ExecutionPhase): + (element) - The execution phase. + quote (UTCTimestamp): + (element) - A Quote predicts when the job is likely to complete. + creation_time (UTCTimestamp): + (element) - The instant at which the job was created. + + Note that the version 1.1 of the specification requires that this element + be present. It is optional only in versions 1.x of the schema for backwards compatibility. + 2.0+ versions of the schema will make this formally mandatory in an XML sense. + start_time (UTCTimestamp): + (element) - The instant at which the job started execution. + end_time (UTCTimestamp): + (element) - The instant at which the job finished execution. + execution_duration (int): + (element) - The duration (in seconds) for which the job should be allowed to run. + + A value of 0 is intended to mean unlimited. + destruction (UTCTimestamp): + (element) - The time at which the whole job + records + results will be destroyed. + parameters (Parameters): + (element) - The parameters to the job (where appropriate) + results (Results): + (element) - The results for the job + error_summary (ErrorSummary): + (element) - A short summary of an error + job_info (list[str]): + (element) - This is arbitrary information that can be added to the job description by the UWS + implementation. + + version: (UWSVersion) + (attr) - The version of the UWS standard that the server complies with. + + Note that this attribute is actually required by the 1.1 specification - however remains optional + in the schema for backwards compatibility. It will be formally required in the next major revision. """ # pylint: disable = too-few-public-methods diff --git a/vo_models/uws/types.py b/vo_models/uws/types.py index bef7b5b..36e59a4 100644 --- a/vo_models/uws/types.py +++ b/vo_models/uws/types.py @@ -7,41 +7,63 @@ class ErrorType(str, Enum): """Enum for error types.""" TRANSIENT = "transient" + """The error is transient and the job may be rerun.""" FATAL = "fatal" + """The error is fatal and the job may not be rerun.""" + class UWSVersion(str, Enum): """The version of the UWS standard that the server complies with.""" V1_1 = "1.1" + """The server complies with UWS 1.1.""" V1_0 = "1.0" + """The server complies with UWS 1.0.""" + class ExecutionPhase(str, Enum): - """Enumeration of possible phases of job execution - - PENDING: The first phase a job is entered into - this is where a job is being set up but no request to run - has occurred. - QUEUED: A job has been accepted for execution but is waiting in a queue. - EXECUTING: A job is running - COMPLETED: A job has completed successfully. - ERROR: Some form of error has occurred. - UNKNOWN: The job is in an unknown state. - HELD: The job is HELD pending execution and will not automatically be executed - can occur after a - PHASE=RUN request has been made (cf PENDING). - SUSPENDED: The job has been suspended by the system during execution. - ABORTED: The job has been aborted, either by user request or by the server because of lack or overuse of - resources. - ARCHIVED: The job has been archived by the server at destruction time. An archived job - may have deleted the results to reclaim resources, but must have job metadata preserved. - This is an alternative that the server may choose in contrast to completely destroying - all record of the job. - """ + """Enumeration of possible phases of job execution.""" + PENDING = "PENDING" + """ + The first phase a job is entered into - this is where a job is being set up but no request to run has occurred. + """ QUEUED = "QUEUED" + """ + A job has been accepted for execution but is waiting in a queue. + """ EXECUTING = "EXECUTING" + """ + A job is running + """ COMPLETED = "COMPLETED" + """ + A job has completed successfully. + """ ERROR = "ERROR" + """ + Some form of error has occurred. + """ UNKNOWN = "UNKNOWN" + """ + The job is in an unknown state. + """ HELD = "HELD" + """ + The job is HELD pending execution and will not automatically be executed. + Can occur after a PHASE=RUN request has been made (cf PENDING). + """ SUSPENDED = "SUSPENDED" + """ + The job has been suspended by the system during execution. + """ ABORTED = "ABORTED" + """ + The job has been aborted, either by user request or by the server because of lack or overuse of resources. + """ ARCHIVED = "ARCHIVED" + """ + The job has been archived by the server at destruction time. An archived job may have deleted the results to reclaim + resources, but must have job metadata preserved. This is an alternative that the server may choose in contrast to + completely destroying all record of the job. + """ diff --git a/vo_models/voresource/types.py b/vo_models/voresource/types.py index d59ad9a..241f71b 100644 --- a/vo_models/voresource/types.py +++ b/vo_models/voresource/types.py @@ -89,6 +89,10 @@ def fromisoformat(cls, date_string): def isoformat(self, sep: str = "T", timespec: str = "milliseconds") -> str: """Overwrites the datetime isoformat output to use a Z UTC indicator + Parameters: + sep (str): Separator between date and time (default: 'T') + timespec (str): Resolution of time to include (default: 'milliseconds') + Returns: str: VO-compliant ISO-8601 datetime string """ diff --git a/vo_models/vosi/availability/models.py b/vo_models/vosi/availability/models.py index 70d257e..a47e1c6 100644 --- a/vo_models/vosi/availability/models.py +++ b/vo_models/vosi/availability/models.py @@ -16,13 +16,18 @@ class Availability(BaseXmlModel, tag="availability", nsmap=NSMAP, skip_empty=True): """VOSI Availability complex type. - Elements: - available (bool): Whether the service is currently available. - upSince (datetime): The instant at which the service last became available. - downAt (datetime): The instant at which the service is next scheduled to become unavailable. - backAt (datetime): The instant at which the service is scheduled to become available again after a period - of unavailability. - note (str): A textual note concerning availability. + Parameters: + available (bool): + (element) - Whether the service is currently available. + up_since (UTCTimestamp): + (element) - The instant at which the service last became available. + down_at (UTCTimestamp): + (element) - The instant at which the service is next scheduled to become unavailable. + back_at (UTCTimestamp): + (element) - The instant at which the service is scheduled to become available again after a period + of unavailability. + note (Optional[list[str]]): + (element) - A textual note concerning availability. """ available: bool = element(tag="available")