Skip to content

Commit

Permalink
WIP merge from main
Browse files Browse the repository at this point in the history
  • Loading branch information
aazuspan committed Jul 16, 2023
2 parents 5030b6d + 145f0d6 commit 212b81b
Show file tree
Hide file tree
Showing 18 changed files with 315 additions and 230 deletions.
2 changes: 1 addition & 1 deletion .github/scripts/make_ee_token.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@
os.makedirs(credential_dir, exist_ok=True)

with open(os.path.join(credential_dir, "credentials"), "w") as dst:
dst.write(credentials)
dst.write(credentials)
35 changes: 19 additions & 16 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -1,37 +1,40 @@
# This workflow sets up the Earth Engine token and runs all tests.
name: Test and lint

name: tests
on: [push, pull_request]

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
permissions:
contents: read

jobs:
build:

test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [ '3.7', '3.8', '3.9', '3.10' ]
python-version: [ '3.8', '3.9', '3.10', '3.11' ]

steps:
- uses: actions/checkout@v2
- name: Checkout
uses: actions/checkout@v3

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
uses: actions/setup-python@v3
with:
python-version: ${{ matrix.python-version }}

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install pytest
pip install -e .[test]
pip install hatch
- name: Store EE token
run: |
python ./.github/scripts/make_ee_token.py
env:
EE_TOKEN: ${{ secrets.EE_TOKEN }}
- name: Test with pytest

- name: Test
run: |
pytest .
hatch run test:all
- name: Pre-commit
uses: pre-commit/[email protected]
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@ __pycache__/
htmlcov/
dist/
*.egg-info/
.tox/
.tox/

tests/data/
18 changes: 18 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.0.278
hooks:
- id: ruff

- repo: https://github.com/psf/black
rev: 22.10.0
hooks:
- id: black
args: [--line-length=88, --preview]

- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.2.0
hooks:
- id: mypy
exclude: ^tests/|^setup.py
args: [--ignore-missing-imports]
80 changes: 80 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Contributing to eeprepr

Contributions are always welcome! Bugs and feature requests can be opened in the [Issues](https://github.com/aazuspan/eerepr/issues). Questions and comments can be posted in the [Discussions](https://github.com/aazuspan/eerepr/discussions). To contribute code, please open an issue to discuss implementation, then follow the guide below to get started!

## Setup

`eeprepr` uses [Hatch](https://hatch.pypa.io/latest/) for package and environment management. To set up a development environment, first fork and clone `eerepr`, then install `hatch` in your environment.

```bash
pip install hatch
```

This will install all required dependencies for development. You can enter the environment using:

```bash
hatch shell
```

and exit by typing `quit` or `CTRL + D`.

## Pre-commit Hooks

Pre-commit hooks automatically run linting, formatting, and type-checking whenever a change is commited. This ensures that the codebase is always in good shape.

The command below registers the pre-commit hooks for the project so that they run before every commit.

```bash
hatch run pre-commit install
```

To run all the checks manually, you can use:

```bash
hatch run pre-commit run --all-files
```

## Testing

### Running Tests

You can run all tests with `pytest` using the command below:

```bash
hatch run test:all
```

To measure test coverage, run:

```bash
hatch run test:cov
```

Additional arguments can be passed to `pytest` after the script name, e.g.:

```bash
hatch run test:all -k feature
```

### Building New Tests

New features should have unit tests. If your test needs to use `getInfo` to retrieve data from an Earth Engine object, you'll need to use the caching system described below.

Using `getInfo` to retrieve data from an Earth Engine object can be slow and network-dependent. To speed up tests, `eerepr` uses a caching function `tests.cache.get_info` to load data. This function takes an Earth Engine object and either 1) retrieves its info from a local cache file if it has been used before, or 2) retrieves it from the server and adds it to the cache. The cache directory and file (`tests/data/data.json`) will be created automatically the first time tests are run.

To demonstrate, let's write a new dummy test that checks the properties of a custom `ee.Image`.

```python
from tests.cache import get_info

def test_my_image():
img = ee.Image.constant(42).set("custom_property", ["a", "b", "c"])
# Use `get_info` instead of `img.getInfo` to utilize the cache
info = get_info(img)

assert "custom_property" in info["properties"]
```

The first time the test is run, `getInfo` will be used to retrieve the image metadata and store it in `tests/data/data.json`. Subsequent runs will pull the data directly from the cache.

Caches are kept locally and are not version-controlled, so there's no need to commit newly added objects.
2 changes: 0 additions & 2 deletions eerepr/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import ee

from eerepr.config import options
from eerepr.repr import initialize

Expand Down
9 changes: 7 additions & 2 deletions eerepr/config.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
from __future__ import annotations

import json


class Config:
def __init__(self, max_cache_size, max_repr_mbs, communication_delay):
def __init__(
self, max_cache_size: int | None, max_repr_mbs: int, communication_delay: float
):
self.max_cache_size = max_cache_size
self.max_repr_mbs = max_repr_mbs
self.communication_delay = communication_delay
Expand All @@ -16,6 +20,7 @@ def __repr__(self):
max_cache_size=None,
# Max size of repr content in MB to prevent performance issues
max_repr_mbs=100,
# Minimum delay in seconds before updating widgets to prevent communication timing issues.
# Minimum delay in seconds before updating widgets to prevent communication timing
# issues. Delayed that are too low can break collapsing behavior.
communication_delay=0.1,
)
48 changes: 27 additions & 21 deletions eerepr/html.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
from __future__ import annotations

import datetime
from itertools import chain
from typing import Any
from html import escape
from itertools import chain
from typing import Any, Hashable

import ee

# Max number of characters to display for a list before truncating to "List (n elements)""
# Max characters to display for a list before truncating to "List (n elements)"
MAX_INLINE_LENGTH = 50
# Sorting priority for Earth Engine properties
PROPERTY_PRIORITY = [
Expand All @@ -17,45 +20,48 @@
"properties",
]


def build_loading_html(obj: Any) -> str:
"""Build an HTML element to display when asynchronously loading an object."""
spinner = """<div class='ee-spinner'></div>"""
return f"<span class='ee-loading'>{spinner} {obj.__class__.__name__} (Computing)</span>"
return (
"<span"
f" class='ee-loading'>{spinner} {obj.__class__.__name__} (Computing)</span>"
)


def build_error_html(err: ee.EEException) -> str:
"""Build an HTML element to display an Earth Engine Exception"""
return f"<div class='ee-error'>{escape(str(err))}</div>"


def build_fallback_html(obj: Any) -> str:
"""Build an HTML element to fall back to if something goes wrong in the main repr."""
"""Build an HTML element to fall back to if something goes wrong in the main repr.
"""
return f"<pre>{escape(repr(obj))}</pre>"

def build_object_html(obj: Any, key=None) -> str:

def build_object_html(obj: Any, key: Hashable | None = None) -> str:
"""Build an HTML <li> element from a Python object.
Parameters
----------
obj : Any
The object to convert to HTML.
key : str, optional
The key to prepend to the object value, in the case of a dictionary value or list element.
The key to prepend to the object value, in the case of a dictionary value or
list element.
"""
if isinstance(obj, list):
return list_to_html(obj, key)
elif isinstance(obj, dict):
if isinstance(obj, dict):
return dict_to_html(obj, key)

key_html = f"<span class='ee-k'>{key}:</span>" if key is not None else ""
return (
"<li>"
f"{key_html}"
f"<span class='ee-v'>{obj}</span>"
"</li>"
)
return f"<li>{key_html}<span class='ee-v'>{obj}</span></li>"


def list_to_html(obj: list, key=None) -> str:
def list_to_html(obj: list, key: Hashable | None = None) -> str:
"""Convert a Python list to an HTML <li> element."""
contents = str(obj)
n = len(obj)
Expand All @@ -67,7 +73,7 @@ def list_to_html(obj: list, key=None) -> str:
return _make_collapsible_li(header, children)


def dict_to_html(obj: dict, key=None) -> str:
def dict_to_html(obj: dict, key: Hashable | None = None) -> str:
"""Convert a Python dictionary to an HTML <li> element."""
obj = _sort_dict(obj)
label = _build_label(obj)
Expand All @@ -82,21 +88,21 @@ def dict_to_html(obj: dict, key=None) -> str:
def _sort_dict(obj: dict) -> dict:
"""Sort the properties of an Earth Engine object.
This follows the Code Editor standard where priority keys are sorted first and the rest are
returned in alphabetical order.
This follows the Code Editor standard where priority keys are sorted first and the
rest are returned in alphabetical order.
"""
priority_keys = [k for k in PROPERTY_PRIORITY if k in obj]
start = {k: obj[k] for k in priority_keys}
end = {k: obj[k] for k in sorted(obj)}
return {**start, **end}


def _make_collapsible_li(header, children) -> str:
"""Package a header and children into a collapsible list element"""
def _make_collapsible_li(header: str, children: list) -> str:
"""Package a header and children into a collapsible list element."""
return (
"<li>"
f"<label class='ee-shut'>{header}"
f"<input type='checkbox' class='ee-toggle'></label>"
"<input type='checkbox' class='ee-toggle'></label>"
f"<ul>{''.join(children)}</ul>"
"</li>"
)
Expand Down
Loading

0 comments on commit 212b81b

Please sign in to comment.