From 1f2798c7bd7cbaf01f506e29ac3df8cf475ad4c9 Mon Sep 17 00:00:00 2001 From: Joachim Ungar Date: Tue, 29 Oct 2024 08:22:20 +0100 Subject: [PATCH] add deprecation warnings for moved objects but try to keep backwards compatibility --- mapchete/__init__.py | 6 +++ mapchete/_deprecated.py | 47 +++++++++++++++++++ mapchete/bounds.py | 6 +++ mapchete/io/vector/__init__.py | 59 ++++++++++++++++++++++++ mapchete/types.py | 84 ++++++++++++++++++++++++++++++++++ 5 files changed, 202 insertions(+) create mode 100644 mapchete/_deprecated.py diff --git a/mapchete/__init__.py b/mapchete/__init__.py index aa3f6978..eb9a8dfa 100644 --- a/mapchete/__init__.py +++ b/mapchete/__init__.py @@ -2,6 +2,7 @@ import os from typing import Union +from mapchete.bounds import Bounds from mapchete.config import MapcheteConfig from mapchete.errors import Empty, MapcheteNodataTile from mapchete.executor import Executor, MFuture @@ -12,14 +13,18 @@ VectorInput, VectorInputGroup, ) +from mapchete.grid import Grid from mapchete.path import MPath from mapchete.processing import Mapchete, MapcheteProcess from mapchete.tile import count_tiles from mapchete.timer import Timer from mapchete.types import MPathLike +from mapchete.zoom_levels import ZoomLevels __all__ = [ + "Bounds", "count_tiles", + "Grid", "Mapchete", "MapcheteProcess", "Timer", @@ -31,6 +36,7 @@ "RasterInputGroup", "VectorInput", "VectorInputGroup", + "ZoomLevels", ] __version__ = "2024.10.0" diff --git a/mapchete/_deprecated.py b/mapchete/_deprecated.py new file mode 100644 index 00000000..41bfbd05 --- /dev/null +++ b/mapchete/_deprecated.py @@ -0,0 +1,47 @@ +import warnings +from functools import wraps +from typing import Callable, TypeVar, Any, cast, Type, Union + +# Define a generic type for functions and classes +F = TypeVar("F", bound=Callable[..., Any]) +C = TypeVar("C", bound=Type[Any]) + + +def deprecated(reason: str = "") -> Callable[[Union[F, C]], Union[F, C]]: + """Decorator to mark functions or classes as deprecated. + + Args: + reason (str): Optional reason or guidance for deprecation. + Returns: + Callable[[Union[F, C]], Union[F, C]]: The decorated function or class that issues a DeprecationWarning. + """ + + def decorator(obj: Union[F, C]) -> Union[F, C]: + message = f"{obj.__name__} is deprecated." + if reason: + message += f" {reason}" + + if isinstance(obj, type): # Check if obj is a class + # Decorate a class to show warning on instantiation + original_init = obj.__init__ + + @wraps(original_init) + def new_init(self, *args: Any, **kwargs: Any) -> None: # pragma: no cover + warnings.warn(message, category=DeprecationWarning, stacklevel=2) + original_init(self, *args, **kwargs) + + obj.__init__ = new_init # Replace the original __init__ with new_init + obj.__doc__ = f"DEPRECATED: {reason}\n\n{obj.__doc__}" # Update docstring + return cast(C, obj) + + else: + # Decorate a function to show warning on call + @wraps(obj) + def wrapper(*args: Any, **kwargs: Any) -> Any: # pragma: no cover + warnings.warn(message, category=DeprecationWarning, stacklevel=2) + return obj(*args, **kwargs) + + wrapper.__doc__ = f"DEPRECATED: {reason}\n\n{obj.__doc__}" + return cast(F, wrapper) + + return decorator diff --git a/mapchete/bounds.py b/mapchete/bounds.py index db8d427c..f4d33c88 100644 --- a/mapchete/bounds.py +++ b/mapchete/bounds.py @@ -224,3 +224,9 @@ def intersects(self, other: BoundsLike) -> bool: or self.bottom <= other.bottom < other.top <= self.top ) return horizontal and vertical + + +def bounds_intersect( + bounds1: BoundsLike, bounds2: BoundsLike +) -> bool: # pragma: no cover + return Bounds.from_inp(bounds1).intersects(bounds2) diff --git a/mapchete/io/vector/__init__.py b/mapchete/io/vector/__init__.py index dadab6db..e236f37e 100644 --- a/mapchete/io/vector/__init__.py +++ b/mapchete/io/vector/__init__.py @@ -1,3 +1,6 @@ +from typing import Any, Optional + +from mapchete._deprecated import deprecated from mapchete.io.vector.convert import convert_vector from mapchete.io.vector.indexed_features import ( IndexedFeatures, @@ -10,6 +13,7 @@ read_vector_window, ) from mapchete.io.vector.write import fiona_write, write_vector_window +from mapchete.types import Geometry, GeometryLike, CRSLike, BoundsLike __all__ = [ "fiona_read", @@ -22,3 +26,58 @@ "read_vector", "read_union_geometry", ] + + +@deprecated( + reason="mapchete.vector.io.to_shape has moved to mapchete.geometry.to_shape" +) +def to_shape(geometry: Any) -> Geometry: # pragma: no cover + from mapchete.geometry import to_shape + + return to_shape(geometry) + + +@deprecated( + reason="mapchete.vector.io.reproject_geometry has moved to mapchete.geometry.reproject_geometry" +) +def reproject_geometry( + geometry: GeometryLike, + src_crs: CRSLike, + dst_crs: CRSLike, + clip_to_crs_bounds: bool = True, + error_on_clip: bool = False, + segmentize_on_clip: bool = False, + segmentize: bool = False, + segmentize_fraction: float = 100.0, + validity_check: bool = True, + antimeridian_cutting: bool = False, + retry_with_clip: bool = True, + fiona_env: Optional[dict] = None, +) -> Geometry: # pragma: no cover + from mapchete.geometry import reproject_geometry + + return reproject_geometry( + geometry=geometry, + src_crs=src_crs, + dst_crs=dst_crs, + clip_to_crs_bounds=clip_to_crs_bounds, + error_on_clip=error_on_clip, + segmentize_on_clip=segmentize_on_clip, + segmentize=segmentize, + segmentize_fraction=segmentize_fraction, + validity_check=validity_check, + antimeridian_cutting=antimeridian_cutting, + retry_with_clip=retry_with_clip, + fiona_env=fiona_env, + ) + + +@deprecated( + reason="mapchete.io.vector.bounds_intersect was removed, use the Bounds.intersect(other) method instead." +) +def bounds_intersect( + bounds1: BoundsLike, bounds2: BoundsLike +) -> bool: # pragma: no cover + from mapchete.bounds import bounds_intersect + + return bounds_intersect(bounds1, bounds2) diff --git a/mapchete/types.py b/mapchete/types.py index 75894719..cb555171 100644 --- a/mapchete/types.py +++ b/mapchete/types.py @@ -14,6 +14,8 @@ runtime_checkable, ) +from mapchete._deprecated import deprecated + from fiona.crs import CRS as FionaCRS # type: ignore from geojson_pydantic import Feature, FeatureCollection as GeoJSONGeometryType from pydantic import BaseModel @@ -86,3 +88,85 @@ def to_resampling(resampling: ResamplingLike) -> Resampling: class Progress(BaseModel): current: int = 0 total: Optional[int] = None + + +# below are deprecated classes once sitting in this module: +@deprecated("mapchete.types.Bounds has been moved to mapchete.bounds.Bounds") +class Bounds: # pragma: no cover + def __new__(cls, *args, **kwargs): + from mapchete.bounds import Bounds + + # Redirect instantiation to the new class + return Bounds(*args, **kwargs) + + @staticmethod + def from_inp( + inp: BoundsLike, strict: bool = True, crs: Optional[CRSLike] = None + ): # pragma: no cover + from mapchete.bounds import Bounds + + return Bounds.from_inp(inp=inp, strict=strict, crs=crs) + + @staticmethod + def from_dict( + inp: dict, strict: bool = True, crs: Optional[CRSLike] = None + ): # pragma: no cover + return Bounds(**inp, strict=strict, crs=crs) + + +@deprecated("mapchete.types.Grid has been moved to mapchete.grid.Grid") +class Grid: # pragma: no cover + def __new__(cls, *args, **kwargs): + from mapchete.grid import Grid + + return Grid(*args, **kwargs) + + @staticmethod + def from_obj(obj): # pragma: no cover + from mapchete.grid import Grid + + return Grid.from_obj(obj) + + @staticmethod + def from_bounds( + bounds: BoundsLike, shape: ShapeLike, crs: CRSLike + ): # pragma: no cover + from mapchete.grid import Grid + + return Grid.from_bounds(bounds=bounds, shape=shape, crs=crs) + + +@deprecated( + "mapchete.types.ZoomLevels has been moved to mapchete.zoom_levels.ZoomLevels" +) +class ZoomLevels: # pragma: no cover + def __new__(cls, *args, **kwargs): # pragma: no cover + from mapchete.zoom_levels import ZoomLevels + + return ZoomLevels(*args, **kwargs) + + @staticmethod + def from_inp( + min: ZoomLevelsLike, max: Optional[int] = None, descending: bool = False + ): # pragma: no cover + from mapchete.zoom_levels import ZoomLevels + + return ZoomLevels.from_inp(min=min, max=max, descending=descending) + + @staticmethod + def from_int(inp: int, **kwargs): # pragma: no cover + from mapchete.zoom_levels import ZoomLevels + + return ZoomLevels.from_int(inp=inp, **kwargs) + + @staticmethod + def from_list(inp: List[int], **kwargs): # pragma: no cover + from mapchete.zoom_levels import ZoomLevels + + return ZoomLevels.from_list(inp=inp, **kwargs) + + @staticmethod + def from_dict(inp: dict, **kwargs): # pragma: no cover + from mapchete.zoom_levels import ZoomLevels + + return ZoomLevels.from_dict(inp=inp, **kwargs)