Skip to content

Commit

Permalink
refactor: improve accessor implementation (#193)
Browse files Browse the repository at this point in the history
  • Loading branch information
12rambau committed Feb 4, 2024
2 parents 5da5b6d + d7da3da commit bf082d3
Show file tree
Hide file tree
Showing 26 changed files with 593 additions and 165 deletions.
22 changes: 18 additions & 4 deletions docs/usage/layout.rst
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,15 @@ data manipulation

- :py:meth:`ee.Array.geetools.set <geetools.Array.Array.set>`: Set the value of a cell in an array. **Manually be loaded**

ee.Authenticate
^^^^^^^^^^^^^^^

- :py:meth:`ee.Authenticate.geetools.new_user <geetools.Authenticate.AuthenticateAccessor.new_user>`: :docstring:`geetools.AuthenticateAccessor.new_user`
- :py:meth:`ee.Authenticate.geetools.delete_user <geetools.Authenticate.AuthenticateAccessor.delete_user>`: :docstring:`geetools.AuthenticateAccessor.delete_user`
- :py:meth:`ee.Authenticate.geetools.list_user <geetools.Authenticate.AuthenticateAccessor.list_user>`: :docstring:`geetools.AuthenticateAccessor.list_user`
- :py:meth:`ee.Authenticate.geetools.rename_user <geetools.Authenticate.AuthenticateAccessor.rename_user>`: :docstring:`geetools.AuthenticateAccessor.rename_user`


ee.ComputedObject
^^^^^^^^^^^^^^^^^

Expand Down Expand Up @@ -110,13 +119,13 @@ Extra operations
- :py:meth:`ee.Dictionary.geetools.sort <geetools.Dictionary.Dictionary.sort>`: :docstring:`geetools.Dictionary.sort`
- :py:meth:`ee.Dictionary.geetools.getMany <geetools.Dictionary.Dictionary.getMany>`: :docstring:`geetools.Dictionary.getMany`

Feature
^^^^^^^
ee.Feature
^^^^^^^^^^

- :py:meth:`ee.Feature.geetools.toFeatureCollection <geetools.Feature.Feature.toFeatureCollection>`: :docstring:`geetools.Feature.toFeatureCollection`

FeatureCollection
^^^^^^^^^^^^^^^^^
ee.FeatureCollection
^^^^^^^^^^^^^^^^^^^^

Properties management
#####################
Expand Down Expand Up @@ -232,6 +241,11 @@ Data extraction
- :py:meth:`ee.ImageCollection.geetools.iloc <geetools.ImageCollection.ImageCollection.iloc>`: :docstring:`geetools.ImageCollection.iloc`
- :py:meth:`ee.ImageCollection.geetools.integral <geetools.ImageCollection.ImageCollection.integral>`: :docstring:`geetools.ImageCollection.integral`

ee.Initialize
^^^^^^^^^^^^^

- :py:meth:`ee.Initialize.geetools.from_user <geetools.Initialize.InitializeAccessor.from_user>`: :docstring:`geetools.InitializeAccessor.from_user`

ee.Join
^^^^^^^

Expand Down
7 changes: 3 additions & 4 deletions docs/usage/pattern.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,12 @@ Writing Custom Accessors

To resolve this issue for more complex cases, ``geetools`` has implemented 3 decorators:

- A class decorator: ``register_class_accessor()``
- A function decorator: ``register_function_accessor()``
- A module decorator: ``register_module_accessor()``
- A class decorator: :py:meth:`register_class_accessor <geetools.accessors.register_class_accessor>`
- A function decorator: :py:meth:`register_function_accessor <geetools.accessors.register_function_accessor>`

They are used to add custom “accessors” on objects/functions/modules thereby “extending” the functionality of your ``ee`` object.

Here’s how we use these decorators to write a custom “geetools” accessor implementing a extra method to ``ee.Number``object:
Here’s how we use these decorators to write a custom “geetools” accessor implementing a extra method to ``ee.Number`` object:

.. code-block:: python
Expand Down
4 changes: 2 additions & 2 deletions geetools/Array/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@

import ee

from geetools.accessors import geetools_accessor
from geetools.accessors import register_class_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
# ee.Initialize()


@geetools_accessor(ee.Array)
@register_class_accessor(ee.Array, "geetools")
class Array:
"""Toolbox for the ``ee.Array`` class."""

Expand Down
82 changes: 22 additions & 60 deletions geetools/User/__init__.py → geetools/Authenticate/__init__.py
Original file line number Diff line number Diff line change
@@ -1,68 +1,28 @@
"""A User manager for Google Earth Engine Python API."""
import json
"""Toolbox for the ``ee.Authenticate`` function."""
from __future__ import annotations

from contextlib import suppress
from pathlib import Path
from shutil import move
from tempfile import TemporaryDirectory
from typing import Callable

import ee
from google.oauth2.credentials import Credentials

from geetools.accessors import geetools_extend


@geetools_extend(ee)
class User:
"""CRUD system to manage multiple user accounts on the same machine."""

@staticmethod
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.
from geetools.accessors import register_function_accessor

Args:
name: The name of the user as saved when created. use default if not set
credential_pathname: The path to the folder where the credentials are stored. If not set, it uses the default path

Example:
.. code-block:: python
import ee
import geetools
geetools.User.set()
# check that GEE is connected
ee.Number(1).getInfo()
"""
name = f"credentials{name}"
credential_pathname = credential_pathname or ee.oauth.get_credentials_path()
credential_path = Path(credential_pathname).parent
@register_function_accessor(ee.Authenticate, "geetools")
class AuthenticateAccessor:
"""Create an accessor for the ``ee.Authenticate`` function."""

try:
tokens = json.loads((credential_path / name).read_text())
refresh_token = tokens["refresh_token"]
client_id = tokens["client_id"]
client_secret = tokens["client_secret"]
credentials = Credentials(
None,
refresh_token=refresh_token,
token_uri=ee.oauth.TOKEN_URI,
client_id=client_id,
client_secret=client_secret,
scopes=ee.oauth.SCOPES,
)
except Exception:
raise ee.EEException(
"Please register this user first by using geetools.User.create first"
)

ee.Initialize(credentials)
def __init__(self, obj: Callable):
"""Initialize the class."""
self._obj = obj

@staticmethod
def create(name: str = "", credential_pathname: str = "") -> None:
"""Create a new user.
def new_user(name: str = "", credential_pathname: str = "") -> None:
"""Authenticate the user and save the credentials in a specific folder.
Equivalent to ee.Authenticate but where the registered user will not be the default one (the one you get when running ee.initialize())
Expand All @@ -88,19 +48,21 @@ def create(name: str = "", credential_pathname: str = "") -> None:
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,
# folder and with the default name. We have to save the existing one in tmp,
# and then exchange places between the newly created and the existing one
default = Path(ee.oauth.get_credentials_path())

with TemporaryDirectory() as dir:
suppress(move(default, Path(dir) / default.name))
with suppress(FileNotFoundError):
move(default, Path(dir) / default.name)
ee.Authenticate()
move(default, credential_path / name)
suppress(move(Path(dir) / default.name, default))
with suppress(FileNotFoundError):
move(Path(dir) / default.name, default)

@staticmethod
def delete(name: str = "", credential_pathname: str = "") -> None:
"""Delete a user.
def delete_user(name: str = "", credential_pathname: str = "") -> None:
"""Delete a user credential file.
Args:
name: The name of the user. If not set, it will delete the default user
Expand All @@ -127,7 +89,7 @@ def delete(name: str = "", credential_pathname: str = "") -> None:
(credential_path / name).unlink()

@staticmethod
def list(credential_pathname: str = "") -> list:
def list_user(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
Expand All @@ -152,7 +114,7 @@ def list(credential_pathname: str = "") -> list:
return [f.name.replace("credentials", "") or "default" for f in files]

@staticmethod
def rename(new: str, old: str = "", credential_pathname: str = "") -> None:
def rename_user(new: str, old: str = "", credential_pathname: str = "") -> None:
"""Rename a user without changing the credentials.
Args:
Expand Down
35 changes: 31 additions & 4 deletions geetools/ComputedObject/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@

import ee

from geetools.accessors import geetools_extend
from geetools.accessors import _register_extention
from geetools.types import pathlike


# -- types management ----------------------------------------------------------
@geetools_extend(ee.ComputedObject)
@_register_extention(ee.ComputedObject)
def isInstance(self, klass: Type) -> ee.Number:
"""Return 1 if the element is the passed type or 0 if not.
Expand All @@ -36,7 +36,7 @@ def isInstance(self, klass: Type) -> ee.Number:


# -- .gee files ----------------------------------------------------------------
@geetools_extend(ee.ComputedObject)
@_register_extention(ee.ComputedObject)
def save(self, path: pathlike) -> Path:
"""Save a ``ComputedObject`` to a .gee file.
Expand Down Expand Up @@ -68,7 +68,7 @@ def save(self, path: pathlike) -> Path:
return path


@geetools_extend(ee.ComputedObject) # type: ignore
@_register_extention(ee.ComputedObject) # type: ignore
@classmethod
def open(cls, path: pathlike) -> ee.ComputedObject:
"""Open a .gee file as a ComputedObject.
Expand Down Expand Up @@ -99,3 +99,30 @@ def open(cls, path: pathlike) -> ee.ComputedObject:
raise ValueError("File must be a .gee file")

return ee.deserializer.decode(json.loads(path.read_text()))


# placeholder classes for the isInstance method --------------------------------
@_register_extention(ee)
class Float:
"""Placeholder Float class to be used in the isInstance method."""

def __init__(self):
"""Avoid initializing the class."""
raise NotImplementedError("This class is a placeholder, it should not be initialized")

def __name__(self):
"""Return the class name."""
return "Float"


@_register_extention(ee)
class Integer:
"""Placeholder Integer class to be used in the isInstance method."""

def __init__(self):
"""Avoid initializing the class."""
raise NotImplementedError("This class is a placeholder, it should not be initialized")

def __name__(self):
"""Return the class name."""
return "Integer"
4 changes: 2 additions & 2 deletions geetools/Date/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@

import ee

from geetools.accessors import geetools_accessor
from geetools.accessors import register_class_accessor

EE_EPOCH = datetime(1970, 1, 1, 0, 0, 0)


@geetools_accessor(ee.Date)
@register_class_accessor(ee.Date, "geetools")
class Date:
"""Toolbox for the ``ee.Date`` class."""

Expand Down
4 changes: 2 additions & 2 deletions geetools/DateRange/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@

import ee

from geetools.accessors import geetools_accessor
from geetools.accessors import register_class_accessor
from geetools.types import ee_int


@geetools_accessor(ee.DateRange)
@register_class_accessor(ee.DateRange, "geetools")
class DateRange:
"""Toolbox for the ``ee.DateRange`` class."""

Expand Down
4 changes: 2 additions & 2 deletions geetools/Dictionary/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@

import ee

from geetools.accessors import geetools_accessor
from geetools.accessors import register_class_accessor
from geetools.types import ee_list


@geetools_accessor(ee.Dictionary)
@register_class_accessor(ee.Dictionary, "geetools")
class Dictionary:
"""Toolbox for the ``ee.Dictionary`` class."""

Expand Down
4 changes: 2 additions & 2 deletions geetools/Feature/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@

import ee

from geetools.accessors import geetools_accessor
from geetools.accessors import register_class_accessor


@geetools_accessor(ee.Feature)
@register_class_accessor(ee.Feature, "geetools")
class Feature:
"""Toolbox for the ``ee.Feature`` class."""

Expand Down
4 changes: 2 additions & 2 deletions geetools/FeatureCollection/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@

import ee

from geetools.accessors import geetools_accessor
from geetools.accessors import register_class_accessor
from geetools.types import ee_int, ee_str


@geetools_accessor(ee.FeatureCollection)
@register_class_accessor(ee.FeatureCollection, "geetools")
class FeatureCollection:
"""Toolbox for the `ee.FeatureCollection` class."""

Expand Down
4 changes: 2 additions & 2 deletions geetools/Filter/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@

import ee

from geetools.accessors import geetools_accessor
from geetools.accessors import register_class_accessor


@geetools_accessor(ee.Filter)
@register_class_accessor(ee.Filter, "geetools")
class Filter:
"""Toolbox for the ``ee.Filter`` class."""

Expand Down
19 changes: 0 additions & 19 deletions geetools/Float/__init__.py

This file was deleted.

4 changes: 2 additions & 2 deletions geetools/Geometry/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@

import ee

from geetools.accessors import geetools_accessor
from geetools.accessors import register_class_accessor


@geetools_accessor(ee.Geometry)
@register_class_accessor(ee.Geometry, "geetools")
class Geometry:
"""Toolbox for the ``ee.Geometry`` class."""

Expand Down

0 comments on commit bf082d3

Please sign in to comment.