Skip to content

Commit 51ee3ad

Browse files
authored
Implement accessor (#36)
* Implement accessor * define a decorator to handle errors * Better decorator and error handling * Update pydomcfg/accessor.py * Update pydomcfg/accessor.py * Update pydomcfg/accessor.py * cast wrapper * fix docs * add tests * what's new * Add attributes to api * bloody darglint
1 parent 6cf13c5 commit 51ee3ad

File tree

9 files changed

+134
-25
lines changed

9 files changed

+134
-25
lines changed

ci/docs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@ channels:
44
dependencies:
55
- numpydoc
66
- sphinx
7+
- sphinx-autosummary-accessors
78
- sphinx_rtd_theme
89
- xarray

docs/conf.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import os
1010
import sys
1111

12+
import sphinx_autosummary_accessors
1213
import sphinx_rtd_theme # noqa: F401
1314

1415
# isort: off
@@ -40,8 +41,10 @@
4041
"sphinx.ext.autosummary",
4142
"sphinx.ext.viewcode",
4243
"sphinx.ext.extlinks",
44+
"sphinx.ext.intersphinx",
4345
"sphinx_rtd_theme",
4446
"numpydoc",
47+
"sphinx_autosummary_accessors",
4548
]
4649

4750
# GitHub links
@@ -50,7 +53,7 @@
5053
}
5154

5255
# Add any paths that contain templates here, relative to this directory.
53-
templates_path = ["_templates"]
56+
templates_path = ["_templates", sphinx_autosummary_accessors.templates_path]
5457

5558
# List of patterns, relative to source directory, that match files and
5659
# directories to ignore when looking for source files.

docs/developers/whats-new.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ What's New
66
Unreleased
77
==========
88

9-
- Added :py:func:`~pydomcfg.bathymetry.sea_mount`
9+
- Introduced the ``domcfg`` accessor (:pr:`36`)
10+
- Added :py:meth:`~pydomcfg.tests.bathymetry.Bathymetry.sea_mount`
1011
useful to generate classic sea mount test case. (:pr:`17`)
1112
- Added :py:class:`~pydomcfg.domzgr.zco.Zco`
1213
to generate geopotential z-coordinates. (:pr:`15`)

docs/users/api.rst

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,31 @@
1-
.. currentmodule:: pydomcfg
2-
31
API reference
42
#############
53

64
This page provides an auto-generated summary of pyDOMCFG's API.
75

6+
Accessor
7+
========
8+
.. currentmodule:: xarray
9+
10+
Attributes
11+
----------
12+
.. autosummary::
13+
:toctree: generated/
14+
:template: autosummary/accessor_attribute.rst
15+
16+
Dataset.domcfg.jpk
17+
18+
Methods
19+
-------
20+
.. autosummary::
21+
:toctree: generated/
22+
:template: autosummary/accessor_method.rst
23+
24+
Dataset.domcfg.zco
25+
826
Domzgr
927
======
28+
.. currentmodule:: pydomcfg
1029

1130
Zco
1231
----
@@ -18,6 +37,8 @@ Zco
1837

1938
Utils
2039
=====
40+
.. currentmodule:: pydomcfg
41+
2142
.. autosummary::
2243
:toctree: generated/
2344

pydomcfg/__init__.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
from pkg_resources import DistributionNotFound, get_distribution
22

33
from . import utils
4+
from .accessor import Accessor
45

56
try:
67
__version__ = get_distribution("pydomcfg").version
78
except DistributionNotFound:
89
# package is not installed
910
__version__ = "unknown"
1011

11-
__all__ = ("utils",)
12+
__all__ = (
13+
"Accessor",
14+
"utils",
15+
)

pydomcfg/accessor.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
from typing import Any, Callable, TypeVar, cast
2+
3+
import xarray as xr
4+
from xarray import Dataset
5+
6+
from .domzgr.zco import Zco
7+
8+
F = TypeVar("F", bound=Callable[..., Any])
9+
10+
11+
def _jpk_check(func: F) -> F:
12+
"""
13+
Decorator to raise an error if jpk was not set
14+
"""
15+
16+
def wrapper(self, *args, **kwargs):
17+
if not self.jpk:
18+
raise ValueError(
19+
f"Set `jpk` before calling `obj.domcfg.{func.__name__}`."
20+
" For example: obj.domcfg.jpk = 31"
21+
)
22+
return func(self, *args, **kwargs)
23+
24+
return cast(F, wrapper)
25+
26+
27+
@xr.register_dataset_accessor("domcfg")
28+
class Accessor:
29+
def __init__(self, xarray_obj: Dataset):
30+
self._obj = xarray_obj
31+
self._jpk = 0
32+
33+
@property
34+
def jpk(self) -> int:
35+
"""
36+
Number of computational levels
37+
38+
Returns
39+
-------
40+
int
41+
"""
42+
return self._jpk
43+
44+
@jpk.setter
45+
def jpk(self, value: int):
46+
if value <= 0:
47+
raise ValueError("`jpk` MUST be > 0")
48+
self._jpk = value
49+
50+
@_jpk_check
51+
def zco(self, *args: Any, **kwargs: Any) -> Dataset:
52+
return Zco(self._obj, self._jpk)(*args, **kwargs)
53+
54+
zco.__doc__ = Zco.__call__.__doc__

pydomcfg/domzgr/zco.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ def __call__(
8888
8989
See Also
9090
--------
91+
pydomcfg.domzgr.zco.Zco.__call__: Underlying method.
9192
NEMOv4.0: ``domzgr/zgr_z`` subroutine
9293
9394
References

pydomcfg/tests/test_accessor.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import pytest
2+
3+
import pydomcfg # noqa: F401
4+
5+
from .bathymetry import Bathymetry
6+
7+
8+
def test_jpk():
9+
"""Make sure jpk is set correctly"""
10+
11+
# Initialize test class
12+
ds_bathy = Bathymetry(1.0e3, 1.2e3, 1, 1).flat(5.0e3)
13+
14+
# Can't call methods without setting jpk first
15+
with pytest.raises(ValueError, match="Set `jpk` before calling `obj.domcfg.zco`"):
16+
ds_bathy.domcfg.zco()
17+
18+
# jpk must be > 0
19+
with pytest.raises(ValueError, match="`jpk` MUST be > 0"):
20+
ds_bathy.domcfg.jpk = -1
21+
22+
# Has been set correctly.
23+
ds_bathy.domcfg.jpk = 1
24+
assert ds_bathy.domcfg.jpk == 1

pydomcfg/tests/test_zco.py

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import pytest
77
import xarray as xr
88

9-
from pydomcfg.domzgr.zco import Zco
9+
import pydomcfg # noqa: F401
1010

1111
from .bathymetry import Bathymetry
1212
from .data import ORCA2_VGRID
@@ -24,12 +24,12 @@ def test_zco_orca2():
2424
# Bathymetry dataset
2525
ds_bathy = Bathymetry(1.0e3, 1.2e3, 1, 1).flat(5.0e3)
2626

27-
# zco grid generator
28-
zco = Zco(ds_bathy, jpk=31)
27+
# Set number of vertical levels
28+
ds_bathy.domcfg.jpk = 31
2929

3030
# zco mesh with analytical e3 using ORCA2 input parameters
3131
# See pag 62 of v3.6 manual for the input parameters
32-
dsz_an = zco(
32+
dsz_an = ds_bathy.domcfg.zco(
3333
ppdzmin=10.0,
3434
pphmax=5000.0,
3535
ppkth=21.43336197938,
@@ -68,12 +68,12 @@ def test_zco_uniform():
6868
# Bathymetry dataset
6969
ds_bathy = Bathymetry(10.0e3, 1.2e3, 1, 1).flat(5.0e3)
7070

71-
# zco grid generator
72-
zco = Zco(ds_bathy, jpk=51)
71+
# Set number of vertical levels
72+
ds_bathy.domcfg.jpk = 31
7373

7474
# Compare zco mesh with analytical VS finite difference e3
75-
expected = zco(**kwargs, ln_e3_dep=True)
76-
actual = zco(**kwargs, ln_e3_dep=False)
75+
expected = ds_bathy.domcfg.zco(**kwargs, ln_e3_dep=True)
76+
actual = ds_bathy.domcfg.zco(**kwargs, ln_e3_dep=False)
7777
eps = 1.0e-14 # truncation errors
7878
xr.testing.assert_allclose(expected, actual, rtol=eps, atol=0)
7979

@@ -83,8 +83,8 @@ def test_zco_x_y_invariant():
8383

8484
# Generate 2x2 flat bathymetry dataset
8585
ds_bathy = Bathymetry(10.0e3, 1.2e3, 2, 2).flat(5.0e3)
86-
zco = Zco(ds_bathy, jpk=10)
87-
ds = zco(ppdzmin=10, pphmax=5.0e3)
86+
ds_bathy.domcfg.jpk = 10
87+
ds = ds_bathy.domcfg.zco(ppdzmin=10, pphmax=5.0e3)
8888

8989
# Check z3 and e3
9090
for varname in ["z3T", "z3W", "e3T", "e3W"]:
@@ -101,50 +101,50 @@ def test_zco_errors():
101101

102102
# Generate test data
103103
ds_bathy = Bathymetry(1.0e3, 1.2e3, 1, 1).flat(5.0e3)
104-
zco = Zco(ds_bathy, jpk=10)
104+
ds_bathy.domcfg.jpk = 10
105105

106106
# Without ldbletanh flag, only allow all pps set or none of them
107107
with pytest.raises(
108108
ValueError, match="ppa2, ppkth2 and ppacr2 MUST be all None or all float"
109109
):
110-
zco(**kwargs, ppa2=1, ppkth2=1, ppacr2=None)
110+
ds_bathy.domcfg.zco(**kwargs, ppa2=1, ppkth2=1, ppacr2=None)
111111

112112
# When ldbletanh flag is True, all coefficients must be specified
113113
with pytest.raises(ValueError, match="ppa2, ppkth2 and ppacr2 MUST be all float"):
114-
zco(**kwargs, ldbletanh=True, ppa2=1, ppkth2=1, ppacr2=None)
114+
ds_bathy.domcfg.zco(**kwargs, ldbletanh=True, ppa2=1, ppkth2=1, ppacr2=None)
115115

116116

117117
def test_zco_warnings():
118118
"""Make sure we warn when arguments are ignored"""
119119

120120
# Initialize test class
121121
ds_bathy = Bathymetry(1.0e3, 1.2e3, 1, 1).flat(5.0e3)
122-
zco = Zco(ds_bathy, jpk=10)
122+
ds_bathy.domcfg.jpk = 10
123123

124124
# Uniform: Ignore stretching
125125
kwargs = dict(ppdzmin=10, pphmax=5.0e3, ppkth=0, ppacr=0)
126-
expected = zco(**kwargs, ppsur=None, ppa0=999_999, ppa1=None)
126+
expected = ds_bathy.domcfg.zco(**kwargs, ppsur=None, ppa0=999_999, ppa1=None)
127127
with pytest.warns(
128128
UserWarning, match="ppsur, ppa0 and ppa1 are ignored when ppacr == ppkth == 0"
129129
):
130-
actual = zco(**kwargs, ppsur=2, ppa0=2, ppa1=2)
130+
actual = ds_bathy.domcfg.zco(**kwargs, ppsur=2, ppa0=2, ppa1=2)
131131
xr.testing.assert_identical(expected, actual)
132132

133133
# ldbletanh OFF: Ignore double tanh
134134
kwargs = dict(ppdzmin=10, pphmax=5.0e3, ldbletanh=False)
135-
expected = zco(**kwargs, ppa2=None, ppkth2=None, ppacr2=None)
135+
expected = ds_bathy.domcfg.zco(**kwargs, ppa2=None, ppkth2=None, ppacr2=None)
136136
with pytest.warns(
137137
UserWarning, match="ppa2, ppkth2 and ppacr2 are ignored when ldbletanh is False"
138138
):
139-
actual = zco(**kwargs, ppa2=2, ppkth2=2, ppacr2=2)
139+
actual = ds_bathy.domcfg.zco(**kwargs, ppa2=2, ppkth2=2, ppacr2=2)
140140
xr.testing.assert_identical(expected, actual)
141141

142142
# Uniform case: Ignore double tanh
143143
kwargs = dict(ppdzmin=10, pphmax=5.0e3, ppkth=0, ppacr=0)
144-
expected = zco(**kwargs, ppa2=None, ppkth2=None, ppacr2=None)
144+
expected = ds_bathy.domcfg.zco(**kwargs, ppa2=None, ppkth2=None, ppacr2=None)
145145
with pytest.warns(
146146
UserWarning,
147147
match="ppa2, ppkth2 and ppacr2 are ignored when ppacr == ppkth == 0",
148148
):
149-
actual = zco(**kwargs, ppa2=2, ppkth2=2, ppacr2=2)
149+
actual = ds_bathy.domcfg.zco(**kwargs, ppa2=2, ppkth2=2, ppacr2=2)
150150
xr.testing.assert_identical(expected, actual)

0 commit comments

Comments
 (0)