feat: deliver Python workflow client
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
"""Small, inert renderers shared by the public workflow-client snapshots.
|
||||
|
||||
Representations are a debugging aid, not another client operation. This
|
||||
module deliberately accepts already-loaded values and never knows about the
|
||||
workflow transport port. The same bounded projector is used for plain and
|
||||
HTML representations so notebooks cannot accidentally expose an unbounded
|
||||
trace, output, or credential-shaped value.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import html
|
||||
import json
|
||||
from collections.abc import Mapping, Sequence
|
||||
|
||||
_SECRET_KEY_PARTS = (
|
||||
"authorization",
|
||||
"cookie",
|
||||
"set-cookie",
|
||||
"token",
|
||||
"secret",
|
||||
"password",
|
||||
"api_key",
|
||||
"api-key",
|
||||
)
|
||||
_MAX_DEPTH = 2
|
||||
_MAX_ITEMS = 8
|
||||
_MAX_STRING = 160
|
||||
_MAX_RENDERED = 1_200
|
||||
|
||||
|
||||
def _secret_key(key: object) -> bool:
|
||||
lowered = str(key).lower()
|
||||
return any(part in lowered for part in _SECRET_KEY_PARTS)
|
||||
|
||||
|
||||
def bounded_value(value: object, *, depth: int = 0) -> object:
|
||||
"""Project loaded JSON-like data into a small, secret-safe preview."""
|
||||
if depth >= _MAX_DEPTH:
|
||||
return "[truncated]"
|
||||
if isinstance(value, str):
|
||||
return value if len(value) <= _MAX_STRING else value[:_MAX_STRING] + "…"
|
||||
if value is None or isinstance(value, bool | int | float):
|
||||
return value
|
||||
if isinstance(value, Mapping):
|
||||
items = list(value.items())
|
||||
preview = {
|
||||
str(key): "[redacted]"
|
||||
if _secret_key(key)
|
||||
else bounded_value(item, depth=depth + 1)
|
||||
for key, item in items[:_MAX_ITEMS]
|
||||
}
|
||||
if len(items) > _MAX_ITEMS:
|
||||
preview["…"] = f"{len(items) - _MAX_ITEMS} more entries"
|
||||
return preview
|
||||
if isinstance(value, Sequence) and not isinstance(value, str | bytes | bytearray):
|
||||
items = list(value)
|
||||
preview = [bounded_value(item, depth=depth + 1) for item in items[:_MAX_ITEMS]]
|
||||
if len(items) > _MAX_ITEMS:
|
||||
preview.append(f"… {len(items) - _MAX_ITEMS} more items")
|
||||
return preview
|
||||
rendered = repr(value)
|
||||
return rendered if len(rendered) <= _MAX_STRING else rendered[:_MAX_STRING] + "…"
|
||||
|
||||
|
||||
def preview(value: object) -> str:
|
||||
"""Render a bounded value without allowing an object's repr to grow freely."""
|
||||
try:
|
||||
rendered = json.dumps(bounded_value(value), sort_keys=True, default=str)
|
||||
except TypeError, ValueError:
|
||||
rendered = str(bounded_value(value))
|
||||
return rendered[:_MAX_RENDERED] + ("…" if len(rendered) > _MAX_RENDERED else "")
|
||||
|
||||
|
||||
def short_repr(type_name: str, **fields: object) -> str:
|
||||
"""Build a compact Python repr from already-loaded field values."""
|
||||
body = ", ".join(f"{name}={preview(value)}" for name, value in fields.items())
|
||||
rendered = f"{type_name}({body})"
|
||||
return rendered[:_MAX_RENDERED] + ("…" if len(rendered) > _MAX_RENDERED else "")
|
||||
|
||||
|
||||
def html_repr(type_name: str, **fields: object) -> str:
|
||||
"""Build a bounded HTML table suitable for IPython rich display."""
|
||||
|
||||
def html_preview(value: object) -> str:
|
||||
rendered = preview(value)
|
||||
return rendered[:400] + ("…" if len(rendered) > 400 else "")
|
||||
|
||||
rows = "".join(
|
||||
'<tr><th scope="row">'
|
||||
+ html.escape(name)
|
||||
+ "</th><td><code>"
|
||||
+ html.escape(html_preview(value))
|
||||
+ "</code></td></tr>"
|
||||
for name, value in fields.items()
|
||||
)
|
||||
return (
|
||||
'<div class="wf-client-repr"><strong>'
|
||||
+ html.escape(type_name)
|
||||
+ "</strong><table><tbody>"
|
||||
+ rows
|
||||
+ "</tbody></table></div>"
|
||||
)
|
||||
Reference in New Issue
Block a user