From 7a67091b13d844562f1ff1cd8f312de56e0e1705 Mon Sep 17 00:00:00 2001 From: dfguerrerom Date: Wed, 10 Apr 2024 17:19:48 +0200 Subject: [PATCH 1/6] refactor(map_fullscreen): simplify fullscreen method for maps --- sepal_ui/frontend/css/custom.css | 10 +++++ sepal_ui/frontend/js/jupyter_embed.js | 20 --------- sepal_ui/frontend/js/jupyter_fullscreen.js | 20 --------- sepal_ui/mapping/fullscreen_control.py | 52 ++++++---------------- 4 files changed, 23 insertions(+), 79 deletions(-) delete mode 100644 sepal_ui/frontend/js/jupyter_embed.js delete mode 100644 sepal_ui/frontend/js/jupyter_fullscreen.js diff --git a/sepal_ui/frontend/css/custom.css b/sepal_ui/frontend/css/custom.css index 3bbe7f04..7a714cf8 100644 --- a/sepal_ui/frontend/css/custom.css +++ b/sepal_ui/frontend/css/custom.css @@ -116,3 +116,13 @@ nav.v-navigation-drawer { contain: revert !important; background-color: revert !important; } + + +.full-screen-map > .leaflet-container { + position : fixed !important; + width : 100vw; + height : calc(100vh - 48px); + z-index: 800; + bottom : 0; + left : 0; +} \ No newline at end of file diff --git a/sepal_ui/frontend/js/jupyter_embed.js b/sepal_ui/frontend/js/jupyter_embed.js deleted file mode 100644 index 15e83ea1..00000000 --- a/sepal_ui/frontend/js/jupyter_embed.js +++ /dev/null @@ -1,20 +0,0 @@ -/* set a selected map to embed mode (i.e. default display) */ -var i = 0; -const wait_unitl_element_appear = setInterval(() => { - var element = document.querySelector(".%s .leaflet-container"); - if (element != null) { - element.style.position = ""; - element.style.width = ""; - element.style.height = ""; - element.style.zIndex = ""; - element.style.bottom = ""; - element.style.left = ""; - window.dispatchEvent(new Event("resize")); - clearInterval(wait_unitl_element_appear); - } else if (i > 50) { - clearInterval(wait_unitl_element_appear); - console.log("cannot find the map element"); - } else { - i++; - } -}, 100); diff --git a/sepal_ui/frontend/js/jupyter_fullscreen.js b/sepal_ui/frontend/js/jupyter_fullscreen.js deleted file mode 100644 index 1a22651c..00000000 --- a/sepal_ui/frontend/js/jupyter_fullscreen.js +++ /dev/null @@ -1,20 +0,0 @@ -/* set a selected map to fullscreen */ -var i = 0; -const wait_unitl_element_appear = setInterval(() => { - var element = document.querySelector(".%s .leaflet-container"); - if (element != null) { - element.style.position = "fixed"; - element.style.width = "100vw"; - element.style.height = "calc(100vh - %s)"; - element.style.zIndex = 800; - element.style.bottom = 0; - element.style.left = 0; - window.dispatchEvent(new Event("resize")); - clearInterval(wait_unitl_element_appear); - } else if (i > 50) { - clearInterval(wait_unitl_element_appear); - console.log("cannot find the map element"); - } else { - i++; - } -}, 100); diff --git a/sepal_ui/mapping/fullscreen_control.py b/sepal_ui/mapping/fullscreen_control.py index 2c4ff28a..069e3e71 100644 --- a/sepal_ui/mapping/fullscreen_control.py +++ b/sepal_ui/mapping/fullscreen_control.py @@ -5,9 +5,9 @@ import ipyvuetify as v from ipyleaflet import Map, WidgetControl -from IPython.display import Javascript, display from sepal_ui.mapping.map_btn import MapBtn +from sepal_ui.frontend.resize_trigger import rt class FullScreenControl(WidgetControl): @@ -43,11 +43,10 @@ def __init__( fullapp: either or not the map will be used as the sole widget/tile of an application kwargs: any available arguments from a ipyleaflet WidgetControl """ - # set the offset - offset = "48px" if fullapp else "0px" # register the required zoom value self.zoomed = fullscreen + self.m = m # create a btn self.w_btn = MapBtn(self.ICONS[self.zoomed]) @@ -63,41 +62,11 @@ def __init__( # add javascrip behaviour self.w_btn.on_event("click", self.toggle_fullscreen) - # save the 2 fullscrenn js code in a table 0 for embedded and 1 for fullscreen - js_dir = Path(__file__).parents[1] / "frontend/js" - embed = (js_dir / "jupyter_embed.js").read_text() % m._id - full = (js_dir / "jupyter_fullscreen.js").read_text() % (m._id, offset) - - # template with js behaviour - # "jupyter_fullscreen" place the "leaflet-container element on the front screen - # and expand it's display to the full screen - # "jupyter_embed" reset all the changed parameter - # both trigger the resize event to force the reload of the Tilelayers - - default = "fullscreen" if self.zoomed else "embed" - - self.template = v.VuetifyTemplate( - template=f""" - - - """ + ( + self.m.add_class("full-screen-map") + if fullapp + else self.m.remove_class("full-screen-map") ) - display(self.template) def toggle_fullscreen(self, *args) -> None: """Toggle fullscreen state. @@ -111,7 +80,12 @@ def toggle_fullscreen(self, *args) -> None: # change button icon self.w_btn.children[0].children = [self.ICONS[self.zoomed]] - # zoom - self.template.send({"method": self.METHODS[self.zoomed], "args": []}) + ( + self.m.add_class("full-screen-map") + if self.zoomed + else self.m.remove_class("full-screen-map") + ) + + rt.resize() return From 287177f3d4f14e5f2e1dc71b51468c5e6e0e9a23 Mon Sep 17 00:00:00 2001 From: dfguerrerom Date: Wed, 10 Apr 2024 17:21:36 +0200 Subject: [PATCH 2/6] feat: add fullscreen control as additional parameter to sepalmap --- sepal_ui/mapping/sepal_map.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sepal_ui/mapping/sepal_map.py b/sepal_ui/mapping/sepal_map.py index 7b8618f6..4a2861ad 100644 --- a/sepal_ui/mapping/sepal_map.py +++ b/sepal_ui/mapping/sepal_map.py @@ -3,6 +3,8 @@ # known bug of rasterio import os +from sepal_ui.mapping.fullscreen_control import FullScreenControl + if "GDAL_DATA" in list(os.environ.keys()): del os.environ["GDAL_DATA"] if "PROJ_LIB" in list(os.environ.keys()): @@ -130,6 +132,9 @@ def __init__( self.add(ipl.AttributionControl(position="bottomleft", prefix="SEPAL")) self.add(ipl.ScaleControl(position="bottomleft", imperial=False)) + if kwargs.get("fullscreen_control", False): + self.add(FullScreenControl(self)) + # specific drawing control self.dc = DrawControl(self) not dc or self.add(self.dc) From c0771b759e8d4b77ea1f2a07c8fca2b778ff9d83 Mon Sep 17 00:00:00 2001 From: dfguerrerom Date: Wed, 10 Apr 2024 17:26:54 +0200 Subject: [PATCH 3/6] style(lint): lint --- sepal_ui/frontend/css/custom.css | 13 ++++++------- sepal_ui/mapping/fullscreen_control.py | 4 +--- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/sepal_ui/frontend/css/custom.css b/sepal_ui/frontend/css/custom.css index 7a714cf8..d8eed29a 100644 --- a/sepal_ui/frontend/css/custom.css +++ b/sepal_ui/frontend/css/custom.css @@ -117,12 +117,11 @@ nav.v-navigation-drawer { background-color: revert !important; } - .full-screen-map > .leaflet-container { - position : fixed !important; - width : 100vw; - height : calc(100vh - 48px); + position: fixed !important; + width: 100vw; + height: calc(100vh - 48px); z-index: 800; - bottom : 0; - left : 0; -} \ No newline at end of file + bottom: 0; + left: 0; +} diff --git a/sepal_ui/mapping/fullscreen_control.py b/sepal_ui/mapping/fullscreen_control.py index 069e3e71..e61f6490 100644 --- a/sepal_ui/mapping/fullscreen_control.py +++ b/sepal_ui/mapping/fullscreen_control.py @@ -1,13 +1,12 @@ """Customized control to toggle the fullscreen state of the map.""" -from pathlib import Path from typing import List, Optional import ipyvuetify as v from ipyleaflet import Map, WidgetControl -from sepal_ui.mapping.map_btn import MapBtn from sepal_ui.frontend.resize_trigger import rt +from sepal_ui.mapping.map_btn import MapBtn class FullScreenControl(WidgetControl): @@ -43,7 +42,6 @@ def __init__( fullapp: either or not the map will be used as the sole widget/tile of an application kwargs: any available arguments from a ipyleaflet WidgetControl """ - # register the required zoom value self.zoomed = fullscreen self.m = m From c5576eebb129c2c4d7ac775ff5556f6ca7a9b078 Mon Sep 17 00:00:00 2001 From: dfguerrerom Date: Fri, 12 Apr 2024 14:38:54 +0200 Subject: [PATCH 4/6] refactor: readabilitty --- sepal_ui/mapping/fullscreen_control.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/sepal_ui/mapping/fullscreen_control.py b/sepal_ui/mapping/fullscreen_control.py index e61f6490..80020418 100644 --- a/sepal_ui/mapping/fullscreen_control.py +++ b/sepal_ui/mapping/fullscreen_control.py @@ -60,11 +60,10 @@ def __init__( # add javascrip behaviour self.w_btn.on_event("click", self.toggle_fullscreen) - ( + if fullapp: self.m.add_class("full-screen-map") - if fullapp - else self.m.remove_class("full-screen-map") - ) + else: + self.m.remove_class("full-screen-map") def toggle_fullscreen(self, *args) -> None: """Toggle fullscreen state. @@ -78,11 +77,10 @@ def toggle_fullscreen(self, *args) -> None: # change button icon self.w_btn.children[0].children = [self.ICONS[self.zoomed]] - ( + if self.zoomed: self.m.add_class("full-screen-map") - if self.zoomed - else self.m.remove_class("full-screen-map") - ) + else: + self.m.remove_class("full-screen-map") rt.resize() From 8d8252028a43d1a8019bcec813b8ffbfb9e22224 Mon Sep 17 00:00:00 2001 From: Rambaud Pierrick <12rambau@users.noreply.github.com> Date: Fri, 12 Apr 2024 15:20:15 +0200 Subject: [PATCH 5/6] chore: update the kaban destination --- .github/workflows/kaban.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/kaban.yml b/.github/workflows/kaban.yml index e5a13cb7..bc805954 100644 --- a/.github/workflows/kaban.yml +++ b/.github/workflows/kaban.yml @@ -17,4 +17,4 @@ jobs: uses: srggrs/assign-one-project-github-action@1.2.1 if: github.event.action == 'opened' with: - project: "https://github.com/12rambau/sepal_ui/projects/4" + project: "https://github.com/12rambau/sepal_ui/projects/5" From f1834556846d63089dc61361a766809c63564e93 Mon Sep 17 00:00:00 2001 From: dfguerrerom Date: Wed, 8 May 2024 16:04:23 +0200 Subject: [PATCH 6/6] refactor: update gee auth process --- .github/workflows/unit.yml | 4 +- docs/source/tutorials/decorator.rst | 3 +- sepal_ui/aoi/aoi_model.py | 4 +- sepal_ui/message/en/locale.json | 3 +- sepal_ui/scripts/decorator.py | 50 +++++++++++++----- sepal_ui/scripts/gee.py | 4 +- sepal_ui/scripts/utils.py | 50 +++++++++++++----- sepal_ui/sepalwidgets/inputs.py | 22 +++++++- tests/conftest.py | 13 +++-- tests/test_scripts/test_decorator.py | 58 +++++++++++++++++++-- tests/test_scripts/test_utils.py | 58 +++++++++++++++++++-- tests/test_sepalwidgets/test_AssetSelect.py | 11 +++- 12 files changed, 231 insertions(+), 49 deletions(-) diff --git a/.github/workflows/unit.yml b/.github/workflows/unit.yml index d7e9d8cf..5b3a0b12 100644 --- a/.github/workflows/unit.yml +++ b/.github/workflows/unit.yml @@ -9,7 +9,9 @@ on: env: PLANET_API_CREDENTIALS: ${{ secrets.PLANET_API_CREDENTIALS }} PLANET_API_KEY: ${{ secrets.PLANET_API_KEY }} - EARTHENGINE_TOKEN: ${{ secrets.EARTHENGINE_TOKEN }} + EARTHENGINE_TOKEN: ${{ secrets.EARTHENGINE_SERVICE_ACCOUNT }} + EARTHENGINE_SERVICE_ACCOUNT: ${{ secrets.EARTHENGINE_SERVICE_ACCOUNT }} + EARTHENGINE_PROJECT: ${{ secrets.EARTHENGINE_PROJECT }} jobs: lint: diff --git a/docs/source/tutorials/decorator.rst b/docs/source/tutorials/decorator.rst index 3ac23b31..552d921d 100644 --- a/docs/source/tutorials/decorator.rst +++ b/docs/source/tutorials/decorator.rst @@ -78,6 +78,7 @@ Let's import the required modules. All the decorators are stored in the utils mo .. code:: python + import ee from time import sleep import ipyvuetify as v import sepal_ui.sepalwidgets as sw @@ -142,7 +143,7 @@ It's time to use the decorators in the class methods. For this example, we will def request_items(self): """Connect to gee and request the root assets id's""" - folder = ee.data.getAssetRoots()[0]["id"] + folder = f"projects/{ee.data._cloud_api_user_project}/assets" return [ asset["id"] for asset diff --git a/sepal_ui/aoi/aoi_model.py b/sepal_ui/aoi/aoi_model.py index 2a230797..9f513810 100644 --- a/sepal_ui/aoi/aoi_model.py +++ b/sepal_ui/aoi/aoi_model.py @@ -154,7 +154,9 @@ def __init__( self.gee = gee if gee: su.init_ee() - self.folder = str(folder) or ee.data.getAssetRoots()[0]["id"] + self.folder = ( + str(folder) or f"projects/{ee.data._cloud_api_user_project}/assets/" + ) # set default values self.set_default(vector, admin, asset) diff --git a/sepal_ui/message/en/locale.json b/sepal_ui/message/en/locale.json index 4b08e551..f03349a2 100644 --- a/sepal_ui/message/en/locale.json +++ b/sepal_ui/message/en/locale.json @@ -42,7 +42,8 @@ "custom": "Custom", "no_access": "It seems like you do not have access to the input asset or it does not exist.", "wrong_type": "The type of the selected asset ({}) does not match authorized asset type ({}).", - "placeholder": "users/custom_user/custom_asset" + "placeholder": "projects/{project}/assets/asset_name", + "no_assets": "No user assets found in: '{}'" }, "load_table": { "too_small": "The provided file have less than 3 columns. Please provide a complete point file with at least ID, lattitude and longitude columns." diff --git a/sepal_ui/scripts/decorator.py b/sepal_ui/scripts/decorator.py index 4dfcf9c8..98de3b84 100644 --- a/sepal_ui/scripts/decorator.py +++ b/sepal_ui/scripts/decorator.py @@ -8,6 +8,7 @@ ... """ +import json import os import warnings from functools import wraps @@ -17,7 +18,6 @@ from warnings import warn import ee -import httplib2 import ipyvuetify as v from deprecated.sphinx import versionadded @@ -34,28 +34,52 @@ def init_ee() -> None: - """Initialize earth engine according to the environment. + r"""Initialize earth engine according using a token. - It will use the creddential file if the EARTHENGINE_TOKEN env variable exist. - Otherwise it use the simple Initialize command (asking the user to register if necessary). + THe environment used to run the tests need to have a EARTHENGINE_TOKEN variable. + The content of this variable must be the copy of a personal credential file that you can find on your local computer if you already run the earth engine command line tool. See the usage question for a github action example. + + - Windows: ``C:\Users\USERNAME\\.config\\earthengine\\credentials`` + - Linux: ``/home/USERNAME/.config/earthengine/credentials`` + - MacOS: ``/Users/USERNAME/.config/earthengine/credentials`` + + Note: + As all init method of pytest-gee, this method will fallback to a regular ``ee.Initialize()`` if the environment variable is not found e.g. on your local computer. """ - # only do the initialization if the credential are missing if not ee.data._credentials: - # if the credentials token is asved in the environment use it - if "EARTHENGINE_TOKEN" in os.environ: + credential_folder_path = Path.home() / ".config" / "earthengine" + credential_file_path = credential_folder_path / "credentials" + + if "EARTHENGINE_TOKEN" in os.environ and not credential_file_path.exists(): + # write the token to the appropriate folder ee_token = os.environ["EARTHENGINE_TOKEN"] - credential_folder_path = Path.home() / ".config" / "earthengine" credential_folder_path.mkdir(parents=True, exist_ok=True) - credential_file_path = credential_folder_path / "credentials" credential_file_path.write_text(ee_token) + # Extract the project name from credentials + _credentials = json.loads(credential_file_path.read_text()) + project_id = _credentials.get("project_id", _credentials.get("project", None)) + + if not project_id: + raise NameError( + "The project name cannot be detected. " + "Please set it using `earthengine set_project project_name`." + ) + + # Check if we are using a google service account + if _credentials.get("type") == "service_account": + ee_user = _credentials.get("client_email") + credentials = ee.ServiceAccountCredentials( + ee_user, str(credential_file_path) + ) + ee.Initialize(credentials=credentials) + ee.data._cloud_api_user_project = project_id + return + # if the user is in local development the authentication should # already be available - ee.Initialize(http_transport=httplib2.Http()) - assert len(ee.data.getAssetRoots()) > 0, ms.utils.ee.no_asset_root - - return + ee.Initialize(project=project_id) ################################################################################ diff --git a/sepal_ui/scripts/gee.py b/sepal_ui/scripts/gee.py index 175a33e7..b3807e2f 100644 --- a/sepal_ui/scripts/gee.py +++ b/sepal_ui/scripts/gee.py @@ -95,7 +95,7 @@ def get_assets(folder: Union[str, Path] = "") -> List[dict]: """ # set the folder and init the list asset_list = [] - folder = str(folder) or ee.data.getAssetRoots()[0]["id"] + folder = str(folder) or f"projects/{ee.data._cloud_api_user_project}/assets/" def _recursive_get(folder, asset_list): @@ -122,7 +122,7 @@ def is_asset(asset_name: str, folder: Union[str, Path] = "") -> bool: true if already in folder """ # get the folder - folder = str(folder) or ee.data.getAssetRoots()[0]["id"] + folder = str(folder) or f"projects/{ee.data._cloud_api_user_project}/assets/" # get all the assets asset_list = get_assets(folder) diff --git a/sepal_ui/scripts/utils.py b/sepal_ui/scripts/utils.py index de2f128b..d59e62f4 100644 --- a/sepal_ui/scripts/utils.py +++ b/sepal_ui/scripts/utils.py @@ -1,6 +1,7 @@ """All the helper function of sepal-ui.""" import configparser +import json import math import os import random @@ -12,7 +13,6 @@ from urllib.parse import urlparse import ee -import httplib2 import ipyvuetify as v import requests import tomli @@ -127,28 +127,52 @@ def get_file_size(filename: Pathlike) -> str: def init_ee() -> None: - """Initialize earth engine according to the environment. + r"""Initialize earth engine according using a token. - It will use the creddential file if the EARTHENGINE_TOKEN env variable exist. - Otherwise it use the simple Initialize command (asking the user to register if necessary). + THe environment used to run the tests need to have a EARTHENGINE_TOKEN variable. + The content of this variable must be the copy of a personal credential file that you can find on your local computer if you already run the earth engine command line tool. See the usage question for a github action example. + + - Windows: ``C:\Users\USERNAME\\.config\\earthengine\\credentials`` + - Linux: ``/home/USERNAME/.config/earthengine/credentials`` + - MacOS: ``/Users/USERNAME/.config/earthengine/credentials`` + + Note: + As all init method of pytest-gee, this method will fallback to a regular ``ee.Initialize()`` if the environment variable is not found e.g. on your local computer. """ - # only do the initialization if the credential are missing if not ee.data._credentials: - # if the credentials token is asved in the environment use it - if "EARTHENGINE_TOKEN" in os.environ: + credential_folder_path = Path.home() / ".config" / "earthengine" + credential_file_path = credential_folder_path / "credentials" + + if "EARTHENGINE_TOKEN" in os.environ and not credential_file_path.exists(): + # write the token to the appropriate folder ee_token = os.environ["EARTHENGINE_TOKEN"] - credential_folder_path = Path.home() / ".config" / "earthengine" credential_folder_path.mkdir(parents=True, exist_ok=True) - credential_file_path = credential_folder_path / "credentials" credential_file_path.write_text(ee_token) + # Extract the project name from credentials + _credentials = json.loads(credential_file_path.read_text()) + project_id = _credentials.get("project_id", _credentials.get("project", None)) + + if not project_id: + raise NameError( + "The project name cannot be detected. " + "Please set it using `earthengine set_project project_name`." + ) + + # Check if we are using a google service account + if _credentials.get("type") == "service_account": + ee_user = _credentials.get("client_email") + credentials = ee.ServiceAccountCredentials( + ee_user, str(credential_file_path) + ) + ee.Initialize(credentials=credentials) + ee.data._cloud_api_user_project = project_id + return + # if the user is in local development the authentication should # already be available - ee.Initialize(http_transport=httplib2.Http()) - assert len(ee.data.getAssetRoots()) > 0, ms.utils.ee.no_asset_root - - return + ee.Initialize(project=project_id) def normalize_str(msg: str, folder: bool = True) -> str: diff --git a/sepal_ui/sepalwidgets/inputs.py b/sepal_ui/sepalwidgets/inputs.py index 2c0de078..5062c698 100644 --- a/sepal_ui/sepalwidgets/inputs.py +++ b/sepal_ui/sepalwidgets/inputs.py @@ -690,7 +690,9 @@ def __init__( self.asset_info = None # if folder is not set use the root one - self.folder = str(folder) or ee.data.getAssetRoots()[0]["id"] + self.folder = ( + str(folder) or f"projects/{ee.data._cloud_api_user_project}/assets/" + ) self.types = types # load the default assets @@ -699,6 +701,8 @@ def __init__( # Validate the input as soon as the object is instantiated self.observe(self._validate, "v_model") + self.observe(self._fill_no_data, "items") + # set the default parameters kwargs.setdefault("v_model", None) kwargs.setdefault("clearable", True) @@ -714,10 +718,26 @@ def __init__( # load the assets in the combobox self._get_items() + self._fill_no_data({}) + # add js behaviours self.on_event("click:prepend", self._get_items) self.observe(self._get_items, "default_asset") + def _fill_no_data(self, _: dict) -> None: + """Fill the items with a no data message if the items are empty.""" + # Done in this way because v_slots are not working + if not self.items: + self.v_model = None + self.items = [ + { + "text": ms.widgets.asset_select.no_assets.format(self.folder), + "disabled": True, + } + ] + + return + @sd.switch("loading") def _validate(self, change: dict) -> None: """Validate the selected asset. Throw an error message if is not accessible or not in the type list.""" diff --git a/tests/conftest.py b/tests/conftest.py index 80a99a5c..e20ce789 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -21,8 +21,9 @@ try: su.init_ee() -except Exception: - pass # try to init earthengine. use ee.data._credentials to skip +except Exception as e: + raise e + # pass # try to init earthengine. use ee.data._credentials to skip # -- a component to fake the display in Ipython -------------------------------- @@ -123,7 +124,7 @@ def gee_dir(_hash: str) -> Optional[Path]: pytest.skip("Eathengine is not connected") # create a test folder with a hash name - root = ee.data.getAssetRoots()[0]["id"] + root = f"projects/{ee.data._cloud_api_user_project}/assets/" gee_dir = Path(root) / f"sepal-ui-{_hash}" ee.data.createAsset({"type": "FOLDER"}, str(gee_dir)) @@ -197,7 +198,7 @@ def fake_asset(gee_dir: Path) -> Path: @pytest.fixture(scope="session") def gee_user_dir(gee_dir: Path) -> Path: - """Return the path to the gee_dir assets without the project elements. + """Return the path to the gee_dir assets. Args: gee_dir: the path to the session defined GEE directory @@ -205,9 +206,7 @@ def gee_user_dir(gee_dir: Path) -> Path: Returns: the path to gee_dir """ - legacy_project = Path("projects/earthengine-legacy/assets") - - return gee_dir.relative_to(legacy_project) + return gee_dir @pytest.fixture(scope="session") diff --git a/tests/test_scripts/test_decorator.py b/tests/test_scripts/test_decorator.py index c32c04b6..1ca0f432 100644 --- a/tests/test_scripts/test_decorator.py +++ b/tests/test_scripts/test_decorator.py @@ -1,6 +1,9 @@ """Test the custom decorators.""" +import json +import os import warnings +from pathlib import Path import ee import ipyvuetify as v @@ -11,11 +14,58 @@ from sepal_ui.scripts.warning import SepalWarning -@pytest.mark.skipif(not ee.data._credentials, reason="GEE is not set") def test_init_ee() -> None: - """Check that ee can be initialized from sepal_ui.""" - # check that no error is raised - sd.init_ee() + """Test the init_ee_from_token function.""" + credentials_filepath = Path(ee.oauth.get_credentials_path()) + existing = False + + try: + # Reset credentials to force the initialization + # It can be initiated from different imports + ee.data._credentials = None + + # Get the credentials path + + # Remove the credentials file if it exists + if credentials_filepath.exists(): + existing = True + credentials_filepath.rename(credentials_filepath.with_suffix(".json.bak")) + + # Act: Earthengine token should be created + sd.init_ee() + + assert credentials_filepath.exists() + + # read the back up and remove the "project_id" key + credentials = json.loads( + credentials_filepath.with_suffix(".json.bak").read_text() + ) + + ## 2. Assert when there's no a project associated + # remove the project_id key if it exists + ee.data._credentials = None + credentials.pop("project_id", None) + credentials.pop("project", None) + if "EARTHENGINE_PROJECT" in os.environ: + del os.environ["EARTHENGINE_PROJECT"] + + # write the new credentials + credentials_filepath.write_text(json.dumps(credentials)) + + with pytest.raises(NameError) as e: + sd.init_ee() + + # Access the exception message via `e.value` + error_message = str(e.value) + assert "The project name cannot be detected" in error_message + + finally: + # restore the file + if existing: + credentials_filepath.with_suffix(".json.bak").rename(credentials_filepath) + + # check that no error is raised + sd.init_ee() return diff --git a/tests/test_scripts/test_utils.py b/tests/test_scripts/test_utils.py index 090899ab..33cdd646 100644 --- a/tests/test_scripts/test_utils.py +++ b/tests/test_scripts/test_utils.py @@ -1,7 +1,10 @@ """Test the helper methods contained in utils file.""" +import json +import os import random from configparser import ConfigParser +from pathlib import Path from unittest.mock import patch import ee @@ -105,11 +108,58 @@ def test_get_file_size() -> None: return -@pytest.mark.skipif(not ee.data._credentials, reason="GEE is not set") def test_init_ee() -> None: - """Check we can init EE.""" - # check that no error is raised - su.init_ee() + """Test the init_ee_from_token function.""" + credentials_filepath = Path(ee.oauth.get_credentials_path()) + existing = False + + try: + # Reset credentials to force the initialization + # It can be initiated from different imports + ee.data._credentials = None + + # Get the credentials path + + # Remove the credentials file if it exists + if credentials_filepath.exists(): + existing = True + credentials_filepath.rename(credentials_filepath.with_suffix(".json.bak")) + + # Act: Earthengine token should be created + su.init_ee() + + assert credentials_filepath.exists() + + # read the back up and remove the "project_id" key + credentials = json.loads( + credentials_filepath.with_suffix(".json.bak").read_text() + ) + + ## 2. Assert when there's no a project associated + # remove the project_id key if it exists + ee.data._credentials = None + credentials.pop("project_id", None) + credentials.pop("project", None) + if "EARTHENGINE_PROJECT" in os.environ: + del os.environ["EARTHENGINE_PROJECT"] + + # write the new credentials + credentials_filepath.write_text(json.dumps(credentials)) + + with pytest.raises(NameError) as e: + su.init_ee() + + # Access the exception message via `e.value` + error_message = str(e.value) + assert "The project name cannot be detected" in error_message + + finally: + # restore the file + if existing: + credentials_filepath.with_suffix(".json.bak").rename(credentials_filepath) + + # check that no error is raised + su.init_ee() return diff --git a/tests/test_sepalwidgets/test_AssetSelect.py b/tests/test_sepalwidgets/test_AssetSelect.py index 0c1c3776..2862c78d 100644 --- a/tests/test_sepalwidgets/test_AssetSelect.py +++ b/tests/test_sepalwidgets/test_AssetSelect.py @@ -7,6 +7,7 @@ import pytest from sepal_ui import sepalwidgets as sw +from sepal_ui.message import ms @pytest.mark.skipif(not ee.data._credentials, reason="GEE is not set") @@ -24,7 +25,15 @@ def test_init(gee_dir: Path, gee_user_dir: Path) -> None: # create an asset select with an undefined type asset_select = sw.AssetSelect(folder=str(gee_dir), types=["toto"]) - assert asset_select.items == [] + + # zero assets are represented by a disabled item + no_asset_item = [ + { + "text": ms.widgets.asset_select.no_assets.format(str(gee_dir)), + "disabled": True, + } + ] + assert asset_select.items == no_asset_item return