Skip to content

Commit

Permalink
Major updates for HTML views.
Browse files Browse the repository at this point in the history
1) Revised rendering argument passing design:
  - Deprecate `pg.View.PresetArgValue` for more straightforward argument passing, which makes code less errorpone.
  - No longer accept `**kwargs`, instead users need to pass extension-specific args via the `extra_flags` dict.
  - Allows extension to use `_html_tree_view_config` to define class-level argument overrides and child-nodes overrides.
  - Introduce a bunch of helper methods to make it easy to deal with rendering arguments in extensions.
    - `HtmlTreeView.get_kwargs`: Handles rendering arguments override.
    - `HtmlTreeView.get_passthrough_kwargs`: Handles arguments passthrough.
    - `HtmlTreeView.get_child_kwargs`: Handles child nodes config override.
    - `HtmlTreeView.get_collapse_level`: Consolidate parent/child collapse level.
    - `HtmlTreeView.merge_uncollapse`: Merges KeyPathSet for uncollapsing nodes.
    - `HtmlTreeView.get_color`: Gets color based on a tuple or a function.

2) Enhanced functionalities:
  - Added `key_style` (and uses `summary` by default), `key_color`, `summary_color`, `debug` flags.
  - Deprecated `special_keys`.
  - Updated the semantics of `include_keys` and `exclude_keys`.

3) Introduce `_html_tree_view_config` and `_html_tree_view_styles` to make it easy for user classes to make new views without coding. (See `langfun.MappingExample` as an example)

4) Upgraded existing HTML views for Langfun components.

PiperOrigin-RevId: 687128151
  • Loading branch information
daiyip authored and langfun authors committed Oct 18, 2024
1 parent 0cc6486 commit 7d1ffee
Show file tree
Hide file tree
Showing 13 changed files with 534 additions and 217 deletions.
48 changes: 48 additions & 0 deletions langfun/core/component.py
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,54 @@ def value_from(
else:
return pg.MISSING_VALUE

def _html_tree_view_content(
self,
*,
view: pg.views.HtmlTreeView,
parent: Any,
root_path: pg.KeyPath,
**kwargs,
) -> pg.Html:
inferred_value = pg.MISSING_VALUE
if isinstance(parent, pg.Symbolic) and root_path:
inferred_value = parent.sym_inferred(root_path.key, pg.MISSING_VALUE)

if inferred_value is not pg.MISSING_VALUE:
kwargs.pop('name', None)
return view.render(
inferred_value, parent=self, root_path=root_path + '<inferred>',
**view.get_passthrough_kwargs(**kwargs)
)
return pg.Html.element(
'div',
[
'(not available)',
],
css_classes=['unavailable-contextual'],
)

def _html_tree_view_config(self) -> dict[str, Any]:
return pg.views.HtmlTreeView.get_kwargs(
super()._html_tree_view_config(),
dict(
collapse_level=1,
)
)

@classmethod
def _html_tree_view_css_styles(cls) -> list[str]:
return super()._html_tree_view_css_styles() + [
"""
.contextual-attribute {
color: purple;
}
.unavailable-contextual {
color: gray;
font-style: italic;
}
"""
]


# NOTE(daiyip): Returning Any instead of `lf.ContextualAttribute` to avoid
# pytype check error as `contextual()` can be assigned to any type.
Expand Down
48 changes: 48 additions & 0 deletions langfun/core/component_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
# limitations under the License.
"""Contextual component and app test."""

import inspect
from typing import Any
import unittest
import weakref

Expand Down Expand Up @@ -297,6 +299,52 @@ class C(lf.Component):
self.assertEqual(c.z, 3)
self.assertEqual(b.z, 3)

def test_to_html(self):
class A(lf.Component):
x: int = 1
y: int = lf.contextual()

def assert_content(html, expected):
expected = inspect.cleandoc(expected).strip()
actual = html.content.strip()
if actual != expected:
print(actual)
self.assertEqual(actual.strip(), expected)

self.assertIn(
inspect.cleandoc(
"""
.contextual-attribute {
color: purple;
}
.unavailable-contextual {
color: gray;
font-style: italic;
}
"""
),
A().to_html().style_section,
)

assert_content(
A().to_html(enable_summary_tooltip=False),
"""
<details open class="pyglove a"><summary><div class="summary-title">A(...)</div></summary><div class="complex-value a"><details open class="pyglove int"><summary><div class="summary-name">x<span class="tooltip">x</span></div><div class="summary-title">int</div></summary><span class="simple-value int">1</span></details><details open class="pyglove contextual-attribute"><summary><div class="summary-name">y<span class="tooltip">y</span></div><div class="summary-title">ContextualAttribute(...)</div></summary><div class="unavailable-contextual">(not available)</div></details></div></details>
"""
)

class B(lf.Component):
z: Any
y: int = 2

b = B(A())
assert_content(
b.z.to_html(enable_summary_tooltip=False),
"""
<details open class="pyglove a"><summary><div class="summary-title">A(...)</div></summary><div class="complex-value a"><details open class="pyglove int"><summary><div class="summary-name">x<span class="tooltip">x</span></div><div class="summary-title">int</div></summary><span class="simple-value int">1</span></details><details open class="pyglove contextual-attribute"><summary><div class="summary-name">y<span class="tooltip">y</span></div><div class="summary-title">ContextualAttribute(...)</div></summary><span class="simple-value int">2</span></details></div></details>
"""
)


if __name__ == '__main__':
unittest.main()
67 changes: 39 additions & 28 deletions langfun/core/logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@

import contextlib
import datetime
import functools
import typing
from typing import Any, Iterator, Literal, Sequence
from typing import Any, Iterator, Literal

from langfun.core import component
from langfun.core import console
Expand Down Expand Up @@ -57,30 +58,30 @@ def _html_tree_view_summary(
self,
view: pg.views.HtmlTreeView,
title: str | pg.Html | None = None,
max_str_len_for_summary: int = pg.View.PresetArgValue(80), # pytype: disable=annotation-type-mismatch
max_summary_len_for_str: int = 80,
**kwargs
) -> str:
if len(self.message) > max_str_len_for_summary:
message = self.message[:max_str_len_for_summary] + '...'
if len(self.message) > max_summary_len_for_str:
message = self.message[:max_summary_len_for_str] + '...'
else:
message = self.message

s = pg.Html(
pg.Html.element(
'span',
[self.time.strftime('%H:%M:%S')],
css_class=['log-time']
css_classes=['log-time']
),
pg.Html.element(
'span',
[pg.Html.escape(message)],
css_class=['log-summary'],
css_classes=['log-summary'],
),
)
return view.summary(
self,
title=title or s,
max_str_len_for_summary=max_str_len_for_summary,
max_summary_len_for_str=max_summary_len_for_str,
**kwargs,
)

Expand All @@ -89,43 +90,45 @@ def _html_tree_view_content(
self,
view: pg.views.HtmlTreeView,
root_path: pg.KeyPath,
collapse_log_metadata_level: int | None = pg.View.PresetArgValue(0),
max_str_len_for_summary: int = pg.View.PresetArgValue(80),
collapse_level: int | None = pg.View.PresetArgValue(1),
max_summary_len_for_str: int = 80,
collapse_level: int | None = 1,
extra_flags: dict[str, Any] | None = None,
**kwargs
) -> pg.Html:
# pytype: enable=annotation-type-mismatch
extra_flags = extra_flags if extra_flags is not None else {}
collapse_log_metadata_level: int | None = extra_flags.get(
'collapse_log_metadata_level', None
)
def render_message_text():
if len(self.message) < max_str_len_for_summary:
if len(self.message) < max_summary_len_for_str:
return None
return pg.Html.element(
'span',
[pg.Html.escape(self.message)],
css_class=['log-text'],
css_classes=['log-text'],
)

def render_metadata():
if not self.metadata:
return None
child_path = root_path + 'metadata'
return pg.Html.element(
'div',
[
view.render(
self.metadata,
name='metadata',
root_path=child_path,
root_path=root_path + 'metadata',
parent=self,
collapse_level=(
view.max_collapse_level(
collapse_level,
collapse_log_metadata_level,
child_path
)
)
collapse_level=view.get_collapse_level(
(collapse_level, -1), collapse_log_metadata_level,
),
max_summary_len_for_str=max_summary_len_for_str,
extra_flags=extra_flags,
**view.get_passthrough_kwargs(**kwargs),
)
],
css_class=['log-metadata'],
css_classes=['log-metadata'],
)

return pg.Html.element(
Expand All @@ -134,12 +137,23 @@ def render_metadata():
render_message_text(),
render_metadata(),
],
css_class=['complex_value'],
css_classes=['complex_value'],
)

def _html_tree_view_config(self) -> dict[str, Any]:
return pg.views.HtmlTreeView.get_kwargs(
super()._html_tree_view_config(),
dict(
css_classes=[f'log-{self.level}'],
)
)

def _html_style(self) -> list[str]:
return super()._html_style() + [
@classmethod
@functools.cache
def _html_tree_view_css_styles(cls) -> list[str]:
return super()._html_tree_view_css_styles() + [
"""
/* Langfun LogEntry styles. */
.log-time {
color: #222;
font-size: 12px;
Expand Down Expand Up @@ -203,9 +217,6 @@ def _html_style(self) -> list[str]:
"""
]

def _html_element_class(self) -> Sequence[str] | None:
return super()._html_element_class() + [f'log-{self.level}']


def log(level: LogLevel,
message: str,
Expand Down
26 changes: 21 additions & 5 deletions langfun/core/logging_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,20 +69,36 @@ def test_html(self):
time=time, metadata={}
).to_html(enable_summary_tooltip=False),
"""
<details open class="pyglove log-entry log-info"><summary><div class="summary_title"><span class="log-time">12:30:45</span><span class="log-summary">5 + 2 &gt; 3</span></div></summary><div class="complex_value"></div></details>
<details open class="pyglove log-entry log-info"><summary><div class="summary-title log-info"><span class="log-time">12:30:45</span><span class="log-summary">5 + 2 &gt; 3</span></div></summary><div class="complex_value"></div></details>
"""
)
self.assert_html_content(
logging.LogEntry(
level='error', message='This is a longer message: 5 + 2 > 3',
time=time, metadata=dict(x=1, y=2)
time=time, metadata=dict(x=dict(z=1), y=2)
).to_html(
max_str_len_for_summary=10,
extra_flags=dict(
collapse_log_metadata_level=1,
),
max_summary_len_for_str=10,
enable_summary_tooltip=False,
collapse_log_metadata_level=1
),
"""
<details open class="pyglove log-entry log-error"><summary><div class="summary_title"><span class="log-time">12:30:45</span><span class="log-summary">This is a ...</span></div></summary><div class="complex_value"><span class="log-text">This is a longer message: 5 + 2 &gt; 3</span><div class="log-metadata"><details open class="pyglove dict"><summary><div class="summary_name">metadata</div><div class="summary_title">Dict(...)</div></summary><div class="complex_value dict"><table><tr><td><span class="object_key str">x</span><span class="tooltip key-path">metadata.x</span></td><td><div><span class="simple_value int">1</span></div></td></tr><tr><td><span class="object_key str">y</span><span class="tooltip key-path">metadata.y</span></td><td><div><span class="simple_value int">2</span></div></td></tr></table></div></details></div></div></details>
<details open class="pyglove log-entry log-error"><summary><div class="summary-title log-error"><span class="log-time">12:30:45</span><span class="log-summary">This is a ...</span></div></summary><div class="complex_value"><span class="log-text">This is a longer message: 5 + 2 &gt; 3</span><div class="log-metadata"><details open class="pyglove dict"><summary><div class="summary-name">metadata<span class="tooltip">metadata</span></div><div class="summary-title">Dict(...)</div></summary><div class="complex-value dict"><details class="pyglove dict"><summary><div class="summary-name">x<span class="tooltip">metadata.x</span></div><div class="summary-title">Dict(...)</div></summary><div class="complex-value dict"><details open class="pyglove int"><summary><div class="summary-name">z<span class="tooltip">metadata.x.z</span></div><div class="summary-title">int</div></summary><span class="simple-value int">1</span></details></div></details><details open class="pyglove int"><summary><div class="summary-name">y<span class="tooltip">metadata.y</span></div><div class="summary-title">int</div></summary><span class="simple-value int">2</span></details></div></details></div></div></details>
"""
)
self.assert_html_content(
logging.LogEntry(
level='error', message='This is a longer message: 5 + 2 > 3',
time=time, metadata=dict(x=dict(z=1), y=2)
).to_html(
extra_flags=dict(
max_summary_len_for_str=10,
),
enable_summary_tooltip=False,
),
"""
<details open class="pyglove log-entry log-error"><summary><div class="summary-title log-error"><span class="log-time">12:30:45</span><span class="log-summary">This is a longer message: 5 + 2 &gt; 3</span></div></summary><div class="complex_value"><div class="log-metadata"><details open class="pyglove dict"><summary><div class="summary-name">metadata<span class="tooltip">metadata</span></div><div class="summary-title">Dict(...)</div></summary><div class="complex-value dict"><details open class="pyglove dict"><summary><div class="summary-name">x<span class="tooltip">metadata.x</span></div><div class="summary-title">Dict(...)</div></summary><div class="complex-value dict"><details open class="pyglove int"><summary><div class="summary-name">z<span class="tooltip">metadata.x.z</span></div><div class="summary-title">int</div></summary><span class="simple-value int">1</span></details></div></details><details open class="pyglove int"><summary><div class="summary-name">y<span class="tooltip">metadata.y</span></div><div class="summary-title">int</div></summary><span class="simple-value int">2</span></details></div></details></div></div></details>
"""
)

Expand Down
Loading

0 comments on commit 7d1ffee

Please sign in to comment.