Source code for xpublish.plugins.hooks
from typing import Callable, Dict, Iterable, List, Optional
import cachey # type: ignore
import pluggy # type: ignore
import xarray as xr
from fastapi import APIRouter
from pydantic import BaseModel, Field
from ..dependencies import get_cache, get_dataset, get_dataset_ids, get_plugin_manager, get_plugins
# Decorator helper to mark functions as Xpublish hook specifications
hookspec = pluggy.HookspecMarker('xpublish')
# Decorator helper to mark functions as Xpublish hook implementations
hookimpl = pluggy.HookimplMarker('xpublish')
[docs]
class Dependencies(BaseModel):
"""A set of dependencies that are passed into plugin routers.
Some routers may be 'borrowed' by other routers to expose different
geometries of data, thus the default dependencies may need to be overridden.
By depending on the passed in version of this class, the dependencies
can be overridden predictably.
"""
dataset_ids: Callable[..., List[str]] = Field(
get_dataset_ids,
description='Returns a list of all valid dataset ids',
)
dataset: Callable[[str], xr.Dataset] = Field(
get_dataset,
description='Returns a dataset using ``/<dataset_id>/`` in the path.',
)
cache: Callable[..., cachey.Cache] = Field(
get_cache,
description='Provide access to :py:class:`cachey.Cache`',
)
plugins: Callable[..., Dict[str, 'Plugin']] = Field(
get_plugins,
description='A dictionary of plugins allowing direct access',
)
plugin_manager: Callable[..., pluggy.PluginManager] = Field(
get_plugin_manager,
description='The plugin manager itself, allowing for maximum creativity',
)
def __hash__(self):
"""Dependency functions aren't easy to hash."""
return 0 # pragma: no cover
[docs]
class Plugin(BaseModel):
"""Xpublish plugins provide ways to extend the core of xpublish with new routers and other functionality.
To create a plugin, subclass `Plugin` and add attributes that are
subclasses of `PluginType` (`Router` for instance).
The specific attributes correspond to how Xpublish should use
the plugin.
"""
name: str = Field(..., description='Fallback name of plugin')
def __hash__(self):
"""Make sure that the plugin is hashable to load with pluggy."""
things_to_hash = []
# try/except is for pydantic backwards compatibility
try:
model_dict = self.model_dump()
except AttributeError:
model_dict = self.dict()
for e in model_dict:
if isinstance(e, list):
things_to_hash.append(tuple(e)) # pragma: no cover
else:
things_to_hash.append(e)
return hash(tuple(things_to_hash))
def __dir__(self) -> Iterable[str]:
"""Overrides the dir.
We need to override the dir as pluggy will otherwise try to inspect it,
and Pydantic has marked it class only
https://github.com/pydantic/pydantic/pull/1466
"""
d = list(super().__dir__())
d.remove('__signature__')
return d
[docs]
class PluginSpec(Plugin):
"""Plugin extension points.
Plugins do not need to implement all of the methods defined here,
instead they implement
"""
[docs]
@hookspec
def app_router(self, deps: Dependencies) -> APIRouter: # type: ignore
"""Create an app (top-level) router for the plugin.
Implementations should return an APIRouter, and define
app_router_prefix, and app_router_tags on the class,
and use those to initialize the router.
"""
[docs]
@hookspec
def dataset_router(self, deps: Dependencies) -> APIRouter: # type: ignore
"""Create a dataset router for the plugin.
Implementations should return an APIRouter, and define
dataset_router_prefix, and dataset_router_tags on the class,
and use those to initialize the router.
"""
[docs]
@hookspec
def get_datasets(self) -> Iterable[str]: # type: ignore
"""Return an iterable of dataset ids that the plugin can provide."""
[docs]
@hookspec(firstresult=True)
# type: ignore
def get_dataset(self, dataset_id: str) -> Optional[xr.Dataset]:
"""Return a dataset by requested dataset_id.
If the plugin does not have the dataset, return None
"""
[docs]
@hookspec
def register_hookspec(self): # type: ignore
"""Return additional hookspec class to register with the plugin manager."""