marimo-utils 0.1.0__tar.gz → 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,320 @@
1
+ # IMPORT_STYLE — Planning doc
2
+
3
+ Opportunities to simplify `dr-llm/src/dr_llm/style/` by leveraging `mohtml`
4
+ more fully and cleaning up the CSS-string-assembly layer. Also captures
5
+ notes on introducing a typing `Protocol` to remove the `# type: ignore`
6
+ burden around mohtml.
7
+
8
+ Context recap: `dr_llm.style` is a small design-system module (tokens →
9
+ atoms → `Card` → `PoolCard`) built on `mohtml` (thin HTML DSL that
10
+ stringifies Python objects to HTML) and rendered into marimo via
11
+ `mo.Html`. Most verbosity in the module lives in f-string CSS assembly,
12
+ not in the HTML-construction layer; mohtml itself is ~60 lines and
13
+ mostly gets out of the way.
14
+
15
+ ## Priority summary
16
+
17
+ | # | Opportunity | Effort | Value | Risk |
18
+ |---|-------------|--------|-------|------|
19
+ | 1 | Drop `render_html()` / `mo.Html(str(...))` wrapper | S | M | L |
20
+ | 2 | `css(**decls)` style-builder helper | S | H | L |
21
+ | 3 | Standardize trailing-`;` convention in token `.css()` | XS | M | L |
22
+ | 4 | Custom semantic tags via `mohtml.anything` | S | L-M | L |
23
+ | 5 | Typing `Protocol` for mohtml elements | S-M | M | L |
24
+ | 6 | Single `<style>` block + `klass=` per card | L | H | M |
25
+
26
+ Recommended first pass: **#1 + #2 + #3 + #5**. Revisit #6 when a second
27
+ card type exists. #4 is optional sugar.
28
+
29
+ ---
30
+
31
+ ## 1. Drop `render_html()` / `mo.Html(str(...))` wrapping
32
+
33
+ **Observation.** mohtml tag classes define `_repr_html_` (it returns
34
+ `__repr__`, which is the serialized HTML). Marimo's output pipeline
35
+ picks up `_repr_html_` on whatever a cell returns, so the extra
36
+ `mo.Html(str(self.render()))` layer may be redundant.
37
+
38
+ **Proposed change.** In `pool_card.py`:
39
+
40
+ ```python
41
+ # Today
42
+ def render(self) -> div: ...
43
+ def render_html(self) -> mo.Html:
44
+ return mo.Html(str(self.render()))
45
+
46
+ # Proposed
47
+ def render(self) -> div: ... # cells can return this directly
48
+ ```
49
+
50
+ **Verification required before deleting.** Smoke-test in a marimo cell:
51
+
52
+ ```python
53
+ card = PoolCard(pool=..., palette=ColorPalette.default())
54
+ card.render() # last expression in a cell
55
+ ```
56
+
57
+ If it renders correctly, `render_html()` can go. Keep `mo.Html` usage
58
+ anywhere a card needs to be embedded inside an f-string with other
59
+ marimo markup — that pathway still needs the wrapper.
60
+
61
+ **Why it's worth doing.** Removes a layer of indirection, eliminates a
62
+ marimo-specific import from the innermost rendering code, and makes
63
+ `PoolCard` render-target-agnostic. The only coupling to marimo then
64
+ lives at the call site of the cell.
65
+
66
+ ---
67
+
68
+ ## 2. `css(**decls)` style-builder helper
69
+
70
+ **Observation.** The current pattern is f-string concatenation of CSS
71
+ fragments, repeated ~10 times across `components.py` and `card.py`:
72
+
73
+ ```python
74
+ style=(
75
+ f"margin-top: {self.spacing.sm}; "
76
+ f"gap: {self.spacing.md}; "
77
+ f"{LayoutToken.css(self.display_styles)}"
78
+ )
79
+ ```
80
+
81
+ This is hard to scan, trailing-`;` rules are subtle, and `None` /
82
+ absent values require more f-string gymnastics.
83
+
84
+ **Proposed helper.**
85
+
86
+ ```python
87
+ # style/settings.py (or a new style/css.py)
88
+ def css(*fragments: str, **decls: str | None) -> str:
89
+ """Build a CSS declaration string.
90
+
91
+ Accepts keyword declarations (underscores → hyphens, None skipped)
92
+ and positional raw fragments (already-formatted `prop: value`
93
+ strings such as LayoutToken values). Emits a canonical trailing
94
+ semicolon iff there is content.
95
+ """
96
+ parts: list[str] = [
97
+ f"{k.replace('_', '-')}: {v}"
98
+ for k, v in decls.items()
99
+ if v is not None
100
+ ]
101
+ parts.extend(f.rstrip(";").strip() for f in fragments if f)
102
+ return "; ".join(parts) + ";" if parts else ""
103
+ ```
104
+
105
+ **Impact on call sites.** The pattern above collapses to:
106
+
107
+ ```python
108
+ style=css(
109
+ margin_top=self.spacing.sm,
110
+ gap=self.spacing.md,
111
+ *(tok.value for tok in self.display_styles),
112
+ )
113
+ ```
114
+
115
+ Each component's `render()` gets 3–6 lines shorter and an entire class
116
+ of trailing-`;` and spacing bugs goes away.
117
+
118
+ **Scope of changes.** Every call site in `card.py`, `components.py`,
119
+ and `pool_card.py` that currently assembles a `style="..."` string.
120
+ Counted occurrences: ~10–12.
121
+
122
+ ---
123
+
124
+ ## 3. Standardize trailing-`;` in token `.css()` methods
125
+
126
+ **Observation.** There is inconsistency today:
127
+
128
+ - `TextStyle.css(...)` → returns `"...;"` (trailing semicolon).
129
+ - `IconStyle.css(...)` → returns `"...;"` (trailing semicolon).
130
+ - `LayoutToken.css(tokens)` → returns `"...;"` iff non-empty, `""`
131
+ otherwise.
132
+
133
+ Call sites handle this differently; some embed with a space before the
134
+ next fragment, some not. This is what creates the need for the
135
+ trailing `;` handling in the `css()` helper above.
136
+
137
+ **Proposed convention.** `.css()` methods return **CSS fragments
138
+ without trailing `;`** (e.g., `"font-size: 1rem; font-weight: 700"`).
139
+ The `css(**decls, *fragments)` builder is the one place that adds the
140
+ final `;`. This makes every `.css()` method composable.
141
+
142
+ **Files touched.** `style/settings.py` only (3 methods). Depends on #2
143
+ landing first so call sites have a unifier.
144
+
145
+ ---
146
+
147
+ ## 4. Custom semantic tags via `mohtml.anything`
148
+
149
+ **Observation.** `mohtml/anything.py` exposes `__getattr__` that mints
150
+ an arbitrary-named tag class on first access. You can write:
151
+
152
+ ```python
153
+ from mohtml.anything import pool_card, data_item, status_badge
154
+ ```
155
+
156
+ and get `<pool-card>...</pool-card>` in the HTML. Browsers render
157
+ unknown block/inline elements as generic containers (no behavioral
158
+ change), but devtools and saved HTML become dramatically more readable
159
+ — `<pool-card>` instead of `<div><div><div>...`.
160
+
161
+ **Proposed change.** Minor, cosmetic. Replace the outermost wrapper
162
+ `div` of each component with a matching semantic tag:
163
+
164
+ | Component | Tag |
165
+ |-----------------------|---------------------|
166
+ | `Card.render()` | `<dr-card>` |
167
+ | `Title.render()` | `<dr-card-title>` |
168
+ | `Badge.render()` | `<dr-badge>` |
169
+ | `DataItem.render()` | `<dr-data-item>` |
170
+ | `MetaStamp` subclass | `<dr-meta-stamp>` |
171
+ | `LabeledList.render()`| `<dr-labeled-list>` |
172
+ | `PoolCard.header()` | `<dr-card-header>` |
173
+
174
+ Prefix (`dr-`) matters only if this will ever coexist with real web
175
+ components on the page.
176
+
177
+ **Why maybe not.** Adds no runtime value — it's developer-ergonomic
178
+ only, and the mohtml return type annotation becomes `div | <new class>`
179
+ which fights with the typing work in #5. Consider doing this *after*
180
+ #5 so the `Protocol` return type absorbs the change for free.
181
+
182
+ ---
183
+
184
+ ## 5. Typing `Protocol` for mohtml elements
185
+
186
+ **Observation.** mohtml's dynamically generated tag classes are opaque
187
+ to type checkers (`ty`, `mypy`, pyright). The `dr_llm.style` module
188
+ currently works around this with:
189
+
190
+ - `# type: ignore` on every `from mohtml import ...`
191
+ - `model_config = ConfigDict(arbitrary_types_allowed=True)` on
192
+ `Card` and `LabeledList` (because they hold mohtml instances).
193
+ - Annotations like `-> div` that describe a dynamically-built class
194
+ the type checker does not actually know about.
195
+
196
+ **Proposed change.** Introduce a tiny structural `Protocol` and use it
197
+ everywhere instead of concrete mohtml classes in annotations. The
198
+ protocol encodes *exactly* what the renderer relies on: string-
199
+ convertibility and `_repr_html_`.
200
+
201
+ ```python
202
+ # style/protocols.py
203
+ from typing import Protocol, runtime_checkable
204
+
205
+ @runtime_checkable
206
+ class HtmlRenderable(Protocol):
207
+ """Anything mohtml-like: stringifies to HTML and exposes _repr_html_."""
208
+ def __str__(self) -> str: ...
209
+ def _repr_html_(self) -> str: ...
210
+ ```
211
+
212
+ Adopt it in annotations:
213
+
214
+ ```python
215
+ # Before
216
+ def render(self) -> div: ...
217
+ def icon(self) -> svg: ...
218
+ header: div | None = None
219
+
220
+ # After
221
+ def render(self) -> HtmlRenderable: ...
222
+ def icon(self) -> HtmlRenderable: ...
223
+ header: HtmlRenderable | None = None
224
+ ```
225
+
226
+ **Secondary improvement.** Provide a single typed re-export module so
227
+ `# type: ignore` lives in exactly one place, not per import:
228
+
229
+ ```python
230
+ # style/_mohtml.py
231
+ # Single place that absorbs the mohtml import warts.
232
+ from mohtml import ( # type: ignore[import-untyped]
233
+ div, span, p, svg, path, rect,
234
+ )
235
+
236
+ __all__ = ["div", "span", "p", "svg", "path", "rect"]
237
+ ```
238
+
239
+ Then `components.py` / `card.py` / `pool_card.py` import from
240
+ `dr_llm.style._mohtml` instead of `mohtml` directly.
241
+
242
+ **Compatibility with Pydantic.** `arbitrary_types_allowed=True` is
243
+ still required on models that *hold* mohtml instances as fields
244
+ (because Pydantic looks at the runtime class, not the annotation).
245
+ The `Protocol` is purely a type-checker affordance; it does not
246
+ satisfy Pydantic's field validation. So:
247
+
248
+ - `Card.header: HtmlRenderable | None` **still needs**
249
+ `arbitrary_types_allowed=True`.
250
+ - `.render() -> HtmlRenderable` method annotations are pure wins.
251
+
252
+ **Open question.** Should this `Protocol` live in `dr_llm.style` or
253
+ upstream in `mohtml` itself? If it lives in mohtml, every downstream
254
+ consumer benefits. Worth raising with that repo — mohtml is small
255
+ enough that a PR adding `mohtml.HtmlRenderable` plus `py.typed` would
256
+ unblock a lot of typing pain at source.
257
+
258
+ ---
259
+
260
+ ## 6. Single `<style>` block + `klass=` per card (bigger refactor)
261
+
262
+ **Observation.** Every `DataItem`, `Badge`, `MetaStamp` instance
263
+ re-emits the same inline-style string on every render. This works but:
264
+
265
+ - It is the biggest chunk of the serialized HTML.
266
+ - Inline styles cannot express `:hover`, `:focus`, `@media`, or
267
+ `light-dark()`.
268
+ - It couples visual polish to Python string assembly.
269
+
270
+ **Proposed change.** mohtml exposes a `style` tag and `klass=`
271
+ attribute. Emit one scoped `<style>` block at the top of each card
272
+ with real CSS rules, and tag elements with classes:
273
+
274
+ ```python
275
+ def stylesheet(self) -> style:
276
+ return style(f"""
277
+ .dr-card {{ ... }}
278
+ .dr-card-title {{ ... }}
279
+ .dr-badge.tone-info {{ background: {self.palette.info.bg}; ... }}
280
+ .dr-badge.tone-info:hover {{ ... }}
281
+ """)
282
+
283
+ def render(self) -> div:
284
+ return div(self.stylesheet(), ..., klass="dr-card")
285
+ ```
286
+
287
+ **Tradeoffs.**
288
+
289
+ - **Pro:** Shorter HTML output, access to pseudo-classes and media
290
+ queries, easier dark-mode via `light-dark()`, easier to hand-inspect.
291
+ - **Con:** CSS scoping becomes a real concern. Multiple cards on a
292
+ page share the global stylesheet namespace; you either accept that
293
+ `.dr-badge` is shared (good: deduplication; bad: first card's palette
294
+ wins) or generate per-instance class suffixes (uglier).
295
+ - **Con:** Loses the current property that `PoolCard` renders to a
296
+ totally self-contained string with no global side effects.
297
+
298
+ **Recommendation.** Defer until a second card type exists. At that
299
+ point the duplication cost is concrete and the scoping decision is
300
+ better informed by actual usage.
301
+
302
+ ---
303
+
304
+ ## Suggested ordering
305
+
306
+ 1. #3 standardize token `.css()` return shape (tiny, enables #2).
307
+ 2. #2 introduce `css()` helper, migrate all call sites.
308
+ 3. #5 add `HtmlRenderable` protocol + `_mohtml.py` re-export module,
309
+ retype signatures.
310
+ 4. #1 verify marimo picks up `_repr_html_` on raw mohtml objects; if
311
+ so, delete `render_html()`.
312
+ 5. #4 (optional) swap outer `div`s for custom semantic tags.
313
+ 6. #6 revisit after a second card type appears.
314
+
315
+ ## Out of scope for this doc
316
+
317
+ - Any actual *new* card types (`EvalRunCard`, etc.) — covered elsewhere.
318
+ - Theming/dark-mode — enabled by #6 but not required by it.
319
+ - Replacing mohtml with another HTML DSL (FastHTML, htpy, etc.) —
320
+ would invalidate most of the above.
@@ -0,0 +1,96 @@
1
+ Metadata-Version: 2.4
2
+ Name: marimo-utils
3
+ Version: 0.2.0
4
+ Summary: Utilities for working with marimo notebooks, including Pydantic model display and a mohtml-based design system
5
+ Project-URL: Homepage, https://github.com/drothermel/marimo_utils
6
+ Project-URL: Repository, https://github.com/drothermel/marimo_utils
7
+ Author-email: Danielle Rothermel <danielle.rothermel@gmail.com>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: display,html,marimo,mohtml,notebooks,pydantic,style
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
17
+ Requires-Python: >=3.12
18
+ Requires-Dist: marimo[recommended]>=0.19.4
19
+ Requires-Dist: mohtml>=0.1.11
20
+ Requires-Dist: pydantic>=2.12.5
21
+ Description-Content-Type: text/markdown
22
+
23
+ # marimo-utils
24
+
25
+ Utilities for working with marimo notebooks.
26
+
27
+ ## Installation
28
+
29
+ ```bash
30
+ pip install marimo-utils
31
+ ```
32
+
33
+ ## Usage
34
+
35
+ ### `@add_marimo_display()` decorator
36
+
37
+ Adds a `_display_` method to Pydantic models for rich rendering in marimo notebooks.
38
+
39
+ ```python
40
+ from pydantic import BaseModel
41
+ from marimo_utils import add_marimo_display
42
+
43
+ @add_marimo_display()
44
+ class MyConfig(BaseModel):
45
+ name: str
46
+ value: int
47
+ ```
48
+
49
+ When a `MyConfig` instance is the last expression in a marimo cell, it renders with the class name, source file path, and all field values.
50
+
51
+ ### `marimo_utils.style` — design-system primitives
52
+
53
+ A small design-system for rendering Pydantic-backed "inspection cards" in marimo notebooks, built on [`mohtml`](https://github.com/koaning/mohtml). Tokens (`ColorPalette`, `Typography`, `SpacingScale`), atoms (`Badge`, `Title`, `DataItem`, `DateStamp`, `ProjectStamp`, `LabeledList`, `MetaStamp`), a `Card` composer, a `css()` style-builder helper, and an `HtmlRenderable` protocol for typing mohtml-produced values.
54
+
55
+ ```python
56
+ import marimo as mo
57
+ from marimo_utils.style import (
58
+ Badge, Card, ColorPalette, PaletteToneName,
59
+ SpacingScale, Title, Typography,
60
+ )
61
+
62
+ palette = ColorPalette.default()
63
+ typography = Typography.default()
64
+ spacing = SpacingScale.default()
65
+
66
+ card = Card(
67
+ palette=palette,
68
+ typography=typography,
69
+ spacing=spacing,
70
+ title=Title(
71
+ palette=palette,
72
+ typography=typography,
73
+ spacing=spacing,
74
+ drop_text="Pool Card",
75
+ text="demo pool",
76
+ ),
77
+ header=Badge(
78
+ palette=palette,
79
+ typography=typography,
80
+ spacing=spacing,
81
+ label="complete",
82
+ tone=PaletteToneName.SUCCESS,
83
+ ).render(),
84
+ )
85
+
86
+ mo.Html(str(card.render()))
87
+ ```
88
+
89
+ See [`IMPORT_STYLE.md`](./IMPORT_STYLE.md) for design notes on the mohtml leverage points and CSS helper.
90
+
91
+ ## Changes
92
+
93
+ ### 0.2.0
94
+
95
+ - Adds `marimo_utils.style` submodule (`Card`, tokens, atoms, `css()` helper, `HtmlRenderable` protocol).
96
+ - Adds `mohtml>=0.1.11` runtime dependency.
@@ -0,0 +1,74 @@
1
+ # marimo-utils
2
+
3
+ Utilities for working with marimo notebooks.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install marimo-utils
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ### `@add_marimo_display()` decorator
14
+
15
+ Adds a `_display_` method to Pydantic models for rich rendering in marimo notebooks.
16
+
17
+ ```python
18
+ from pydantic import BaseModel
19
+ from marimo_utils import add_marimo_display
20
+
21
+ @add_marimo_display()
22
+ class MyConfig(BaseModel):
23
+ name: str
24
+ value: int
25
+ ```
26
+
27
+ When a `MyConfig` instance is the last expression in a marimo cell, it renders with the class name, source file path, and all field values.
28
+
29
+ ### `marimo_utils.style` — design-system primitives
30
+
31
+ A small design-system for rendering Pydantic-backed "inspection cards" in marimo notebooks, built on [`mohtml`](https://github.com/koaning/mohtml). Tokens (`ColorPalette`, `Typography`, `SpacingScale`), atoms (`Badge`, `Title`, `DataItem`, `DateStamp`, `ProjectStamp`, `LabeledList`, `MetaStamp`), a `Card` composer, a `css()` style-builder helper, and an `HtmlRenderable` protocol for typing mohtml-produced values.
32
+
33
+ ```python
34
+ import marimo as mo
35
+ from marimo_utils.style import (
36
+ Badge, Card, ColorPalette, PaletteToneName,
37
+ SpacingScale, Title, Typography,
38
+ )
39
+
40
+ palette = ColorPalette.default()
41
+ typography = Typography.default()
42
+ spacing = SpacingScale.default()
43
+
44
+ card = Card(
45
+ palette=palette,
46
+ typography=typography,
47
+ spacing=spacing,
48
+ title=Title(
49
+ palette=palette,
50
+ typography=typography,
51
+ spacing=spacing,
52
+ drop_text="Pool Card",
53
+ text="demo pool",
54
+ ),
55
+ header=Badge(
56
+ palette=palette,
57
+ typography=typography,
58
+ spacing=spacing,
59
+ label="complete",
60
+ tone=PaletteToneName.SUCCESS,
61
+ ).render(),
62
+ )
63
+
64
+ mo.Html(str(card.render()))
65
+ ```
66
+
67
+ See [`IMPORT_STYLE.md`](./IMPORT_STYLE.md) for design notes on the mohtml leverage points and CSS helper.
68
+
69
+ ## Changes
70
+
71
+ ### 0.2.0
72
+
73
+ - Adds `marimo_utils.style` submodule (`Card`, tokens, atoms, `css()` helper, `HtmlRenderable` protocol).
74
+ - Adds `mohtml>=0.1.11` runtime dependency.