posit-dev / posit-dev/py-shiny
[Bug]: `shiny.module` re-exports are stripped from generated type stubs, so Pyright rejects them in Shinylive
@jat255 is already working on this.
Since Aug 7, 2026.
- Dominant language
- Python
- Stars
- 1.8k
- Forks
- 135
- Avg merge
- 2d 18h
- Merged PRs (30d)
- 21
Description
Summary
In the Shinylive editor, these imports are flagged as errors even though the code runs correctly:
from shiny.module import resolve_id # "resolve_id" is unknown import symbol
from shiny.module import current_namespace # "current_namespace" is unknown import symbol
from shiny.module import ResolvedId # "ResolvedId" is unknown import symbol
This came up while working on some doc changes in https://github.com/posit-dev/py-shiny-site/pull/436. For example:
shiny/module.py re-exports these names from shiny._namespaces but never references them in
its own body. pyright --createstub drops imports that aren't referenced, so the generated
shiny/module.pyi still advertises the names in __all__ while never importing them. Shinylive
bundles those stubs for its in-browser Pyright (via scripts/create_typeshed.py in the shinylive
repo), so the documented import surfaces a spurious error.
This is editor-only. Runtime behavior is correct.
Evidence
The stub actually shipped in the shinylive 0.10.14 assets
(shinylive/pyright/typeshed.en.json → /src/typings/shiny/module.pyi):
from typing import Callable, TYPE_CHECKING, TypeVar
from ._docstring import add_example
from ._namespaces import Id # ← only Id survived
from ._typing_extensions import Concatenate, ParamSpec
from .session import Inputs, Outputs, Session
__all__ = ("current_namespace", "resolve_id", "ui", "server", "ResolvedId")
shiny/_namespaces.pyi in the same bundle defines resolve_id, current_namespace, and
ResolvedId correctly — only the re-export hop through shiny/module.py is lost.
Status of every name in shiny.module.__all__:
| Name | Stub status | Why |
|---|---|---|
current_namespace |
broken | re-export only |
resolve_id |
broken | re-export only |
ResolvedId |
broken | re-export only |
ui |
OK | defined in module.py |
server |
OK | defined in module.py |
Id survives only because of the existing workaround at shiny/module.py:23-24:
# Ensure that Id type is not stripped out from .pyi file when generating type stubs
_: Id # type: ignore
which suggests this was hit once already and patched for a single symbol.
Reproduction
Any Shinylive example using the documented import reproduces it. Minimal standalone repro of the
stub generation, with a package mirroring module.py's re-export pattern:
# mypkg/module.py
__all__ = ("current_namespace", "resolve_id", "ui", "server", "ResolvedId")
from ._namespaces import Id, ResolvedId, current_namespace, namespace_context, resolve_id
_: Id # type: ignore
def ui(fn): ...
pyright --createstub mypkg && cat typings/mypkg/module.pyi
Output keeps only from ._namespaces import Id; the other three names are dropped while __all__
still lists them.
Options tested (pyright 1.1.411)
| Approach | Result |
|---|---|
Current form (plain import + __all__) |
Reproduces the bug; only Id retained |
PEP 484 redundant alias — import resolve_id as resolve_id |
Regresses — entire import line dropped, Id lost too |
Annotation probe — _r: ResolvedId |
Retains type symbols |
Module-level assignment — _keep = (resolve_id, ...) |
Does not retain functions (RHS elided to ...) |
Defining thin wrappers in module.py |
Emits real def resolve_id(...) / def current_namespace(...) |
Worth calling out: the redundant-alias form is the idiomatic PEP 484 re-export fix and is the
natural first thing to reach for, but it makes this worse under --createstub.
Suggested fix
Annotations retain type symbols; they can't retain functions, and assignments don't either. So the
two categories need different handling in shiny/module.py:
ResolvedId— extend the existing annotation workaround:_r: ResolvedId # type: ignoreresolve_id/current_namespace— define thin delegating wrappers socreatestubemits
real function signatures:def resolve_id(id: Id) -> ResolvedId: """Resolve an ID, possibly with a module namespace.""" return _namespaces.resolve_id(id)
Both were verified to produce a correct stub. A wrapper adds one call of indirection; if that's
unwanted, the alternative is fixing it on the shinylive side (post-process the generated stubs, or
don't use --createstub for shiny) — happy to move this issue there if that's the better venue.
Impact
from shiny.module import resolve_id is the documented public API for making custom components and
hand-written HTML ids module-aware (see the "Custom JavaScript component" docs page and the modules
article). Anyone following those docs in Shinylive gets a red squiggle on correct code.
Environment
- shiny
1.7.1.dev6(py-shinyf85a444); the pattern is unchanged in currentmain - shinylive JS assets
0.10.14(via shinylive PyPI0.8.11) - pyright
1.1.411 - Runtime unaffected — apps using these imports run correctly
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.