Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

built-in accessor documentation #3988

Merged
merged 25 commits into from
Jun 13, 2020
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
0d808d4
ad a property-like descriptor that works both on objects and classes
keewis Apr 21, 2020
8b60a2a
generate documentation for the plotting accessor methods
keewis Apr 21, 2020
fc0fe04
Merge branch 'master' into accessor-documentation
keewis Apr 21, 2020
4f3780f
add a docstring to the custom property-like descriptor
keewis Apr 21, 2020
001c8d0
use the accessor syntax in the main plotting section
keewis Apr 22, 2020
ee183b0
explain why we need a custom property class
keewis Apr 22, 2020
c96663f
rename the custom property to UncachedAccessor
keewis Apr 22, 2020
261c69f
declare that __call__ wraps plot
keewis Apr 22, 2020
6ba1b52
add accessor tests
keewis Apr 23, 2020
e904e65
Merge branch 'master' into accessor-documentation
keewis May 3, 2020
eb54df8
add the autosummary templates from pandas
keewis May 7, 2020
b2b5432
update the plotting section to use the accessor templates
keewis May 7, 2020
f6a3eaa
Merge branch 'master' into accessor-documentation
keewis May 7, 2020
c933365
remove the separate callable section
keewis May 7, 2020
a456bd1
fix the import order
keewis May 8, 2020
08cb916
add the DataArray.str accessor as a new subsection
keewis May 14, 2020
00fa2e6
Merge branch 'master' into accessor-documentation
keewis Jun 1, 2020
eaf95c4
add the datetime accessor to the main api page
keewis Jun 1, 2020
494c4f1
move the plotting functions into the DataArray / Dataset sections
keewis Jun 1, 2020
1e45275
remove the documentation of the accessor class itself
keewis Jun 2, 2020
371bd0f
manually copy the docstring since functools.wraps does more than that
keewis Jun 2, 2020
5cd9f12
also copy the annotations and mark __call__ as wrapping plot
keewis Jun 2, 2020
7df8068
re-enable __slots__
keewis Jun 2, 2020
6fb736b
update whats-new.rst
keewis Jun 8, 2020
4940b53
Merge branch 'master' into accessor-documentation
dcherian Jun 12, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions doc/_templates/autosummary/accessor.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{{ fullname }}
{{ underline }}

.. currentmodule:: {{ module.split('.')[0] }}

.. autoaccessor:: {{ (module.split('.')[1:] + [objname]) | join('.') }}
6 changes: 6 additions & 0 deletions doc/_templates/autosummary/accessor_attribute.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{{ fullname }}
{{ underline }}

.. currentmodule:: {{ module.split('.')[0] }}

.. autoaccessorattribute:: {{ (module.split('.')[1:] + [objname]) | join('.') }}
6 changes: 6 additions & 0 deletions doc/_templates/autosummary/accessor_callable.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{{ fullname }}
{{ underline }}

.. currentmodule:: {{ module.split('.')[0] }}

.. autoaccessorcallable:: {{ (module.split('.')[1:] + [objname]) | join('.') }}.__call__
6 changes: 6 additions & 0 deletions doc/_templates/autosummary/accessor_method.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{{ fullname }}
{{ underline }}

.. currentmodule:: {{ module.split('.')[0] }}

.. autoaccessormethod:: {{ (module.split('.')[1:] + [objname]) | join('.') }}
77 changes: 66 additions & 11 deletions doc/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,56 @@ Computation
:py:attr:`~core.groupby.DataArrayGroupBy.where`
:py:attr:`~core.groupby.DataArrayGroupBy.quantile`


String manipulation
-------------------

.. autosummary::
:toctree: generated/
:template: autosummary/accessor_method.rst
keewis marked this conversation as resolved.
Show resolved Hide resolved

DataArray.str.capitalize
keewis marked this conversation as resolved.
Show resolved Hide resolved
DataArray.str.center
DataArray.str.contains
DataArray.str.count
DataArray.str.decode
DataArray.str.encode
DataArray.str.endswith
DataArray.str.find
DataArray.str.get
DataArray.str.index
DataArray.str.isalnum
DataArray.str.isalpha
DataArray.str.isdecimal
DataArray.str.isdigit
DataArray.str.isnumeric
DataArray.str.isspace
DataArray.str.istitle
DataArray.str.isupper
DataArray.str.len
DataArray.str.ljust
DataArray.str.lower
DataArray.str.lstrip
DataArray.str.match
DataArray.str.pad
DataArray.str.repeat
DataArray.str.replace
DataArray.str.rfind
DataArray.str.rindex
DataArray.str.rjust
DataArray.str.rstrip
DataArray.str.slice
DataArray.str.slice_replace
DataArray.str.startswith
DataArray.str.strip
DataArray.str.swapcase
DataArray.str.title
DataArray.str.translate
DataArray.str.upper
DataArray.str.wrap
DataArray.str.zfill


Reshaping and reorganizing
--------------------------

Expand Down Expand Up @@ -669,19 +719,24 @@ Plotting

.. autosummary::
:toctree: generated/
:template: autosummary/accessor.rst

Dataset.plot
plot.scatter
DataArray.plot
plot.plot
plot.contourf
plot.contour
plot.hist
plot.imshow
plot.line
plot.pcolormesh
plot.step
plot.FacetGrid
Dataset.plot

.. autosummary::
:toctree: generated/
:template: autosummary/accessor_method.rst

Dataset.plot.scatter
DataArray.plot.contourf
DataArray.plot.contour
DataArray.plot.hist
DataArray.plot.imshow
DataArray.plot.line
DataArray.plot.pcolormesh
DataArray.plot.step


Faceting
--------
Expand Down
116 changes: 116 additions & 0 deletions doc/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@
import sys
from contextlib import suppress

# --------- autosummary templates ------------------
# TODO: eventually replace this with a sphinx.ext.auto_accessor module
import sphinx
from sphinx.ext.autodoc import AttributeDocumenter, Documenter, MethodDocumenter
from sphinx.util import rpartition

# make sure the source version is preferred (#3567)
root = pathlib.Path(__file__).absolute().parent.parent
os.environ["PYTHONPATH"] = str(root)
Expand Down Expand Up @@ -358,3 +364,113 @@
"dask": ("https://docs.dask.org/en/latest", None),
"cftime": ("https://unidata.github.io/cftime", None),
}


# --------- autosummary templates ------------------
# TODO: eventually replace this with a sphinx.ext.auto_accessor module
class AccessorDocumenter(MethodDocumenter):
"""
Specialized Documenter subclass for accessors.
"""

objtype = "accessor"
directivetype = "method"

# lower than MethodDocumenter so this is not chosen for normal methods
priority = 0.6

def format_signature(self):
# this method gives an error/warning for the accessors, therefore
# overriding it (accessor has no arguments)
return ""


class AccessorLevelDocumenter(Documenter):
"""
Specialized Documenter subclass for objects on accessor level (methods,
attributes).
"""

# This is the simple straightforward version
# modname is None, base the last elements (eg 'hour')
# and path the part before (eg 'Series.dt')
# def resolve_name(self, modname, parents, path, base):
# modname = 'pandas'
# mod_cls = path.rstrip('.')
# mod_cls = mod_cls.split('.')
#
# return modname, mod_cls + [base]

def resolve_name(self, modname, parents, path, base):
if modname is None:
if path:
mod_cls = path.rstrip(".")
else:
mod_cls = None
# if documenting a class-level object without path,
# there must be a current class, either from a parent
# auto directive ...
mod_cls = self.env.temp_data.get("autodoc:class")
# ... or from a class directive
if mod_cls is None:
mod_cls = self.env.temp_data.get("py:class")
# ... if still None, there's no way to know
if mod_cls is None:
return None, []
# HACK: this is added in comparison to ClassLevelDocumenter
# mod_cls still exists of class.accessor, so an extra
# rpartition is needed
modname, accessor = rpartition(mod_cls, ".")
modname, cls = rpartition(modname, ".")
parents = [cls, accessor]
# if the module name is still missing, get it like above
if not modname:
modname = self.env.temp_data.get("autodoc:module")
if not modname:
if sphinx.__version__ > "1.3":
modname = self.env.ref_context.get("py:module")
else:
modname = self.env.temp_data.get("py:module")
# ... else, it stays None, which means invalid
return modname, parents + [base]


class AccessorAttributeDocumenter(AccessorLevelDocumenter, AttributeDocumenter):

objtype = "accessorattribute"
directivetype = "attribute"

# lower than AttributeDocumenter so this is not chosen for normal attributes
priority = 0.6


class AccessorMethodDocumenter(AccessorLevelDocumenter, MethodDocumenter):

objtype = "accessormethod"
directivetype = "method"

# lower than MethodDocumenter so this is not chosen for normal methods
priority = 0.6


class AccessorCallableDocumenter(AccessorLevelDocumenter, MethodDocumenter):
"""
This documenter lets us removes .__call__ from the method signature for
callable accessors like Series.plot
"""

objtype = "accessorcallable"
directivetype = "method"

# lower than MethodDocumenter; otherwise the doc build prints warnings
priority = 0.5

def format_name(self):
return MethodDocumenter.format_name(self).rstrip(".__call__")


def setup(app):
app.add_autodocumenter(AccessorDocumenter)
app.add_autodocumenter(AccessorAttributeDocumenter)
app.add_autodocumenter(AccessorMethodDocumenter)
app.add_autodocumenter(AccessorCallableDocumenter)
21 changes: 2 additions & 19 deletions xarray/core/dataarray.py
Original file line number Diff line number Diff line change
Expand Up @@ -2670,24 +2670,7 @@ def func(self, other):
def _copy_attrs_from(self, other: Union["DataArray", Dataset, Variable]) -> None:
self.attrs = other.attrs

@property
def plot(self) -> _PlotMethods:
"""
Access plotting functions for DataArray's

>>> d = xr.DataArray([[1, 2], [3, 4]])

For convenience just call this directly

>>> d.plot()

Or use it as a namespace to use xarray.plot functions as
DataArray methods

>>> d.plot.imshow() # equivalent to xarray.plot.imshow(d)

"""
return _PlotMethods(self)
plot = utils.UncachedAccessor(_PlotMethods)

def _title_for_slice(self, truncate: int = 50) -> str:
"""
Expand Down Expand Up @@ -3733,7 +3716,7 @@ def idxmax(

# this needs to be at the end, or mypy will confuse with `str`
# https://mypy.readthedocs.io/en/latest/common_issues.html#dealing-with-conflicting-names
str = property(StringAccessor)
str = utils.UncachedAccessor(StringAccessor)


# priority most be higher than Variable to properly work with binary ufuncs
Expand Down
11 changes: 1 addition & 10 deletions xarray/core/dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -5563,16 +5563,7 @@ def real(self):
def imag(self):
return self._unary_op(lambda x: x.imag, keep_attrs=True)(self)

@property
def plot(self):
"""
Access plotting functions for Datasets.
Use it as a namespace to use xarray.plot functions as Dataset methods

>>> ds.plot.scatter(...) # equivalent to xarray.plot.scatter(ds,...)

"""
return _Dataset_PlotMethods(self)
plot = utils.UncachedAccessor(_Dataset_PlotMethods)

def filter_by_attrs(self, **kwargs):
"""Returns a ``Dataset`` with variables that match specific conditions.
Expand Down
18 changes: 18 additions & 0 deletions xarray/core/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -787,6 +787,24 @@ def drop_dims_from_indexers(
)


class UncachedAccessor:
""" Acts like a property, but on both classes and class instances

This class is necessary because some tools (e.g. pydoc and sphinx)
inspect classes for which property returns itself and not the
accessor.
"""

def __init__(self, accessor):
self._accessor = accessor

def __get__(self, obj, cls):
if obj is None:
return self._accessor

return self._accessor(obj)


# Singleton type, as per https://github.com/python/typing/pull/240
class Default(Enum):
token = 0
Expand Down
3 changes: 2 additions & 1 deletion xarray/plot/plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -437,11 +437,12 @@ class _PlotMethods:
For example, DataArray.plot.imshow
"""

__slots__ = ("_da",)
# __slots__ = ("_da",)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


def __init__(self, darray):
self._da = darray

@functools.wraps(plot)
def __call__(self, **kwargs):
return plot(self._da, **kwargs)

Expand Down
12 changes: 12 additions & 0 deletions xarray/tests/test_plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,12 @@ class TestPlot(PlotTestCase):
def setup_array(self):
self.darray = DataArray(easy_array((2, 3, 4)))

def test_accessor(self):
from ..plot.plot import _PlotMethods

assert DataArray.plot is _PlotMethods
assert isinstance(self.darray.plot, _PlotMethods)

def test_label_from_attrs(self):
da = self.darray.copy()
assert "" == label_from_attrs(da)
Expand Down Expand Up @@ -2098,6 +2104,12 @@ def setUp(self):
ds.B.attrs["units"] = "Bunits"
self.ds = ds

def test_accessor(self):
from ..plot.dataset_plot import _Dataset_PlotMethods

assert Dataset.plot is _Dataset_PlotMethods
assert isinstance(self.ds.plot, _Dataset_PlotMethods)

@pytest.mark.parametrize(
"add_guide, hue_style, legend, colorbar",
[
Expand Down