Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Require explicit initialization of eerepr #39

Merged
merged 3 commits into from
Dec 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,11 @@ $ conda install -c conda-forge eerepr

```python
import eerepr

eerepr.initialize()
```

Importing `eerepr` in a Jupyter notebook adds an HTML repr method to all Earth Engine objects. When you print them, you'll see an interactive HTML repr instead of a boring old string repr. Simple as that!
Running `eerepr.initialize` adds an HTML repr method to all Earth Engine objects. When you print them in an IPython environment, you'll see an interactive HTML repr instead of a boring old string repr. Simple as that!

> [!TIP]
> If you're using [geemap](https://github.com/gee-community/geemap), `eerepr` is automatically imported and activated by default!
Expand All @@ -51,6 +53,7 @@ import ee
import eerepr

ee.Initialize()
eerepr.initialize()

display(ee.FeatureCollection("LARSE/GEDI/GEDI02_A_002_INDEX").limit(3))
```
Expand All @@ -63,5 +66,3 @@ display(ee.FeatureCollection("LARSE/GEDI/GEDI02_A_002_INDEX").limit(3))
## Caching

`eerepr` uses caching to improve performance. Server data will only be requested once for each unique Earth Engine object, and all subsequent requests will be retrieved from the cache until the Jupyter session is restarted.

When you import `eerepr`, it is automatically initialized with an unlimited cache size. You can manually set the number of unique objects to cache using `eerepr.initialize(max_cache_size=n)`. A value of `None` sets an unlimited cache while a value of `0` disables caching.
6 changes: 2 additions & 4 deletions eerepr/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
from eerepr.config import options
from eerepr.repr import initialize
from eerepr.repr import initialize, reset

__version__ = "0.0.4"
__all__ = ["clear_cache", "initialize", "options"]

initialize(max_cache_size=options.max_cache_size)
__all__ = ["clear_cache", "initialize", "reset", "options"]
13 changes: 10 additions & 3 deletions eerepr/repr.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
EEObject = Union[ee.Element, ee.ComputedObject]

# Track which repr methods have been set so we can overwrite them if needed.
reprs_set = set()
reprs_set: set[EEObject] = set()


def _load_file(package: str, resource: str) -> str:
Expand Down Expand Up @@ -52,8 +52,8 @@ def _attach_html_repr(cls: type, repr: Any) -> None:
"""Add a HTML repr method to an EE class. Only overwrite the method if it was set by
this function.
"""
if not hasattr(cls, REPR_HTML) or cls.__name__ in reprs_set:
reprs_set.update([cls.__name__])
if not hasattr(cls, REPR_HTML) or cls in reprs_set:
reprs_set.update([cls])
setattr(cls, REPR_HTML, repr)


Expand Down Expand Up @@ -141,3 +141,10 @@ def initialize(max_cache_size: int | None = None) -> None:

for cls in [ee.Element, ee.ComputedObject]:
_attach_html_repr(cls, _ee_repr)


def reset():
"""Remove HTML repr methods added by eerepr to EE objects."""
for cls in reprs_set:
if hasattr(cls, REPR_HTML):
delattr(cls, REPR_HTML)
9 changes: 9 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import ee
import pytest

import eerepr

CACHE_DIR = Path(__file__).parent / "data/"
CACHE_PATH = CACHE_DIR / ".cache.json"

Expand All @@ -13,6 +15,13 @@ def pytest_sessionstart(session):
ee.Initialize()


@pytest.fixture(autouse=True)
def reset_eerepr():
"""Reset eerepr after each test to avoid inconsistent state."""
yield
eerepr.reset()


@pytest.fixture(scope="session")
def original_datadir():
"""Set the pytest-regressions directory."""
Expand Down
31 changes: 30 additions & 1 deletion tests/test_reprs.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
import ee
import pytest

import eerepr # noqa: F401
import eerepr
from eerepr.repr import _repr_html_


def test_error():
"""Test that an object that raises on getInfo falls back to the string repr and
warns.
"""
eerepr.initialize()
with pytest.warns(UserWarning, match="Getting info failed"):
rep = ee.Projection("not a real epsg")._repr_html_()

assert "Projection object" in rep
eerepr.reset()


def test_full_repr(data_regression):
Expand All @@ -22,3 +24,30 @@ def test_full_repr(data_regression):
objects = get_test_objects().items()
rendered = _repr_html_(ee.List([obj[1] for obj in objects]))
data_regression.check(rendered)


def test_initialize_and_reset():
"""Test that _repr_html_ is added and removed by eerepr."""
assert not hasattr(ee.Number(1), "_repr_html_")
eerepr.initialize()
assert hasattr(ee.Number(1), "_repr_html_")
eerepr.reset()
assert not hasattr(ee.Number(1), "_repr_html_")


def test_existing_repr_html():
"""If an object already has a _repr_html_, eerepr shouldn't touch it."""

class SpecialNumber(ee.Number):
def _repr_html_(self):
return "foo"

obj = SpecialNumber(0)

# initialize shouldn't overwrite the existing repr
eerepr.initialize()
assert obj._repr_html_() == "foo"

# reset shouldn't remove the existing repr
eerepr.reset()
assert obj._repr_html_() == "foo"