Skip to content

Commit

Permalink
Add methods to API docs and use typehints
Browse files Browse the repository at this point in the history
  • Loading branch information
abkfenris committed Mar 11, 2023
1 parent 8a2e5d3 commit 47626f4
Show file tree
Hide file tree
Showing 5 changed files with 126 additions and 25 deletions.
1 change: 1 addition & 0 deletions docs/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
sphinx>=3.1
sphinx-autosummary-accessors
sphinx_rtd_theme
sphinx-autodoc-typehints
39 changes: 37 additions & 2 deletions docs/source/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,49 @@ Top-level Rest class
The :class:`~xpublish.Rest` class can be used for publishing a
:class:`xarray.Dataset` object or a collection of Dataset objects.

The main interfaces to Xpublish that many users may use.

.. autosummary::
:toctree: generated/

Rest
Rest.app
Rest.cache
Rest.plugins
Rest.serve
Rest.register_plugin
Rest.dependencies

There are also a handful of methods that are more likely to be used
when subclassing `xpublish.Rest` to modify functionality, or are used
by plugin dependencies.

.. autosummary::
:toctree: generated/

Rest.setup_datasets
Rest.get_datasets_from_plugins
Rest.get_dataset_from_plugins
Rest.setup_plugins
Rest.init_cache_kwargs
Rest.init_app_kwargs
Rest.plugin_routers

There is also a specialized version of :class:`xpublish.Rest` for use
when only a single dataset is being served, instead of a collection
of datasets.

.. autosummary::
:toctree: generated/

SingleDatasetRest
SingleDatasetRest.setup_datasets

Dataset.rest (xarray accessor)
==============================

This accessor extends :py:class:`xarray.Dataset` with the same interface than
:class:`~xpublish.Rest`. It is a convenient method for publishing one single
:class:`~xpublish.SingleDatasetRest`. It is a convenient method for publishing one single
dataset. Proper use of this accessor should be like:

.. code-block:: python
Expand Down Expand Up @@ -65,7 +95,10 @@ FastAPI dependencies

The functions below are defined in module ``xpublish.dependencies`` and can
be used as `FastAPI dependencies <https://fastapi.tiangolo.com/tutorial/dependencies>`_
when creating custom API endpoints.
when creating custom API endpoints directly.

When creating routers with plugins, instead use ``xpublish.Dependency`` that will be
passed in to the ``Plugin.app_router`` or ``Plugin.dataset_router`` method.

.. currentmodule:: xpublish.dependencies

Expand All @@ -77,3 +110,5 @@ when creating custom API endpoints.
get_cache
get_zvariables
get_zmetadata
get_plugins
get_plugin_manager
1 change: 1 addition & 0 deletions docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
'sphinx.ext.extlinks',
'sphinx.ext.napoleon',
'sphinx_autosummary_accessors',
'sphinx_autodoc_typehints',
]

extlinks = {
Expand Down
16 changes: 15 additions & 1 deletion xpublish/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ def get_dataset_ids() -> List[str]:
This dummy dependency will be overridden when creating the FastAPI
application.
Returns:
A list of unique keys for datasets
"""
return [] # pragma: no cover

Expand All @@ -38,6 +41,13 @@ def get_dataset(dataset_id: str) -> xr.Dataset:
This dummy dependency will be overridden when creating the FastAPI
application.
Parameters:
dataset_id:
Unique path-safe key identifying dataset
Returns:
Requested Xarray dataset
"""
return xr.Dataset() # pragma: no cover

Expand Down Expand Up @@ -93,7 +103,11 @@ def get_zmetadata(


def get_plugins() -> Dict[str, 'Plugin']:
"""FastAPI dependency that returns the a dictionary of loaded plugins"""
"""FastAPI dependency that returns the a dictionary of loaded plugins
Returns:
Dictionary of names to initialized plugins.
"""

return {} # pragma: no cover

Expand Down
94 changes: 72 additions & 22 deletions xpublish/rest.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Dict, Optional
from typing import Dict, List, Optional, Tuple

import cachey
import pluggy
Expand All @@ -16,6 +16,9 @@
normalize_datasets,
)

RouterKwargs = Dict
RouterAndKwargs = Tuple[APIRouter, RouterKwargs]


class Rest:
"""Used to publish multiple Xarray Datasets via a REST API (FastAPI application).
Expand All @@ -27,10 +30,10 @@ class Rest:
Parameters
----------
datasets : dict
datasets :
A mapping of datasets objects to be served. If a mapping is given, keys
are used as dataset ids and are converted to strings. See also the notes below.
routers : list, optional
routers :
A list of dataset-specific :class:`fastapi.APIRouter` instances to
include in the fastAPI application. These routers are in addition
to any loaded via plugins.
Expand All @@ -39,15 +42,15 @@ class Rest:
the 1st tuple element is a :class:`fastapi.APIRouter` instance and the
2nd element is a dictionary that is used to pass keyword arguments to
:meth:`fastapi.FastAPI.include_router`.
cache_kws : dict, optional
cache_kws :
Dictionary of keyword arguments to be passed to
:meth:`cachey.Cache.__init__()`.
By default, the cache size is set to 1MB, but this can be changed with
``available_bytes``.
app_kws : dict, optional
app_kws :
Dictionary of keyword arguments to be passed to
:meth:`fastapi.FastAPI.__init__()`.
plugins : dict, optional
plugins :
Optional dictionary of loaded, configured plugins.
Overrides automatic loading of plugins.
If no plugins are desired, set to an empty dict.
Expand All @@ -67,8 +70,8 @@ def __init__(
self,
datasets: Optional[Dict[str, xr.Dataset]] = None,
routers: Optional[APIRouter] = None,
cache_kws=None,
app_kws=None,
cache_kws: Optional[Dict] = None,
app_kws: Optional[Dict] = None,
plugins: Optional[Dict[str, Plugin]] = None,
):
self.setup_datasets(datasets or {})
Expand All @@ -81,25 +84,49 @@ def __init__(
self.init_app_kwargs(app_kws)
self.init_cache_kwargs(cache_kws)

def setup_datasets(self, datasets: Dict[str, xr.Dataset]):
"""Initialize datasets and getter functions"""
def setup_datasets(self, datasets: Dict[str, xr.Dataset]) -> str:
"""Initialize datasets and dataset accessor function
Returns:
Prefix for dataset routers
"""
self._datasets = normalize_datasets(datasets)

self._get_dataset_func = self.get_dataset_from_plugins
self._dataset_route_prefix = '/datasets/{dataset_id}'
return self._dataset_route_prefix

def get_datasets_from_plugins(self):
"""Get dataset ids from directly loaded datasets and plugins"""
def get_datasets_from_plugins(self) -> List[str]:
"""Return dataset ids from directly loaded datasets and plugins
Used as a FastAPI dependency in dataset router plugins
via :meth:`Rest.dependencies`.
Returns:
Dataset IDs from plugins and datasets loaded into
:class:`xpublish.Rest` at initialization.
"""
dataset_ids = list(self._datasets)

for plugin_dataset_ids in self.pm.hook.get_datasets():
dataset_ids.extend(plugin_dataset_ids)

return dataset_ids

def get_dataset_from_plugins(self, dataset_id: str):
"""Attempt to load dataset from plugins, otherwise load"""
def get_dataset_from_plugins(self, dataset_id: str) -> xr.Dataset:
"""Attempt to load dataset from plugins, otherwise return dataset from passed in dictionary of datasets
Parameters:
dataset_id:
Unique key of dataset to attempt to load from plugins or
those provided to :class:`xpublish.Rest` at initialization.
Returns:
Dataset for selected ``dataset_id``
Raises:
FastAPI.HTTPException: When a dataset is not found a 404 error is returned.
"""
dataset = self.pm.hook.get_dataset(dataset_id=dataset_id)

if dataset:
Expand All @@ -111,7 +138,16 @@ def get_dataset_from_plugins(self, dataset_id: str):
return self._datasets[dataset_id]

def setup_plugins(self, plugins: Optional[Dict[str, Plugin]] = None):
"""Initialize and load plugins from entry_points"""
"""Initialize and load plugins from entry_points unless explicitly provided
Parameters:
plugins:
If a dictionary of initialized plugins is provided,
then the automatic loading of plugins is disabled.
Providing an empty dictionary will also disable
automatic loading of plugins.
"""
if plugins is None:
plugins = load_default_plugins()

Expand Down Expand Up @@ -183,8 +219,13 @@ def _init_routers(self, dataset_routers: Optional[APIRouter]):

self._app_routers = app_routers

def plugin_routers(self):
"""Load the app and dataset routers for plugins"""
def plugin_routers(self) -> Tuple[List[RouterAndKwargs], List[RouterAndKwargs]]:
"""Load the app and dataset routers for plugins
Returns:
A tuple containing a list of top-level routers from plugins
and a list of per-dataset routers from plugins
"""
app_routers = []
dataset_routers = []

Expand All @@ -199,6 +240,7 @@ def plugin_routers(self):
return app_routers, dataset_routers

def dependencies(self) -> Dependencies:
"""FastAPI dependencies to pass to plugin router methods"""
deps = Dependencies(
dataset_ids=self.get_datasets_from_plugins,
dataset=self._get_dataset_func,
Expand Down Expand Up @@ -234,21 +276,27 @@ def _init_app(self):

@property
def app(self) -> FastAPI:
"""Returns the :class:`fastapi.FastAPI` application instance."""
"""Returns the :class:`fastapi.FastAPI` application instance.
Notes
-----
Plugins registered with :meth:`xpublish.Rest.register_plugin` after :meth:`xpublish.Rest.app`
is accessed or :meth:`xpublish.Rest.serve` is called once may not take effect.
"""
if self._app is None:
self._app = self._init_app()
return self._app

def serve(self, host='0.0.0.0', port=9000, log_level='debug', **kwargs):
def serve(self, host: str = '0.0.0.0', port: int = 9000, log_level: str = 'debug', **kwargs):
"""Serve this FastAPI application via :func:`uvicorn.run`.
Parameters
----------
host : str
host :
Bind socket to this host.
port : int
port :
Bind socket to this port.
log_level : str
log_level :
App logging level, valid options are
{'critical', 'error', 'warning', 'info', 'debug', 'trace'}.
**kwargs :
Expand Down Expand Up @@ -285,6 +333,8 @@ def __init__(
super().__init__({}, routers, cache_kws, app_kws, plugins)

def setup_datasets(self, datasets):
"""Modifies the dataset loading to instead connect to the
single dataset"""
self._dataset_route_prefix = ''
self._datasets = {}

Expand Down

0 comments on commit 47626f4

Please sign in to comment.