diff --git a/.github/release.yml b/.github/release.yml index 686ae86e..6c93d111 100644 --- a/.github/release.yml +++ b/.github/release.yml @@ -9,6 +9,9 @@ changelog: - title: New Features labels: - changelog-enhancement + - title: Bug Fixes + labels: + - bug - title: Other Changes labels: - "*" diff --git a/.github/workflows/link_issue.yml b/.github/workflows/link_issue.yml index 82050ab8..b9de19f7 100644 --- a/.github/workflows/link_issue.yml +++ b/.github/workflows/link_issue.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Verify Linked Issue - uses: actions/github-script@v6 + uses: actions/github-script@v7 with: github-token: ${{secrets.GITHUB_TOKEN}} script: | diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4f2005ac..553404e7 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -28,8 +28,6 @@ jobs: # https://github.com/mamba-org/setup-micromamba/issues/227 micromamba-version: 1.5.10-0 environment-file: ${{ matrix.env }} - cache-environment: true - cache-downloads: true - name: Test env: diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 793d555c..7c79df95 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -95,6 +95,40 @@ jobs: '${{ github.ref_name }}' dist/** --repo '${{ github.repository }}' + build-for-prefix: + name: Build prefix package + needs: + - build + - install + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/tags/') + steps: + - uses: actions/checkout@v4 + - name: Download the built dist + uses: actions/download-artifact@v4 + with: + name: pip-dist + path: dist/ + - name: Add tag to recipe + run: sed -i "s/TAG_VERSION/${{ github.ref_name }}/g" publish/recipes/release/ecoscope.yaml + - name: Add dist to recipe + run: sed -i "s/DIST_NAME/$(find ./dist/*.tar.gz -printf "%f\n")/g" publish/recipes/release/ecoscope.yaml + - name: Log the generated recipe + run: cat publish/recipes/release/ecoscope.yaml + - name: Create channel + run: mkdir -p /tmp/ecoscope/release/artifacts + - name: Build prefix release + uses: prefix-dev/rattler-build-action@v0.2.16 + with: + recipe-path: publish/recipes/release/ecoscope.yaml + build-args: --output-dir /tmp/ecoscope/release/artifacts --channel https://prefix.dev/ecoscope-workflows --channel conda-forge + - run: | + for file in /tmp/ecoscope/release/artifacts/**/*.conda; do + rattler-build upload prefix -c ecoscope-workflows "$file" + done + env: + PREFIX_API_KEY: ${{ secrets.PREFIX_API_KEY }} + publish-to-pypi: name: Publish to PyPI needs: diff --git a/doc/source/notebooks/01. IO/EarthRanger_IO.ipynb b/doc/source/notebooks/01. IO/EarthRanger_IO.ipynb index 92f6f311..86bb9670 100644 --- a/doc/source/notebooks/01. IO/EarthRanger_IO.ipynb +++ b/doc/source/notebooks/01. IO/EarthRanger_IO.ipynb @@ -29,7 +29,7 @@ }, "outputs": [], "source": [ - "%pip install 'ecoscope[analysis,mapping,plotting] @ git+https://github.com/wildlife-dynamics/ecoscope@v1.8.5' &> /dev/null" + "%pip install 'ecoscope[analysis,mapping,plotting] @ git+https://github.com/wildlife-dynamics/ecoscope@v1.8.8' &> /dev/null" ] }, { diff --git a/doc/source/notebooks/01. IO/GEE_IO.ipynb b/doc/source/notebooks/01. IO/GEE_IO.ipynb index 73d01f72..0dbdaa97 100644 --- a/doc/source/notebooks/01. IO/GEE_IO.ipynb +++ b/doc/source/notebooks/01. IO/GEE_IO.ipynb @@ -27,7 +27,7 @@ "metadata": {}, "outputs": [], "source": [ - "!pip install 'ecoscope[analysis,mapping,plotting] @ git+https://github.com/wildlife-dynamics/ecoscope@v1.8.5' &> /dev/null" + "!pip install 'ecoscope[analysis,mapping,plotting] @ git+https://github.com/wildlife-dynamics/ecoscope@v1.8.8' &> /dev/null" ] }, { diff --git a/doc/source/notebooks/01. IO/Landscape Dynamics Data.ipynb b/doc/source/notebooks/01. IO/Landscape Dynamics Data.ipynb index 468ede93..5ea660ab 100644 --- a/doc/source/notebooks/01. IO/Landscape Dynamics Data.ipynb +++ b/doc/source/notebooks/01. IO/Landscape Dynamics Data.ipynb @@ -29,7 +29,7 @@ "metadata": {}, "outputs": [], "source": [ - "!pip install 'ecoscope[analysis,mapping,plotting] @ git+https://github.com/wildlife-dynamics/ecoscope@v1.8.5' &> /dev/null" + "!pip install 'ecoscope[analysis,mapping,plotting] @ git+https://github.com/wildlife-dynamics/ecoscope@v1.8.8' &> /dev/null" ] }, { diff --git a/doc/source/notebooks/02. Relocations & Trajectories/Relocations_and_Trajectories.ipynb b/doc/source/notebooks/02. Relocations & Trajectories/Relocations_and_Trajectories.ipynb index 67285736..9082fe1c 100644 --- a/doc/source/notebooks/02. Relocations & Trajectories/Relocations_and_Trajectories.ipynb +++ b/doc/source/notebooks/02. Relocations & Trajectories/Relocations_and_Trajectories.ipynb @@ -36,7 +36,7 @@ "metadata": {}, "outputs": [], "source": [ - "%pip install 'ecoscope[analysis,mapping,plotting] @ git+https://github.com/wildlife-dynamics/ecoscope@v1.8.5' &> /dev/null" + "%pip install 'ecoscope[analysis,mapping,plotting] @ git+https://github.com/wildlife-dynamics/ecoscope@v1.8.8' &> /dev/null" ] }, { diff --git a/doc/source/notebooks/03. Home Range & Movescape/EcoGraph.ipynb b/doc/source/notebooks/03. Home Range & Movescape/EcoGraph.ipynb index 281f9de5..657a3711 100644 --- a/doc/source/notebooks/03. Home Range & Movescape/EcoGraph.ipynb +++ b/doc/source/notebooks/03. Home Range & Movescape/EcoGraph.ipynb @@ -29,7 +29,7 @@ "source": [ "ECOSCOPE_RAW = \"https://raw.githubusercontent.com/wildlife-dynamics/ecoscope/master\"\n", "\n", - "!pip install 'ecoscope[analysis,mapping,plotting] @ git+https://github.com/wildlife-dynamics/ecoscope@v1.8.5' &> /dev/null" + "!pip install 'ecoscope[analysis,mapping,plotting] @ git+https://github.com/wildlife-dynamics/ecoscope@v1.8.8' &> /dev/null" ] }, { diff --git a/doc/source/notebooks/03. Home Range & Movescape/Elliptical Time Density (ETD).ipynb b/doc/source/notebooks/03. Home Range & Movescape/Elliptical Time Density (ETD).ipynb index 0b54a721..11030f72 100644 --- a/doc/source/notebooks/03. Home Range & Movescape/Elliptical Time Density (ETD).ipynb +++ b/doc/source/notebooks/03. Home Range & Movescape/Elliptical Time Density (ETD).ipynb @@ -29,7 +29,7 @@ "source": [ "ECOSCOPE_RAW = \"https://raw.githubusercontent.com/wildlife-dynamics/ecoscope/master\"\n", "\n", - "!pip install 'ecoscope[analysis,mapping,plotting] @ git+https://github.com/wildlife-dynamics/ecoscope@v1.8.5' &> /dev/null" + "!pip install 'ecoscope[analysis,mapping,plotting] @ git+https://github.com/wildlife-dynamics/ecoscope@v1.8.8' &> /dev/null" ] }, { diff --git a/doc/source/notebooks/03. Home Range & Movescape/Reduce Regions.ipynb b/doc/source/notebooks/03. Home Range & Movescape/Reduce Regions.ipynb index aba9972c..1efd5ded 100644 --- a/doc/source/notebooks/03. Home Range & Movescape/Reduce Regions.ipynb +++ b/doc/source/notebooks/03. Home Range & Movescape/Reduce Regions.ipynb @@ -29,7 +29,7 @@ "source": [ "ECOSCOPE_RAW = \"https://raw.githubusercontent.com/wildlife-dynamics/ecoscope/master\"\n", "\n", - "!pip install 'ecoscope[analysis,mapping,plotting] @ git+https://github.com/wildlife-dynamics/ecoscope@v1.8.5' &> /dev/null" + "!pip install 'ecoscope[analysis,mapping,plotting] @ git+https://github.com/wildlife-dynamics/ecoscope@v1.8.8' &> /dev/null" ] }, { diff --git a/doc/source/notebooks/04. EcoMap & EcoPlot/EcoMap.ipynb b/doc/source/notebooks/04. EcoMap & EcoPlot/EcoMap.ipynb index 68d6d675..de1af90b 100644 --- a/doc/source/notebooks/04. EcoMap & EcoPlot/EcoMap.ipynb +++ b/doc/source/notebooks/04. EcoMap & EcoPlot/EcoMap.ipynb @@ -36,7 +36,7 @@ "metadata": {}, "outputs": [], "source": [ - "%pip install 'ecoscope[analysis,mapping,plotting] @ git+https://github.com/wildlife-dynamics/ecoscope@v1.8.5' &> /dev/null" + "%pip install 'ecoscope[analysis,mapping,plotting] @ git+https://github.com/wildlife-dynamics/ecoscope@v1.8.8' &> /dev/null" ] }, { diff --git a/doc/source/notebooks/04. EcoMap & EcoPlot/EcoPlot.ipynb b/doc/source/notebooks/04. EcoMap & EcoPlot/EcoPlot.ipynb index 6c151407..667496ed 100644 --- a/doc/source/notebooks/04. EcoMap & EcoPlot/EcoPlot.ipynb +++ b/doc/source/notebooks/04. EcoMap & EcoPlot/EcoPlot.ipynb @@ -36,7 +36,7 @@ "metadata": {}, "outputs": [], "source": [ - "%pip install 'ecoscope[analysis,mapping,plotting] @ git+https://github.com/wildlife-dynamics/ecoscope@v1.8.5' &> /dev/null" + "%pip install 'ecoscope[analysis,mapping,plotting] @ git+https://github.com/wildlife-dynamics/ecoscope@v1.8.8' &> /dev/null" ] }, { diff --git a/doc/source/notebooks/05. Environmental Analyses/Landscape Grid.ipynb b/doc/source/notebooks/05. Environmental Analyses/Landscape Grid.ipynb index d500532a..ec0d9c50 100644 --- a/doc/source/notebooks/05. Environmental Analyses/Landscape Grid.ipynb +++ b/doc/source/notebooks/05. Environmental Analyses/Landscape Grid.ipynb @@ -29,7 +29,7 @@ "source": [ "ECOSCOPE_RAW = \"https://raw.githubusercontent.com/wildlife-dynamics/ecoscope/master\"\n", "\n", - "!pip install 'ecoscope[analysis,mapping,plotting] @ git+https://github.com/wildlife-dynamics/ecoscope@v1.8.5' &> /dev/null" + "!pip install 'ecoscope[analysis,mapping,plotting] @ git+https://github.com/wildlife-dynamics/ecoscope@v1.8.8' &> /dev/null" ] }, { diff --git a/doc/source/notebooks/05. Environmental Analyses/Remote Sensing Time Series Anomaly.ipynb b/doc/source/notebooks/05. Environmental Analyses/Remote Sensing Time Series Anomaly.ipynb index 3d3755ab..76c897db 100644 --- a/doc/source/notebooks/05. Environmental Analyses/Remote Sensing Time Series Anomaly.ipynb +++ b/doc/source/notebooks/05. Environmental Analyses/Remote Sensing Time Series Anomaly.ipynb @@ -29,7 +29,7 @@ "source": [ "ECOSCOPE_RAW = \"https://raw.githubusercontent.com/wildlife-dynamics/ecoscope/master\"\n", "\n", - "!pip install 'ecoscope[analysis,mapping,plotting] @ git+https://github.com/wildlife-dynamics/ecoscope@v1.8.5' &> /dev/null" + "!pip install 'ecoscope[analysis,mapping,plotting] @ git+https://github.com/wildlife-dynamics/ecoscope@v1.8.8' &> /dev/null" ] }, { diff --git a/doc/source/notebooks/05. Environmental Analyses/Seasonal Calculation.ipynb b/doc/source/notebooks/05. Environmental Analyses/Seasonal Calculation.ipynb index 12422f60..7889ab50 100644 --- a/doc/source/notebooks/05. Environmental Analyses/Seasonal Calculation.ipynb +++ b/doc/source/notebooks/05. Environmental Analyses/Seasonal Calculation.ipynb @@ -29,7 +29,7 @@ "source": [ "ECOSCOPE_RAW = \"https://raw.githubusercontent.com/wildlife-dynamics/ecoscope/master\"\n", "\n", - "!pip install 'ecoscope[analysis,mapping,plotting] @ git+https://github.com/wildlife-dynamics/ecoscope@v1.8.5' &> /dev/null" + "!pip install 'ecoscope[analysis,mapping,plotting] @ git+https://github.com/wildlife-dynamics/ecoscope@v1.8.8' &> /dev/null" ] }, { diff --git a/doc/source/notebooks/06. Data Management/Tracking Data Gantt Chart.ipynb b/doc/source/notebooks/06. Data Management/Tracking Data Gantt Chart.ipynb index 2d51fbf8..2d8ac523 100644 --- a/doc/source/notebooks/06. Data Management/Tracking Data Gantt Chart.ipynb +++ b/doc/source/notebooks/06. Data Management/Tracking Data Gantt Chart.ipynb @@ -29,7 +29,7 @@ "source": [ "ECOSCOPE_RAW = \"https://raw.githubusercontent.com/wildlife-dynamics/ecoscope/master\"\n", "\n", - "!pip install 'ecoscope[analysis,mapping,plotting] @ git+https://github.com/wildlife-dynamics/ecoscope@v1.8.5' &> /dev/null" + "!pip install 'ecoscope[analysis,mapping,plotting] @ git+https://github.com/wildlife-dynamics/ecoscope@v1.8.8' &> /dev/null" ] }, { diff --git a/ecoscope/base/base.py b/ecoscope/base/base.py index 38234a45..776109b2 100644 --- a/ecoscope/base/base.py +++ b/ecoscope/base/base.py @@ -412,6 +412,7 @@ def _create_trajsegments(gdf): "heading": track_properties.heading, "geometry": shapely.linestrings(coords), "junk_status": gdf.junk_status, + "nsd": track_properties.nsd, }, crs=4326, index=gdf.index, @@ -625,6 +626,13 @@ def dist_meters(self): _, _, distance = self.inverse_transformation return distance + @property + def nsd(self): + start_point = df["geometry"].iloc[0] + geod = Geod(ellps="WGS84") + geod_displacement = [geod.inv(start_point.x, start_point.y, geo.x, geo.y)[2] for geo in df["_geometry"]] + return [(x**2) / (1000 * 2) for x in geod_displacement] + @property def timespan_seconds(self): return (df["_fixtime"] - df["fixtime"]).dt.total_seconds() diff --git a/ecoscope/io/async_earthranger.py b/ecoscope/io/async_earthranger.py index ecf1b901..96b993d8 100644 --- a/ecoscope/io/async_earthranger.py +++ b/ecoscope/io/async_earthranger.py @@ -6,6 +6,7 @@ import ecoscope from ecoscope.io.utils import to_hex from ecoscope.io.earthranger_utils import clean_kwargs, to_gdf, clean_time_cols +from erclient.client import ERClientException, ERClientNotFound try: from erclient.client import AsyncERClient @@ -30,6 +31,28 @@ def __init__(self, sub_page_size=4000, tcp_limit=5, **kwargs): kwargs["client_id"] = kwargs.get("client_id", "das_web_client") super().__init__(**kwargs) + @classmethod + async def create(cls, **kwargs): + client = cls(**kwargs) + + await client._init_client() + return client + + async def _init_client(self): + if not self.auth: + try: + await self.login() + except ERClientNotFound: + raise ERClientNotFound("Failed login. Check Stack Trace for specific reason.") + else: + try: + await self.get_me() + except ERClientException: + raise ERClientException("Authorization token is invalid or expired.") + + async def get_me(self): + return await self._get("user/me", params={}) + async def get_sources( self, manufacturer_id=None, diff --git a/ecoscope/io/earthranger.py b/ecoscope/io/earthranger.py index ca2b3d50..6efc5774 100644 --- a/ecoscope/io/earthranger.py +++ b/ecoscope/io/earthranger.py @@ -34,22 +34,29 @@ def __init__(self, sub_page_size=4000, tcp_limit=5, **kwargs): self.tcp_limit = tcp_limit kwargs["client_id"] = kwargs.get("client_id", "das_web_client") super().__init__(**kwargs) - try: - self.login() - except ERClientNotFound: - raise ERClientNotFound("Failed login. Check Stack Trace for specific reason.") + + if not self.auth: + try: + self.login() + except ERClientNotFound: + raise ERClientNotFound("Failed login. Check Stack Trace for specific reason.") + else: + try: + self.get_me() + except ERClientException: + raise ERClientException("Authorization token is invalid or expired.") def _token_request(self, payload): response = requests.post(self.token_url, data=payload) if response.ok: - self.auth = json.loads(response.text) + self.auth = response.json() expires_in = int(self.auth["expires_in"]) - 5 * 60 self.auth_expires = pytz.utc.localize(datetime.datetime.utcnow()) + datetime.timedelta(seconds=expires_in) return True self.auth = None self.auth_expires = pytz.utc.localize(datetime.datetime.min) - raise ERClientNotFound(json.loads(response.text)["error_description"]) + raise ERClientNotFound(response.json().get("error_description", "invalid token")) """ GET Functions diff --git a/ecoscope/mapping/map.py b/ecoscope/mapping/map.py index 3d54e03e..13565923 100644 --- a/ecoscope/mapping/map.py +++ b/ecoscope/mapping/map.py @@ -493,7 +493,7 @@ def pil_layer(image, bounds, opacity=1): return layer @staticmethod - def get_named_tile_layer(layer: str) -> BitmapTileLayer: + def get_named_tile_layer(layer: str, opacity: float = 1) -> BitmapTileLayer: # From Leafmap # https://github.com/opengeos/leafmap/blob/master/leafmap/basemaps.py xyz_tiles = { @@ -534,6 +534,7 @@ def get_named_tile_layer(layer: str) -> BitmapTileLayer: max_zoom=layer.get("max_zoom", None), min_zoom=layer.get("min_zoom", None), max_requests=layer.get("max_requests", None), + opacity=opacity, ) def to_html( diff --git a/publish/recipes/release/ecoscope.yaml b/publish/recipes/release/ecoscope.yaml new file mode 100644 index 00000000..669f566e --- /dev/null +++ b/publish/recipes/release/ecoscope.yaml @@ -0,0 +1,73 @@ +context: + name: ecoscope + version: TAG_VERSION + +package: + name: ecoscope + version: ${{ version }} + +source: + path: ../../../dist/DIST_NAME + +build: + noarch: python + script: SETUPTOOLS_SCM_PRETEND_VERSION=${{ version }} pip install . -v + number: 5 + +requirements: + host: + - python + - setuptools >=45 + - setuptools-scm >=6.2 + - pip + run: + - python + - backoff + - earthengine-api + - earthranger-client + - fiona <1.10.0 + - geopandas <=0.14.2 + - numpy <2 # added by me, but should be ensured by geopandas anyway + - pyproj + - rasterio + - tqdm + # ~ analysis ~ + - astroplan + # - datashader # (cisaacstern) per atmorling, not required for current workflows + - igraph + - mapclassify + # - matplotlib # disabling in favor of matplotlib-base which is smaller + - matplotlib-base + - networkx + - numba + - scipy + - scikit-image + - scikit-learn + # ~ plotting ~ + # - kaleido # not available on conda and do we actually use this? + - plotly + # - scikit-learn # duplicate with analysis + # ~ mapping ~ + - lonboard==0.0.3 + # - matplotlib # duplicate with analysis + # - mapclassify # duplicate with analysis + +tests: + - python: + imports: + - ecoscope + - ecoscope.analysis + - ecoscope.base + - ecoscope.io + - ecoscope.mapping + - ecoscope.plotting + +about: + summary: Standard Analytical Reporting Framework for Conservation + license: BSD-3-Clause + license_file: LICENSE + +extra: + recipe-maintainers: + - cisaacstern + - atmorling diff --git a/tests/test_asyncearthranger_io.py b/tests/test_asyncearthranger_io.py index 8eafb57d..2703edb5 100644 --- a/tests/test_asyncearthranger_io.py +++ b/tests/test_asyncearthranger_io.py @@ -2,7 +2,9 @@ import pandas as pd import pytest +import pytest_asyncio import ecoscope +from erclient import ERClientException if not pytest.earthranger: pytest.skip( @@ -11,12 +13,12 @@ ) -@pytest.fixture -def er_io_async(): +@pytest_asyncio.fixture +async def er_io_async(): ER_SERVER = "https://mep-dev.pamdas.org" ER_USERNAME = os.getenv("ER_USERNAME") ER_PASSWORD = os.getenv("ER_PASSWORD") - er_io = ecoscope.io.AsyncEarthRangerIO(server=ER_SERVER, username=ER_USERNAME, password=ER_PASSWORD) + er_io = await ecoscope.io.AsyncEarthRangerIO.create(server=ER_SERVER, username=ER_USERNAME, password=ER_PASSWORD) return er_io @@ -251,3 +253,36 @@ async def test_display_map(er_io_async): await er_io_async.get_event_type_display_name(event_type="shot_rep", event_property="shotrep_timeofshot") == "Time when shot was heard" ) + + +@pytest.mark.asyncio +async def test_existing_token(er_io_async): + await er_io_async.login() + new_client = ecoscope.io.AsyncEarthRangerIO( + service_root=er_io_async.service_root, + token_url=er_io_async.token_url, + token=er_io_async.auth.get("access_token"), + ) + + sources = await new_client.get_sources_dataframe() + assert not sources.empty + + +@pytest.mark.asyncio +async def test_existing_token_expired(er_io_async): + await er_io_async.login() + token = er_io_async.auth.get("access_token") + await er_io_async.refresh_token() + + new_client = ecoscope.io.AsyncEarthRangerIO( + service_root=er_io_async.service_root, token_url=er_io_async.token_url, token=token + ) + + with pytest.raises(ERClientException): + await new_client.get_sources_dataframe() + + +@pytest.mark.asyncio +async def test_get_me(er_io_async): + me = await er_io_async.get_me() + assert me.get("username") diff --git a/tests/test_base.py b/tests/test_base.py index a2c21677..18c10e26 100644 --- a/tests/test_base.py +++ b/tests/test_base.py @@ -50,6 +50,30 @@ def test_relocations_from_gdf_preserve_fields(er_io): gpd.testing.assert_geodataframe_equal(relocations, ecoscope.base.Relocations.from_gdf(relocations)) +def test_trajectory_properties(movebank_relocations): + trajectory = ecoscope.base.Trajectory.from_relocations(movebank_relocations) + + assert "groupby_col" in trajectory + assert "segment_start" in trajectory + assert "segment_end" in trajectory + assert "timespan_seconds" in trajectory + assert "speed_kmhr" in trajectory + assert "heading" in trajectory + assert "geometry" in trajectory + assert "junk_status" in trajectory + assert "nsd" in trajectory + + trajectory = trajectory.loc[trajectory.groupby_col == "Habiba"].head(5) + + expected_nsd = pd.Series( + [0.446425, 1.803153, 2.916319, 28.909629, 72.475410], + dtype=np.float64, + index=pd.Index([368706890, 368706891, 368706892, 368706893, 368706894], name="event-id"), + name="nsd", + ) + pandas.testing.assert_series_equal(trajectory["nsd"], expected_nsd) + + def test_displacement_property(movebank_relocations): trajectory = ecoscope.base.Trajectory.from_relocations(movebank_relocations) expected = pd.Series( diff --git a/tests/test_earthranger_io.py b/tests/test_earthranger_io.py index add3d6c0..f18d92ce 100644 --- a/tests/test_earthranger_io.py +++ b/tests/test_earthranger_io.py @@ -8,6 +8,7 @@ from shapely.geometry import Point import ecoscope +from erclient import ERClientException if not pytest.earthranger: pytest.skip( @@ -291,3 +292,23 @@ def test_get_subjects_chunking(er_io): chunked_request_result = er_io.get_subjects(id=subject_ids, max_ids_per_request=1) pd.testing.assert_frame_equal(single_request_result, chunked_request_result) + + +def test_existing_token(er_io): + new_client = ecoscope.io.EarthRangerIO( + service_root=er_io.service_root, token_url=er_io.token_url, token=er_io.auth.get("access_token") + ) + + events = new_client.get_patrol_events( + since=pd.Timestamp("2017-01-01").isoformat(), + until=pd.Timestamp("2017-04-01").isoformat(), + ) + assert not events.empty + + +def test_existing_token_expired(er_io): + token = er_io.auth.get("access_token") + er_io.refresh_token() + + with pytest.raises(ERClientException, match="Authorization token is invalid or expired."): + ecoscope.io.EarthRangerIO(service_root=er_io.service_root, token_url=er_io.token_url, token=token) diff --git a/tests/test_ecomap.py b/tests/test_ecomap.py index e636423f..773907cb 100644 --- a/tests/test_ecomap.py +++ b/tests/test_ecomap.py @@ -326,3 +326,12 @@ def test_add_polygon_with_color(poly_gdf): # validating zoom param by checking view state is non-default assert m.view_state.longitude != 10 assert m.view_state.latitude != 0 + + +def test_add_named_tile_layer(): + m = EcoMap() + m.add_layer(m.get_named_tile_layer("HYBRID", opacity=0.3)) + + assert len(m.layers) == 2 + assert isinstance(m.layers[1], BitmapTileLayer) + assert m.layers[1].opacity == 0.3 diff --git a/tests/test_output/geofence_crossing_point.feather b/tests/test_output/geofence_crossing_point.feather index f0ed3ad3..cd4bf130 100644 Binary files a/tests/test_output/geofence_crossing_point.feather and b/tests/test_output/geofence_crossing_point.feather differ