Skip to content

fix: adapt to htmltools tagified-type changes (#105)#2244

Open
schloerke wants to merge 11 commits into
mainfrom
schloerke/htmltools-105-tagified-types
Open

fix: adapt to htmltools tagified-type changes (#105)#2244
schloerke wants to merge 11 commits into
mainfrom
schloerke/htmltools-105-tagified-types

Conversation

@schloerke
Copy link
Copy Markdown
Collaborator

@schloerke schloerke commented May 15, 2026

Summary

Adapts py-shiny to the upcoming htmltools changes in posit-dev/py-htmltools#106 (implementing posit-dev/py-htmltools#105).

In that release:

  • Tagifiable.tagify()'s Protocol return is tightened to the new Tagified union — the only tagified-shape alias htmltools exports. Tagified excludes the un-resolved Tagifiable arm of TagNode, so it's a proper subtype of the old return.
  • Tag and TagList become generic in their child type (ChildT, defaulting to TagNode). Because ChildT is invariant, a tagified Tag (internally Tag[TagifiedNode]) is no longer a subtype of bare Tag (= Tag[TagNode]); same for TagList.

Without this PR, strict-mode pyright on py-shiny reports ~20 errors against the new htmltools. With this PR, py-shiny is clean again (modulo a single pre-existing unrelated matplotlib Figure | SubFigure | None error in shiny/render/_try_render_plot.py:272).

Changes

  • _app.py: widen App.__init__'s ui parameter and is_uifunc's x parameter to also accept Tagified in addition to bare Tag / TagList. Callable variants accept either too.
  • _accordion.py / _card.py / _sidebar.py: change tagify() return annotations to Tagified to match the new protocol contract.
  • _navs.py: same as above plus narrow content.children items via cast("Tag[TagNode]", child) after isinstance(Tag) so the downstream generic-Tag helpers compose.
  • _page.py: same cast("Tag[TagNode]", body) narrow before passing to as_fillable_container.
  • _toolbar.py / _utils.py: cast narrowed Tag values back to Tag[TagNode] so .children doesn't leak Unknown into recursive helpers.
  • _func_displayhook.py: suppress two narrow-induced Unknown reports on the object -> object decorator (the public signature doesn't carry the narrowing).
  • tests/pytest/test_sidebar.py: cast sb.tagify() to TagList before unpacking — the Tagified union includes non-iterable arms.
  • Makefile: ci-install-py-shiny-templates-deps pre-installs htmltools from the PR branch as a workaround for uv's URL-dep resolution. Tracked by Remove temporary htmltools git URL workaround in Makefile once htmltools 0.7.0 ships #2245 for removal post-release.

Test plan

  • uv run --with-editable <path>/py-htmltools pyright shiny → 1 error, unrelated to htmltools (pre-existing matplotlib Figure | SubFigure issue in _try_render_plot.py:272).
  • Runtime behavior is unchanged — all changes are type-level annotations and narrowing casts.

Dependency

This PR should land alongside (or shortly after) posit-dev/py-htmltools#106. The Tagified alias referenced here is new in that release; this PR requires htmltools 0.7.0+. The pyproject.toml git pin and Makefile workaround (#2245) should be reverted in favor of htmltools>=0.7.0 once 0.7.0 ships on PyPI.

The upcoming htmltools release (posit-dev/py-htmltools#105) tightens the
`Tagifiable.tagify()` Protocol return type to the new `Tagified` union
(`TagifiedTag | TagifiedTagList | TagLeaf`), and `Tag` / `TagList` become
generic in their child type (`ChildT`, defaulting to `TagNode`). Because
`ChildT` is invariant, `TagifiedTag` (= `Tag[TagifiedNode]`) is no longer
a subtype of bare `Tag` (= `Tag[TagNode]`), and the same for TagList.

This commit makes the minimum changes so py-shiny's strict-mode pyright
run stays green against the new htmltools:

* `_app.py`: widen `App.__init__`'s `ui` parameter and `is_uifunc`'s `x`
  parameter to accept `TagifiedTag` / `TagifiedTagList` in addition to
  bare `Tag` / `TagList`. Callable variants accept either too.
* `_accordion.py` / `_card.py` / `_sidebar.py`: change `tagify()` return
  annotations from `Tag` / `TagList` to `TagifiedTag` / `TagifiedTagList`
  to match what `.tagify()` actually produces.
* `_navs.py`: same plus narrow `content.children` items via
  `cast("Tag[TagNode]", child)` after `isinstance(Tag)` so the
  downstream generic-Tag helpers compose.
* `_page.py`: same `cast("Tag[TagNode]", body)` narrow before passing to
  `as_fillable_container`.
* `_toolbar.py` / `_utils.py`: cast narrowed Tag values back to
  `Tag[TagNode]` so `.children` doesn't leak `Unknown` into recursive
  helpers.
* `_func_displayhook.py`: suppress two narrow-induced `Unknown` reports
  on the `object -> object` decorator (the public signature doesn't
  carry the narrowing).
schloerke added a commit to posit-dev/py-htmltools that referenced this pull request May 15, 2026
…ype changes

* py-shiny@schloerke/htmltools-105-tagified-types — posit-dev/py-shiny#2244
* shinychat@schloerke/htmltools-105-tagified-types — posit-dev/shinychat#226
* chatlas@schloerke/htmltools-105-tagified-types — posit-dev/chatlas#311
* brand-yml@schloerke/htmltools-105-tagified-types — posit-dev/brand-yml#115

When each downstream PR merges, flip its `ref` back to `main` (or remove
the explicit ref). The matrix is restructured to use `include:` so each
entry can carry its own `ref`.
`Sidebar.tagify()` now returns `TagifiedTagList` (per the htmltools
type changes); its items narrow via isinstance(Tag) to `Tag[Unknown] |
Tag[TagifiedNode]`, which doesn't satisfy the bare `Tag` return type
of `get_sidebar_tags`. Add a `cast(Tag, …)` on each item to re-widen
to the default `Tag[TagNode]`.
schloerke and others added 6 commits May 18, 2026 09:59
…and guard import

Per htmltools maintainer guidance, downstreams should only reference the
single `Tagified` alias rather than the more specific `TagifiedTag` /
`TagifiedTagList`. Substitute throughout.

Also move the `Tagified` import under `if TYPE_CHECKING:` so each
module still loads against the currently-released htmltools (0.6.x),
which doesn't export the new alias yet. The existing `from __future__
import annotations` line in each file defers all annotations to
strings, so `Tagified` only needs to resolve at type-check time.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The `Tagified` alias used by this PR is only available in htmltools
>= 0.7.0 (posit-dev/py-htmltools#105). Pin htmltools directly to the
PR branch's git ref so `uv pip install -e ".[dev,test]"` (used by
py-shiny's CI via `make ci-install-deps`) picks up the new alias.

**Revert this dependency pin before merging** once htmltools 0.7.0 is
on PyPI.
The PR introduces `Tagified`, only available in htmltools 0.7.0+
(posit-dev/py-htmltools#105). Document the post-release version
constraint that the temporary git-url pin should be flipped to.
The is_uifunc signature was below the line-length threshold after
collapsing TagifiedTag/TagifiedTagList down to Tagified, and the
imports needed re-wrapping.
`Sidebar.tagify()` returns `Tagified`, which includes non-iterable
arms (Tag, MetadataNode, ReprHtml). The unpack `sidebar, collapse = ...`
made pyright complain about every non-iterable arm. Narrow to `TagList`
via cast before unpacking; the runtime shape is unchanged.
uv refuses to resolve `py-shiny-templates/requirements.txt` while
py-shiny's own `pyproject.toml` pins htmltools to a git URL, because
URL-pinned transitive deps must be repeated as direct requirements.
Pass the URL inline to satisfy uv. Remove this workaround once
htmltools 0.7.0 ships.
`TagifiedNode` is no longer exported from htmltools (it lives in
`htmltools._core` for internal use). Rewrite the two cast-narrowing
comments to describe the situation without naming the internal alias.
The schloerke/tagify-tag-class-issue branch was deleted after
posit-dev/py-htmltools#106 merged. Point at main so CI resolves
again while we wait for the htmltools 0.7.0 PyPI release. The
direct git URL dep and the Makefile workaround will be removed
once 0.7.0 ships (see posit-dev/py-htmltools#113 and #2245).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant