Skip to content

Commit

Permalink
EP-3617 improve handling/docs of deprecated/legacy DataCube methods
Browse files Browse the repository at this point in the history
  • Loading branch information
soxofaan committed Oct 15, 2020
1 parent 50ee995 commit 2c7e564
Show file tree
Hide file tree
Showing 6 changed files with 149 additions and 49 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

### Changed
- Deprecated legacy functions/methods are better documented as such and link to a recommended alternative (EP-3617).

### Removed
- Remove support for old, non-standard `stretch_colors` process (Use `linear_scale_range` instead).
Expand Down
8 changes: 4 additions & 4 deletions openeo/rest/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
from openeo.rest.job import RESTJob
from openeo.rest.rest_capabilities import RESTCapabilities
from openeo.rest.udp import RESTUserDefinedProcess, Parameter
from openeo.util import ensure_list
from openeo.util import ensure_list, legacy_alias

_log = logging.getLogger(__name__)

Expand Down Expand Up @@ -537,16 +537,16 @@ def capabilities(self) -> RESTCapabilities:
self._capabilities_cache["capabilities"] = RESTCapabilities(self.get('/').json())
return self._capabilities_cache["capabilities"]

@deprecated("Use 'list_output_formats' instead")
def list_file_types(self) -> dict:
return self.list_output_formats()


def list_output_formats(self) -> dict:
if self._api_version.at_least("1.0.0"):
return self.list_file_formats()["output"]
else:
return self.get('/output_formats').json()

list_file_types = legacy_alias(list_output_formats, "list_file_types")

def list_file_formats(self) -> dict:
"""
Get available input and output formats
Expand Down
36 changes: 11 additions & 25 deletions openeo/rest/datacube.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@
import logging
import pathlib
import typing
from builtins import staticmethod
from typing import List, Dict, Union, Tuple

import numpy
import numpy as np
import shapely.geometry
import shapely.geometry.base
Expand All @@ -29,10 +31,8 @@
from openeo.rest import BandMathException, OperatorException, OpenEoClientException
from openeo.rest.job import RESTJob
from openeo.rest.udp import RESTUserDefinedProcess
from openeo.util import get_temporal_extent, dict_no_none
from openeo.util import get_temporal_extent, dict_no_none, legacy_alias
from openeo.vectorcube import VectorCube
import numpy
from builtins import staticmethod

if hasattr(typing, 'TYPE_CHECKING') and typing.TYPE_CHECKING:
# Only import this for type hinting purposes. Runtime import causes circular dependency issues.
Expand Down Expand Up @@ -100,8 +100,7 @@ def process(self, process_id: str, arguments: dict = None, metadata: CollectionM
arguments=arguments,
), metadata=metadata)

# Legacy `graph_add_node` method
graph_add_node = deprecated(reason="just use `process()`")(process)
graph_add_node = legacy_alias(process, "graph_add_node")

def process_with_node(self, pg: PGNode, metadata: CollectionMetadata = None) -> 'DataCube':
"""
Expand Down Expand Up @@ -162,10 +161,7 @@ def load_collection(
metadata = metadata.filter_bands(bands)
return cls(graph=pg, connection=connection, metadata=metadata)

@classmethod
@deprecated("use load_collection instead")
def create_collection(cls, *args, **kwargs):
return cls.load_collection(*args, **kwargs)
create_collection = legacy_alias(load_collection, name="create_collection")

@classmethod
def load_disk_collection(cls, connection: 'openeo.Connection', file_format: str, glob_pattern: str,
Expand Down Expand Up @@ -230,9 +226,7 @@ def filter_bands(self, bands: Union[List[Union[str, int]], str]) -> 'DataCube':
cube.metadata = cube.metadata.filter_bands(bands)
return cube

@deprecated("use `filter_bands()` instead")
def band_filter(self, bands) -> 'DataCube':
return self.filter_bands(bands)
band_filter = legacy_alias(filter_bands, "band_filter")

def band(self, band: Union[str, int]) -> 'DataCube':
"""Filter the imagery by the given bands
Expand Down Expand Up @@ -682,22 +676,15 @@ def _create_run_udf(self, code, runtime, version) -> PGNode:
def reduce_temporal_udf(self, code: str, runtime="Python", version="latest"):
"""
Apply reduce (`reduce_dimension`) process with given UDF along temporal dimension.
"""
# TODO EP-3555: unify better with UDF(PGNode) class and avoid doing same UDF code-runtime-version argument stuff in each method
return self._reduce_temporal(reducer=self._create_run_udf(code, runtime, version))

@deprecated("use `reduce_temporal_udf` instead")
def reduce_tiles_over_time(self, code: str, runtime="Python", version="latest"):
"""
Applies a user defined function to a timeseries of tiles. The size of the tile is backend specific, and can be limited to one pixel.
The function should reduce the given timeseries into a single (multiband) tile.
:param code: The UDF code, compatible with the given runtime and version
:param runtime: The UDF runtime
:param version: The UDF runtime version
:return:
"""
return self.reduce_temporal_udf(code=code, runtime=runtime, version=version)
# TODO EP-3555: unify better with UDF(PGNode) class and avoid doing same UDF code-runtime-version argument stuff in each method
return self._reduce_temporal(reducer=self._create_run_udf(code, runtime, version))

reduce_tiles_over_time = legacy_alias(reduce_temporal_udf, name="reduce_tiles_over_time")

def apply_neighborhood(
self, process: [str, PGNode, typing.Callable],
Expand Down Expand Up @@ -999,8 +986,7 @@ def merge_cubes(
# TODO: set metadata of reduced cube?
return self.process(process_id="merge_cubes", arguments=arguments)

# Legacy alias
merge = merge_cubes
merge = legacy_alias(merge_cubes, name="merge")

def apply_kernel(self, kernel: Union[np.ndarray, List[List[float]]], factor=1.0, border = 0, replace_invalid=0) -> 'DataCube':
"""
Expand Down
26 changes: 7 additions & 19 deletions openeo/rest/imagecollectionclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from openeo.internal.graphbuilder_040 import GraphBuilder
from openeo.rest import BandMathException
from openeo.rest.job import RESTJob
from openeo.util import get_temporal_extent
from openeo.util import get_temporal_extent, legacy_alias
from shapely.geometry import Polygon, MultiPolygon, mapping

if hasattr(typing, 'TYPE_CHECKING') and typing.TYPE_CHECKING:
Expand Down Expand Up @@ -85,10 +85,7 @@ def load_collection(
metadata = metadata.filter_bands(bands)
return cls(node_id, builder, session, metadata=metadata)

@classmethod
@deprecated("use load_collection instead")
def create_collection(cls, *args, **kwargs):
return cls.load_collection(*args, **kwargs)
create_collection = legacy_alias(load_collection, "create_collection")

@classmethod
def load_disk_collection(cls, session: 'Connection', file_format: str, glob_pattern: str, **options) -> 'ImageCollection':
Expand Down Expand Up @@ -158,9 +155,7 @@ def filter_bands(self, bands: Union[List[Union[str, int]], str]) -> 'ImageCollec
im.metadata = im.metadata.filter_bands(bands)
return im

@deprecated("use `filter_bands()` instead")
def band_filter(self, bands) -> ImageCollection:
return self.filter_bands(bands)
band_filter = legacy_alias(filter_bands, "band_filter")

def band(self, band: Union[str, int]) -> 'ImageCollection':
"""Filter the imagery by the given bands
Expand Down Expand Up @@ -626,22 +621,13 @@ def _create_run_udf(self, code, runtime, version):
"result": True
}

@deprecated("use `reduce_temporal_udf` instead")
def reduce_tiles_over_time(self,code: str,runtime="Python",version="latest"):
def reduce_temporal_udf(self, code: str, runtime="Python", version="latest"):
"""
Applies a user defined function to a timeseries of tiles. The size of the tile is backend specific, and can be limited to one pixel.
The function should reduce the given timeseries into a single (multiband) tile.
Apply reduce (`reduce_dimension`) process with given UDF along temporal dimension.
:param code: The UDF code, compatible with the given runtime and version
:param runtime: The UDF runtime
:param version: The UDF runtime version
:return:
"""
return self.reduce_temporal_udf(code=code, runtime=runtime, version=version)

def reduce_temporal_udf(self, code: str, runtime="Python", version="latest"):
"""
Apply reduce (`reduce_dimension`) process with given UDF along temporal dimension.
"""
process_id = 'reduce'
args = {
Expand All @@ -658,6 +644,8 @@ def reduce_temporal_udf(self, code: str, runtime="Python", version="latest"):
}
return self.graph_add_process(process_id, args)

reduce_tiles_over_time = legacy_alias(reduce_temporal_udf, "reduce_tiles_over_time")

def apply(self, process: str, data_argument='data',arguments={}) -> 'ImageCollection':
process_id = 'apply'
arguments[data_argument] = \
Expand Down
47 changes: 47 additions & 0 deletions openeo/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
"""
import datetime as dt
import functools
import inspect
import json
import logging
import os
import platform
import re
import warnings
from collections import OrderedDict
from pathlib import Path
from typing import Any, Union, Tuple, Callable
Expand Down Expand Up @@ -427,3 +429,48 @@ def get_user_data_dir(app_name=DEFAULT_APP_NAME, auto_create=True) -> Path:
fallback='~/.local/share', win_fallback='~\\AppData\\Roaming', macos_fallback='~/Library',
auto_create=auto_create
)


def legacy_alias(orig: Callable, name: str, action="always", category=DeprecationWarning):
"""
Create legacy alias of given function/method/classmethod/staticmethod
:param orig: function/method to create legacy alias for
:param name: name of the alias
:return:
"""
post_process = None
if isinstance(orig, classmethod):
post_process = classmethod
orig = orig.__func__
kind = "class method"
elif isinstance(orig, staticmethod):
post_process = staticmethod
orig = orig.__func__
kind = "static method"
elif inspect.ismethod(orig) or "self" in inspect.signature(orig).parameters:
kind = "method"
elif inspect.isfunction(orig):
kind = "function"
else:
raise ValueError(orig)

msg = "Call to deprecated {k} `{n}`, use `{o}` instead.".format(k=kind, n=name, o=orig.__name__)

@functools.wraps(orig)
def wrapper(*args, **kwargs):
# This is based on warning handling/throwing implemented in `deprecated` package
with warnings.catch_warnings():
warnings.simplefilter(action, category)
warnings.warn(msg, category=category, stacklevel=2)

return orig(*args, **kwargs)

# TODO: make this more Sphinx aware
wrapper.__doc__ = "Use of this legacy {k} is deprecated, use :py:{r}:`.{o}` instead.".format(
k=kind, r="meth" if "method" in kind else "func", o=orig.__name__
)

if post_process:
wrapper = post_process(wrapper)
return wrapper
80 changes: 79 additions & 1 deletion tests/test_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import pytest

from openeo.util import first_not_none, get_temporal_extent, TimingLogger, ensure_list, ensure_dir, dict_no_none, \
deep_get, DeepKeyError, get_user_config_dir, get_user_data_dir, Rfc3339, rfc3339, deep_set
deep_get, DeepKeyError, get_user_config_dir, get_user_data_dir, Rfc3339, rfc3339, deep_set, legacy_alias


def test_rfc3339_date():
Expand Down Expand Up @@ -380,3 +380,81 @@ def test_get_user_config_dir():

def test_get_user_data_dir():
assert get_user_data_dir() == pathlib.Path(__file__).parent / "data/user_dirs/data/openeo-python-client"


def test_legacy_alias_function(recwarn):
def add(x, y):
"""Add x and y."""
return x + y

do_plus = legacy_alias(add, "do_plus")

assert add.__doc__ == "Add x and y."
assert do_plus.__doc__ == "Use of this legacy function is deprecated, use :py:func:`.add` instead."

assert add(2, 3) == 5
assert len(recwarn) == 0

with pytest.warns(DeprecationWarning, match="Call to deprecated function `do_plus`, use `add` instead."):
res = do_plus(2, 3)
assert res == 5


def test_legacy_alias_method(recwarn):
class Foo:
def add(self, x, y):
"""Add x and y."""
return x + y

do_plus = legacy_alias(add, "do_plus")

assert Foo.add.__doc__ == "Add x and y."
assert Foo.do_plus.__doc__ == "Use of this legacy method is deprecated, use :py:meth:`.add` instead."

assert Foo().add(2, 3) == 5
assert len(recwarn) == 0

with pytest.warns(DeprecationWarning, match="Call to deprecated method `do_plus`, use `add` instead."):
res = Foo().do_plus(2, 3)
assert res == 5


def test_legacy_alias_classmethod(recwarn):
class Foo:
@classmethod
def add(cls, x, y):
"""Add x and y."""
assert cls is Foo
return x + y

do_plus = legacy_alias(add, "do_plus")

assert Foo.add.__doc__ == "Add x and y."
assert Foo.do_plus.__doc__ == "Use of this legacy class method is deprecated, use :py:meth:`.add` instead."

assert Foo().add(2, 3) == 5
assert len(recwarn) == 0

with pytest.warns(DeprecationWarning, match="Call to deprecated class method `do_plus`, use `add` instead."):
res = Foo().do_plus(2, 3)
assert res == 5


def test_legacy_alias_staticmethod(recwarn):
class Foo:
@staticmethod
def add(x, y):
"""Add x and y."""
return x + y

do_plus = legacy_alias(add, "do_plus")

assert Foo.add.__doc__ == "Add x and y."
assert Foo.do_plus.__doc__ == "Use of this legacy static method is deprecated, use :py:meth:`.add` instead."

assert Foo().add(2, 3) == 5
assert len(recwarn) == 0

with pytest.warns(DeprecationWarning, match="Call to deprecated static method `do_plus`, use `add` instead."):
res = Foo().do_plus(2, 3)
assert res == 5

0 comments on commit 2c7e564

Please sign in to comment.