Skip to content

Commit 2c7e564

Browse files
committed
EP-3617 improve handling/docs of deprecated/legacy DataCube methods
1 parent 50ee995 commit 2c7e564

File tree

6 files changed

+149
-49
lines changed

6 files changed

+149
-49
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99
### Added
1010

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

1314
### Removed
1415
- Remove support for old, non-standard `stretch_colors` process (Use `linear_scale_range` instead).

openeo/rest/connection.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
from openeo.rest.job import RESTJob
3131
from openeo.rest.rest_capabilities import RESTCapabilities
3232
from openeo.rest.udp import RESTUserDefinedProcess, Parameter
33-
from openeo.util import ensure_list
33+
from openeo.util import ensure_list, legacy_alias
3434

3535
_log = logging.getLogger(__name__)
3636

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

540-
@deprecated("Use 'list_output_formats' instead")
541-
def list_file_types(self) -> dict:
542-
return self.list_output_formats()
540+
543541

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

548+
list_file_types = legacy_alias(list_output_formats, "list_file_types")
549+
550550
def list_file_formats(self) -> dict:
551551
"""
552552
Get available input and output formats

openeo/rest/datacube.py

Lines changed: 11 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@
1212
import logging
1313
import pathlib
1414
import typing
15+
from builtins import staticmethod
1516
from typing import List, Dict, Union, Tuple
1617

18+
import numpy
1719
import numpy as np
1820
import shapely.geometry
1921
import shapely.geometry.base
@@ -29,10 +31,8 @@
2931
from openeo.rest import BandMathException, OperatorException, OpenEoClientException
3032
from openeo.rest.job import RESTJob
3133
from openeo.rest.udp import RESTUserDefinedProcess
32-
from openeo.util import get_temporal_extent, dict_no_none
34+
from openeo.util import get_temporal_extent, dict_no_none, legacy_alias
3335
from openeo.vectorcube import VectorCube
34-
import numpy
35-
from builtins import staticmethod
3636

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

103-
# Legacy `graph_add_node` method
104-
graph_add_node = deprecated(reason="just use `process()`")(process)
103+
graph_add_node = legacy_alias(process, "graph_add_node")
105104

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

165-
@classmethod
166-
@deprecated("use load_collection instead")
167-
def create_collection(cls, *args, **kwargs):
168-
return cls.load_collection(*args, **kwargs)
164+
create_collection = legacy_alias(load_collection, name="create_collection")
169165

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

233-
@deprecated("use `filter_bands()` instead")
234-
def band_filter(self, bands) -> 'DataCube':
235-
return self.filter_bands(bands)
229+
band_filter = legacy_alias(filter_bands, "band_filter")
236230

237231
def band(self, band: Union[str, int]) -> 'DataCube':
238232
"""Filter the imagery by the given bands
@@ -682,22 +676,15 @@ def _create_run_udf(self, code, runtime, version) -> PGNode:
682676
def reduce_temporal_udf(self, code: str, runtime="Python", version="latest"):
683677
"""
684678
Apply reduce (`reduce_dimension`) process with given UDF along temporal dimension.
685-
"""
686-
# TODO EP-3555: unify better with UDF(PGNode) class and avoid doing same UDF code-runtime-version argument stuff in each method
687-
return self._reduce_temporal(reducer=self._create_run_udf(code, runtime, version))
688-
689-
@deprecated("use `reduce_temporal_udf` instead")
690-
def reduce_tiles_over_time(self, code: str, runtime="Python", version="latest"):
691-
"""
692-
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.
693-
The function should reduce the given timeseries into a single (multiband) tile.
694679
695680
:param code: The UDF code, compatible with the given runtime and version
696681
:param runtime: The UDF runtime
697682
:param version: The UDF runtime version
698-
:return:
699683
"""
700-
return self.reduce_temporal_udf(code=code, runtime=runtime, version=version)
684+
# TODO EP-3555: unify better with UDF(PGNode) class and avoid doing same UDF code-runtime-version argument stuff in each method
685+
return self._reduce_temporal(reducer=self._create_run_udf(code, runtime, version))
686+
687+
reduce_tiles_over_time = legacy_alias(reduce_temporal_udf, name="reduce_tiles_over_time")
701688

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

1002-
# Legacy alias
1003-
merge = merge_cubes
989+
merge = legacy_alias(merge_cubes, name="merge")
1004990

1005991
def apply_kernel(self, kernel: Union[np.ndarray, List[List[float]]], factor=1.0, border = 0, replace_invalid=0) -> 'DataCube':
1006992
"""

openeo/rest/imagecollectionclient.py

Lines changed: 7 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from openeo.internal.graphbuilder_040 import GraphBuilder
1111
from openeo.rest import BandMathException
1212
from openeo.rest.job import RESTJob
13-
from openeo.util import get_temporal_extent
13+
from openeo.util import get_temporal_extent, legacy_alias
1414
from shapely.geometry import Polygon, MultiPolygon, mapping
1515

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

88-
@classmethod
89-
@deprecated("use load_collection instead")
90-
def create_collection(cls, *args, **kwargs):
91-
return cls.load_collection(*args, **kwargs)
88+
create_collection = legacy_alias(load_collection, "create_collection")
9289

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

161-
@deprecated("use `filter_bands()` instead")
162-
def band_filter(self, bands) -> ImageCollection:
163-
return self.filter_bands(bands)
158+
band_filter = legacy_alias(filter_bands, "band_filter")
164159

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

629-
@deprecated("use `reduce_temporal_udf` instead")
630-
def reduce_tiles_over_time(self,code: str,runtime="Python",version="latest"):
624+
def reduce_temporal_udf(self, code: str, runtime="Python", version="latest"):
631625
"""
632-
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.
633-
The function should reduce the given timeseries into a single (multiband) tile.
626+
Apply reduce (`reduce_dimension`) process with given UDF along temporal dimension.
634627
635628
:param code: The UDF code, compatible with the given runtime and version
636629
:param runtime: The UDF runtime
637630
:param version: The UDF runtime version
638-
:return:
639-
"""
640-
return self.reduce_temporal_udf(code=code, runtime=runtime, version=version)
641-
642-
def reduce_temporal_udf(self, code: str, runtime="Python", version="latest"):
643-
"""
644-
Apply reduce (`reduce_dimension`) process with given UDF along temporal dimension.
645631
"""
646632
process_id = 'reduce'
647633
args = {
@@ -658,6 +644,8 @@ def reduce_temporal_udf(self, code: str, runtime="Python", version="latest"):
658644
}
659645
return self.graph_add_process(process_id, args)
660646

647+
reduce_tiles_over_time = legacy_alias(reduce_temporal_udf, "reduce_tiles_over_time")
648+
661649
def apply(self, process: str, data_argument='data',arguments={}) -> 'ImageCollection':
662650
process_id = 'apply'
663651
arguments[data_argument] = \

openeo/util.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@
33
"""
44
import datetime as dt
55
import functools
6+
import inspect
67
import json
78
import logging
89
import os
910
import platform
1011
import re
12+
import warnings
1113
from collections import OrderedDict
1214
from pathlib import Path
1315
from typing import Any, Union, Tuple, Callable
@@ -427,3 +429,48 @@ def get_user_data_dir(app_name=DEFAULT_APP_NAME, auto_create=True) -> Path:
427429
fallback='~/.local/share', win_fallback='~\\AppData\\Roaming', macos_fallback='~/Library',
428430
auto_create=auto_create
429431
)
432+
433+
434+
def legacy_alias(orig: Callable, name: str, action="always", category=DeprecationWarning):
435+
"""
436+
Create legacy alias of given function/method/classmethod/staticmethod
437+
438+
:param orig: function/method to create legacy alias for
439+
:param name: name of the alias
440+
:return:
441+
"""
442+
post_process = None
443+
if isinstance(orig, classmethod):
444+
post_process = classmethod
445+
orig = orig.__func__
446+
kind = "class method"
447+
elif isinstance(orig, staticmethod):
448+
post_process = staticmethod
449+
orig = orig.__func__
450+
kind = "static method"
451+
elif inspect.ismethod(orig) or "self" in inspect.signature(orig).parameters:
452+
kind = "method"
453+
elif inspect.isfunction(orig):
454+
kind = "function"
455+
else:
456+
raise ValueError(orig)
457+
458+
msg = "Call to deprecated {k} `{n}`, use `{o}` instead.".format(k=kind, n=name, o=orig.__name__)
459+
460+
@functools.wraps(orig)
461+
def wrapper(*args, **kwargs):
462+
# This is based on warning handling/throwing implemented in `deprecated` package
463+
with warnings.catch_warnings():
464+
warnings.simplefilter(action, category)
465+
warnings.warn(msg, category=category, stacklevel=2)
466+
467+
return orig(*args, **kwargs)
468+
469+
# TODO: make this more Sphinx aware
470+
wrapper.__doc__ = "Use of this legacy {k} is deprecated, use :py:{r}:`.{o}` instead.".format(
471+
k=kind, r="meth" if "method" in kind else "func", o=orig.__name__
472+
)
473+
474+
if post_process:
475+
wrapper = post_process(wrapper)
476+
return wrapper

tests/test_util.py

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import pytest
1010

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

1414

1515
def test_rfc3339_date():
@@ -380,3 +380,81 @@ def test_get_user_config_dir():
380380

381381
def test_get_user_data_dir():
382382
assert get_user_data_dir() == pathlib.Path(__file__).parent / "data/user_dirs/data/openeo-python-client"
383+
384+
385+
def test_legacy_alias_function(recwarn):
386+
def add(x, y):
387+
"""Add x and y."""
388+
return x + y
389+
390+
do_plus = legacy_alias(add, "do_plus")
391+
392+
assert add.__doc__ == "Add x and y."
393+
assert do_plus.__doc__ == "Use of this legacy function is deprecated, use :py:func:`.add` instead."
394+
395+
assert add(2, 3) == 5
396+
assert len(recwarn) == 0
397+
398+
with pytest.warns(DeprecationWarning, match="Call to deprecated function `do_plus`, use `add` instead."):
399+
res = do_plus(2, 3)
400+
assert res == 5
401+
402+
403+
def test_legacy_alias_method(recwarn):
404+
class Foo:
405+
def add(self, x, y):
406+
"""Add x and y."""
407+
return x + y
408+
409+
do_plus = legacy_alias(add, "do_plus")
410+
411+
assert Foo.add.__doc__ == "Add x and y."
412+
assert Foo.do_plus.__doc__ == "Use of this legacy method is deprecated, use :py:meth:`.add` instead."
413+
414+
assert Foo().add(2, 3) == 5
415+
assert len(recwarn) == 0
416+
417+
with pytest.warns(DeprecationWarning, match="Call to deprecated method `do_plus`, use `add` instead."):
418+
res = Foo().do_plus(2, 3)
419+
assert res == 5
420+
421+
422+
def test_legacy_alias_classmethod(recwarn):
423+
class Foo:
424+
@classmethod
425+
def add(cls, x, y):
426+
"""Add x and y."""
427+
assert cls is Foo
428+
return x + y
429+
430+
do_plus = legacy_alias(add, "do_plus")
431+
432+
assert Foo.add.__doc__ == "Add x and y."
433+
assert Foo.do_plus.__doc__ == "Use of this legacy class method is deprecated, use :py:meth:`.add` instead."
434+
435+
assert Foo().add(2, 3) == 5
436+
assert len(recwarn) == 0
437+
438+
with pytest.warns(DeprecationWarning, match="Call to deprecated class method `do_plus`, use `add` instead."):
439+
res = Foo().do_plus(2, 3)
440+
assert res == 5
441+
442+
443+
def test_legacy_alias_staticmethod(recwarn):
444+
class Foo:
445+
@staticmethod
446+
def add(x, y):
447+
"""Add x and y."""
448+
return x + y
449+
450+
do_plus = legacy_alias(add, "do_plus")
451+
452+
assert Foo.add.__doc__ == "Add x and y."
453+
assert Foo.do_plus.__doc__ == "Use of this legacy static method is deprecated, use :py:meth:`.add` instead."
454+
455+
assert Foo().add(2, 3) == 5
456+
assert len(recwarn) == 0
457+
458+
with pytest.warns(DeprecationWarning, match="Call to deprecated static method `do_plus`, use `add` instead."):
459+
res = Foo().do_plus(2, 3)
460+
assert res == 5

0 commit comments

Comments
 (0)