Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
33d727b
Add rudimentary optional _repr_html_()
DahnJ Feb 2, 2022
e590a29
Refactor Jinja templates, Assets show title
DahnJ Feb 3, 2022
18b9952
All pre-commit checks pass
DahnJ Feb 3, 2022
0a4b911
Jinja templates are distributed with the package
DahnJ Feb 9, 2022
20eb1aa
Jinja is imported lazily at the first _repr_html_ call
DahnJ Feb 9, 2022
0667dc2
REF: Separate out Catalog and Collection templates
DahnJ Feb 9, 2022
ca32ef6
Style: All list <detail>s have identical margins
DahnJ Feb 9, 2022
b8b0f69
Refactor: Collection template inherits from Catalog
DahnJ Feb 9, 2022
69e3135
Remove unused parameter
DahnJ Feb 9, 2022
54c2417
Add provider template
DahnJ Feb 9, 2022
2f19599
Catalog-derived templates use class name as title
DahnJ Feb 9, 2022
69e7c61
Add PR link to changelog
DahnJ Feb 9, 2022
14caa5d
WIP: HTML repr does not retrieve all children&items
DahnJ Feb 18, 2022
a2e02d7
Merge branch 'main' into feature/html-repr-optional
duckontheweb Feb 25, 2022
3a850a8
Merge branch 'main' into feature/html-repr-optional
duckontheweb Feb 28, 2022
dc82b43
Merge branch 'main' into feature/html-repr-optional
duckontheweb May 4, 2022
20334ae
Merge branch 'main' into feature/html-repr-optional
gadomski May 4, 2022
2f359dc
Remove accidentally commited notebook
DahnJ May 11, 2022
b9ab7de
Formatting (black)
DahnJ May 11, 2022
07042aa
Refactor: Limit try/except scope
DahnJ May 11, 2022
d048a70
Merge remote-tracking branch 'refs/remotes/origin/feature/html-repr-o…
DahnJ May 11, 2022
cbf7021
Merge branch 'main' into feature/html-repr-optional
gadomski May 11, 2022
6cf3fa4
Mypy ignores optional dependency jinja2
DahnJ May 11, 2022
aa332ed
Merge remote-tracking branch 'refs/remotes/origin/feature/html-repr-o…
DahnJ May 11, 2022
0e21a7a
Moving unreleased PR from 1.4.0 to unreleased
DahnJ May 11, 2022
4328a77
ItemCollection implements _repr_html_
DahnJ May 12, 2022
2137ff0
ItemCollection only shows at most 10 items
DahnJ May 12, 2022
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
- Replace test.com with special-use domain name. ([#769](https://github.com/stac-utils/pystac/pull/769))
- Updated AssetDefinition to have create, apply methods ([#768](https://github.com/stac-utils/pystac/pull/768))
- Add Grid Extension support ([#799](https://github.com/stac-utils/pystac/pull/799))
- Rich HTML representations for Jupyter Notebook display ([#743](https://github.com/stac-utils/pystac/pull/743))

### Removed

Expand Down
3 changes: 3 additions & 0 deletions mypy.ini
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
show_error_codes = True
strict = True

[mypy-jinja2.*]
ignore_missing_imports = True

[mypy-jsonschema.*]
ignore_missing_imports = True

Expand Down
10 changes: 10 additions & 0 deletions pystac/asset.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from html import escape
from copy import copy
from typing import Any, Dict, List, Optional, TYPE_CHECKING, Union

from pystac import common_metadata
from pystac.html.jinja_env import get_jinja_env
from pystac import utils

if TYPE_CHECKING:
Expand Down Expand Up @@ -156,6 +158,14 @@ def common_metadata(self) -> "CommonMetadata_Type":
def __repr__(self) -> str:
return "<Asset href={}>".format(self.href)

def _repr_html_(self) -> str:
jinja_env = get_jinja_env()
if jinja_env:
template = jinja_env.get_template("Asset.jinja2")
return str(template.render(asset=self))
else:
return escape(repr(self))

@classmethod
def from_dict(cls, d: Dict[str, Any]) -> "Asset":
"""Constructs an Asset from a dict.
Expand Down
10 changes: 10 additions & 0 deletions pystac/catalog.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import os
from html import escape
from copy import deepcopy
from pystac.errors import STACTypeError
from pystac.html.jinja_env import get_jinja_env
from typing import (
Any,
Callable,
Expand Down Expand Up @@ -196,6 +198,14 @@ def __init__(
def __repr__(self) -> str:
return "<Catalog id={}>".format(self.id)

def _repr_html_(self) -> str:
jinja_env = get_jinja_env()
if jinja_env:
template = jinja_env.get_template("Catalog.jinja2")
return str(template.render(catalog=self))
else:
return escape(repr(self))

def set_root(self, root: Optional["Catalog"]) -> None:
STACObject.set_root(self, root)
if root is not None:
Expand Down
10 changes: 10 additions & 0 deletions pystac/collection.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from html import escape
from copy import deepcopy
from datetime import datetime
from pystac.errors import STACTypeError
from pystac.html.jinja_env import get_jinja_env
from typing import (
Any,
Dict,
Expand Down Expand Up @@ -525,6 +527,14 @@ def __init__(
def __repr__(self) -> str:
return "<Collection id={}>".format(self.id)

def _repr_html_(self) -> str:
jinja_env = get_jinja_env()
if jinja_env:
template = jinja_env.get_template("Collection.jinja2")
return str(template.render(catalog=self))
else:
return escape(repr(self))

def add_item(
self,
item: "Item_Type",
Expand Down
31 changes: 31 additions & 0 deletions pystac/html/Asset.jinja2
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{% import 'Macros.jinja2' as macros %}
Comment thread
DahnJ marked this conversation as resolved.

<div class="jp-RenderedHTMLCommon jp-RenderedHTML jp-mod-trusted jp-OutputArea-output">
{{ macros.square('#FED7E5', '#FA4C8C') }}
<div>
<details style="margin-left: 48px;">
<summary style="margin-bottom: 20px;">
<h3 style="margin-bottom: 0px; display: inline;">Asset: {{ asset.title }}</h3>
</summary>
<table style="width: 100%; text-align: left;">
<tr><td style="text-align: left;"><strong>href</strong>: {{ asset.href }} </td></tr>
{% if asset.title %}
<tr><td style="text-align: left;"><strong>Title:</strong> {{ asset.title }} </td></tr>
{% endif %}
{% if asset.description %}
<tr><td style="text-align: left;"><strong>Description:</strong> {{ asset.description }} </td></tr>
{% endif %}
{% if asset.media_type %}
<tr><td style="text-align: left;"><strong>Media type:</strong> {{ asset.media_type }} </td></tr>
{% endif %}
{% if asset.roles %}
<tr><td style="text-align: left;"><strong>Roles:</strong> {{ asset.roles }} </td></tr>
{% endif %}
{% if asset.owner %}
<tr><td style="text-align: left;"><strong>Owner:</strong> {{ asset.Owner }} </td></tr>
{% endif %}
{{ macros.extra_fields(asset) }}
</table>
</details>
</div>
</div>
30 changes: 30 additions & 0 deletions pystac/html/Catalog.jinja2
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{% import 'Macros.jinja2' as macros %}

<div class="jp-RenderedHTMLCommon jp-RenderedHTML jp-mod-trusted jp-OutputArea-output">
{{ macros.square() }}
<div>
<details style="margin-left: 48px;">
<summary style="margin-bottom: 20px;">
<h3 style="margin-bottom: 0px; display: inline;">
{{catalog.__class__.__name__}}: {{catalog.id}}
</h3>
</summary>
<table style="width: 100%; text-align: left;">
<tr><td style="text-align: left;">ID: {{catalog.id}} </td></tr>
{% if catalog.title %}
<tr><td style="text-align: left;"><strong>Title:</strong> {{catalog.title}} </td></tr>
{% endif %}
{% if catalog.description %}
<tr><td style="text-align: left;"><strong>Description:</strong> {{catalog.description}} </td></tr>
{% endif %}
{% block subclass_fields %} {% endblock %}
{{ macros.extra_fields(catalog) }}
</table>
{{ macros.stac_extensions(catalog) }}
{{ macros.children(catalog) }}
{{ macros.items(catalog) }}
{{ macros.links(catalog) }}
{% block subclass_details %} {% endblock %}
</details>
</div>
</div>
20 changes: 20 additions & 0 deletions pystac/html/Collection.jinja2
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{% extends 'Catalog.jinja2' %}
{% import 'Macros.jinja2' as macros %}

{% block subclass_fields %}
{# Providers field #}
{% if catalog.providers %}
<tr><td style="text-align: left;"><strong>Providers:</strong>
<ul>
{% for provider in catalog.providers %}
<li>{{ provider._repr_html_() }}</li>
{% endfor %}
</ul>
</td></tr>
{% endif %}
{% endblock %}

{% block subclass_details %}
{{ macros.assets(catalog) }}
{% endblock %}

33 changes: 33 additions & 0 deletions pystac/html/Item.jinja2
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{% import 'Macros.jinja2' as macros %}

<div class="jp-RenderedHTMLCommon jp-RenderedHTML jp-mod-trusted jp-OutputArea-output">
{{ macros.square('#DBF5FF', '#4CC9FF') }}
<div>
<details style="margin-left: 48px;">
<summary style="margin-bottom: 20px;">
<h3 style="margin-bottom: 0px; display: inline;">Item: {{item.id}}</h3>
</summary>
<table style="width: 100%; text-align: left;">
<tr><td style="text-align: left;"><strong>ID</strong>: {{item.id}} </td></tr>
{% if item.title %}
<tr><td style="text-align: left;"><strong>Title:</strong> {{item.title}} </td></tr>
{% endif %}
{% if item.bbox %}
<tr><td style="text-align: left;"><strong>Bounding Box:</strong> {{item.bbox}} </td></tr>
{% endif %}
{% if item.datetime %}
<tr><td style="text-align: left;"><strong>Datetime:</strong> {{item.datetime}} </td></tr>
{% endif %}
{% if item.properties %}
{% for key in item.properties %}
<tr><td style="text-align: left;"><strong>{{ key }}:</strong> {{ item.properties[key] }} </td></tr>
{% endfor %}
{% endif %}
{{ macros.extra_fields(item) }}
</table>
{{ macros.stac_extensions(item) }}
{{ macros.assets(item) }}
{{ macros.links(item) }}
</details>
</div>
</div>
31 changes: 31 additions & 0 deletions pystac/html/ItemCollection.jinja2
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{% import 'Macros.jinja2' as macros %}

<div class="jp-RenderedHTMLCommon jp-RenderedHTML jp-mod-trusted jp-OutputArea-output">
{{ macros.square() }}
<div>
<details style="margin-left: 48px;">
<summary style="margin-bottom: 20px;">
<h3 style="margin-bottom: 0px; display: inline;">
{{item_collection.__class__.__name__}}
</h3>
</summary>
<table style="width: 100%; text-align: left;">
{{ macros.extra_fields(item_collection) }}
</table>

{% if item_collection.__iter__()|is_nonempty_generator %}
<details>
{% if item_collection.items | length > 10 %}
<i> Only the first 10 items shown </i>
{% endif %}
<summary style="margin-bottom: 10px; margin-top: 10px">
<h4 style="margin-bottom: 0px; display: inline;">Items</h4>
</summary>
{% for item in item_collection.items[:10] %}
{{ item._repr_html_() }}
{% endfor %}
</details>
{% endif %}
</details>
</div>
</div>
23 changes: 23 additions & 0 deletions pystac/html/Link.jinja2
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{% import 'Macros.jinja2' as macros %}

<div class="jp-RenderedHTMLCommon jp-RenderedHTML jp-mod-trusted jp-OutputArea-output">
{{ macros.square('#FFF7E5', '#FF6132') }}
<div style="margin-left: 48px;">
<h4 style="margin-bottom: 0px;">Link: {{ link.id }}</h4>
{% if link.title %}
<p style="color: #9D9D9D; margin-bottom: 0px;">{{ link.title }}</p>
{% endif %}
<table style="width: 100%; text-align: left;">
<tr><td style="text-align: left;"><strong>Rel:</strong> {{ link.rel }} </td></tr>
<tr><td style="text-align: left;"><strong>Target:</strong> {{ link.target }} </td></tr>
{% if link.media_type %}
<tr><td style="text-align: left;"><strong>Media Type:</strong> {{ link.media_type }} </td></tr>
{% endif %}
{{ macros.extra_fields(link) }}
</table>
{% if link.extra_fields %}
<table style="width: 100%; text-align: left;">
</table>
{% endif %}
</div>
</div>
93 changes: 93 additions & 0 deletions pystac/html/Macros.jinja2
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
{% macro square(background_color='#E1E1E1', border_color='#9D9D9D') -%}
<div style="
width: 24px;
height: 24px;
background-color: {{ background_color }};
border: 3px solid {{ border_color }};
border-radius: 5px;
position: absolute;">
</div>
{%- endmacro %}


{% macro extra_fields(parent) -%}
{% if parent.extra_fields %}
{% for key, value in parent.extra_fields.items() %}
<tr><td style="text-align: left;"><strong>{{ key }}:</strong> {{ value }} </td></tr>
{% endfor %}
{% endif %}
{%- endmacro %}


{% macro links(parent) -%}
{% if parent.links|length > 0 %}
<details>
<summary style="margin-bottom: 10px; margin-top: 10px">
<h4 style="margin-bottom: 0px; display: inline;">Links</h4>
</summary>
{% for link in parent.links %}
{{ link._repr_html_() }}
{% endfor %}
</details>
{% endif %}
{%- endmacro %}


{% macro items(parent) -%}
{% if parent.get_items()|is_nonempty_generator %}
<details>
<summary style="margin-bottom: 10px; margin-top: 10px">
<h4 style="margin-bottom: 0px; display: inline;">Items</h4>
</summary>
<i> Only the first item shown </i>
{% for item in parent.get_items()|first %}
{{ item._repr_html_() }}
{% endfor %}
</details>
{% endif %}
{%- endmacro %}


{% macro children(parent) -%}
{% if parent.get_children()|is_nonempty_generator %}
<details>
<summary style="margin-bottom: 10px; margin-top: 10px">
<h4 style="margin-bottom: 0px; display: inline;">Children</h4>
</summary>
<i> Only the first child shown </i>
{% for child in parent.get_children()|first %}
{{ child._repr_html_() }}
{% endfor %}
</details>
{% endif %}
{%- endmacro %}


{% macro stac_extensions(parent) -%}
{% if parent.stac_extensions|length > 0 %}
<details>
<summary style="margin-bottom: 10px; margin-top: 10px;">
<h4 style="margin-bottom: 0px; display: inline;">STAC Extensions</h4>
</summary>
<table style="width: 100%; text-align: left;">
{% for stac_extension in parent.stac_extensions %}
<tr><td style="text-align: left;"><a href="{{stac_extension}}">{{stac_extension}}</a></td></tr>
{% endfor %}
</table>
</details>
{% endif %}
{%- endmacro %}


{% macro assets(parent) -%}
{% if parent.assets|length > 0 %}
<details>
<summary style="margin-bottom: 10px; margin-top: 10px;">
<h4 style="margin-bottom: 0px; display: inline;">Assets</h4>
</summary>
{% for key, asset in parent.assets.items() %}
{{ asset._repr_html_() }}
{% endfor %}
</details>
{% endif %}
{%- endmacro %}
12 changes: 12 additions & 0 deletions pystac/html/Provider.jinja2
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{% import 'Macros.jinja2' as macros %}

<div>
{% if provider.url %}
<a href="{{ provider.url }}"> {{ provider.name }} </a>
{% else %}
{{ provider.name }}
{% endif %}
(<i>{{ provider.roles|join(", ") }}</i>){% if provider.description %}: {{ provider.description }} {% endif %}
{{ macros.extra_fields(provider) }}
</div>

3 changes: 3 additions & 0 deletions pystac/html/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .jinja_env import get_jinja_env

__all__ = ["get_jinja_env"]
19 changes: 19 additions & 0 deletions pystac/html/jinja_env.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from functools import lru_cache
from itertools import islice


@lru_cache()
def get_jinja_env(): # type: ignore
try:
from jinja2 import Environment, PackageLoader, select_autoescape
except ModuleNotFoundError:
return None

environment = Environment(
loader=PackageLoader("pystac", "html"), autoescape=select_autoescape()
)

environment.filters["first"] = lambda x: islice(x, 1)
environment.filters["is_nonempty_generator"] = lambda x: next(x, None) is not None

return environment
Loading