Skip to content

Commit

Permalink
Merge branch 'main' into pre-commit-ci-update-config
Browse files Browse the repository at this point in the history
  • Loading branch information
abkfenris authored Jul 20, 2023
2 parents 15f3823 + e9466e8 commit 880b433
Show file tree
Hide file tree
Showing 19 changed files with 217 additions and 100 deletions.
8 changes: 5 additions & 3 deletions .github/workflows/main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ on:

jobs:
test:
name: ${{ matrix.python-version }}-build
name: ${{ matrix.python-version }}-pydantic${{ matrix.pydantic-version }}-build
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.9", "3.10", "3.11"]
pydantic-version: ["<2", ">=2"]
steps:
- uses: actions/checkout@v3

Expand All @@ -26,8 +27,9 @@ jobs:
uses: actions/cache@v3.3.1
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ matrix.python-version }}-${{ hashFiles('**/dev-requirements.txt') }}
key: ${{ runner.os }}-pip-${{ matrix.python-version }}-pydantic${{ matrix.pydantic-version }}-${{ hashFiles('**/dev-requirements.txt') }}
restore-keys: |
${{ runner.os }}-pip-${{ matrix.python-version }}-pydantic${{ matrix.pydantic-version }}
${{ runner.os }}-pip-${{ matrix.python-version }}
${{ runner.os }}-pip
${{ runner.os }}-pip-dev
Expand All @@ -36,6 +38,7 @@ jobs:
run: |
python -m pip install -r dev-requirements.txt
python -m pip install --no-deps -e .
python -m pip install "pydantic${{ matrix.pydantic-version }}"
python -m pip list
- name: Running Tests
Expand All @@ -44,7 +47,6 @@ jobs:
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3.1.4
if: ${{ matrix.python-version }} == 3.9
with:
file: ./coverage.xml
fail_ci_if_error: false
Expand Down
2 changes: 1 addition & 1 deletion .readthedocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,6 @@ build:
# Optionally set the version of Python and requirements required to build your docs
python:
install:
- requirements: docs/requirements.txt
- method: pip
path: .
- requirements: docs/requirements.txt
11 changes: 10 additions & 1 deletion dev-requirements.txt
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
cachey
dask
fastapi>=0.78.0
fsspec
httpx
importlib-metadata
netcdf4
numcodecs
numpy
pluggy
pooch
pytest
pytest-mock
pytest-sugar
pytest-cov
requests
-r requirements.txt
toolz
uvicorn
xarray
zarr
1 change: 1 addition & 0 deletions docs/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
sphinx>=3.1
pydantic<2.0
sphinx-autosummary-accessors
pydata-sphinx-theme
sphinx-autodoc-typehints
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@


class TutorialDataset(Plugin):
name = 'xarray-tutorial-dataset'
name: str = 'xarray-tutorial-dataset'

@hookimpl
def get_datasets(self):
Expand Down
8 changes: 5 additions & 3 deletions docs/source/getting-started/tutorial/dataset-router-plugin.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
from typing import Sequence

import xarray as xr
from fastapi import APIRouter, Depends, HTTPException

from xpublish import Dependencies, Plugin, SingleDatasetRest, hookimpl


class MeanPlugin(Plugin):
name = 'mean'
name: str = 'mean'

dataset_router_prefix = ''
dataset_router_tags = ['mean']
dataset_router_prefix: str = ''
dataset_router_tags: Sequence[str] = ['mean']

@hookimpl
def dataset_router(self, deps: Dependencies):
Expand Down
14 changes: 7 additions & 7 deletions docs/source/user-guide/plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,10 @@ from xpublish import Plugin
class HelloWorldPlugin(Plugin):
name = "hello_world"
name: str = "hello_world"
```

At the minimum, a plugin needs to specify a `name` attribute.
At the minimum, a plugin needs to specify a `name` attribute with a type annotation. For example, `name: str = my_plugin_name`.

### Marking implementation methods

Expand All @@ -66,7 +66,7 @@ from xpublish import Plugin, hookimpl
from fastapi import APIRouter
class HelloWorldPlugin(Plugin):
name = "hello_world"
name: str = "hello_world"
@hookimpl
def app_router(self):
Expand Down Expand Up @@ -188,7 +188,7 @@ from xpublish import Plugin, Dependencies, hookimpl


class DatasetAttrs(Plugin):
name = "dataset-attrs"
name: str = "dataset-attrs"

@hookimpl
def dataset_router(self, deps: Dependencies):
Expand Down Expand Up @@ -224,7 +224,7 @@ from xpublish import Plugin, Dependencies, hookimpl


class DatasetInfoPlugin(Plugin):
name = "dataset-info"
name: str = "dataset-info"

dataset_router_prefix = "/info"
dataset_router_tags = ["info"]
Expand Down Expand Up @@ -257,7 +257,7 @@ from xpublish import Plugin, Dependencies, hookimpl


class PluginInfo(Plugin):
name = "plugin_info"
name: str = "plugin_info"

app_router_prefix = "/info"
app_router_tags = ["info"]
Expand Down Expand Up @@ -298,7 +298,7 @@ from xpublish import Plugin, hookimpl


class TutorialDataset(Plugin):
name = "xarray-tutorial-dataset"
name: str = "xarray-tutorial-dataset"

@hookimpl
def get_datasets(self):
Expand Down
6 changes: 3 additions & 3 deletions tests/test_rest_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ def get_dims(dataset: xr.Dataset = Depends(get_dataset)):
@pytest.fixture(scope='function')
def dataset_plugin(airtemp_ds):
class AirtempPlugin(Plugin):
name = 'airtemp'
name: str = 'airtemp'

@hookimpl
def get_dataset(self, dataset_id: str):
Expand All @@ -86,7 +86,7 @@ def hello(self):
pass

class HookSpecPlugin(Plugin):
name = 'hook_spec'
name: str = 'hook_spec'

@hookimpl
def register_hookspec(self):
Expand All @@ -98,7 +98,7 @@ def register_hookspec(self):
@pytest.fixture(scope='function')
def hook_implementation_plugin():
class HookImplementationPlugin(Plugin):
name = 'hook_implementation'
name: str = 'hook_implementation'

@hookimpl
def hello(self):
Expand Down
4 changes: 2 additions & 2 deletions xpublish/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from fastapi import Depends

from .utils.api import DATASET_ID_ATTR_KEY
from .utils.zarr import create_zmetadata, create_zvariables, zarr_metadata_key
from .utils.zarr import ZARR_METADATA_KEY, create_zmetadata, create_zvariables

if TYPE_CHECKING:
from .plugins import Plugin # pragma: no cover
Expand Down Expand Up @@ -90,7 +90,7 @@ def get_zmetadata(
):
"""FastAPI dependency that returns a consolidated zmetadata dictionary."""

cache_key = dataset.attrs.get(DATASET_ID_ATTR_KEY, '') + '/' + zarr_metadata_key
cache_key = dataset.attrs.get(DATASET_ID_ATTR_KEY, '') + '/' + ZARR_METADATA_KEY
zmeta = cache.get(cache_key)

if zmeta is None:
Expand Down
26 changes: 19 additions & 7 deletions xpublish/plugins/hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,24 @@ class Dependencies(BaseModel):
"""

dataset_ids: Callable[..., List[str]] = Field(
get_dataset_ids, description='Returns a list of all valid dataset ids'
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.'
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`'
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'
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'
get_plugin_manager,
description='The plugin manager itself, allowing for maximum creativity',
)

def __hash__(self):
Expand All @@ -64,7 +69,13 @@ def __hash__(self):
"""Make sure that the plugin is hashable to load with pluggy"""
things_to_hash = []

for e in self.dict():
# 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:
Expand Down Expand Up @@ -115,7 +126,8 @@ def get_datasets(self) -> Iterable[str]: # type: ignore
"""Return an iterable of dataset ids that the plugin can provide"""

@hookspec(firstresult=True)
def get_dataset(self, dataset_id: str) -> Optional[xr.Dataset]: # type: ignore
# 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
Expand Down
16 changes: 10 additions & 6 deletions xpublish/plugins/included/dataset_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,22 @@
class DatasetInfoPlugin(Plugin):
"""Dataset metadata"""

name = 'dataset_info'
name: str = 'dataset_info'

dataset_router_prefix: str = ''
dataset_router_tags: Sequence[str] = ['dataset_info']

@hookimpl
def dataset_router(self, deps: Dependencies):
router = APIRouter(prefix=self.dataset_router_prefix, tags=list(self.dataset_router_tags))
def dataset_router(self, deps: Dependencies) -> APIRouter:
router = APIRouter(
prefix=self.dataset_router_prefix,
tags=list(self.dataset_router_tags),
)

@router.get('/')
def html_representation(
dataset=Depends(deps.dataset),
):
) -> HTMLResponse:
"""Returns the xarray HTML representation of the dataset."""

with xr.set_options(display_style='html'):
Expand All @@ -43,15 +46,15 @@ def list_keys(
@router.get('/dict')
def to_dict(
dataset=Depends(deps.dataset),
):
) -> dict:
"""The full dataset as a dictionary"""
return JSONResponse(dataset.to_dict(data=False))

@router.get('/info')
def info(
dataset=Depends(deps.dataset),
cache=Depends(deps.cache),
):
) -> dict:
"""Dataset schema (close to the NCO-JSON schema)."""

zvariables = get_zvariables(dataset, cache)
Expand All @@ -66,6 +69,7 @@ def info(
for name, var in zvariables.items():
attrs = meta[f'{name}/{attrs_key}'].copy()
attrs.pop('_ARRAY_DIMENSIONS')

info['variables'][name] = {
'type': var.data.dtype.name,
'dimensions': list(var.dims),
Expand Down
15 changes: 9 additions & 6 deletions xpublish/plugins/included/module_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"""
import importlib
import sys
from typing import List
from typing import Sequence

from fastapi import APIRouter

Expand All @@ -14,17 +14,20 @@
class ModuleVersionPlugin(Plugin):
"""Share the currently loaded versions of key libraries"""

name = 'module_version'
name: str = 'module_version'

app_router_prefix: str = ''
app_router_tags: List[str] = ['module_version']
app_router_tags: Sequence[str] = ['module_version']

@hookimpl
def app_router(self):
router = APIRouter(prefix=self.app_router_prefix, tags=self.app_router_tags)
def app_router(self) -> APIRouter:
router = APIRouter(
prefix=self.app_router_prefix,
tags=self.app_router_tags,
)

@router.get('/versions')
def get_versions():
def get_versions() -> dict:
"""Currently loaded versions of key libraries"""
versions = dict(get_sys_info() + netcdf_and_hdf5_versions())
modules = [
Expand Down
14 changes: 9 additions & 5 deletions xpublish/plugins/included/plugin_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,23 @@

class PluginInfo(BaseModel):
path: str
version: Optional[str]
version: Optional[str] = None


class PluginInfoPlugin(Plugin):
"""Expose plugin source and version"""

name = 'plugin_info'
name: str = 'plugin_info'

app_router_prefix: str = ''
app_router_tags: Sequence[str] = ['plugin_info']

@hookimpl
def app_router(self, deps: Dependencies):
router = APIRouter(prefix=self.app_router_prefix, tags=list(self.app_router_tags))
def app_router(self, deps: Dependencies) -> APIRouter:
router = APIRouter(
prefix=self.app_router_prefix,
tags=list(self.app_router_tags),
)

@router.get('/plugins')
def get_plugins(
Expand All @@ -44,7 +47,8 @@ def get_plugins(
version = None # pragma: no cover

plugin_info[name] = PluginInfo(
path=f'{plugin_type.__module__}.{plugin.__repr_name__()}', version=version
path=f'{plugin_type.__module__}.{plugin.__repr_name__()}',
version=version,
)

return plugin_info
Expand Down
Loading

0 comments on commit 880b433

Please sign in to comment.