From 3c8009783e756fc5e52aba70dd27ff8e64853bcc Mon Sep 17 00:00:00 2001 From: Rambaud Pierrick <12rambau@users.noreply.github.com> Date: Mon, 22 Jan 2024 10:23:59 +0000 Subject: [PATCH] refactor: use custom types --- geetools/Array/__init__.py | 15 ++-- geetools/ComputedObject/__init__.py | 9 +- geetools/Date/__init__.py | 4 +- geetools/DateRange/__init__.py | 5 +- geetools/Dictionary/__init__.py | 7 +- geetools/FeatureCollection/__init__.py | 9 +- geetools/Image/__init__.py | 119 ++++++++++++------------- geetools/ImageCollection/__init__.py | 41 ++++----- geetools/Join/__init__.py | 5 +- geetools/List/__init__.py | 23 +++-- geetools/Number/__init__.py | 5 +- geetools/String/__init__.py | 7 +- geetools/User/__init__.py | 46 +++++----- geetools/accessors.py | 4 +- geetools/types.py | 18 ++++ pyproject.toml | 7 ++ test.ipynb | 111 +++-------------------- 17 files changed, 183 insertions(+), 252 deletions(-) create mode 100644 geetools/types.py diff --git a/geetools/Array/__init__.py b/geetools/Array/__init__.py index 532f216d..e3f170e1 100644 --- a/geetools/Array/__init__.py +++ b/geetools/Array/__init__.py @@ -1,11 +1,10 @@ """Extra methods for the ``ee.Array`` class.""" from __future__ import annotations -from typing import Union - import ee from geetools.accessors import geetools_accessor +from geetools.types import ee_int, ee_number # hack to have the generated Array class available # it might create issues in the future with libs that have exotic init methods @@ -23,9 +22,9 @@ def __init__(self, obj: ee.Array): # -- alternative constructor ----------------------------------------------- def full( self, - width: Union[int, float, ee.Number], - height: Union[int, float, ee.Number], - value: Union[int, ee.Number, float], + width: ee_number, + height: ee_number, + value: ee_number, ) -> ee.Array: """Create an array with the given dimensions, initialized to the given value. @@ -53,9 +52,9 @@ def full( # -- data maniputlation ---------------------------------------------------- def set( self, - x: Union[int, ee.number], - y: Union[int, ee.number], - value: Union[int, float, ee.Number], + x: ee_int, + y: ee_int, + value: ee_number, ) -> ee.Array: """Set the value of a cell in an array. diff --git a/geetools/ComputedObject/__init__.py b/geetools/ComputedObject/__init__.py index b8a29ff3..eb3beae9 100644 --- a/geetools/ComputedObject/__init__.py +++ b/geetools/ComputedObject/__init__.py @@ -3,11 +3,12 @@ import json from pathlib import Path -from typing import Type, Union +from typing import Type import ee from geetools.accessors import geetools_extend +from geetools.types import pathlike # -- types management ---------------------------------------------------------- @@ -36,7 +37,7 @@ def isInstance(self, klass: Type) -> ee.Number: # -- .gee files ---------------------------------------------------------------- @geetools_extend(ee.ComputedObject) -def save(self, path: Union[str, Path]) -> Path: +def save(self, path: pathlike) -> Path: """Save a ``ComputedObject`` to a .gee file. The file contains the JSON representation of the object. it still need to be computed via ``getInfo()`` to be used. @@ -67,9 +68,9 @@ def save(self, path: Union[str, Path]) -> Path: return path -@geetools_extend(ee.ComputedObject) +@geetools_extend(ee.ComputedObject) # type: ignore @classmethod -def open(cls, path: Union[str, Path]) -> ee.ComputedObject: +def open(cls, path: pathlike) -> ee.ComputedObject: """Open a .gee file as a ComputedObject. Parameters: diff --git a/geetools/Date/__init__.py b/geetools/Date/__init__.py index d4478a6d..1ee1d03b 100644 --- a/geetools/Date/__init__.py +++ b/geetools/Date/__init__.py @@ -64,8 +64,8 @@ def fromDOY(cls, doy: int, year: int) -> ee.Date: d = ee.Date.geetools.fromDOY(1, 2020) d.getInfo() """ - doy, year = ee.Number(doy).toInt(), ee.Number(year).toInt() - return ee.Date.fromYMD(year, 1, 1).advance(doy.subtract(1), "day") + d, y = ee.Number(doy).toInt(), ee.Number(year).toInt() + return ee.Date.fromYMD(y, 1, 1).advance(d.subtract(1), "day") # -- export date ----------------------------------------------------------- def to_datetime(self) -> datetime: diff --git a/geetools/DateRange/__init__.py b/geetools/DateRange/__init__.py index c1d98662..7f35acd7 100644 --- a/geetools/DateRange/__init__.py +++ b/geetools/DateRange/__init__.py @@ -1,11 +1,10 @@ """Extra tools for the ``ee.DateRange`` class.""" from __future__ import annotations -from typing import Union - import ee from geetools.accessors import geetools_accessor +from geetools.types import ee_int @geetools_accessor(ee.DateRange) @@ -17,7 +16,7 @@ def __init__(self, obj: ee.DateRange): self._obj = obj # -- date range operations ------------------------------------------------- - def split(self, interval: Union[int, ee.Number], unit: str = "day") -> ee.List: + def split(self, interval: ee_int, unit: str = "day") -> ee.List: """Convert a ``ee.DateRange`` to a list of ``ee.DateRange``. The DateRange will be split in multiple DateRanges of the specified interval and Unit. diff --git a/geetools/Dictionary/__init__.py b/geetools/Dictionary/__init__.py index b8a60616..66d49e87 100644 --- a/geetools/Dictionary/__init__.py +++ b/geetools/Dictionary/__init__.py @@ -1,11 +1,10 @@ """Extra methods for the ``ee.Dictionary`` class.""" from __future__ import annotations -from typing import Union - import ee from geetools.accessors import geetools_accessor +from geetools.types import ee_list @geetools_accessor(ee.Dictionary) @@ -17,7 +16,7 @@ def __init__(self, obj: ee.Dictionary): self._obj = obj # -- alternative constructor ----------------------------------------------- - def fromPairs(self, list: Union[list, ee.List]) -> ee.Dictionary: + def fromPairs(self, list: ee_list) -> ee.Dictionary: """Create a dictionary from a list of [[key, value], ...]] pairs. Parameters: @@ -62,7 +61,7 @@ def sort(self) -> ee.Dictionary: values = orderededKeys.map(lambda key: self._obj.get(key)) return ee.Dictionary.fromLists(orderededKeys, values) - def getMany(self, list: Union[ee.List, list]) -> ee.List: + def getMany(self, list: ee_list) -> ee.List: """Extract values from a list of keys. Parameters: diff --git a/geetools/FeatureCollection/__init__.py b/geetools/FeatureCollection/__init__.py index 72f8bc33..fab594d8 100644 --- a/geetools/FeatureCollection/__init__.py +++ b/geetools/FeatureCollection/__init__.py @@ -6,6 +6,7 @@ import ee from geetools.accessors import geetools_accessor +from geetools.types import ee_int, ee_str @geetools_accessor(ee.FeatureCollection) @@ -18,8 +19,8 @@ def __init__(self, obj: ee.FeatureCollection): def toImage( self, - color: Union[ee.String, str, ee.Number, int] = 0, - width: Union[ee.String, str, ee.Number, int] = "", + color: Union[ee_str, ee_int] = 0, + width: Union[ee_str, ee_int] = "", ) -> ee.Image: """Paint the current FeatureCollection to an Image. @@ -33,9 +34,7 @@ def toImage( width == "" or params.update(width=width) return ee.Image().paint(self._obj, **params) - def addId( - self, name: Union[str, ee.String] = "id", start: Union[int, ee.Number] = 1 - ) -> ee.FeatureCollection: + def addId(self, name: ee_str = "id", start: ee_int = 1) -> ee.FeatureCollection: """Add a unique numeric identifier, starting from parameter ``start``. Returns: diff --git a/geetools/Image/__init__.py b/geetools/Image/__init__.py index 0623c9bd..67a1c12f 100644 --- a/geetools/Image/__init__.py +++ b/geetools/Image/__init__.py @@ -1,13 +1,22 @@ """Toolbox for the ``ee.Image`` class.""" from __future__ import annotations -from typing import Optional, Union +from typing import Optional import ee import ee_extra import ee_extra.Algorithms.core from geetools.accessors import geetools_accessor +from geetools.types import ( + ee_dict, + ee_geomlike, + ee_int, + ee_list, + ee_number, + ee_str, + number, +) @geetools_accessor(ee.Image) @@ -44,9 +53,7 @@ def addDate(self) -> ee.Image: date = self._obj.date().millis() return self._obj.addBands(ee.Image.constant(date).rename("date")) - def addSuffix( - self, suffix: Union[str, ee.String], bands: Union[ee.List, list] = [] - ) -> ee.Image: + def addSuffix(self, suffix: ee_str, bands: ee_list = []) -> ee.Image: """Add a suffix to the image selected band. Add a suffix to the selected band. If no band is specified, the suffix is added to all bands. @@ -77,9 +84,7 @@ def addSuffix( ) return self._obj.rename(bandNames) - def addPrefix( - self, prefix: Union[str, ee.String], bands: Union[ee.List, list] = [] - ): + def addPrefix(self, prefix: ee_str, bands: ee_list = []): """Add a prefix to the image selected band. Add a prefix to the selected band. If no band is specified, the prefix is added to all bands. @@ -110,7 +115,7 @@ def addPrefix( ) return self._obj.rename(bandNames) - def rename(self, names: Union[ee.Dictionary, dict]) -> ee.Image: + def rename(self, names: ee_dict) -> ee.Image: """Rename the bands of the image. It's the same function as the one from GEE but it takes a dictionary as input. @@ -140,7 +145,7 @@ def rename(self, names: Union[ee.Dictionary, dict]) -> ee.Image: ) return self._obj.rename(bands) - def remove(self, bands: Union[list, ee.List]) -> ee.Image: + def remove(self, bands: ee_list) -> ee.Image: """Remove bands from the image. Parameters: @@ -167,8 +172,8 @@ def remove(self, bands: Union[list, ee.List]) -> ee.Image: def doyToDate( self, year, - dateFormat: Union[str, ee.String] = "yyyyMMdd", - band: Union[ee.String, str] = "", + dateFormat: ee_str = "yyyyMMdd", + band: ee_str = "", ) -> ee.Image: """Convert the DOY band to a date band. @@ -209,9 +214,7 @@ def doyToDate( # -- the rest -------------------------------------------------------------- - def getValues( - self, point: ee.Geometry.Point, scale: Union[ee.Number, int] = 0 - ) -> ee.Dictionary: + def getValues(self, point: ee.Geometry.Point, scale: ee_int = 0) -> ee.Dictionary: """Get the value of the image at the given point using specified geometry. The result is presented as a dictionary where the keys are the bands name and the value the mean value of the band at the given point. @@ -265,7 +268,7 @@ def minScale(self) -> ee.Number: ) return ee.Number(scales.sort().get(0)) - def merge(self, images: Union[ee.List, list]) -> ee.Image: + def merge(self, images: ee_list) -> ee.Image: """Merge images into a single image. Parameters: @@ -293,8 +296,8 @@ def merge(self, images: Union[ee.List, list]) -> ee.Image: def toGrid( self, - size: Union[ee.Number, int] = 1, - band: Union[str, ee.String] = "", + size: ee_int = 1, + band: ee_str = "", geometry: Optional[ee.Geometry] = None, ) -> ee.FeatureCollection: """Convert an image to a grid of polygons. @@ -352,7 +355,7 @@ def toGrid( return ee.FeatureCollection(features) def clipOnCollection( - self, fc: ee.FeatureCollection, keepProperties: Union[ee.Number, int] = 1 + self, fc: ee.FeatureCollection, keepProperties: ee_int = 1 ) -> ee.ImageCollection: """Clip an image to a FeatureCollection. @@ -389,9 +392,9 @@ def fcClip(feat): def bufferMask( self, - radius: Union[int, ee.Number] = 1.5, - kernelType: Union[ee.String, str] = "square", - units: Union[ee.String, str] = "pixels", + radius: ee_int = 1.5, + kernelType: ee_str = "square", + units: ee_str = "pixels", ) -> ee.Image: """Make a buffer around every masked pixel of the Image. @@ -426,8 +429,8 @@ def bufferMask( @classmethod def full( self, - values: Union[list, ee.List] = [0], - names: Union[list, ee.List] = ["constant"], + values: ee_list = [0], + names: ee_list = ["constant"], ) -> ee.Image: """Create an image with the given values and names. @@ -462,9 +465,9 @@ def full( def fullLike( self, - fillValue: Union[int, float, ee.Number], - copyProperties: Union[ee.Number, int] = 0, - keepMask: Union[ee.Number, int] = 0, + fillValue: ee_number, + copyProperties: ee_int = 0, + keepMask: ee_int = 0, ) -> ee.Image: """Create an image with the same band names, projection and scale as the original image. @@ -506,9 +509,9 @@ def fullLike( def reduceBands( self, - reducer: Union[str, ee.String], - bands: Union[list, ee.List] = [], - name: Union[str, ee.String] = "", + reducer: ee_str, + bands: ee_list = [], + name: ee_str = "", ) -> ee.Image: """Reduce the image using the selected reducer and adding the result as a band using the selected name. @@ -541,9 +544,7 @@ def reduceBands( reduceImage = self._obj.select(ee.List(bands)).reduce(reducer).rename([name]) return self._obj.addBands(reduceImage) - def negativeClip( - self, geometry: Union[ee.FeatureCollection, ee.Geometry] - ) -> ee.Image: + def negativeClip(self, geometry: ee_geomlike) -> ee.Image: """The opposite of the clip method. The inside of the geometry will be masked from the image. @@ -572,8 +573,8 @@ def negativeClip( def format( self, - string: Union[str, ee.String], - dateFormat: Union[str, ee.String] = "yyyy-MM-dd", + string: ee_str, + dateFormat: ee_str = "yyyy-MM-dd", ) -> ee.String: """Create a string from using the given pattern and using the image properties. @@ -611,7 +612,7 @@ def replaceProperties(p, s): return patternList.iterate(replaceProperties, string) - def gauss(self, band: Union[ee.String, str] = "") -> ee.Image: + def gauss(self, band: ee_str = "") -> ee.Image: """Apply a gaussian filter to the image. We apply the following function to the image: "exp(((val-mean)**2)/(-2*(std**2)))" @@ -652,7 +653,7 @@ def gauss(self, band: Union[ee.String, str] = "") -> ee.Image: }, ).rename(band.cat("_gauss")) - def repeat(self, band, repeats: Union[ee.Number, int]) -> ee.image: + def repeat(self, band, repeats: ee_int) -> ee.image: """Repeat a band of the image. Args: @@ -718,9 +719,7 @@ def remove(band): return ee.ImageCollection(bands.map(remove)).toBands().rename(bands) - def interpolateBands( - self, src: Union[list, ee.List], to: Union[list, ee.List] - ) -> ee.Image: + def interpolateBands(self, src: ee_list, to: ee_list) -> ee.Image: """Interpolate bands from the "src" value range to the "to" value range. The Interpolation is performed linearly using the "extrapolate" option of the "interpolate" method. @@ -755,7 +754,7 @@ def interpolate(band): return ee.ImageCollection(bands.map(interpolate)).toBands().rename(bands) - def isletMask(self, offset: Union[ee.Number, float, int]) -> ee.Image: + def isletMask(self, offset: ee_number) -> ee.Image: """Compute the islet mask from an image. An islet is a set of non-masked pixels connected together by their edges of very small surface. The user define the offset of the island size and we compute the max number of pixels to improve computation speed. The inpt Image needs to be a single band binary image. @@ -813,28 +812,28 @@ def index_list(cls) -> dict: def spectralIndices( self, index: str = "NDVI", - G: Union[float, int] = 2.5, - C1: Union[float, int] = 6.0, - C2: Union[float, int] = 7.5, - L: Union[float, int] = 1.0, - cexp: Union[float, int] = 1.16, - nexp: Union[float, int] = 2.0, - alpha: Union[float, int] = 0.1, - slope: Union[float, int] = 1.0, - intercept: Union[float, int] = 0.0, - gamma: Union[float, int] = 1.0, - omega: Union[float, int] = 2.0, - beta: Union[float, int] = 0.05, - k: Union[float, int] = 0.0, - fdelta: Union[float, int] = 0.581, + G: number = 2.5, + C1: number = 6.0, + C2: number = 7.5, + L: number = 1.0, + cexp: number = 1.16, + nexp: number = 2.0, + alpha: number = 0.1, + slope: number = 1.0, + intercept: number = 0.0, + gamma: number = 1.0, + omega: number = 2.0, + beta: number = 0.05, + k: number = 0.0, + fdelta: number = 0.581, kernel: str = "RBF", sigma: str = "0.5 * (a + b)", - p: Union[float, int] = 2.0, - c: Union[float, int] = 1.0, - lambdaN: Union[float, int] = 858.5, - lambdaR: Union[float, int] = 645.0, - lambdaG: Union[float, int] = 555.0, - online: Union[float, int] = False, + p: number = 2.0, + c: number = 1.0, + lambdaN: number = 858.5, + lambdaR: number = 645.0, + lambdaG: number = 555.0, + online: number = False, ) -> ee.Image: """Computes one or more spectral indices (indices are added as bands) for an image from the Awesome List of Spectral Indices. diff --git a/geetools/ImageCollection/__init__.py b/geetools/ImageCollection/__init__.py index 22eef906..8b7867e9 100644 --- a/geetools/ImageCollection/__init__.py +++ b/geetools/ImageCollection/__init__.py @@ -7,6 +7,7 @@ import ee_extra from geetools.accessors import geetools_accessor +from geetools.types import number @geetools_accessor(ee.ImageCollection) @@ -106,28 +107,28 @@ def closest( def spectralIndices( self, index: str = "NDVI", - G: Union[float, int] = 2.5, - C1: Union[float, int] = 6.0, - C2: Union[float, int] = 7.5, - L: Union[float, int] = 1.0, - cexp: Union[float, int] = 1.16, - nexp: Union[float, int] = 2.0, - alpha: Union[float, int] = 0.1, - slope: Union[float, int] = 1.0, - intercept: Union[float, int] = 0.0, - gamma: Union[float, int] = 1.0, - omega: Union[float, int] = 2.0, - beta: Union[float, int] = 0.05, - k: Union[float, int] = 0.0, - fdelta: Union[float, int] = 0.581, + G: number = 2.5, + C1: number = 6.0, + C2: number = 7.5, + L: number = 1.0, + cexp: number = 1.16, + nexp: number = 2.0, + alpha: number = 0.1, + slope: number = 1.0, + intercept: number = 0.0, + gamma: number = 1.0, + omega: number = 2.0, + beta: number = 0.05, + k: number = 0.0, + fdelta: number = 0.581, kernel: str = "RBF", sigma: str = "0.5 * (a + b)", - p: Union[float, int] = 2.0, - c: Union[float, int] = 1.0, - lambdaN: Union[float, int] = 858.5, - lambdaR: Union[float, int] = 645.0, - lambdaG: Union[float, int] = 555.0, - online: Union[float, int] = False, + p: number = 2.0, + c: number = 1.0, + lambdaN: number = 858.5, + lambdaR: number = 645.0, + lambdaG: number = 555.0, + online: number = False, ) -> ee.ImageCollection: """Computes one or more spectral indices (indices are added as bands) for an image from the Awesome List of Spectral Indices. diff --git a/geetools/Join/__init__.py b/geetools/Join/__init__.py index 325b0635..8dd69433 100644 --- a/geetools/Join/__init__.py +++ b/geetools/Join/__init__.py @@ -1,11 +1,10 @@ """Extra methods for the ``ee.Join`` class.""" from __future__ import annotations -from typing import Union - import ee from geetools.accessors import geetools_accessor +from geetools.types import ee_str @geetools_accessor(ee.Join) @@ -20,7 +19,7 @@ def __init__(self, obj: ee.join): def byProperty( primary: ee.Collection, secondary: ee.Collection, - field: Union[ee.String, str], + field: ee_str, outer: bool = False, ) -> ee.Collection: """Join 2 collections by a given property field. diff --git a/geetools/List/__init__.py b/geetools/List/__init__.py index b540804a..964b9153 100644 --- a/geetools/List/__init__.py +++ b/geetools/List/__init__.py @@ -1,11 +1,10 @@ """Extra methods for the ``ee.List`` class.""" from __future__ import annotations -from typing import Union - import ee from geetools.accessors import geetools_accessor +from geetools.types import ee_dict, ee_int, ee_list, ee_str @geetools_accessor(ee.List) @@ -16,7 +15,7 @@ def __init__(self, obj: ee.List): """Initialize the List class.""" self._obj = obj - def product(self, other: Union[list, ee.List]) -> ee.List: + def product(self, other: ee_list) -> ee.List: """Compute the cartesian product of 2 list. Values will all be considered as string and will be joined with **no spaces**. @@ -48,7 +47,7 @@ def product(self, other: Union[list, ee.List]) -> ee.List: ) return product.flatten() - def complement(self, other: Union[list, ee.List]) -> ee.List: + def complement(self, other: ee_list) -> ee.List: """Compute the complement of the current list and the ``other`` list. The mthematical complement is the list of elements that are in the current list but not in the ``other`` list and vice-versa. @@ -74,7 +73,7 @@ def complement(self, other: Union[list, ee.List]) -> ee.List: l1, l2 = ee.List(self._obj), ee.List(other) return l1.removeAll(l2).cat(l2.removeAll(l1)) - def intersection(self, other: Union[list, ee.List]) -> ee.List: + def intersection(self, other: ee_list) -> ee.List: """Compute the intersection of the current list and the ``other`` list. The intersection is the list of elements that are in both lists. @@ -100,7 +99,7 @@ def intersection(self, other: Union[list, ee.List]) -> ee.List: l1, l2 = ee.List(self._obj), ee.List(other) return l1.removeAll(l1.removeAll(l2)) - def union(self, other: Union[list, ee.List]) -> ee.List: + def union(self, other: ee_list) -> ee.List: """Compute the union of the current list and the ``other`` list. This list will drop duplicated items. @@ -127,7 +126,7 @@ def union(self, other: Union[list, ee.List]) -> ee.List: return l1.cat(l2).distinct() # this method is simply a del but the name is protected in the GEE context - def delete(self, index: Union[int, ee.Number]) -> ee.List: + def delete(self, index: ee_int) -> ee.List: """Delete an element from a list. Parameters: @@ -152,9 +151,9 @@ def delete(self, index: Union[int, ee.Number]) -> ee.List: @classmethod def sequence( cls, - ini: Union[int, ee.Number], - end: Union[int, ee.Number], - step: Union[int, ee.Number] = 1, + ini: ee_int, + end: ee_int, + step: ee_int = 1, ) -> ee.List: """Create a sequence from ini to end by step. @@ -182,7 +181,7 @@ def sequence( step = ee.Number(step).toInt().max(1) return ee.List.sequence(ini, end, step).add(end.toFloat()).distinct() - def replaceMany(self, replace: Union[ee.Dictionary, dict]) -> ee.List: + def replaceMany(self, replace: ee_dict) -> ee.List: """Replace many values in a list. Parameters: @@ -210,7 +209,7 @@ def replaceMany(self, replace: Union[ee.Dictionary, dict]) -> ee.List: ) return ee.List(list) # to avoid returning a COmputedObject - def join(self, separator: Union[str, ee.String] = ", ") -> ee.string: + def join(self, separator: ee_str = ", ") -> ee.string: """Format a list to a string. Same as the join method but elements that cannot be stringified will be returned as the object type. diff --git a/geetools/Number/__init__.py b/geetools/Number/__init__.py index 9cca5958..f89e7146 100644 --- a/geetools/Number/__init__.py +++ b/geetools/Number/__init__.py @@ -1,11 +1,10 @@ """Extra methods for the ``ee.Number`` class.""" from __future__ import annotations -from typing import Union - import ee from geetools.accessors import geetools_accessor +from geetools.types import ee_int @geetools_accessor(ee.Number) @@ -16,7 +15,7 @@ def __init__(self, obj: ee.Number): """Initialize the Number class.""" self._obj = obj - def truncate(self, nbDecimals: Union[ee.Number, int] = 2) -> ee.Number: + def truncate(self, nbDecimals: ee_int = 2) -> ee.Number: """Truncate a number to a given number of decimals. Parameters: diff --git a/geetools/String/__init__.py b/geetools/String/__init__.py index 838e6269..046c1dec 100644 --- a/geetools/String/__init__.py +++ b/geetools/String/__init__.py @@ -1,11 +1,10 @@ """Extra methods for the ``ee.String`` class.""" from __future__ import annotations -from typing import Union - import ee from geetools.accessors import geetools_accessor +from geetools.types import ee_dict, ee_str @geetools_accessor(ee.String) @@ -16,7 +15,7 @@ def __init__(self, obj: ee.String): """Initialize the String class.""" self._obj = obj - def eq(self, other: Union[str, ee.String]) -> ee.Number: + def eq(self, other: ee_str) -> ee.Number: """Compare two strings and return a ``ee.Number``. Parameters: @@ -37,7 +36,7 @@ def eq(self, other: Union[str, ee.String]) -> ee.Number: """ return self._obj.compareTo(ee.String(other)).Not() - def format(self, template: Union[ee.Dictionary, dict]) -> ee.String: + def format(self, template: ee_dict) -> ee.String: """Format a string with a dictionary. Replace the keys in the string using the values provided in the dictionary. Follow the same pattern: value format as Python string.format method. diff --git a/geetools/User/__init__.py b/geetools/User/__init__.py index 97a9a42d..fcb17f90 100644 --- a/geetools/User/__init__.py +++ b/geetools/User/__init__.py @@ -16,14 +16,14 @@ class User: """CRUD system to manage multiple user accounts on the same machine.""" @staticmethod - def set(name: str = "", credential_path: str = "") -> None: + def set(name: str = "", credential_pathname: str = "") -> None: """Set the current user. Equivalent to the ``ee.initialize`` function but with a specific credential file stored in the machine. Args: name: The name of the user as saved when created. use default if not set - credential_path: The path to the folder where the credentials are stored. If not set, it uses the default path + credential_pathname: The path to the folder where the credentials are stored. If not set, it uses the default path Example: .. jupyter-execture:: @@ -37,8 +37,8 @@ def set(name: str = "", credential_path: str = "") -> None: ee.Number(1).getInfo() """ name = f"credentials{name}" - credential_path = credential_path or ee.oauth.get_credentials_path() - credential_path = Path(credential_path).parent + credential_pathname = credential_pathname or ee.oauth.get_credentials_path() + credential_path = Path(credential_pathname).parent try: tokens = json.loads((credential_path / name).read_text()) @@ -61,14 +61,14 @@ def set(name: str = "", credential_path: str = "") -> None: ee.Initialize(credentials) @staticmethod - def create(name: str = "", credential_path: str = "") -> None: + def create(name: str = "", credential_pathname: str = "") -> None: """Create a new user. Equivalent to ee.Authenticate but where the registered user will not be the default one (the one you get when running ee.initialize()) Args: name: The name of the user. If not set, it will reauthenticate default. - credential_path: The path to the folder where the credentials are stored. If not set, it uses the default path + credential_pathname: The path to the folder where the credentials are stored. If not set, it uses the default path Example: .. jupyter-execture:: @@ -84,8 +84,8 @@ def create(name: str = "", credential_path: str = "") -> None: # ee.Number(1).getInfo() """ name = f"credentials{name}" - credential_path = credential_path or ee.oauth.get_credentials_path() - credential_path = Path(credential_path).parent + credential_pathname = credential_pathname or ee.oauth.get_credentials_path() + credential_path = Path(credential_pathname).parent # the authenticate method will write the credentials in the default # folder and with the default name. We to save the existing one in tmp, @@ -99,12 +99,12 @@ def create(name: str = "", credential_path: str = "") -> None: suppress(move(Path(dir) / default.name, default)) @staticmethod - def delete(name: str = "", credential_path: str = "") -> None: + def delete(name: str = "", credential_pathname: str = "") -> None: """Delete a user. Args: name: The name of the user. If not set, it will delete the default user - credential_path: The path to the folder where the credentials are stored. If not set, it uses the default path + credential_pathname: The path to the folder where the credentials are stored. If not set, it uses the default path Example: .. jupyter-execture:: @@ -121,18 +121,19 @@ def delete(name: str = "", credential_path: str = "") -> None: # will raise an error as the user does not exist anymore """ name = f"credentials{name}" - credential_path = credential_path or ee.oauth.get_credentials_path() - credential_path = Path(credential_path).parent - suppress((credential_path / name).unlink()) + credential_pathname = credential_pathname or ee.oauth.get_credentials_path() + credential_path = Path(credential_pathname).parent + with suppress(FileNotFoundError): + (credential_path / name).unlink() @staticmethod - def list(credential_path: str = "") -> list: + def list(credential_pathname: str = "") -> list: """return all the available users in the set folder. To reach "default" simply omit the ``name`` parameter in the User methods Args: - credential_path: The path to the folder where the credentials are stored. If not set, it uses the default path + credential_pathname: The path to the folder where the credentials are stored. If not set, it uses the default path Returns: A list of strings with the names of the users @@ -145,19 +146,19 @@ def list(credential_path: str = "") -> list: geetools.User.list( """ - credential_path = credential_path or ee.oauth.get_credentials_path() - credential_path = Path(credential_path).parent + credential_pathname = credential_pathname or ee.oauth.get_credentials_path() + credential_path = Path(credential_pathname).parent files = [f for f in credential_path.glob("credentials*") if f.is_file()] return [f.name.replace("credentials", "") or "default" for f in files] @staticmethod - def rename(new: str, old: str = "", credential_path: str = "") -> None: + def rename(new: str, old: str = "", credential_pathname: str = "") -> None: """Rename a user without changing the credentials. Args: new: The new name of the user old: The name of the user to rename - credential_path: The path to the folder where the credentials are stored. If not set, it uses the default path + credential_pathname: The path to the folder where the credentials are stored. If not set, it uses the default path Example: .. jupyter-execute:: @@ -171,6 +172,7 @@ def rename(new: str, old: str = "", credential_path: str = "") -> None: """ old = f"credentials{old}" new = f"credentials{new}" - credential_path = credential_path or ee.oauth.get_credentials_path() - credential_path = Path(credential_path).parent - suppress((credential_path / old).rename(credential_path / new)) + credential_pathname = credential_pathname or ee.oauth.get_credentials_path() + credential_path = Path(credential_pathname).parent + with suppress(FileNotFoundError): + (credential_path / old).rename(credential_path / new) diff --git a/geetools/accessors.py b/geetools/accessors.py index 5fc48883..337a4520 100644 --- a/geetools/accessors.py +++ b/geetools/accessors.py @@ -34,7 +34,7 @@ def decorator(accessor: Any) -> Any: def geetools_extend(obj: Any) -> Callable: - """Extends the objcect whatever the type. + """Extends the object whatever the type. Be careful when using this decorator as it can override direct members of the existing object. @@ -44,4 +44,4 @@ def geetools_extend(obj: Any) -> Callable: Returns: Decorator for extending classes. """ - return lambda f: (setattr(obj, f.__name__, f) or f) + return lambda f: (setattr(obj, f.__name__, f) or f) # type: ignore diff --git a/geetools/types.py b/geetools/types.py new file mode 100644 index 00000000..412d83e8 --- /dev/null +++ b/geetools/types.py @@ -0,0 +1,18 @@ +"""A set of custom mixin types to use in the package when dealing with Python/GEE functions.""" +from __future__ import annotations + +from pathlib import Path +from typing import Union + +import ee + +ee_str = Union[str, ee.String] +ee_int = Union[int, ee.Number] +ee_float = Union[float, ee.Number] +ee_number = Union[float, int, ee.Number] +ee_list = Union[list, ee.List] +ee_dict = Union[dict, ee.Dictionary] +ee_geomlike = Union[ee.Geometry, ee.Feature, ee.FeatureCollection] + +pathlike = Union[str, Path] +number = Union[float, int] diff --git a/pyproject.toml b/pyproject.toml index 4880a6b0..c16c9ba8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -126,3 +126,10 @@ version_files = [ [tool.codespell] skip = "geetools/tools/*,geetools/classification.py,**/*.ipynb" + +[tool.mypy] +scripts_are_modules = true +ignore_missing_imports = true +install_types = true +non_interactive = true +warn_redundant_casts = true diff --git a/test.ipynb b/test.ipynb index dbfd8a4b..e1a81a98 100644 --- a/test.ipynb +++ b/test.ipynb @@ -4,114 +4,25 @@ "cell_type": "code", "execution_count": 1, "metadata": {}, - "outputs": [], - "source": [ - "import ee\n", - "import json\n", - "import os" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'username': 'geetools-test@ee-geetools.iam.gserviceaccount.com',\n", - " 'key': {'type': 'service_account',\n", - " 'project_id': 'ee-geetools',\n", - " 'private_key_id': '4b729f8be1e911961c5e39c46376f372f3d17b7d',\n", - " 'private_key': '-----BEGIN PRIVATE KEY-----\\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDIE4s/6UqSGI5Q\\nHap+iYU9GbU58IYIzjGX434b5Xtuq5UqDm3rZ8S97pFoJvmWrTz99nwsGhMVPlMf\\n1bdcG4itEZtlHY++YzZwmkJKchlVIksP/15rHq+8Pp5LhBxYT5aZ2jG1x/VBXv2k\\nF72Uu6gyxZBUcRyVkbZSo9vt77rmJgOJBLOqGtgXJL2bD0IA8bB7KOJc3O0ufkOc\\nDBUEJ7BwcSWs3pJ1vnsoTGT2JRgY+nTz2H5fs1O0mJOzO7uQlFXndmYHwNoGnmxQ\\n8jNgZLTcesfIpQJwiGvUGS+zq1j3uH8jLC0lwuL6GHe3dTjwypJ5TFMrPWM5oIcC\\nvfnuNybJAgMBAAECggEAJ9KOBYlFXKlTEBqrf/aWxLfMdK0NPIzv3Yu74skNBZTU\\nwnNvSOrVQ7mLiwew517xVBoekneZI0INsPksfwKC9HGb9KcK4InmQMjPV3p41NNX\\nzeYV8KvBri3ne1/e9WvX7aT6fnQ/ekSyJtpL02H7gymEBe6ikhmXyMCRCn2L/Xkq\\n5MwO3UhnQ7LhoLjpVWsY5CYHxbwAXnxZzKXg+VIfpIMntCpEPxmu8QyFyromVlpd\\n+x9NVfe+XM+wwr1fiHKhGXMkb3jlUX+QrXVvC+BJ1IDjHwkRfv4dedONwYfYa+EF\\npn4fQyrg5sY9LxMcJhFuNMaM1yj5bAe0bP1VYcyztQKBgQDi5TBIVSKnLX7aVbEf\\nNKmjmzxIQOFsDef1CLloCipJ5KBjWLygrfhgXlV973JcrF0uRPwUfRCD5oyCjgzd\\nZ84vv7QKZyu3MBdBMWA02+ZPADk9IkLKPSJasxQRRc1IoK39/j0bSXV/HY+vyL+O\\nc7Sj7Jb3uyJAbJZQQ9UbkO99pQKBgQDhvawvDifJcey1tpYkIEPnOmi+oxG5LZCp\\n9jAWl5hwGKCl2IMaLVtZ046gHUOEnXaQKoLGYoOj0/QQRfW/UP+K/BUzDeAazu9q\\nakSG/z6lMirVGxA6fGdFA9sdH1bBuIHfAgPGSrFo/ZN0AzZnp8ZFfJD6oYgUlkrE\\nldQQLRuDVQKBgDK/VBc5g3CkylSHCgCSxMZk+AypBkImshSqCN3uOBsi2YSe2kGN\\n55mWP8TVA10a6BRrNX5XopP9wruIjfQPPZyMYfZZsPtd9DWOl5f6/v34bNTxjsKw\\n/bgPiZN7azitR30hmgU6Xt85e2OzoLR5yJNJXVK3Nif2oX/+S/HIbuhJAoGBAID2\\n/GSRoLdcZ5BUtKgE6uYyH18yCFETr/75j/WIO+VmnHjDHfsZiIPj8iqVLVqZHwAz\\n2Sx/YZd54ohdf40COEvtwiq9tZd7O5o/BdFeBysXYxMGeBoBsnniPw7/NXBM+Z0v\\nKHrjd0F1BQWVREKpvgM6rBUTrYudZS+0LUfkjUBVAoGAaa1v0VMmFRnbEz9bbvj5\\n+UCTOs273Cz6ZPzXdogRcenCUixa/jU85b73VikW2WFn2LDtR6yxUWIhA8O3PcPP\\nOVVmL/QcbL65voWdM8yM7ZwV1hnntajqTViQz3gLvldQC6EeYoPehHuYk/FR5cGW\\nZF14AmnKFD9ruXZ3d7qFFdQ=\\n-----END PRIVATE KEY-----\\n',\n", - " 'client_email': 'geetools-test@ee-geetools.iam.gserviceaccount.com',\n", - " 'client_id': '107829403003588030989',\n", - " 'auth_uri': 'https://accounts.google.com/o/oauth2/auth',\n", - " 'token_uri': 'https://oauth2.googleapis.com/token',\n", - " 'auth_provider_x509_cert_url': 'https://www.googleapis.com/oauth2/v1/certs',\n", - " 'client_x509_cert_url': 'https://www.googleapis.com/robot/v1/metadata/x509/geetools-test%40ee-geetools.iam.gserviceaccount.com',\n", - " 'universe_domain': 'googleapis.com'}}" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "ee_token = os.environ[\"EARTHENGINE_TOKEN\"]\n", - "ee_token = json.loads(ee_token)\n", - "ee_token" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, "outputs": [ { - "data": { - "text/plain": [ - "('geetools-test@ee-geetools.iam.gserviceaccount.com',\n", - " {'type': 'service_account',\n", - " 'project_id': 'ee-geetools',\n", - " 'private_key_id': '4b729f8be1e911961c5e39c46376f372f3d17b7d',\n", - " 'private_key': '-----BEGIN PRIVATE KEY-----\\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDIE4s/6UqSGI5Q\\nHap+iYU9GbU58IYIzjGX434b5Xtuq5UqDm3rZ8S97pFoJvmWrTz99nwsGhMVPlMf\\n1bdcG4itEZtlHY++YzZwmkJKchlVIksP/15rHq+8Pp5LhBxYT5aZ2jG1x/VBXv2k\\nF72Uu6gyxZBUcRyVkbZSo9vt77rmJgOJBLOqGtgXJL2bD0IA8bB7KOJc3O0ufkOc\\nDBUEJ7BwcSWs3pJ1vnsoTGT2JRgY+nTz2H5fs1O0mJOzO7uQlFXndmYHwNoGnmxQ\\n8jNgZLTcesfIpQJwiGvUGS+zq1j3uH8jLC0lwuL6GHe3dTjwypJ5TFMrPWM5oIcC\\nvfnuNybJAgMBAAECggEAJ9KOBYlFXKlTEBqrf/aWxLfMdK0NPIzv3Yu74skNBZTU\\nwnNvSOrVQ7mLiwew517xVBoekneZI0INsPksfwKC9HGb9KcK4InmQMjPV3p41NNX\\nzeYV8KvBri3ne1/e9WvX7aT6fnQ/ekSyJtpL02H7gymEBe6ikhmXyMCRCn2L/Xkq\\n5MwO3UhnQ7LhoLjpVWsY5CYHxbwAXnxZzKXg+VIfpIMntCpEPxmu8QyFyromVlpd\\n+x9NVfe+XM+wwr1fiHKhGXMkb3jlUX+QrXVvC+BJ1IDjHwkRfv4dedONwYfYa+EF\\npn4fQyrg5sY9LxMcJhFuNMaM1yj5bAe0bP1VYcyztQKBgQDi5TBIVSKnLX7aVbEf\\nNKmjmzxIQOFsDef1CLloCipJ5KBjWLygrfhgXlV973JcrF0uRPwUfRCD5oyCjgzd\\nZ84vv7QKZyu3MBdBMWA02+ZPADk9IkLKPSJasxQRRc1IoK39/j0bSXV/HY+vyL+O\\nc7Sj7Jb3uyJAbJZQQ9UbkO99pQKBgQDhvawvDifJcey1tpYkIEPnOmi+oxG5LZCp\\n9jAWl5hwGKCl2IMaLVtZ046gHUOEnXaQKoLGYoOj0/QQRfW/UP+K/BUzDeAazu9q\\nakSG/z6lMirVGxA6fGdFA9sdH1bBuIHfAgPGSrFo/ZN0AzZnp8ZFfJD6oYgUlkrE\\nldQQLRuDVQKBgDK/VBc5g3CkylSHCgCSxMZk+AypBkImshSqCN3uOBsi2YSe2kGN\\n55mWP8TVA10a6BRrNX5XopP9wruIjfQPPZyMYfZZsPtd9DWOl5f6/v34bNTxjsKw\\n/bgPiZN7azitR30hmgU6Xt85e2OzoLR5yJNJXVK3Nif2oX/+S/HIbuhJAoGBAID2\\n/GSRoLdcZ5BUtKgE6uYyH18yCFETr/75j/WIO+VmnHjDHfsZiIPj8iqVLVqZHwAz\\n2Sx/YZd54ohdf40COEvtwiq9tZd7O5o/BdFeBysXYxMGeBoBsnniPw7/NXBM+Z0v\\nKHrjd0F1BQWVREKpvgM6rBUTrYudZS+0LUfkjUBVAoGAaa1v0VMmFRnbEz9bbvj5\\n+UCTOs273Cz6ZPzXdogRcenCUixa/jU85b73VikW2WFn2LDtR6yxUWIhA8O3PcPP\\nOVVmL/QcbL65voWdM8yM7ZwV1hnntajqTViQz3gLvldQC6EeYoPehHuYk/FR5cGW\\nZF14AmnKFD9ruXZ3d7qFFdQ=\\n-----END PRIVATE KEY-----\\n',\n", - " 'client_email': 'geetools-test@ee-geetools.iam.gserviceaccount.com',\n", - " 'client_id': '107829403003588030989',\n", - " 'auth_uri': 'https://accounts.google.com/o/oauth2/auth',\n", - " 'token_uri': 'https://oauth2.googleapis.com/token',\n", - " 'auth_provider_x509_cert_url': 'https://www.googleapis.com/oauth2/v1/certs',\n", - " 'client_x509_cert_url': 'https://www.googleapis.com/robot/v1/metadata/x509/geetools-test%40ee-geetools.iam.gserviceaccount.com',\n", - " 'universe_domain': 'googleapis.com'})" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" + "ename": "FileNotFoundError", + "evalue": "[Errno 2] No such file or directory: 'toto'", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mFileNotFoundError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32m/workspaces/gee_tools/test.ipynb Cell 1\u001b[0m line \u001b[0;36m3\n\u001b[1;32m 1\u001b[0m \u001b[39mfrom\u001b[39;00m \u001b[39mpathlib\u001b[39;00m \u001b[39mimport\u001b[39;00m Path\n\u001b[0;32m----> 3\u001b[0m Path(\u001b[39m\"\u001b[39;49m\u001b[39mtoto\u001b[39;49m\u001b[39m\"\u001b[39;49m)\u001b[39m.\u001b[39;49munlink()\n", + "File \u001b[0;32m/usr/local/lib/python3.11/pathlib.py:1147\u001b[0m, in \u001b[0;36mPath.unlink\u001b[0;34m(self, missing_ok)\u001b[0m\n\u001b[1;32m 1142\u001b[0m \u001b[39m\u001b[39m\u001b[39m\"\"\"\u001b[39;00m\n\u001b[1;32m 1143\u001b[0m \u001b[39mRemove this file or link.\u001b[39;00m\n\u001b[1;32m 1144\u001b[0m \u001b[39mIf the path is a directory, use rmdir() instead.\u001b[39;00m\n\u001b[1;32m 1145\u001b[0m \u001b[39m\"\"\"\u001b[39;00m\n\u001b[1;32m 1146\u001b[0m \u001b[39mtry\u001b[39;00m:\n\u001b[0;32m-> 1147\u001b[0m os\u001b[39m.\u001b[39;49munlink(\u001b[39mself\u001b[39;49m)\n\u001b[1;32m 1148\u001b[0m \u001b[39mexcept\u001b[39;00m \u001b[39mFileNotFoundError\u001b[39;00m:\n\u001b[1;32m 1149\u001b[0m \u001b[39mif\u001b[39;00m \u001b[39mnot\u001b[39;00m missing_ok:\n", + "\u001b[0;31mFileNotFoundError\u001b[0m: [Errno 2] No such file or directory: 'toto'" + ] } ], "source": [ - "username, key = ee_token[\"username\"], ee_token[\"key\"]\n", - "username, key" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "1" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "import tempfile\n", "from pathlib import Path\n", "\n", - "with tempfile.TemporaryDirectory() as d:\n", - " file = Path(d) / \"token.json\"\n", - " file.write_text(json.dumps(key))\n", - " credentials = ee.ServiceAccountCredentials(username, str(file))\n", - " ee.Initialize(credentials)\n", - "\n", - "ee.Number(1).getInfo()\n", - "\n" + "Path(\"toto\").unlink()" ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": {