Skip to content

Commit

Permalink
Cleaner way to make sure deprecation warnings are shown by default
Browse files Browse the repository at this point in the history
Using a custom UserDeprecationWarning class is cleaner than force-showing with temporary `warnings.simplefilter`
  • Loading branch information
soxofaan committed Sep 29, 2022
1 parent 9e77631 commit a184da6
Show file tree
Hide file tree
Showing 18 changed files with 211 additions and 143 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- Improve default dimension metadata of a datacube created with `openeo.rest.datacube.DataCube.load_disk_collection`
- `DataCube.download()`: only automatically add `save_result` node when there is none yet.
- Deprecation warnings: make sure they are shown by default and can be hidden when necessary.

### Removed

Expand Down
3 changes: 2 additions & 1 deletion openeo/imagecollection.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@
from deprecated import deprecated
from shapely.geometry import Polygon, MultiPolygon

from openeo.internal.warnings import legacy_alias
from openeo.rest.job import BatchJob, RESTJob
from openeo.rest.service import Service
from openeo.util import get_temporal_extent, first_not_none, dict_no_none, legacy_alias
from openeo.util import get_temporal_extent, first_not_none, dict_no_none

if typing.TYPE_CHECKING:
# Imports for type checking only (circular import issue at runtime).
Expand Down
4 changes: 2 additions & 2 deletions openeo/internal/graph_building.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
import abc
import collections
from pathlib import Path
from typing import Union, Dict, Any, Optional
from typing import Union, Any, Optional

from openeo.api.process import Parameter
from openeo.internal.process_graph_visitor import ProcessGraphVisitor, ProcessGraphUnflattener, \
ProcessGraphVisitException
from openeo.util import legacy_alias, dict_no_none, load_json_resource
from openeo.util import dict_no_none, load_json_resource


class _FromNodeMixin(abc.ABC):
Expand Down
5 changes: 2 additions & 3 deletions openeo/internal/process_graph_visitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
from abc import ABC
from typing import Union, Tuple, Any

from deprecated import deprecated

from openeo.internal.warnings import deprecated
from openeo.rest import OpenEoClientException


Expand Down Expand Up @@ -76,7 +75,7 @@ def accept_process_graph(self, graph: dict) -> 'ProcessGraphVisitor':
self.accept_node(graph[top_level_node])
return self

@deprecated(reason="Use accept_node() instead")
@deprecated(reason="Use accept_node() instead", version="0.4.6")
def accept(self, node: dict):
self.accept_node(node)

Expand Down
80 changes: 80 additions & 0 deletions openeo/internal/warnings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import functools
import inspect
import warnings
from typing import Callable
from deprecated.sphinx import deprecated as _deprecated


class UserDeprecationWarning(Warning):
"""
Python has a built-in `DeprecationWarning` class to warn about deprecated features,
but as the docs state (https://docs.python.org/3/library/warnings.html):
when those warnings are intended for other Python developers
Consequently, the default warning filters are set up to ignore (hide) these warnings
to the software end user. The developer is expected to explicitly set up
the warning filters to show the deprecation warnings again.
In case of the openeo Python client however, this does not work because the client user
is usually the developer, but probably won't bother setting up warning filters properly.
This custom warning class can be used as drop in replacement for `DeprecationWarning`,
where the deprecation warning should be visible by default.
"""
pass


def test_warnings(stacklevel=1):
"""Trigger some warnings (for test contexts)."""
for warning in [UserWarning, DeprecationWarning, UserDeprecationWarning]:
warnings.warn(
f"This is a {warning.__name__} (stacklevel {stacklevel})",
category=warning, stacklevel=stacklevel
)


def legacy_alias(orig: Callable, name: str):
"""
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):
warnings.warn(msg, category=UserDeprecationWarning, 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


def deprecated(reason: str, version: str):
"""Wrapper around `deprecated.sphinx.deprecated` to explicitly set the warning category."""
return _deprecated(reason=reason, version=version, category=UserDeprecationWarning)
1 change: 0 additions & 1 deletion openeo/rest/_datacube.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

from openeo.internal.compat import nullcontext
from openeo.internal.graph_building import PGNode, _FromNodeMixin
from openeo.util import legacy_alias

if typing.TYPE_CHECKING:
# Imports for type checking only (circular import issue at runtime).
Expand Down
4 changes: 2 additions & 2 deletions openeo/rest/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
from urllib.parse import urljoin

import requests
from deprecated.sphinx import deprecated
from requests import Response
from requests.auth import HTTPBasicAuth, AuthBase

Expand All @@ -23,6 +22,7 @@
from openeo.internal.graph_building import PGNode, as_flat_graph
from openeo.internal.jupyter import VisualDict, VisualList
from openeo.internal.processes.builder import ProcessBuilderBase
from openeo.internal.warnings import legacy_alias, deprecated
from openeo.metadata import CollectionMetadata
from openeo.rest import OpenEoClientException, OpenEoApiError, OpenEoRestError
from openeo.rest.auth.auth import NullAuth, BearerAuth, BasicBearerAuth, OidcBearerAuth, OidcRefreshInfo
Expand All @@ -37,7 +37,7 @@
from openeo.rest.rest_capabilities import RESTCapabilities
from openeo.rest.service import Service
from openeo.rest.udp import RESTUserDefinedProcess, Parameter
from openeo.util import ensure_list, legacy_alias, dict_no_none, rfc3339, load_json_resource, LazyLoadCache, \
from openeo.util import ensure_list, dict_no_none, rfc3339, load_json_resource, LazyLoadCache, \
ContextTimer, str_truncate

_log = logging.getLogger(__name__)
Expand Down
2 changes: 1 addition & 1 deletion openeo/rest/conversions.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@

import numpy as np
import pandas
from deprecated.sphinx import deprecated

from openeo.internal.warnings import deprecated

if typing.TYPE_CHECKING:
# Imports for type checking only (circular import issue at runtime).
Expand Down
4 changes: 2 additions & 2 deletions openeo/rest/datacube.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
import numpy as np
import shapely.geometry
import shapely.geometry.base
from deprecated.sphinx import deprecated
from shapely.geometry import Polygon, MultiPolygon, mapping

import openeo
Expand All @@ -27,6 +26,7 @@
from openeo.internal.documentation import openeo_process
from openeo.internal.graph_building import PGNode, ReduceNode, _FromNodeMixin
from openeo.internal.processes.builder import get_parameter_names, convert_callable_to_pgnode
from openeo.internal.warnings import legacy_alias, UserDeprecationWarning, deprecated
from openeo.metadata import CollectionMetadata, Band, BandDimension, TemporalDimension, SpatialDimension
from openeo.processes import ProcessBuilder
from openeo.rest import BandMathException, OperatorException, OpenEoClientException
Expand All @@ -36,7 +36,7 @@
from openeo.rest.service import Service
from openeo.rest.udp import RESTUserDefinedProcess
from openeo.rest.vectorcube import VectorCube
from openeo.util import get_temporal_extent, dict_no_none, legacy_alias, rfc3339, guess_format
from openeo.util import get_temporal_extent, dict_no_none, rfc3339, guess_format

if typing.TYPE_CHECKING:
# Imports for type checking only (circular import issue at runtime).
Expand Down
3 changes: 2 additions & 1 deletion openeo/rest/imagecollectionclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@

from openeo.imagecollection import ImageCollection
from openeo.internal.graphbuilder_040 import GraphBuilder
from openeo.internal.warnings import legacy_alias
from openeo.metadata import CollectionMetadata
from openeo.rest import BandMathException
from openeo.rest.job import BatchJob, RESTJob
from openeo.rest.service import Service
from openeo.util import get_temporal_extent, legacy_alias, dict_no_none, guess_format
from openeo.util import get_temporal_extent, dict_no_none, guess_format

if typing.TYPE_CHECKING:
# Imports for type checking only (circular import issue at runtime).
Expand Down
2 changes: 1 addition & 1 deletion openeo/rest/job.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
from pathlib import Path
from typing import List, Union, Dict, Optional

from deprecated.sphinx import deprecated
import requests

from openeo.api.logs import LogEntry
from openeo.internal.jupyter import render_component, render_error, VisualDict, VisualList
from openeo.internal.warnings import deprecated
from openeo.rest import OpenEoClientException, JobFailedException, OpenEoApiError
from openeo.util import ensure_dir

Expand Down
3 changes: 1 addition & 2 deletions openeo/rest/udp.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import typing
from typing import List, Union, Optional

from deprecated import deprecated

from openeo.api.process import Parameter
from openeo.internal.graph_building import as_flat_graph
from openeo.internal.jupyter import render_component
from openeo.internal.processes.builder import ProcessBuilderBase
from openeo.internal.warnings import deprecated
from openeo.util import dict_no_none

if typing.TYPE_CHECKING:
Expand Down
3 changes: 2 additions & 1 deletion openeo/rest/vectorcube.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@

from openeo.internal.documentation import openeo_process
from openeo.internal.graph_building import PGNode
from openeo.internal.warnings import legacy_alias
from openeo.metadata import CollectionMetadata
from openeo.rest._datacube import _ProcessGraphAbstraction
from openeo.rest.job import BatchJob
from openeo.util import legacy_alias, dict_no_none
from openeo.util import dict_no_none

if typing.TYPE_CHECKING:
# Imports for type checking only (circular import issue at runtime).
Expand Down
47 changes: 0 additions & 47 deletions openeo/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,11 @@
"""
import datetime as dt
import functools
import inspect
import json
import logging
import re
import sys
import time
import warnings
from collections import OrderedDict
from pathlib import Path
from typing import Any, Union, Tuple, Callable, Optional
Expand Down Expand Up @@ -458,51 +456,6 @@ def load_json_resource(src: Union[str, Path]) -> dict:
raise ValueError(src)


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


class LazyLoadCache:
"""Simple cache that allows to (lazy) load on cache miss."""

Expand Down
2 changes: 2 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

from openeo.util import ensure_dir

pytest_plugins = "pytester"

# Make tests more predictable and avoid loading real configs in tests.
os.environ["XDG_CONFIG_HOME"] = str(Path(__file__).parent / "data/user_dirs/config")
os.environ["XDG_DATA_HOME"] = str(Path(__file__).parent / "data/user_dirs/data")
Expand Down
Loading

0 comments on commit a184da6

Please sign in to comment.