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")