Skip to content

Commit

Permalink
Python: Improve docstrings and output formatting
Browse files Browse the repository at this point in the history
  • Loading branch information
dbaston committed Aug 28, 2024
1 parent 83a6680 commit 0200bf6
Show file tree
Hide file tree
Showing 6 changed files with 104 additions and 36 deletions.
21 changes: 16 additions & 5 deletions python/doc/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#

import datetime
import re

# -- Project information -----------------------------------------------------

Expand Down Expand Up @@ -67,17 +68,27 @@
autoapi_dirs = ["../src/exactextract"]
autoapi_keep_files = False
autoapi_add_toctree_entry = False
autoapi_python_class_content = "both" # include both class and __init__ docstrings

autoapi_options = ["members", "undoc-members", "show-module-summary", "special-members"]


def autoapi_skip_member(app, what, name, obj, skip, options):
if name.startswith("exactextract.processor"):

shortname = name.split(".")[-1]

# Don't emit documentation for anything named beginning with
# an underscore, except for __init__
if re.match("^_[a-z]", shortname):
return True
if name.startswith("exactextract.feature."):

# Don't emit documentation for member variables, which are
# assumed to be private even if not prefixed with an
# underscore.
if what == "attribute":
return True
if name.startswith("_"):
return name != "__init__"

return False
return None # Fallback to default behavior


def setup(sphinx):
Expand Down
3 changes: 0 additions & 3 deletions python/src/exactextract/exact_extract.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,10 +177,8 @@ def prep_ops(stats, values, weights=None, *, add_unique=False):
# these operations to create a set of PythonOperations.
nargs = stat.__code__.co_argcount
if nargs == 3:
weighted = True
dummy_stat = "weighted_sum"
elif nargs == 2:
weighted = False
dummy_stat = "count"
else:
raise Exception(
Expand All @@ -195,7 +193,6 @@ def prep_ops(stats, values, weights=None, *, add_unique=False):
op.name.replace(dummy_stat, stat.__name__),
op.values,
op.weights,
weighted,
)
)
except RuntimeError as e:
Expand Down
26 changes: 17 additions & 9 deletions python/src/exactextract/feature.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@
from ._exactextract import Feature
from ._exactextract import FeatureSource as _FeatureSource

__all__ = [
"FeatureSource",
"GDALFeatureSource",
"JSONFeatureSource",
"GeoPandasFeatureSource",
"QGISFeatureSource",
]


class FeatureSource(_FeatureSource):
"""
Expand All @@ -13,6 +21,7 @@ class FeatureSource(_FeatureSource):
- :py:class:`GDALFeatureSource`
- :py:class:`JSONFeatureSource`
- :py:class:`GeoPandasFeatureSource`
- :py:class:`QGISFeatureSource`
"""

def __init__(self):
Expand All @@ -26,17 +35,14 @@ class GDALFeatureSource(FeatureSource):

def __init__(self, src):
"""
Create a GDALFeatureSource.
Args:
src: one of the following:
src: one of the following
- string or Path to a file/datasource that can be opened with GDAL/OGR
- a `gdal.Dataset`
- an `ogr.DataSource`
- an `ogr.Layer`
If the file has more than one layer, e.g., a GeoPackage, an `ogr.Layer` must be provided
directly.
- a ``gdal.Dataset``
- an ``ogr.DataSource``
- an ``ogr.Layer``
If the file has more than one layer, e.g., a GeoPackage, an
``ogr.Layer`` must be provided directly.
"""
super().__init__()

Expand Down Expand Up @@ -265,6 +271,8 @@ def default(self, obj):


class QGISFeatureSource(FeatureSource):
"""FeatureSource providing features from a QGIS vector layer."""

def __init__(self, src):
super().__init__()
self.src = src
Expand Down
58 changes: 49 additions & 9 deletions python/src/exactextract/operation.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
from __future__ import annotations

from typing import Mapping, Optional
from typing import Callable, Mapping, Optional

from ._exactextract import Operation as _Operation
from ._exactextract import PythonOperation # noqa: F401
from ._exactextract import PythonOperation as _PythonOperation
from ._exactextract import change_stat # noqa: F401
from ._exactextract import prepare_operations # noqa: F401
from .raster import RasterSource

__all__ = ["Operation", "PythonOperation"]


class Operation(_Operation):
"""
Summary of pixel values
Summarize of pixel values using a built-in function
Defines a summary operation to be performed on pixel values intersecting
a geometry. May return a scalar (e.g., ``weighted_mean``), or a
Expand All @@ -27,13 +29,11 @@ def __init__(
options: Optional[Mapping] = None,
):
"""
Create an Operation
Args:
stat_name (str): Name of the stat. Refer to docs for options.
field_name (str): Name of the result field that is assigned by this Operation.
raster (RasterSource): Raster to compute over.
weights (Optional[RasterSource], optional): Weight raster to use. Defaults to None.
stat_name: Name of the stat. Refer to docs for options.
field_name: Name of the result field that is assigned by this Operation.
raster: Raster to compute over.
weights: Weight raster to use. Defaults to None.
options: Arguments used to control the behavior of an Operation, e.g. ``options={"q": 0.667}``
with ``stat_name = "quantile"``
"""
Expand All @@ -46,3 +46,43 @@ def __init__(
args = {str(k): str(v) for k, v in options.items()}

super().__init__(stat_name, field_name, raster, weights, args)


class PythonOperation(_PythonOperation):
"""
Summarize of pixel values using a Python function
Defines a summary operation to be performed on pixel values intersecting
a geometry.
"""

def __init__(
self,
function: Callable,
field_name: str,
raster: RasterSource,
weights: Optional[RasterSource],
):
"""
Args:
function: Function accepting either two arguments (if `weights` is `None`),
or three arguments. The function will be called with
arrays of equal length containing:
- pixel values from `raster` (masked array)
- cell coverage fractions
- pixel values from `weights` (masked array)
field_name: Name of the result field that is assigned by this Operation.
raster: Raster to compute over.
weights: Weight raster to use. Defaults to None.
"""

if raster is None:
raise TypeError

# Inspect the function to determine if it should be called with
# or without weights. This allows us to pass weights even if
# they are unused, which is important to have weighted and
# unweighted stats using the same common grid.
weighted = function.__code__.co_argcount == 3

super().__init__(function, field_name, raster, weights, weighted)
12 changes: 7 additions & 5 deletions python/src/exactextract/processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
from .operation import Operation
from .writer import Writer

__all__ = ["FeatureSequentialProcessor", "RasterSequentialProcessor"]


class FeatureSequentialProcessor(_FeatureSequentialProcessor):
"""Binding class around exactextract FeatureSequentialProcessor"""
Expand All @@ -19,12 +21,12 @@ def __init__(
include_cols: Optional[List[Operation]] = None,
):
"""
Create FeatureSequentialProcessor object
Args:
ds (FeatureSource): Dataset to use
writer (Writer): Writer to use
op_list (List[Operation]): List of operations
op_list: List of Operations to perform
include_cols: List of columns to copy from
input features
"""
super().__init__(ds, writer)
for col in include_cols or []:
Expand All @@ -44,12 +46,12 @@ def __init__(
include_cols: Optional[List[Operation]] = None,
):
"""
Create RasterSequentialProcessor object
Args:
ds (FeatureSource): Dataset to use
writer (Writer): Writer to use
op_list (List[Operation]): List of operations
include_cols: List of columns to copy from
input features
"""
super().__init__(ds, writer)
for col in include_cols or []:
Expand Down
20 changes: 15 additions & 5 deletions python/src/exactextract/writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
from ._exactextract import Writer as _Writer
from .feature import GDALFeature, JSONFeature, QGISFeature

__all__ = ["Writer", "JSONWriter", "PandasWriter", "QGISWriter", "GDALWriter"]


class Writer(_Writer):
"""Writes the results of summary operations to a desired format"""
Expand All @@ -25,14 +27,12 @@ def __init__(
map_fields: Optional[Mapping[str, Tuple[str]]] = None
):
"""
Create a new JSONWriter.
Args:
array_type: type that should be used to represent array outputs.
Either "numpy" (default), "list", or "set"
map_fields: An optional dictionary of fields to be created by
either "numpy" (default), "list", or "set"
map_fields: an optional dictionary of fields to be created by
interpreting one field as keys and another as values, in the format
``{ dst_field : (src_keys, src_vals) }``. For example, the fields
``{ dst_field : (src_keys, src_vals) }``. for example, the fields
"values" and "frac" would be combined into a field called
"frac_map" using ``map_fields = {"frac_map": ("values", "frac")}``.
"""
Expand Down Expand Up @@ -321,6 +321,16 @@ class GDALWriter(Writer):
def __init__(
self, dataset=None, *, filename=None, driver=None, layer_name="", srs_wkt=None
):
"""
Args:
dataset: a ``gdal.Dataset`` or ``ogr.DataSource`` to which results
should be created in a new layer
filename: file to write results to, if ``dataset`` is ``None``
driver: driver to use when creating ``filename``
layer_name: name of new layer to create in output dataset
srs_wkt: spatial reference system to assign to output dataset. No
coordinate transformation will be performed.
"""
super().__init__()

from osgeo import gdal, ogr
Expand Down

0 comments on commit 0200bf6

Please sign in to comment.