Skip to content

API Reference

Components (shiny_kit.component)

Core OOP PyShiny lifecycle (Component, SubComponentProxy, subcomponent, Tab, ParentTab, BaseHeroTab).

Classes:

  • BaseHeroTab –

    Reusable glassmorphic Hero & Workflow landing Tab component.

  • Component –

    Base class for OOP-driven Shiny modules with integrated UI, Server, Subcomponents, and Resource Gating.

  • HeroConfig –

    Configuration contract for a BaseHeroTab landing view.

  • HeroStatSpec –

    Static specification for a metric tile in the .sv-stat-strip hero strip.

  • HomeTabEvents –

    Read-only reactive event streams emitted by BaseHeroTab.server().

  • ParentTab –

    Container tab hosting child Tab modules inside a card navigation panel.

  • SubComponentProxy –

    Class-level proxy returned when accessing a subcomponent descriptor on a Component class.

  • Tab –

    Specialized Component representing a navigable tab in a tabbed layout.

  • WorkflowStepSpec –

    Static specification for a pillar/workflow card below the Hero section.

  • subcomponent –

    Descriptor declaring a child Component auto-mounted and auto-bound by its parent Component.

BaseHeroTab

BaseHeroTab(
    *, config: HeroConfig | None = None, color_mode: Callable[[], str] | None = None
)

              flowchart TD
              shiny_kit.component.BaseHeroTab[BaseHeroTab]
              shiny_kit.component.Tab[Tab]
              shiny_kit.component.Component[Component]

                              shiny_kit.component.Tab --> shiny_kit.component.BaseHeroTab
                                shiny_kit.component.Component --> shiny_kit.component.Tab
                



              click shiny_kit.component.BaseHeroTab href "" "shiny_kit.component.BaseHeroTab"
              click shiny_kit.component.Tab href "" "shiny_kit.component.Tab"
              click shiny_kit.component.Component href "" "shiny_kit.component.Component"
            

Reusable glassmorphic Hero & Workflow landing Tab component.

Renders a two-column .sv-hero banner with gradient headline, primary/secondary CTA buttons, an interactive live preview widget card, .sv-stat-strip metric tiles, and .sv-pillar-card pipeline cards below the fold.

Methods:

  • has_changed –

    Tracks version under key and returns True when version differs from the previously recorded value.

  • is_unlocked –

    Reactively checks whether all declarative requires rules are satisfied.

  • notify –

    Displays a toast notification if running inside an active Shiny session.

  • notify_error –

    Formats and displays an error toast notification in the active Shiny session.

  • notify_warning –

    Displays a warning toast notification in the active Shiny session.

  • render_preview_controls_ui –

    Optional header controls rendered on the right side of the live preview card.

  • render_preview_widget_ui –

    Renders the output widget container inside the glassmorphic preview card.

  • server –

    Binds server logic for the Hero tab and returns HomeTabEvents streams.

  • sync_version –

    Updates the recorded version for key without reporting a change.

  • title –

    Derives title from the class docstring without inheriting parent docstrings.

  • ui –

    Wraps the component UI in a Shiny NavPanel.

  • unmet_requirements –

    Reactively returns the list of currently unmet Requirement rules.

  • val –

    Safely reads self.input[id_]() with fallback default, optional reactive isolation, and string stripping.

Attributes:

  • has_session (bool) –

    Whether this component is currently bound to an active Shiny session.

Source code in src/shiny_kit/component.py
def __init__(
    self,
    *,
    config: HeroConfig | None = None,
    color_mode: Callable[[], str] | None = None,
) -> None:
    super().__init__()
    self.instance_config: HeroConfig = config if config is not None else self.config
    self._color_mode_fn = color_mode

has_session property

has_session: bool

Whether this component is currently bound to an active Shiny session.

has_changed

has_changed(key: str, version: Any, *, initial: Any = 0) -> bool

Tracks version under key and returns True when version differs from the previously recorded value.

Source code in src/shiny_kit/component.py
def has_changed(self, key: str, version: Any, *, initial: Any = 0) -> bool:
    """Tracks `version` under `key` and returns `True` when `version` differs from the previously recorded value."""
    prev = self._version_tracker.get(key, initial)
    if version != prev:
        self._version_tracker[key] = version
        return True
    return False

is_unlocked

is_unlocked() -> bool

Reactively checks whether all declarative requires rules are satisfied.

Source code in src/shiny_kit/component.py
def is_unlocked(self) -> bool:
    """Reactively checks whether all declarative `requires` rules are satisfied."""
    if not self.requires or self._state is None:
        return True
    return all(r.is_met(self._state) for r in self.requires)

notify

notify(
    message: str,
    *,
    type_: Literal["message", "warning", "error", "default"] = "message",
    duration: int | None = 5,
) -> None

Displays a toast notification if running inside an active Shiny session.

Source code in src/shiny_kit/component.py
def notify(
    self,
    message: str,
    *,
    type_: Literal["message", "warning", "error", "default"] = "message",
    duration: int | None = 5,
) -> None:
    """Displays a toast notification if running inside an active Shiny session."""
    shiny_ui.notification_show(message, type=type_, duration=duration)

notify_error

notify_error(
    error: BaseException | str, *, context: str | None = None, duration: int | None = 8
) -> None

Formats and displays an error toast notification in the active Shiny session.

Source code in src/shiny_kit/component.py
def notify_error(
    self,
    error: BaseException | str,
    *,
    context: str | None = None,
    duration: int | None = 8,
) -> None:
    """Formats and displays an error toast notification in the active Shiny session."""
    detail = str(error).strip() or error.__class__.__name__
    msg = f"{context}: {detail}" if context else detail
    self.notify(msg, type_="error", duration=duration)

notify_warning

notify_warning(message: str, *, duration: int | None = 8) -> None

Displays a warning toast notification in the active Shiny session.

Source code in src/shiny_kit/component.py
def notify_warning(self, message: str, *, duration: int | None = 8) -> None:
    """Displays a warning toast notification in the active Shiny session."""
    clean = str(message).strip()
    if clean:
        self.notify(clean, type_="warning", duration=duration)

render_preview_controls_ui classmethod

render_preview_controls_ui() -> TagChild

Optional header controls rendered on the right side of the live preview card.

Source code in src/shiny_kit/component.py
@classmethod
def render_preview_controls_ui(cls) -> TagChild:
    """Optional header controls rendered on the right side of the live preview card."""
    return None

render_preview_widget_ui classmethod

render_preview_widget_ui() -> TagChild

Renders the output widget container inside the glassmorphic preview card.

Source code in src/shiny_kit/component.py
@classmethod
def render_preview_widget_ui(cls) -> TagChild:
    """Renders the output widget container inside the glassmorphic preview card."""
    shinywidgets = importlib.import_module("shinywidgets")
    return shinywidgets.output_widget(
        cls.preview_output_id,
        width="100%",
        height=cls.preview_height,
        fill=True,
        fillable=True,
    )

server

server(
    id_: str, *, color_mode: Callable[[], str] | None = None, **_: Any
) -> HomeTabEvents

Binds server logic for the Hero tab and returns HomeTabEvents streams.

Source code in src/shiny_kit/component.py
def server(
    self,
    id_: str,
    *,
    color_mode: Callable[[], str] | None = None,
    **_: Any,
) -> HomeTabEvents:
    """Binds server logic for the Hero tab and returns `HomeTabEvents` streams."""
    if color_mode is not None:
        self._color_mode_fn = color_mode
    super().server(id_)

    def _launch_clicked() -> int:
        return int(self.val("btn_launch", default=0) or 0)

    def _demo_clicked() -> int:
        return int(self.val("btn_load_demo", default=0) or 0)

    return HomeTabEvents(
        launch_clicked=_launch_clicked,
        demo_clicked=_demo_clicked,
    )

sync_version

sync_version(key: str, version: Any) -> None

Updates the recorded version for key without reporting a change.

Source code in src/shiny_kit/component.py
def sync_version(self, key: str, version: Any) -> None:
    """Updates the recorded version for `key` without reporting a change."""
    self._version_tracker[key] = version

title classmethod

title() -> str

Derives title from the class docstring without inheriting parent docstrings.

Source code in src/shiny_kit/component.py
@classmethod
def title(cls) -> str:
    """Derives title from the class docstring without inheriting parent docstrings."""
    return DocstringParser.display_label(cls, strip_suffix="Tab", inherit_doc=False)

ui classmethod

ui(id_: str, *args: Any, **kwargs: Any) -> NavPanel

Wraps the component UI in a Shiny NavPanel.

Source code in src/shiny_kit/component.py
@classmethod
def ui(cls, id_: str, *args: Any, **kwargs: Any) -> shiny_ui._navs.NavPanel:
    """Wraps the component UI in a Shiny `NavPanel`."""
    return shiny_ui.nav_panel(cls.title(), super().ui(id_, *args, **kwargs), icon=cls.icon, value=id_)

unmet_requirements

unmet_requirements() -> list[Requirement]

Reactively returns the list of currently unmet Requirement rules.

Source code in src/shiny_kit/component.py
def unmet_requirements(self) -> list[Requirement]:
    """Reactively returns the list of currently unmet `Requirement` rules."""
    if not self.requires or self._state is None:
        return []
    return [r for r in self.requires if not r.is_met(self._state)]

val

val(id_: str, default: Any = None, *, isolate: bool = False, strip: bool = True) -> Any

Safely reads self.input[id_]() with fallback default, optional reactive isolation, and string stripping.

Source code in src/shiny_kit/component.py
def val(
    self,
    id_: str,
    default: Any = None,
    *,
    isolate: bool = False,
    strip: bool = True,
) -> Any:
    """Safely reads `self.input[id_]()` with fallback `default`, optional reactive isolation, and string stripping."""
    if self._input is None:
        return default

    def _read() -> Any:
        if self._input is None or id_ not in self._input:
            return default
        raw = self._input[id_]()
        if raw is None:
            return default
        if isinstance(raw, str):
            cleaned = raw.strip() if strip else raw
            if not cleaned and default is not None:
                return default
            return cleaned
        return raw

    if isolate:
        with reactive.isolate():
            return _read()
    return _read()

Component

Component()

Base class for OOP-driven Shiny modules with integrated UI, Server, Subcomponents, and Resource Gating.

Subclasses can bind a typed BaseSessionState subclass via PEP 695 type parameter S and state_cls, compose child Components via subcomponent(...), and declare pipeline prerequisites via requires.

Attributes:

  • state_cls (type[S]) –

    Concrete BaseSessionState associated with this component.

  • requires (ClassVar[tuple[Requirement, ...]]) –

    Declarative prerequisite rules gating the component.

  • module_id (str) –

    Shiny module namespace ID assigned when server(id_) is bound.

Examples:

from shiny import render, ui
from shiny_kit.component import Component
from shiny_kit.decorators import bind_output
from shiny_kit.state import BaseSessionState

class AppState(BaseSessionState):
    pass

class CounterCard(Component[AppState]):
    @classmethod
    def _render_ui(cls):
        return ui.card(ui.output_text("label"))

    @bind_output(render.text)
    def label(self) -> str:
        return f"Theme: {self.state.color_mode}"

Methods:

  • has_changed –

    Tracks version under key and returns True when version differs from the previously recorded value.

  • is_unlocked –

    Reactively checks whether all declarative requires rules are satisfied.

  • notify –

    Displays a toast notification if running inside an active Shiny session.

  • notify_error –

    Formats and displays an error toast notification in the active Shiny session.

  • notify_warning –

    Displays a warning toast notification in the active Shiny session.

  • server –

    Binds server logic, state lifecycle, declarative BaseBinders, and subcomponents to a session.

  • sync_version –

    Updates the recorded version for key without reporting a change.

  • ui –

    Constructs and namespaces the UI tree for this component, adding a resource-gate container if requires is set.

  • unmet_requirements –

    Reactively returns the list of currently unmet Requirement rules.

  • val –

    Safely reads self.input[id_]() with fallback default, optional reactive isolation, and string stripping.

Source code in src/shiny_kit/component.py
def __init__(self) -> None:
    self._input: Inputs | None = None
    self._output: Outputs | None = None
    self._session: Session | None = None
    self._state: S | None = None
    self.module_id: str = ""
    self._version_tracker: dict[str, Any] = {}

has_session property

has_session: bool

Whether this component is currently bound to an active Shiny session.

has_changed

has_changed(key: str, version: Any, *, initial: Any = 0) -> bool

Tracks version under key and returns True when version differs from the previously recorded value.

Source code in src/shiny_kit/component.py
def has_changed(self, key: str, version: Any, *, initial: Any = 0) -> bool:
    """Tracks `version` under `key` and returns `True` when `version` differs from the previously recorded value."""
    prev = self._version_tracker.get(key, initial)
    if version != prev:
        self._version_tracker[key] = version
        return True
    return False

is_unlocked

is_unlocked() -> bool

Reactively checks whether all declarative requires rules are satisfied.

Source code in src/shiny_kit/component.py
def is_unlocked(self) -> bool:
    """Reactively checks whether all declarative `requires` rules are satisfied."""
    if not self.requires or self._state is None:
        return True
    return all(r.is_met(self._state) for r in self.requires)

notify

notify(
    message: str,
    *,
    type_: Literal["message", "warning", "error", "default"] = "message",
    duration: int | None = 5,
) -> None

Displays a toast notification if running inside an active Shiny session.

Source code in src/shiny_kit/component.py
def notify(
    self,
    message: str,
    *,
    type_: Literal["message", "warning", "error", "default"] = "message",
    duration: int | None = 5,
) -> None:
    """Displays a toast notification if running inside an active Shiny session."""
    shiny_ui.notification_show(message, type=type_, duration=duration)

notify_error

notify_error(
    error: BaseException | str, *, context: str | None = None, duration: int | None = 8
) -> None

Formats and displays an error toast notification in the active Shiny session.

Source code in src/shiny_kit/component.py
def notify_error(
    self,
    error: BaseException | str,
    *,
    context: str | None = None,
    duration: int | None = 8,
) -> None:
    """Formats and displays an error toast notification in the active Shiny session."""
    detail = str(error).strip() or error.__class__.__name__
    msg = f"{context}: {detail}" if context else detail
    self.notify(msg, type_="error", duration=duration)

notify_warning

notify_warning(message: str, *, duration: int | None = 8) -> None

Displays a warning toast notification in the active Shiny session.

Source code in src/shiny_kit/component.py
def notify_warning(self, message: str, *, duration: int | None = 8) -> None:
    """Displays a warning toast notification in the active Shiny session."""
    clean = str(message).strip()
    if clean:
        self.notify(clean, type_="warning", duration=duration)

server

server(id_: str) -> Any

Binds server logic, state lifecycle, declarative BaseBinders, and subcomponents to a session.

Source code in src/shiny_kit/component.py
def server(self, id_: str) -> Any:
    """Binds server logic, state lifecycle, declarative `BaseBinder`s, and subcomponents to a session."""
    self.module_id = id_

    @module.server
    def _server(input: Inputs, output: Outputs, session: Session) -> None:
        self._input = input
        self._output = output
        self._session = session
        self._state = self.state_cls.from_session(session)

        for name in self._registered_binders:
            binder: BaseBinder = getattr(self.__class__, name)
            binder.bind(self, input, output, session)

        for sub_name in self._registered_subcomponents:
            sub_desc: subcomponent[Any] = getattr(self.__class__, sub_name)
            child_inst: Component[Any] = getattr(self, sub_name)
            child_inst.server(sub_desc.child_id)

        if self.requires:
            self._bind_resource_gate(input, output, session)

        self._register_server()

    return _server(id_)

sync_version

sync_version(key: str, version: Any) -> None

Updates the recorded version for key without reporting a change.

Source code in src/shiny_kit/component.py
def sync_version(self, key: str, version: Any) -> None:
    """Updates the recorded version for `key` without reporting a change."""
    self._version_tracker[key] = version

ui classmethod

ui(id_: str, *args: Any, **kwargs: Any) -> Any

Constructs and namespaces the UI tree for this component, adding a resource-gate container if requires is set.

Source code in src/shiny_kit/component.py
@classmethod
def ui(cls, id_: str, *args: Any, **kwargs: Any) -> Any:
    """Constructs and namespaces the UI tree for this component, adding a resource-gate container if `requires` is set."""

    @module.ui
    def _ui() -> Any:
        content = cls._render_ui(*args, **kwargs)
        if not cls.requires:
            return content
        return shiny_ui.TagList(
            shiny_ui.output_ui("_gate_callout"),
            shiny_ui.div(content, id=module.resolve_id("_tab_content_wrapper")),
        )

    return _ui(id_)

unmet_requirements

unmet_requirements() -> list[Requirement]

Reactively returns the list of currently unmet Requirement rules.

Source code in src/shiny_kit/component.py
def unmet_requirements(self) -> list[Requirement]:
    """Reactively returns the list of currently unmet `Requirement` rules."""
    if not self.requires or self._state is None:
        return []
    return [r for r in self.requires if not r.is_met(self._state)]

val

val(id_: str, default: Any = None, *, isolate: bool = False, strip: bool = True) -> Any

Safely reads self.input[id_]() with fallback default, optional reactive isolation, and string stripping.

Source code in src/shiny_kit/component.py
def val(
    self,
    id_: str,
    default: Any = None,
    *,
    isolate: bool = False,
    strip: bool = True,
) -> Any:
    """Safely reads `self.input[id_]()` with fallback `default`, optional reactive isolation, and string stripping."""
    if self._input is None:
        return default

    def _read() -> Any:
        if self._input is None or id_ not in self._input:
            return default
        raw = self._input[id_]()
        if raw is None:
            return default
        if isinstance(raw, str):
            cleaned = raw.strip() if strip else raw
            if not cleaned and default is not None:
                return default
            return cleaned
        return raw

    if isolate:
        with reactive.isolate():
            return _read()
    return _read()

HeroConfig dataclass

HeroConfig(
    badge_text: str,
    headline: str,
    subtitle: str,
    primary_cta_label: str,
    secondary_cta_label: str,
    preview_title: str,
    workflow_steps: tuple[WorkflowStepSpec, ...],
    stats: tuple[HeroStatSpec, ...] = (),
    pipeline_heading: str = "Workflow Pipeline",
    pipeline_subtitle: str = "Explore each stage of the workflow below.",
)

Configuration contract for a BaseHeroTab landing view.

Attributes:

  • badge_text (str) –

    Eyebrow pill text displayed above the headline.

  • headline (str) –

    Main gradient headline text.

  • subtitle (str) –

    Lead paragraph text beneath the headline.

  • primary_cta_label (str) –

    Label for the primary action button.

  • secondary_cta_label (str) –

    Label for the secondary demo-loading button.

  • preview_title (str) –

    Header title of the glassmorphic live preview card.

  • workflow_steps (tuple[WorkflowStepSpec, ...]) –

    Sequence of workflow cards rendered below the fold.

  • stats (tuple[HeroStatSpec, ...]) –

    Sequence of metrics rendered in the .sv-stat-strip bar.

HeroStatSpec dataclass

HeroStatSpec(value: str, label: str)

Static specification for a metric tile in the .sv-stat-strip hero strip.

Attributes:

  • value (str) –

    Highlighted headline metric value (rendered with gradient text).

  • label (str) –

    Descriptive caption displayed beneath value.

HomeTabEvents dataclass

HomeTabEvents(launch_clicked: Callable[[], int], demo_clicked: Callable[[], int])

Read-only reactive event streams emitted by BaseHeroTab.server().

Attributes:

  • launch_clicked (Callable[[], int]) –

    Reactive getter returning the click count of the primary launch button.

  • demo_clicked (Callable[[], int]) –

    Reactive getter returning the click count of the demo cohort button.

ParentTab

ParentTab()

              flowchart TD
              shiny_kit.component.ParentTab[ParentTab]
              shiny_kit.component.Tab[Tab]
              shiny_kit.component.Component[Component]

                              shiny_kit.component.Tab --> shiny_kit.component.ParentTab
                                shiny_kit.component.Component --> shiny_kit.component.Tab
                



              click shiny_kit.component.ParentTab href "" "shiny_kit.component.ParentTab"
              click shiny_kit.component.Tab href "" "shiny_kit.component.Tab"
              click shiny_kit.component.Component href "" "shiny_kit.component.Component"
            

Container tab hosting child Tab modules inside a card navigation panel.

Attributes:

Methods:

  • has_changed –

    Tracks version under key and returns True when version differs from the previously recorded value.

  • is_unlocked –

    Reactively checks whether all declarative requires rules are satisfied.

  • notify –

    Displays a toast notification if running inside an active Shiny session.

  • notify_error –

    Formats and displays an error toast notification in the active Shiny session.

  • notify_warning –

    Displays a warning toast notification in the active Shiny session.

  • server –

    Binds server logic, state lifecycle, declarative BaseBinders, and subcomponents to a session.

  • sync_version –

    Updates the recorded version for key without reporting a change.

  • title –

    Derives title from the class docstring without inheriting parent docstrings.

  • ui –

    Wraps the component UI in a Shiny NavPanel.

  • unmet_requirements –

    Reactively returns the list of currently unmet Requirement rules.

  • val –

    Safely reads self.input[id_]() with fallback default, optional reactive isolation, and string stripping.

Source code in src/shiny_kit/component.py
def __init__(self) -> None:
    super().__init__()
    self._child_instances: list[Tab[Any]] = []

has_session property

has_session: bool

Whether this component is currently bound to an active Shiny session.

has_changed

has_changed(key: str, version: Any, *, initial: Any = 0) -> bool

Tracks version under key and returns True when version differs from the previously recorded value.

Source code in src/shiny_kit/component.py
def has_changed(self, key: str, version: Any, *, initial: Any = 0) -> bool:
    """Tracks `version` under `key` and returns `True` when `version` differs from the previously recorded value."""
    prev = self._version_tracker.get(key, initial)
    if version != prev:
        self._version_tracker[key] = version
        return True
    return False

is_unlocked

is_unlocked() -> bool

Reactively checks whether all declarative requires rules are satisfied.

Source code in src/shiny_kit/component.py
def is_unlocked(self) -> bool:
    """Reactively checks whether all declarative `requires` rules are satisfied."""
    if not self.requires or self._state is None:
        return True
    return all(r.is_met(self._state) for r in self.requires)

notify

notify(
    message: str,
    *,
    type_: Literal["message", "warning", "error", "default"] = "message",
    duration: int | None = 5,
) -> None

Displays a toast notification if running inside an active Shiny session.

Source code in src/shiny_kit/component.py
def notify(
    self,
    message: str,
    *,
    type_: Literal["message", "warning", "error", "default"] = "message",
    duration: int | None = 5,
) -> None:
    """Displays a toast notification if running inside an active Shiny session."""
    shiny_ui.notification_show(message, type=type_, duration=duration)

notify_error

notify_error(
    error: BaseException | str, *, context: str | None = None, duration: int | None = 8
) -> None

Formats and displays an error toast notification in the active Shiny session.

Source code in src/shiny_kit/component.py
def notify_error(
    self,
    error: BaseException | str,
    *,
    context: str | None = None,
    duration: int | None = 8,
) -> None:
    """Formats and displays an error toast notification in the active Shiny session."""
    detail = str(error).strip() or error.__class__.__name__
    msg = f"{context}: {detail}" if context else detail
    self.notify(msg, type_="error", duration=duration)

notify_warning

notify_warning(message: str, *, duration: int | None = 8) -> None

Displays a warning toast notification in the active Shiny session.

Source code in src/shiny_kit/component.py
def notify_warning(self, message: str, *, duration: int | None = 8) -> None:
    """Displays a warning toast notification in the active Shiny session."""
    clean = str(message).strip()
    if clean:
        self.notify(clean, type_="warning", duration=duration)

server

server(id_: str) -> Any

Binds server logic, state lifecycle, declarative BaseBinders, and subcomponents to a session.

Source code in src/shiny_kit/component.py
def server(self, id_: str) -> Any:
    """Binds server logic, state lifecycle, declarative `BaseBinder`s, and subcomponents to a session."""
    self.module_id = id_

    @module.server
    def _server(input: Inputs, output: Outputs, session: Session) -> None:
        self._input = input
        self._output = output
        self._session = session
        self._state = self.state_cls.from_session(session)

        for name in self._registered_binders:
            binder: BaseBinder = getattr(self.__class__, name)
            binder.bind(self, input, output, session)

        for sub_name in self._registered_subcomponents:
            sub_desc: subcomponent[Any] = getattr(self.__class__, sub_name)
            child_inst: Component[Any] = getattr(self, sub_name)
            child_inst.server(sub_desc.child_id)

        if self.requires:
            self._bind_resource_gate(input, output, session)

        self._register_server()

    return _server(id_)

sync_version

sync_version(key: str, version: Any) -> None

Updates the recorded version for key without reporting a change.

Source code in src/shiny_kit/component.py
def sync_version(self, key: str, version: Any) -> None:
    """Updates the recorded version for `key` without reporting a change."""
    self._version_tracker[key] = version

title classmethod

title() -> str

Derives title from the class docstring without inheriting parent docstrings.

Source code in src/shiny_kit/component.py
@classmethod
def title(cls) -> str:
    """Derives title from the class docstring without inheriting parent docstrings."""
    return DocstringParser.display_label(cls, strip_suffix="Tab", inherit_doc=False)

ui classmethod

ui(id_: str, *args: Any, **kwargs: Any) -> NavPanel

Wraps the component UI in a Shiny NavPanel.

Source code in src/shiny_kit/component.py
@classmethod
def ui(cls, id_: str, *args: Any, **kwargs: Any) -> shiny_ui._navs.NavPanel:
    """Wraps the component UI in a Shiny `NavPanel`."""
    return shiny_ui.nav_panel(cls.title(), super().ui(id_, *args, **kwargs), icon=cls.icon, value=id_)

unmet_requirements

unmet_requirements() -> list[Requirement]

Reactively returns the list of currently unmet Requirement rules.

Source code in src/shiny_kit/component.py
def unmet_requirements(self) -> list[Requirement]:
    """Reactively returns the list of currently unmet `Requirement` rules."""
    if not self.requires or self._state is None:
        return []
    return [r for r in self.requires if not r.is_met(self._state)]

val

val(id_: str, default: Any = None, *, isolate: bool = False, strip: bool = True) -> Any

Safely reads self.input[id_]() with fallback default, optional reactive isolation, and string stripping.

Source code in src/shiny_kit/component.py
def val(
    self,
    id_: str,
    default: Any = None,
    *,
    isolate: bool = False,
    strip: bool = True,
) -> Any:
    """Safely reads `self.input[id_]()` with fallback `default`, optional reactive isolation, and string stripping."""
    if self._input is None:
        return default

    def _read() -> Any:
        if self._input is None or id_ not in self._input:
            return default
        raw = self._input[id_]()
        if raw is None:
            return default
        if isinstance(raw, str):
            cleaned = raw.strip() if strip else raw
            if not cleaned and default is not None:
                return default
            return cleaned
        return raw

    if isolate:
        with reactive.isolate():
            return _read()
    return _read()

SubComponentProxy

SubComponentProxy(comp_cls: type[C], child_id: str)

Class-level proxy returned when accessing a subcomponent descriptor on a Component class.

Methods:

  • ui –

    Renders the child component's UI namespaced under self.child_id.

Source code in src/shiny_kit/component.py
def __init__(self, comp_cls: type[C], child_id: str) -> None:
    self.comp_cls = comp_cls
    self.child_id = child_id

ui

ui(*args: Any, **kwargs: Any) -> Any

Renders the child component's UI namespaced under self.child_id.

Source code in src/shiny_kit/component.py
def ui(self, *args: Any, **kwargs: Any) -> Any:
    """Renders the child component's UI namespaced under `self.child_id`."""
    return self.comp_cls.ui(self.child_id, *args, **kwargs)

Tab

Tab()

              flowchart TD
              shiny_kit.component.Tab[Tab]
              shiny_kit.component.Component[Component]

                              shiny_kit.component.Component --> shiny_kit.component.Tab
                


              click shiny_kit.component.Tab href "" "shiny_kit.component.Tab"
              click shiny_kit.component.Component href "" "shiny_kit.component.Component"
            

Specialized Component representing a navigable tab in a tabbed layout.

Extracts its display title from the first line of the subclass's docstring via title() and wraps super().ui(id_) inside a shiny.ui.nav_panel.

Attributes:

  • icon (HTML | None) –

    Optional Lucide SVG icon rendered in the tab header.

Methods:

  • has_changed –

    Tracks version under key and returns True when version differs from the previously recorded value.

  • is_unlocked –

    Reactively checks whether all declarative requires rules are satisfied.

  • notify –

    Displays a toast notification if running inside an active Shiny session.

  • notify_error –

    Formats and displays an error toast notification in the active Shiny session.

  • notify_warning –

    Displays a warning toast notification in the active Shiny session.

  • server –

    Binds server logic, state lifecycle, declarative BaseBinders, and subcomponents to a session.

  • sync_version –

    Updates the recorded version for key without reporting a change.

  • title –

    Derives title from the class docstring without inheriting parent docstrings.

  • ui –

    Wraps the component UI in a Shiny NavPanel.

  • unmet_requirements –

    Reactively returns the list of currently unmet Requirement rules.

  • val –

    Safely reads self.input[id_]() with fallback default, optional reactive isolation, and string stripping.

Source code in src/shiny_kit/component.py
def __init__(self) -> None:
    self._input: Inputs | None = None
    self._output: Outputs | None = None
    self._session: Session | None = None
    self._state: S | None = None
    self.module_id: str = ""
    self._version_tracker: dict[str, Any] = {}

has_session property

has_session: bool

Whether this component is currently bound to an active Shiny session.

has_changed

has_changed(key: str, version: Any, *, initial: Any = 0) -> bool

Tracks version under key and returns True when version differs from the previously recorded value.

Source code in src/shiny_kit/component.py
def has_changed(self, key: str, version: Any, *, initial: Any = 0) -> bool:
    """Tracks `version` under `key` and returns `True` when `version` differs from the previously recorded value."""
    prev = self._version_tracker.get(key, initial)
    if version != prev:
        self._version_tracker[key] = version
        return True
    return False

is_unlocked

is_unlocked() -> bool

Reactively checks whether all declarative requires rules are satisfied.

Source code in src/shiny_kit/component.py
def is_unlocked(self) -> bool:
    """Reactively checks whether all declarative `requires` rules are satisfied."""
    if not self.requires or self._state is None:
        return True
    return all(r.is_met(self._state) for r in self.requires)

notify

notify(
    message: str,
    *,
    type_: Literal["message", "warning", "error", "default"] = "message",
    duration: int | None = 5,
) -> None

Displays a toast notification if running inside an active Shiny session.

Source code in src/shiny_kit/component.py
def notify(
    self,
    message: str,
    *,
    type_: Literal["message", "warning", "error", "default"] = "message",
    duration: int | None = 5,
) -> None:
    """Displays a toast notification if running inside an active Shiny session."""
    shiny_ui.notification_show(message, type=type_, duration=duration)

notify_error

notify_error(
    error: BaseException | str, *, context: str | None = None, duration: int | None = 8
) -> None

Formats and displays an error toast notification in the active Shiny session.

Source code in src/shiny_kit/component.py
def notify_error(
    self,
    error: BaseException | str,
    *,
    context: str | None = None,
    duration: int | None = 8,
) -> None:
    """Formats and displays an error toast notification in the active Shiny session."""
    detail = str(error).strip() or error.__class__.__name__
    msg = f"{context}: {detail}" if context else detail
    self.notify(msg, type_="error", duration=duration)

notify_warning

notify_warning(message: str, *, duration: int | None = 8) -> None

Displays a warning toast notification in the active Shiny session.

Source code in src/shiny_kit/component.py
def notify_warning(self, message: str, *, duration: int | None = 8) -> None:
    """Displays a warning toast notification in the active Shiny session."""
    clean = str(message).strip()
    if clean:
        self.notify(clean, type_="warning", duration=duration)

server

server(id_: str) -> Any

Binds server logic, state lifecycle, declarative BaseBinders, and subcomponents to a session.

Source code in src/shiny_kit/component.py
def server(self, id_: str) -> Any:
    """Binds server logic, state lifecycle, declarative `BaseBinder`s, and subcomponents to a session."""
    self.module_id = id_

    @module.server
    def _server(input: Inputs, output: Outputs, session: Session) -> None:
        self._input = input
        self._output = output
        self._session = session
        self._state = self.state_cls.from_session(session)

        for name in self._registered_binders:
            binder: BaseBinder = getattr(self.__class__, name)
            binder.bind(self, input, output, session)

        for sub_name in self._registered_subcomponents:
            sub_desc: subcomponent[Any] = getattr(self.__class__, sub_name)
            child_inst: Component[Any] = getattr(self, sub_name)
            child_inst.server(sub_desc.child_id)

        if self.requires:
            self._bind_resource_gate(input, output, session)

        self._register_server()

    return _server(id_)

sync_version

sync_version(key: str, version: Any) -> None

Updates the recorded version for key without reporting a change.

Source code in src/shiny_kit/component.py
def sync_version(self, key: str, version: Any) -> None:
    """Updates the recorded version for `key` without reporting a change."""
    self._version_tracker[key] = version

title classmethod

title() -> str

Derives title from the class docstring without inheriting parent docstrings.

Source code in src/shiny_kit/component.py
@classmethod
def title(cls) -> str:
    """Derives title from the class docstring without inheriting parent docstrings."""
    return DocstringParser.display_label(cls, strip_suffix="Tab", inherit_doc=False)

ui classmethod

ui(id_: str, *args: Any, **kwargs: Any) -> NavPanel

Wraps the component UI in a Shiny NavPanel.

Source code in src/shiny_kit/component.py
@classmethod
def ui(cls, id_: str, *args: Any, **kwargs: Any) -> shiny_ui._navs.NavPanel:
    """Wraps the component UI in a Shiny `NavPanel`."""
    return shiny_ui.nav_panel(cls.title(), super().ui(id_, *args, **kwargs), icon=cls.icon, value=id_)

unmet_requirements

unmet_requirements() -> list[Requirement]

Reactively returns the list of currently unmet Requirement rules.

Source code in src/shiny_kit/component.py
def unmet_requirements(self) -> list[Requirement]:
    """Reactively returns the list of currently unmet `Requirement` rules."""
    if not self.requires or self._state is None:
        return []
    return [r for r in self.requires if not r.is_met(self._state)]

val

val(id_: str, default: Any = None, *, isolate: bool = False, strip: bool = True) -> Any

Safely reads self.input[id_]() with fallback default, optional reactive isolation, and string stripping.

Source code in src/shiny_kit/component.py
def val(
    self,
    id_: str,
    default: Any = None,
    *,
    isolate: bool = False,
    strip: bool = True,
) -> Any:
    """Safely reads `self.input[id_]()` with fallback `default`, optional reactive isolation, and string stripping."""
    if self._input is None:
        return default

    def _read() -> Any:
        if self._input is None or id_ not in self._input:
            return default
        raw = self._input[id_]()
        if raw is None:
            return default
        if isinstance(raw, str):
            cleaned = raw.strip() if strip else raw
            if not cleaned and default is not None:
                return default
            return cleaned
        return raw

    if isolate:
        with reactive.isolate():
            return _read()
    return _read()

WorkflowStepSpec dataclass

WorkflowStepSpec(
    step_label: str,
    description: str,
    icon: str = "sparkles",
    badge: str = "",
    metrics: tuple[str, ...] = (),
    target_tab: str | None = None,
)

Static specification for a pillar/workflow card below the Hero section.

Attributes:

  • step_label (str) –

    Card heading for the pipeline step.

  • description (str) –

    Body copy describing the step's capabilities.

  • icon (str) –

    Lucide icon name rendered in the card header. Defaults to "sparkles".

  • badge (str) –

    Optional uppercase pill badge text rendered at the top-right of the card.

  • metrics (tuple[str, ...]) –

    Optional metric pills displayed inside the card footer.

  • target_tab (str | None) –

    Optional target navbar tab ID associated with the step.

subcomponent

subcomponent(comp_cls: type[C], *, id_: str | None = None)

Descriptor declaring a child Component auto-mounted and auto-bound by its parent Component.

Source code in src/shiny_kit/component.py
def __init__(self, comp_cls: type[C], *, id_: str | None = None) -> None:
    self.comp_cls = comp_cls
    self.custom_id = id_
    self.attr_name: str = ""

Decorators (shiny_kit.decorators)

Reactive descriptors and class-based server binding decorators (BaseBinder, bind_*).

Classes:

  • BaseBinder –

    Abstract base descriptor and decorator for binding Component methods to a PyShiny server session.

  • BoundTask –

    Per-instance handle to a PyShiny ExtendedTask bound via @bind_task.

  • bind_datagrid –

    Descriptor & decorator binding a method returning a tabular object to shiny.render.DataGrid.

  • bind_download –

    Descriptor & decorator binding a method yielding or returning bytes to @render.download_button.

  • bind_effect –

    Descriptor & decorator binding a Component method (sync or async) to a guarded reactive effect.

  • bind_output –

    Descriptor & decorator binding a Component method (sync or async) to a Shiny renderer.

  • bind_task –

    Descriptor & decorator wrapping a method into a non-blocking PyShiny ExtendedTask.

  • bind_widget –

    Descriptor & decorator binding a method returning an interactive widget/chart to shinywidgets.

  • reactive_calc –

    Descriptor wrapping a method into a per-instance, session-isolated reactive.calc.

  • reactive_prop –

    Descriptor wrapping a PyShiny reactive.value with defaults and static type inference.

BaseBinder

BaseBinder(fn: Callable[..., Any] | None = None)

              flowchart TD
              shiny_kit.decorators.BaseBinder[BaseBinder]

              

              click shiny_kit.decorators.BaseBinder href "" "shiny_kit.decorators.BaseBinder"
            

Abstract base descriptor and decorator for binding Component methods to a PyShiny server session.

All bind_* decorators (bind_output, bind_datagrid, bind_download, bind_effect, bind_task, and custom domain binders) inherit from BaseBinder and implement bind(...), allowing Component.server() to discover and bind them uniformly without hardcoded per-type loops.

Attributes:

  • fn (Callable[..., Any] | None) –

    Underlying component method decorated by this binder.

  • name (str) –

    Attribute name on the owning Component class.

Methods:

  • bind –

    Binds this descriptor's method onto the active PyShiny module server session.

Source code in src/shiny_kit/decorators.py
def __init__(self, fn: Callable[..., Any] | None = None) -> None:
    self.fn: Callable[..., Any] | None = fn
    self.name: str = getattr(fn, "__name__", "") if fn is not None else ""

bind abstractmethod

bind(
    component: Component[Any], input: Inputs, output: Outputs, session: Session
) -> None

Binds this descriptor's method onto the active PyShiny module server session.

Source code in src/shiny_kit/decorators.py
@abstractmethod
def bind(
    self,
    component: "Component[Any]",
    input: Inputs,
    output: Outputs,
    session: Session,
) -> None:
    """Binds this descriptor's method onto the active PyShiny module server session."""
    raise NotImplementedError

BoundTask

BoundTask(task: ExtendedTask[Any, R])

Per-instance handle to a PyShiny ExtendedTask bound via @bind_task.

Attributes:

  • task (ExtendedTask[Any, R]) –

    Underlying PyShiny ExtendedTask.

  • invocation_count (int) –

    Monotonically increasing count of task invocations.

  • warnings (tuple[str, ...]) –

    User-facing warnings captured during the latest run.

  • last_error (BaseException | None) –

    Exception raised by the latest failed run, if any.

Methods:

  • cancel –

    Cancels an in-flight background execution.

  • invoke –

    Invokes the background task with the given arguments.

  • result –

    Reactively retrieves the completed task result (raises silent cancel while running).

  • status –

    Reactively returns the current execution status of the task.

Source code in src/shiny_kit/decorators.py
def __init__(self, task: reactive.ExtendedTask[Any, R]) -> None:
    self.task = task
    self.invocation_count: int = 0
    self.warnings: tuple[str, ...] = ()
    self.last_error: BaseException | None = None

cancel

cancel() -> None

Cancels an in-flight background execution.

Source code in src/shiny_kit/decorators.py
def cancel(self) -> None:
    """Cancels an in-flight background execution."""
    self.task.cancel()

invoke

invoke(*args: Any, **kwargs: Any) -> None

Invokes the background task with the given arguments.

Source code in src/shiny_kit/decorators.py
def invoke(self, *args: Any, **kwargs: Any) -> None:
    """Invokes the background task with the given arguments."""
    self.invocation_count += 1
    self.task.invoke(*args, **kwargs)

result

result() -> R

Reactively retrieves the completed task result (raises silent cancel while running).

Source code in src/shiny_kit/decorators.py
def result(self) -> R:
    """Reactively retrieves the completed task result (raises silent cancel while running)."""
    return self.task.result()

status

status() -> Literal['initial', 'running', 'success', 'error', 'cancelled']

Reactively returns the current execution status of the task.

Source code in src/shiny_kit/decorators.py
def status(self) -> Literal["initial", "running", "success", "error", "cancelled"]:
    """Reactively returns the current execution status of the task."""
    return self.task.status()

bind_datagrid

bind_datagrid(
    id_: str | None = None,
    *,
    id_attr: str | None = None,
    selection_mode: Literal["none", "row", "rows"] = "none",
    filters: bool = True,
    height: str | int | None = None,
    height_attr: str | None = None,
    width: str | int = "100%",
)

              flowchart TD
              shiny_kit.decorators.bind_datagrid[bind_datagrid]
              shiny_kit.decorators.bind_output[bind_output]
              shiny_kit.decorators.BaseBinder[BaseBinder]

                              shiny_kit.decorators.bind_output --> shiny_kit.decorators.bind_datagrid
                                shiny_kit.decorators.BaseBinder --> shiny_kit.decorators.bind_output
                



              click shiny_kit.decorators.bind_datagrid href "" "shiny_kit.decorators.bind_datagrid"
              click shiny_kit.decorators.bind_output href "" "shiny_kit.decorators.bind_output"
              click shiny_kit.decorators.BaseBinder href "" "shiny_kit.decorators.BaseBinder"
            

Descriptor & decorator binding a method returning a tabular object to shiny.render.DataGrid.

Attributes:

  • selection_mode (Literal['none', 'row', 'rows']) –

    Row selection mode.

  • filters (bool) –

    Whether to enable per-column filter headers in the DataGrid.

  • height (str | int | None) –

    Optional CSS height string or pixel integer.

  • height_attr (str | None) –

    Optional attribute name on the component resolving the DataGrid height.

  • width (str | int) –

    CSS width string or pixel integer. Defaults to "100%".

Methods:

  • bind –

    Wraps the bound method with requires gating and registers it on output.

  • client_dimensions –

    Reads (width, height) in pixels from session.clientdata for the resolved output ID.

  • resolve_target_id –

    Resolves the Shiny output ID from component.<id_attr>, self.id_, or self.name.

  • transform –

    Wraps non-null tabular values into shiny.render.DataGrid.

Source code in src/shiny_kit/decorators.py
def __init__(
    self,
    id_: str | None = None,
    *,
    id_attr: str | None = None,
    selection_mode: Literal["none", "row", "rows"] = "none",
    filters: bool = True,
    height: str | int | None = None,
    height_attr: str | None = None,
    width: str | int = "100%",
) -> None:
    super().__init__(render.data_frame, id_=id_, id_attr=id_attr)
    self.selection_mode: Literal["none", "row", "rows"] = selection_mode
    self.filters = filters
    self.height = height
    self.height_attr = height_attr
    self.width = width

target_id property

target_id: str

Resolved Shiny output ID.

bind

bind(
    component: Component[Any], input: Inputs, output: Outputs, session: Session
) -> None

Wraps the bound method with requires gating and registers it on output.

Source code in src/shiny_kit/decorators.py
def bind(
    self,
    component: "Component[Any]",
    input: Inputs,
    output: Outputs,
    session: Session,
) -> None:
    """Wraps the bound method with `requires` gating and registers it on `output`."""
    method = getattr(component, self.name)
    target_id = self.resolve_target_id(component)
    guarded = self._wrap_gated_renderer(component, method, target_id)
    output(id=target_id)(self.renderer_factory(guarded))

client_dimensions

client_dimensions(component: Component[Any]) -> tuple[float | None, float | None]

Reads (width, height) in pixels from session.clientdata for the resolved output ID.

Source code in src/shiny_kit/decorators.py
def client_dimensions(self, component: "Component[Any]") -> tuple[float | None, float | None]:
    """Reads `(width, height)` in pixels from `session.clientdata` for the resolved output ID."""
    w: float | None = None
    h: float | None = None
    sess = getattr(component, "_session", None)
    target_id = self.resolve_target_id(component)
    if sess is not None and hasattr(sess, "clientdata"):
        try:
            w = sess.clientdata.output_width(target_id)
        except Exception:
            w = None
        try:
            h = sess.clientdata.output_height(target_id)
        except Exception:
            h = None
    return w, h

resolve_target_id

resolve_target_id(component: Component[Any] | None = None) -> str

Resolves the Shiny output ID from component.<id_attr>, self.id_, or self.name.

Source code in src/shiny_kit/decorators.py
def resolve_target_id(self, component: "Component[Any] | None" = None) -> str:
    """Resolves the Shiny output ID from `component.<id_attr>`, `self.id_`, or `self.name`."""
    if component is not None and self.id_attr:
        attr_val = getattr(component, self.id_attr, None)
        if isinstance(attr_val, str) and attr_val:
            return attr_val
    return self.id_ or self.name

transform

transform(component: Component[Any], value: Any) -> Any

Wraps non-null tabular values into shiny.render.DataGrid.

Source code in src/shiny_kit/decorators.py
def transform(self, component: "Component[Any]", value: Any) -> Any:
    """Wraps non-null tabular values into `shiny.render.DataGrid`."""
    if value is not None and not isinstance(value, (render.DataGrid, render.DataTable)):
        resolved_height = getattr(component, self.height_attr, self.height) if self.height_attr else self.height
        return render.DataGrid(
            value,
            selection_mode=self.selection_mode,
            filters=self.filters,
            height=resolved_height,
            width=self.width,
        )
    return value

bind_download

bind_download(
    id_: str | None = None, *, filename: str | Callable[[Any], str] = "download.bin"
)

              flowchart TD
              shiny_kit.decorators.bind_download[bind_download]
              shiny_kit.decorators.bind_output[bind_output]
              shiny_kit.decorators.BaseBinder[BaseBinder]

                              shiny_kit.decorators.bind_output --> shiny_kit.decorators.bind_download
                                shiny_kit.decorators.BaseBinder --> shiny_kit.decorators.bind_output
                



              click shiny_kit.decorators.bind_download href "" "shiny_kit.decorators.bind_download"
              click shiny_kit.decorators.bind_output href "" "shiny_kit.decorators.bind_output"
              click shiny_kit.decorators.BaseBinder href "" "shiny_kit.decorators.BaseBinder"
            

Descriptor & decorator binding a method yielding or returning bytes to @render.download_button.

Attributes:

  • id_ (str | None) –

    Optional explicit download button output ID.

  • filename (str | Callable[[Any], str]) –

    Static filename string or callable (self) -> str.

Methods:

  • bind –

    Wraps the download generator with requires gating and registers it on output.

  • client_dimensions –

    Reads (width, height) in pixels from session.clientdata for the resolved output ID.

  • resolve_target_id –

    Resolves the Shiny output ID from component.<id_attr>, self.id_, or self.name.

  • transform –

    Hook for subclasses to transform the method's return value before passing it to the renderer.

Source code in src/shiny_kit/decorators.py
def __init__(
    self,
    id_: str | None = None,
    *,
    filename: str | Callable[[Any], str] = "download.bin",
) -> None:
    super().__init__(render.download_button, id_=id_)
    self.filename = filename

target_id property

target_id: str

Resolved Shiny output ID.

bind

bind(
    component: Component[Any], input: Inputs, output: Outputs, session: Session
) -> None

Wraps the download generator with requires gating and registers it on output.

Source code in src/shiny_kit/decorators.py
def bind(
    self,
    component: "Component[Any]",
    input: Inputs,
    output: Outputs,
    session: Session,
) -> None:
    """Wraps the download generator with `requires` gating and registers it on `output`."""
    method = getattr(component, self.name)
    dl_id = self.target_id
    raw_fn = self.filename
    if callable(raw_fn):

        def _resolve_filename() -> str:
            return str(raw_fn(component))

        fn_arg: str | Callable[[], str] | None = _resolve_filename
    else:
        fn_arg = raw_fn
    guarded = self._wrap_gated_renderer(component, method, dl_id)
    output(id=dl_id)(render.download_button(filename=fn_arg)(guarded))

client_dimensions

client_dimensions(component: Component[Any]) -> tuple[float | None, float | None]

Reads (width, height) in pixels from session.clientdata for the resolved output ID.

Source code in src/shiny_kit/decorators.py
def client_dimensions(self, component: "Component[Any]") -> tuple[float | None, float | None]:
    """Reads `(width, height)` in pixels from `session.clientdata` for the resolved output ID."""
    w: float | None = None
    h: float | None = None
    sess = getattr(component, "_session", None)
    target_id = self.resolve_target_id(component)
    if sess is not None and hasattr(sess, "clientdata"):
        try:
            w = sess.clientdata.output_width(target_id)
        except Exception:
            w = None
        try:
            h = sess.clientdata.output_height(target_id)
        except Exception:
            h = None
    return w, h

resolve_target_id

resolve_target_id(component: Component[Any] | None = None) -> str

Resolves the Shiny output ID from component.<id_attr>, self.id_, or self.name.

Source code in src/shiny_kit/decorators.py
def resolve_target_id(self, component: "Component[Any] | None" = None) -> str:
    """Resolves the Shiny output ID from `component.<id_attr>`, `self.id_`, or `self.name`."""
    if component is not None and self.id_attr:
        attr_val = getattr(component, self.id_attr, None)
        if isinstance(attr_val, str) and attr_val:
            return attr_val
    return self.id_ or self.name

transform

transform(component: Component[Any], value: Any) -> Any

Hook for subclasses to transform the method's return value before passing it to the renderer.

Source code in src/shiny_kit/decorators.py
def transform(self, component: "Component[Any]", value: Any) -> Any:
    """Hook for subclasses to transform the method's return value before passing it to the renderer."""
    return value

bind_effect

bind_effect(*input_ids: str, notify_error: bool = True, notify_warning: bool = True)

              flowchart TD
              shiny_kit.decorators.bind_effect[bind_effect]
              shiny_kit.decorators.BaseBinder[BaseBinder]

                              shiny_kit.decorators.BaseBinder --> shiny_kit.decorators.bind_effect
                


              click shiny_kit.decorators.bind_effect href "" "shiny_kit.decorators.bind_effect"
              click shiny_kit.decorators.BaseBinder href "" "shiny_kit.decorators.BaseBinder"
            

Descriptor & decorator binding a Component method (sync or async) to a guarded reactive effect.

Attributes:

  • event_ids (tuple[str, ...]) –

    Input IDs triggering the effect via reactive.event.

  • notify_error (bool) –

    Whether uncaught non-silent exceptions are displayed as error toasts.

  • notify_warning (bool) –

    Whether captured user-facing warnings are displayed as warning toasts.

Methods:

  • bind –

    Wraps the effect method with WarningCatcher and error handling and registers reactive.effect.

Source code in src/shiny_kit/decorators.py
def __init__(
    self,
    *input_ids: str,
    notify_error: bool = True,
    notify_warning: bool = True,
) -> None:
    super().__init__()
    self.event_ids: tuple[str, ...] = input_ids
    self.notify_error = notify_error
    self.notify_warning = notify_warning

bind

bind(
    component: Component[Any], input: Inputs, output: Outputs, session: Session
) -> None

Wraps the effect method with WarningCatcher and error handling and registers reactive.effect.

Source code in src/shiny_kit/decorators.py
def bind(
    self,
    component: "Component[Any]",
    input: Inputs,
    output: Outputs,
    session: Session,
) -> None:
    """Wraps the effect method with `WarningCatcher` and error handling and registers `reactive.effect`."""
    method = getattr(component, self.name)
    guarded_effect = self._wrap_guarded_effect(component, method)
    if self.event_ids:
        triggers = [input[eid] for eid in self.event_ids]
        reactive.effect(priority=10)(reactive.event(*triggers)(guarded_effect))
    else:
        reactive.effect(priority=10)(guarded_effect)

bind_output

bind_output(
    renderer_factory: Any, id_: str | None = None, *, id_attr: str | None = None
)

              flowchart TD
              shiny_kit.decorators.bind_output[bind_output]
              shiny_kit.decorators.BaseBinder[BaseBinder]

                              shiny_kit.decorators.BaseBinder --> shiny_kit.decorators.bind_output
                


              click shiny_kit.decorators.bind_output href "" "shiny_kit.decorators.bind_output"
              click shiny_kit.decorators.BaseBinder href "" "shiny_kit.decorators.BaseBinder"
            

Descriptor & decorator binding a Component method (sync or async) to a Shiny renderer.

Subclasses (such as bind_datagrid or bind_altair) can set a default renderer_factory and override transform(self, component, value) to post-process the method's return value without writing manual sync/async wrapper boilerplate.

Attributes:

  • renderer_factory (Any) –

    Shiny renderer decorator/factory (e.g. render.ui, render.text).

  • id_ (str | None) –

    Optional explicit output ID. Defaults to the decorated method's name.

Methods:

  • bind –

    Wraps the bound method with requires gating and registers it on output.

  • client_dimensions –

    Reads (width, height) in pixels from session.clientdata for the resolved output ID.

  • resolve_target_id –

    Resolves the Shiny output ID from component.<id_attr>, self.id_, or self.name.

  • transform –

    Hook for subclasses to transform the method's return value before passing it to the renderer.

Source code in src/shiny_kit/decorators.py
def __init__(
    self,
    renderer_factory: Any,
    id_: str | None = None,
    *,
    id_attr: str | None = None,
) -> None:
    super().__init__()
    self.renderer_factory = renderer_factory
    self.id_ = id_
    self.id_attr = id_attr

target_id property

target_id: str

Resolved Shiny output ID.

bind

bind(
    component: Component[Any], input: Inputs, output: Outputs, session: Session
) -> None

Wraps the bound method with requires gating and registers it on output.

Source code in src/shiny_kit/decorators.py
def bind(
    self,
    component: "Component[Any]",
    input: Inputs,
    output: Outputs,
    session: Session,
) -> None:
    """Wraps the bound method with `requires` gating and registers it on `output`."""
    method = getattr(component, self.name)
    target_id = self.resolve_target_id(component)
    guarded = self._wrap_gated_renderer(component, method, target_id)
    output(id=target_id)(self.renderer_factory(guarded))

client_dimensions

client_dimensions(component: Component[Any]) -> tuple[float | None, float | None]

Reads (width, height) in pixels from session.clientdata for the resolved output ID.

Source code in src/shiny_kit/decorators.py
def client_dimensions(self, component: "Component[Any]") -> tuple[float | None, float | None]:
    """Reads `(width, height)` in pixels from `session.clientdata` for the resolved output ID."""
    w: float | None = None
    h: float | None = None
    sess = getattr(component, "_session", None)
    target_id = self.resolve_target_id(component)
    if sess is not None and hasattr(sess, "clientdata"):
        try:
            w = sess.clientdata.output_width(target_id)
        except Exception:
            w = None
        try:
            h = sess.clientdata.output_height(target_id)
        except Exception:
            h = None
    return w, h

resolve_target_id

resolve_target_id(component: Component[Any] | None = None) -> str

Resolves the Shiny output ID from component.<id_attr>, self.id_, or self.name.

Source code in src/shiny_kit/decorators.py
def resolve_target_id(self, component: "Component[Any] | None" = None) -> str:
    """Resolves the Shiny output ID from `component.<id_attr>`, `self.id_`, or `self.name`."""
    if component is not None and self.id_attr:
        attr_val = getattr(component, self.id_attr, None)
        if isinstance(attr_val, str) and attr_val:
            return attr_val
    return self.id_ or self.name

transform

transform(component: Component[Any], value: Any) -> Any

Hook for subclasses to transform the method's return value before passing it to the renderer.

Source code in src/shiny_kit/decorators.py
def transform(self, component: "Component[Any]", value: Any) -> Any:
    """Hook for subclasses to transform the method's return value before passing it to the renderer."""
    return value

bind_task

bind_task(
    button_id: str | None = None,
    *,
    register_to: str | None = None,
    on_success: Callable[[Any, Any], str | None] | str | None = None,
    notify_success: str | Callable[[Any], str] | bool = True,
    notify_warning: bool = True,
    notify_error: bool = True,
    progress_message: str | None = None,
    **register_kwargs: Any,
)

              flowchart TD
              shiny_kit.decorators.bind_task[bind_task]
              shiny_kit.decorators.BaseBinder[BaseBinder]

                              shiny_kit.decorators.BaseBinder --> shiny_kit.decorators.bind_task
                


              click shiny_kit.decorators.bind_task href "" "shiny_kit.decorators.bind_task"
              click shiny_kit.decorators.BaseBinder href "" "shiny_kit.decorators.BaseBinder"
            

Descriptor & decorator wrapping a method into a non-blocking PyShiny ExtendedTask.

Synchronous methods are automatically offloaded to a background worker thread via asyncio.to_thread so heavy workloads never block the main asyncio event loop. Supports declarative register_to artifact registration, on_success callbacks, automatic LazyShinyProgress reporting, and toast notifications upon completion.

Methods:

  • bind –

    Binds action-button triggers, busy-state synchronization, and completion handlers for the task.

Attributes:

  • display_label (str) –

    Human-readable label for progress bars and error notifications.

Source code in src/shiny_kit/decorators.py
def __init__(
    self,
    button_id: str | None = None,
    *,
    register_to: str | None = None,
    on_success: Callable[[Any, Any], str | None] | str | None = None,
    notify_success: str | Callable[[Any], str] | bool = True,
    notify_warning: bool = True,
    notify_error: bool = True,
    progress_message: str | None = None,
    **register_kwargs: Any,
) -> None:
    super().__init__()
    self.button_id = button_id
    self.register_to = register_to
    self.register_kwargs = register_kwargs
    self.on_success = on_success
    self.notify_success = notify_success
    self.notify_warning = notify_warning
    self.notify_error = notify_error
    self.progress_message = progress_message

display_label property

display_label: str

Human-readable label for progress bars and error notifications.

bind

bind(
    component: Component[Any], input: Inputs, output: Outputs, session: Session
) -> None

Binds action-button triggers, busy-state synchronization, and completion handlers for the task.

Source code in src/shiny_kit/decorators.py
def bind(
    self,
    component: "Component[Any]",
    input: Inputs,
    output: Outputs,
    session: Session,
) -> None:
    """Binds action-button triggers, busy-state synchronization, and completion handlers for the task."""
    bound_task: BoundTask[Any] = getattr(component, self.name)
    if self.button_id:
        btn_id = self.button_id
        was_running = False

        @reactive.effect(priority=10)
        @reactive.event(input[btn_id])
        def _on_task_btn() -> None:
            bound_task.invoke()

        @reactive.effect(priority=10)
        def _sync_btn_busy_state() -> None:
            nonlocal was_running
            is_running = bound_task.status() == "running"
            if is_running or was_running:
                was_running = is_running
                shiny_ui.update_action_button(btn_id, disabled=is_running, session=session)

    if (
        self.register_to is not None
        or self.on_success is not None
        or bool(self.notify_success)
        or self.notify_warning
        or self.notify_error
    ):
        last_handled_invocation = -1

        @reactive.effect(priority=10)
        def _on_task_done() -> None:
            nonlocal last_handled_invocation
            status = bound_task.status()
            if status not in ("success", "error") or bound_task.invocation_count == last_handled_invocation:
                return
            last_handled_invocation = bound_task.invocation_count
            if status == "error":
                if self.notify_error:
                    try:
                        bound_task.result()
                    except Exception as exc:
                        with reactive.isolate():
                            component.notify_error(exc, context=self.display_label)
                return
            res = bound_task.result()
            with reactive.isolate():
                self._handle_completion(component, res, warnings=bound_task.warnings)

bind_widget

bind_widget(
    id_: str | None = None,
    *,
    id_attr: str | None = None,
    renderer_factory: Any = None,
    autosize: bool = True,
    resizer: Callable[[Any, float | None, float | None], Any] | None = None,
)

              flowchart TD
              shiny_kit.decorators.bind_widget[bind_widget]
              shiny_kit.decorators.bind_output[bind_output]
              shiny_kit.decorators.BaseBinder[BaseBinder]

                              shiny_kit.decorators.bind_output --> shiny_kit.decorators.bind_widget
                                shiny_kit.decorators.BaseBinder --> shiny_kit.decorators.bind_output
                



              click shiny_kit.decorators.bind_widget href "" "shiny_kit.decorators.bind_widget"
              click shiny_kit.decorators.bind_output href "" "shiny_kit.decorators.bind_output"
              click shiny_kit.decorators.BaseBinder href "" "shiny_kit.decorators.BaseBinder"
            

Descriptor & decorator binding a method returning an interactive widget/chart to shinywidgets.

Lazily imports shinywidgets (defaulting to shinywidgets.render_altair or render_widget) and optionally adapts the returned widget to its client container dimensions via a pluggable resizer callback (widget, width, height) -> widget (or component.resizer).

Attributes:

  • autosize (bool) –

    Whether to resize the returned widget using client_dimensions(component).

  • resizer (Callable[[Any, float | None, float | None], Any] | None) –

    Optional resizer callback.

Methods:

  • client_dimensions –

    Reads (width, height) in pixels from session.clientdata for the resolved output ID.

  • resolve_target_id –

    Resolves the Shiny output ID from component.<id_attr>, self.id_, or self.name.

  • transform –

    Resizes the returned widget to fit the active client container dimensions when autosize=True.

Source code in src/shiny_kit/decorators.py
def __init__(
    self,
    id_: str | None = None,
    *,
    id_attr: str | None = None,
    renderer_factory: Any = None,
    autosize: bool = True,
    resizer: Callable[[Any, float | None, float | None], Any] | None = None,
) -> None:
    resolved_factory = renderer_factory
    if resolved_factory is None:
        try:
            resolved_factory = self._default_renderer_factory()
        except Exception:
            resolved_factory = None
    super().__init__(resolved_factory, id_=id_, id_attr=id_attr)
    self.autosize = autosize
    self.resizer = resizer

target_id property

target_id: str

Resolved Shiny output ID.

client_dimensions

client_dimensions(component: Component[Any]) -> tuple[float | None, float | None]

Reads (width, height) in pixels from session.clientdata for the resolved output ID.

Source code in src/shiny_kit/decorators.py
def client_dimensions(self, component: "Component[Any]") -> tuple[float | None, float | None]:
    """Reads `(width, height)` in pixels from `session.clientdata` for the resolved output ID."""
    w: float | None = None
    h: float | None = None
    sess = getattr(component, "_session", None)
    target_id = self.resolve_target_id(component)
    if sess is not None and hasattr(sess, "clientdata"):
        try:
            w = sess.clientdata.output_width(target_id)
        except Exception:
            w = None
        try:
            h = sess.clientdata.output_height(target_id)
        except Exception:
            h = None
    return w, h

resolve_target_id

resolve_target_id(component: Component[Any] | None = None) -> str

Resolves the Shiny output ID from component.<id_attr>, self.id_, or self.name.

Source code in src/shiny_kit/decorators.py
def resolve_target_id(self, component: "Component[Any] | None" = None) -> str:
    """Resolves the Shiny output ID from `component.<id_attr>`, `self.id_`, or `self.name`."""
    if component is not None and self.id_attr:
        attr_val = getattr(component, self.id_attr, None)
        if isinstance(attr_val, str) and attr_val:
            return attr_val
    return self.id_ or self.name

transform

transform(component: Component[Any], value: Any) -> Any

Resizes the returned widget to fit the active client container dimensions when autosize=True.

Source code in src/shiny_kit/decorators.py
def transform(self, component: "Component[Any]", value: Any) -> Any:
    """Resizes the returned widget to fit the active client container dimensions when `autosize=True`."""
    if not self.autosize or value is None:
        return value
    w, h = self.client_dimensions(component)
    resizer_fn = self.resizer
    if resizer_fn is None:
        comp_resizer = getattr(type(component), "resizer", None) or getattr(component, "resizer", None)
        if callable(comp_resizer):
            resizer_fn = comp_resizer
    if resizer_fn is not None:
        return resizer_fn(value, w, h)
    if hasattr(value, "fit_to_container") and callable(value.fit_to_container):
        return value.fit_to_container(width=w, height=h)
    return value

reactive_calc

reactive_calc(fn: Callable[..., Any] | None = None, *, events: tuple[str, ...] = ())

Descriptor wrapping a method into a per-instance, session-isolated reactive.calc.

Supports synchronous and asynchronous functions as well as optional input event filtering.

Attributes:

  • fn (Callable[..., Any] | None) –

    Underlying method wrapped by the descriptor.

  • events (tuple[str, ...]) –

    Optional input IDs to trigger reactive recalculation via reactive.event.

  • name (str) –

    Attribute name on the owning class.

Source code in src/shiny_kit/decorators.py
def __init__(
    self,
    fn: Callable[..., Any] | None = None,
    *,
    events: tuple[str, ...] = (),
) -> None:
    self.fn = fn
    self.events = events
    self.name: str = fn.__name__ if fn else ""  # ty: ignore[unresolved-attribute]

reactive_prop

reactive_prop(default: T | None = None)

Descriptor wrapping a PyShiny reactive.value with defaults and static type inference.

Attributes:

  • default (T | None) –

    Default value stored in the per-instance reactive.value.

  • name (str) –

    Internal instance dictionary key storing the reactive.Value.

Examples:

from shiny_kit.decorators import reactive_prop
from shiny_kit.state import BaseSessionState

class AppState(BaseSessionState):
    selected_year: reactive_prop[int] = reactive_prop(2024)
Source code in src/shiny_kit/decorators.py
def __init__(self, default: T | None = None) -> None:
    self.default = default
    self.name: str = ""

Session State & Artifacts (shiny_kit.state)

Session state (BaseSessionState) and reactive artifact registries (ArtifactRegistry, artifact_registry, Requirement).

Classes:

  • ArtifactRegistry –

    Reactive, session-isolated store for named pipeline artifacts.

  • BaseSessionState –

    Abstract session state base class holding reactive artifact_registry stores, theme state, and cache.

  • Requirement –

    Declarative prerequisite rule gating a Tab or Component on BaseSessionState resources.

  • artifact_registry –

    Class-level descriptor declaring an ArtifactRegistry[T] on a BaseSessionState subclass.

ArtifactRegistry

ArtifactRegistry(
    label: str,
    *,
    icon: str | None = None,
    badge_class: str = "bg-primary",
    show_in_navbar: bool = True,
    namer: Callable[[T], str] | None = None,
    toast: Callable[[T], str] | str | None = None,
    on_register: Callable[..., None] | str | None = None,
    on_select: Callable[..., None] | str | None = None,
    on_remove: Callable[..., None] | str | None = None,
    owner_state: BaseSessionState | None = None,
)

Reactive, session-isolated store for named pipeline artifacts.

Tracks a dictionary of named artifacts alongside an active selection key and a version counter, triggering PyShiny reactive invalidation automatically whenever artifacts are added, removed, or re-selected.

Methods:

  • clear –

    Removes all artifacts from the registry.

  • format_toast –

    Formats a completion toast message for item using the registry's toast specification.

  • get –

    Reactively looks up a specific artifact by name.

  • register –

    Registers or updates a named artifact and triggers reactive dependents and on_register hooks.

  • remove –

    Removes a named artifact from the registry and updates the active selection if needed.

  • resolve_name –

    Resolves the registry key for item using explicit_name, self._namer, or common attributes.

  • select –

    Sets the active artifact by name and invokes on_select hooks.

Attributes:

  • active (T | None) –

    Reactively returns the currently active artifact instance (or None if empty).

  • active_key (str | None) –

    Reactively returns the name of the currently active artifact.

  • count (int) –

    Reactively returns the number of registered artifacts.

  • items (dict[str, T]) –

    Reactively returns the mapping of registered artifact names to instances.

  • names (list[str]) –

    Reactively returns the ordered list of registered artifact names.

  • version (int) –

    Reactively returns the mutation counter for this registry.

Source code in src/shiny_kit/state.py
def __init__(
    self,
    label: str,
    *,
    icon: str | None = None,
    badge_class: str = "bg-primary",
    show_in_navbar: bool = True,
    namer: Callable[[T], str] | None = None,
    toast: Callable[[T], str] | str | None = None,
    on_register: Callable[..., None] | str | None = None,
    on_select: Callable[..., None] | str | None = None,
    on_remove: Callable[..., None] | str | None = None,
    owner_state: "BaseSessionState | None" = None,
) -> None:
    self.label = label
    self.icon = icon
    self.badge_class = badge_class
    self.show_in_navbar = show_in_navbar
    self._namer = namer
    self._toast = toast
    self._on_register = on_register
    self._on_select = on_select
    self._on_remove = on_remove
    self._owner_state = owner_state
    self._items: reactive.Value[dict[str, T]] = reactive.value({})
    self._active_key: reactive.Value[str | None] = reactive.value(None)
    self._version: reactive.Value[int] = reactive.value(0)

active property

active: T | None

Reactively returns the currently active artifact instance (or None if empty).

active_key property writable

active_key: str | None

Reactively returns the name of the currently active artifact.

count property

count: int

Reactively returns the number of registered artifacts.

items property

items: dict[str, T]

Reactively returns the mapping of registered artifact names to instances.

names property

names: list[str]

Reactively returns the ordered list of registered artifact names.

version property

version: int

Reactively returns the mutation counter for this registry.

clear

clear() -> None

Removes all artifacts from the registry.

Source code in src/shiny_kit/state.py
def clear(self) -> None:
    """Removes all artifacts from the registry."""
    with reactive.isolate():
        ver_now = self._version.get()
    self._items.set({})
    self._active_key.set(None)
    self._version.set(ver_now + 1)

format_toast

format_toast(item: T) -> str

Formats a completion toast message for item using the registry's toast specification.

Source code in src/shiny_kit/state.py
def format_toast(self, item: T) -> str:
    """Formats a completion toast message for `item` using the registry's `toast` specification."""
    if self._toast is None:
        return ""
    if callable(self._toast):
        return str(self._toast(item) or "")
    return self._toast.format(
        item=item,
        res=item,
        name=getattr(item, "name", ""),
        rows=len(item) if isinstance(item, Sized) else 0,
    )

get

get(name: str) -> T | None

Reactively looks up a specific artifact by name.

Source code in src/shiny_kit/state.py
def get(self, name: str) -> T | None:
    """Reactively looks up a specific artifact by name."""
    return self._items.get().get(name)

register

register(name: str, item: T, *, set_active: bool = True, **kwargs: Any) -> T
register(
    item: T, *, name: str | None = None, set_active: bool = True, **kwargs: Any
) -> T
register(
    *args: Any, name: str | None = None, set_active: bool = True, **kwargs: Any
) -> T

Registers or updates a named artifact and triggers reactive dependents and on_register hooks.

Source code in src/shiny_kit/state.py
def register(self, *args: Any, name: str | None = None, set_active: bool = True, **kwargs: Any) -> T:
    """Registers or updates a named artifact and triggers reactive dependents and `on_register` hooks."""
    if len(args) == 2:
        raw_name, item = str(args[0]), args[1]
        clean_name = self.resolve_name(item, explicit_name=raw_name, sync_item_name=False)
    elif len(args) == 1:
        item = args[0]
        clean_name = self.resolve_name(item, explicit_name=name, sync_item_name=True)
    else:
        raise TypeError(f"ArtifactRegistry.register() expected 1 or 2 positional arguments, got {len(args)}.")

    if not clean_name:
        raise ValueError(f"{self.label} name cannot be empty.")
    with reactive.isolate():
        updated = dict(self._items.get())
        updated[clean_name] = item
        active_now = self._active_key.get()
        ver_now = self._version.get()
    self._items.set(updated)
    if set_active or active_now is None:
        self._active_key.set(clean_name)
    self._version.set(ver_now + 1)
    self._invoke_state_hook(self._on_register, item, name=clean_name, **kwargs)
    return item

remove

remove(name: str, **kwargs: Any) -> None

Removes a named artifact from the registry and updates the active selection if needed.

Source code in src/shiny_kit/state.py
def remove(self, name: str, **kwargs: Any) -> None:
    """Removes a named artifact from the registry and updates the active selection if needed."""
    with reactive.isolate():
        current = self._items.get()
        if name not in current:
            return
        removed_item = current[name]
        updated = {k: v for k, v in current.items() if k != name}
        active_now = self._active_key.get()
        ver_now = self._version.get()
    self._items.set(updated)
    if active_now == name:
        self._active_key.set(next(iter(updated.keys()), None))
    self._version.set(ver_now + 1)
    self._invoke_state_hook(self._on_remove, removed_item, name=name, **kwargs)

resolve_name

resolve_name(
    item: T, explicit_name: str | None = None, *, sync_item_name: bool = False
) -> str

Resolves the registry key for item using explicit_name, self._namer, or common attributes.

Source code in src/shiny_kit/state.py
def resolve_name(self, item: T, explicit_name: str | None = None, *, sync_item_name: bool = False) -> str:
    """Resolves the registry key for `item` using `explicit_name`, `self._namer`, or common attributes."""
    if explicit_name and explicit_name.strip():
        clean = explicit_name.strip()
        if sync_item_name and hasattr(item, "name"):
            try:
                item.name = clean  # ty: ignore[invalid-assignment]
            except Exception:
                pass
        return clean
    if self._namer is not None:
        return str(self._namer(item)).strip()
    fallback = (
        getattr(item, "name", None)
        or getattr(item, "summary_label", None)
        or getattr(item, "display_name", None)
        or str(item)
    )
    return str(fallback).strip()

select

select(name: str | None, **kwargs: Any) -> None

Sets the active artifact by name and invokes on_select hooks.

Source code in src/shiny_kit/state.py
def select(self, name: str | None, **kwargs: Any) -> None:
    """Sets the active artifact by name and invokes `on_select` hooks."""
    with reactive.isolate():
        current = self._items.get()
        if name is not None and name not in current:
            raise KeyError(f"'{name}' is not registered in {self.label}: {list(current.keys())}")
        selected_item = current.get(name) if name is not None else None
    self._active_key.set(name)
    self._invoke_state_hook(self._on_select, selected_item, name=name, **kwargs)

BaseSessionState

BaseSessionState(*, cache: BaseCache | None = None, navset_id: str = 'main_nav')

              flowchart TD
              shiny_kit.state.BaseSessionState[BaseSessionState]

              

              click shiny_kit.state.BaseSessionState href "" "shiny_kit.state.BaseSessionState"
            

Abstract session state base class holding reactive artifact_registry stores, theme state, and cache.

Subclasses declare domain registries as class-level artifact_registry[T](...) attributes. Ties state instances to session.id and auto-cleans via PyShiny's lifecycle hooks.

Attributes:

  • color_mode (Literal['light', 'dark']) –

    Reactive UI color theme mode.

  • user_id (str | None) –

    Optional authenticated user identifier.

  • preferences (dict[str, str]) –

    Arbitrary per-session user preferences dictionary.

  • root_session (Session | None) –

    Root Shiny Session owning this state instance.

  • navset_id (str) –

    Top-level navbar DOM ID used by navigate_to().

  • cache (BaseCache) –

    Two-tier BaseCache shared across the application.

Examples:

from shiny_kit.state import BaseSessionState, artifact_registry

class MySessionState(BaseSessionState):
    tables = artifact_registry[object]("Tables", icon="table")

state = MySessionState()
assert state.tables.count == 0

Methods:

  • bind_navbar –

    Binds top-level navbar dark-mode synchronization and reactive registry status badges.

  • format_task_success –

    Formats a default completion toast message for a task result res.

  • from_session –

    Retrieves or initializes typed state for the given session.

  • get_registry –

    Retrieves a named ArtifactRegistry attribute on this state instance.

  • navbar_controls –

    Returns (nav_spacer(), nav_control(output_ui(badges_id)), nav_control(input_dark_mode(...))) items.

  • navigate_to –

    Switches the active navigation panel in the root session.

  • on_reset –

    Optional hook called by reset_all() when clearing session state.

  • register_artifact –

    Registers item into the named ArtifactRegistry, triggering its namer and on_register hooks.

  • render_navbar_badges –

    Renders reactive status badges for all declared registries with show_in_navbar=True.

  • reset_all –

    Clears all declared ArtifactRegistry stores and calls on_reset().

  • summary_counts –

    Reactively returns {registry_name: count} across all declared registries.

Source code in src/shiny_kit/state.py
def __init__(self, *, cache: BaseCache | None = None, navset_id: str = "main_nav") -> None:
    self.user_id: str | None = None
    self.preferences: dict[str, str] = {}
    self.root_session: Session | None = None
    self.navset_id: str = navset_id
    self.cache: BaseCache = cache or default_cache
    for reg_name in self._registry_specs:
        _ = getattr(self, reg_name)

registries property

registries: dict[str, ArtifactRegistry[Any]]

Returns the ordered mapping of declared registry names to bound ArtifactRegistry instances.

bind_navbar

bind_navbar(
    input: Inputs,
    output: Outputs,
    *,
    color_mode_id: str = "color_mode",
    badges_id: str = "navbar_registry_badges",
) -> None

Binds top-level navbar dark-mode synchronization and reactive registry status badges.

Source code in src/shiny_kit/state.py
def bind_navbar(
    self,
    input: Inputs,
    output: Outputs,
    *,
    color_mode_id: str = "color_mode",
    badges_id: str = "navbar_registry_badges",
) -> None:
    """Binds top-level navbar dark-mode synchronization and reactive registry status badges."""

    @reactive.effect
    def _sync_color_mode() -> None:
        try:
            mode_val = input[color_mode_id]()
        except Exception:
            mode_val = "light"
        self.color_mode = "dark" if mode_val == "dark" else "light"

    @render.ui
    def _render_badges() -> TagChild:
        return self.render_navbar_badges()

    output(id=badges_id)(_render_badges)

format_task_success

format_task_success(res: Any, *, registry_name: str | None = None) -> str

Formats a default completion toast message for a task result res.

Source code in src/shiny_kit/state.py
def format_task_success(self, res: Any, *, registry_name: str | None = None) -> str:
    """Formats a default completion toast message for a task result `res`."""
    if registry_name and registry_name in self._registry_specs:
        msg = self.get_registry(registry_name).format_toast(res)
        if msg:
            return msg
    for reg in self.registries.values():
        if reg._toast is not None and any(res is v for v in reg.items.values()):
            return reg.format_toast(res)
    return ""

from_session classmethod

from_session(session: Session | None = None) -> Self

Retrieves or initializes typed state for the given session.

Source code in src/shiny_kit/state.py
@classmethod
def from_session(cls, session: Session | None = None) -> Self:
    """Retrieves or initializes typed state for the given session."""
    sess = session or get_current_session()
    if sess is None:
        raise RuntimeError("No active Shiny session available.")

    root_sess = getattr(sess, "_root_session", sess)
    existing = cls._registry.get(root_sess.id)
    if isinstance(existing, cls):
        return existing

    instance = cls()
    instance.root_session = root_sess
    cls._registry[root_sess.id] = instance

    if existing is None:

        def _cleanup() -> None:
            cls._registry.pop(root_sess.id, None)

        root_sess.on_ended(_cleanup)

    return instance

get_registry

get_registry(name: str) -> ArtifactRegistry[Any]

Retrieves a named ArtifactRegistry attribute on this state instance.

Source code in src/shiny_kit/state.py
def get_registry(self, name: str) -> ArtifactRegistry[Any]:
    """Retrieves a named `ArtifactRegistry` attribute on this state instance."""
    reg = getattr(self, name, None)
    if not isinstance(reg, ArtifactRegistry):
        raise AttributeError(f"{type(self).__name__} has no ArtifactRegistry named '{name}'.")
    return reg

navbar_controls classmethod

navbar_controls(
    *,
    color_mode_id: str = "color_mode",
    badges_id: str = "navbar_registry_badges",
    default_mode: Literal["light", "dark"] = "light",
) -> tuple[Any, ...]

Returns (nav_spacer(), nav_control(output_ui(badges_id)), nav_control(input_dark_mode(...))) items.

Source code in src/shiny_kit/state.py
@classmethod
def navbar_controls(
    cls,
    *,
    color_mode_id: str = "color_mode",
    badges_id: str = "navbar_registry_badges",
    default_mode: Literal["light", "dark"] = "light",
) -> tuple[Any, ...]:
    """Returns `(nav_spacer(), nav_control(output_ui(badges_id)), nav_control(input_dark_mode(...)))` items."""
    return (
        shiny_ui.nav_spacer(),
        shiny_ui.nav_control(
            shiny_ui.output_ui(badges_id),
        ),
        shiny_ui.nav_control(
            shiny_ui.div(
                shiny_ui.input_dark_mode(id=color_mode_id, mode=default_mode),
                class_="d-flex align-items-center ps-1 pe-2",
            ),
        ),
    )

navigate_to

navigate_to(tab_id: str, *, navset_id: str | None = None) -> None

Switches the active navigation panel in the root session.

Source code in src/shiny_kit/state.py
def navigate_to(self, tab_id: str, *, navset_id: str | None = None) -> None:
    """Switches the active navigation panel in the root session."""
    target_navset = navset_id or self.navset_id
    sess = self.root_session or get_current_session()
    if sess is not None:
        shiny_ui.update_navset(target_navset, selected=tab_id, session=sess)

on_reset

on_reset() -> None

Optional hook called by reset_all() when clearing session state.

Source code in src/shiny_kit/state.py
def on_reset(self) -> None:  # noqa: B027
    """Optional hook called by `reset_all()` when clearing session state."""
    pass

register_artifact

register_artifact(
    registry_name: str, item: Any, *, name: str | None = None, **kwargs: Any
) -> Any

Registers item into the named ArtifactRegistry, triggering its namer and on_register hooks.

Source code in src/shiny_kit/state.py
def register_artifact(
    self,
    registry_name: str,
    item: Any,
    *,
    name: str | None = None,
    **kwargs: Any,
) -> Any:
    """Registers `item` into the named `ArtifactRegistry`, triggering its `namer` and `on_register` hooks."""
    reg = self.get_registry(registry_name)
    return reg.register(item, name=name, set_active=True, **kwargs)

render_navbar_badges

render_navbar_badges() -> Tag

Renders reactive status badges for all declared registries with show_in_navbar=True.

Source code in src/shiny_kit/state.py
def render_navbar_badges(self) -> Tag:
    """Renders reactive status badges for all declared registries with `show_in_navbar=True`."""
    visible = [(name, reg) for name, reg in self.registries.items() if reg.show_in_navbar]
    badges: list[Any] = []
    for idx, (_, reg) in enumerate(visible):
        cnt = reg.count
        active_cls = reg.badge_class if cnt > 0 else "bg-secondary"
        margin_cls = "me-2 " if idx < len(visible) - 1 else ""
        badges.append(
            shiny_ui.span(
                Icon.lucide(reg.icon, size=13) if reg.icon else None,
                f"{reg.label}: {cnt}",
                class_=f"badge {active_cls} {margin_cls}p-2",
            )
        )
    return shiny_ui.div(*badges, class_="d-flex align-items-center pe-2")

reset_all

reset_all() -> None

Clears all declared ArtifactRegistry stores and calls on_reset().

Source code in src/shiny_kit/state.py
def reset_all(self) -> None:
    """Clears all declared `ArtifactRegistry` stores and calls `on_reset()`."""
    for reg in self.registries.values():
        reg.clear()
    self.on_reset()

summary_counts

summary_counts() -> dict[str, int]

Reactively returns {registry_name: count} across all declared registries.

Source code in src/shiny_kit/state.py
def summary_counts(self) -> dict[str, int]:
    """Reactively returns `{registry_name: count}` across all declared registries."""
    return {name: reg.count for name, reg in self.registries.items()}

Requirement dataclass

Requirement(
    registry: str, min_count: int = 1, label: str = "", target_tab: str | None = None
)

Declarative prerequisite rule gating a Tab or Component on BaseSessionState resources.

Parameters:

  • registry

    (str) –

    Name of the ArtifactRegistry attribute on BaseSessionState.

  • min_count

    (int, default: 1 ) –

    Minimum number of registered items required to unlock the view.

  • label

    (str, default: '' ) –

    Human-readable description of the required resource.

  • target_tab

    (str | None, default: None ) –

    Optional tab id_ to jump to when the user clicks the unlock action button.

Methods:

  • is_met –

    Reactively checks whether this requirement is satisfied in state.

  • status_text –

    Returns a formatted progress string (e.g. 'Formulations: 1 / 2 available').

is_met

is_met(state: BaseSessionState) -> bool

Reactively checks whether this requirement is satisfied in state.

Source code in src/shiny_kit/state.py
def is_met(self, state: "BaseSessionState") -> bool:
    """Reactively checks whether this requirement is satisfied in `state`."""
    return self.get_registry(state).count >= self.min_count

status_text

status_text(state: BaseSessionState) -> str

Returns a formatted progress string (e.g. 'Formulations: 1 / 2 available').

Source code in src/shiny_kit/state.py
def status_text(self, state: "BaseSessionState") -> str:
    """Returns a formatted progress string (e.g. 'Formulations: 1 / 2 available')."""
    reg = self.get_registry(state)
    return f"{self.display_label}: {reg.count} / {self.min_count} available"

artifact_registry

artifact_registry(
    label: str = "",
    *,
    icon: str | None = None,
    badge_class: str = "bg-primary",
    show_in_navbar: bool = True,
    namer: Callable[[T], str] | None = None,
    toast: Callable[[T], str] | str | None = None,
    on_register: Callable[..., None] | str | None = None,
    on_select: Callable[..., None] | str | None = None,
    on_remove: Callable[..., None] | str | None = None,
)

Class-level descriptor declaring an ArtifactRegistry[T] on a BaseSessionState subclass.

Source code in src/shiny_kit/state.py
def __init__(
    self,
    label: str = "",
    *,
    icon: str | None = None,
    badge_class: str = "bg-primary",
    show_in_navbar: bool = True,
    namer: Callable[[T], str] | None = None,
    toast: Callable[[T], str] | str | None = None,
    on_register: Callable[..., None] | str | None = None,
    on_select: Callable[..., None] | str | None = None,
    on_remove: Callable[..., None] | str | None = None,
) -> None:
    self.label = label
    self.attr_name: str = ""
    self._config: dict[str, Any] = {
        "icon": icon,
        "badge_class": badge_class,
        "show_in_navbar": show_in_navbar,
        "namer": namer,
        "toast": toast,
        "on_register": on_register,
        "on_select": on_select,
        "on_remove": on_remove,
    }

Explorers (shiny_kit.explorers)

High-level domain explorer components (ArtifactSwitcher, TableExplorer, BasePlotterExplorer, WidgetPlotterExplorer).

Classes:

  • ArtifactSwitcher –

    Reusable selector, summary badge bar, deletion control, and file I/O component for an ArtifactRegistry.

  • BasePlotterExplorer –

    Generic interactive plotter explorer with WidgetSelector-introspected controls.

  • TableExplorer –

    Generic multi-view DataFrame and CSV export explorer.

  • WidgetPlotterExplorer –

    Interactive widget plotter explorer with PlotterWidgetSelector controls and container auto-sizing.

ArtifactSwitcher

ArtifactSwitcher()

              flowchart TD
              shiny_kit.explorers.ArtifactSwitcher[ArtifactSwitcher]
              shiny_kit.component.Component[Component]

                              shiny_kit.component.Component --> shiny_kit.explorers.ArtifactSwitcher
                


              click shiny_kit.explorers.ArtifactSwitcher href "" "shiny_kit.explorers.ArtifactSwitcher"
              click shiny_kit.component.Component href "" "shiny_kit.component.Component"
            

Reusable selector, summary badge bar, deletion control, and file I/O component for an ArtifactRegistry.

Attributes:

  • registry_kind (ClassVar[str]) –

    Name of the ArtifactRegistry attribute on self.state.

  • label (ClassVar[str]) –

    Display label for the artifact selector dropdown.

  • show_count_in_label (ClassVar[bool]) –

    Whether to append (count) to the selector label.

  • enable_delete (ClassVar[bool]) –

    Whether to render a delete button for the active artifact.

  • enable_clear (ClassVar[bool]) –

    Whether to render a "Clear All" button.

  • enable_download (ClassVar[bool]) –

    Whether to render an artifact download button.

  • enable_upload (ClassVar[bool]) –

    Whether to render an artifact file upload control.

  • file_extension (ClassVar[str]) –

    Default file extension appended to downloaded artifacts.

  • upload_accept (ClassVar[Sequence[str]]) –

    Accepted file extensions for the upload control.

  • download_button_label (ClassVar[str]) –

    Label for the download button.

  • upload_label (ClassVar[str]) –

    Heading label above the file upload input.

  • upload_placeholder (ClassVar[str]) –

    Placeholder text inside the file upload input.

Methods:

  • active_for_download –

    Returns the artifact instance to serialize when downloading (defaults to self.registry.active).

  • choice_label –

    Formats the dropdown label for item.

  • download_filename –

    Returns a sanitized filename with self.file_extension for the active artifact.

  • empty_ui –

    Rendered when the registry has 0 items.

  • extra_buttons_ui –

    Optional extra action buttons placed alongside the Save/Delete controls.

  • has_changed –

    Tracks version under key and returns True when version differs from the previously recorded value.

  • is_unlocked –

    Reactively checks whether all declarative requires rules are satisfied.

  • load_artifact_from_path –

    Override when enable_upload = True to deserialize an uploaded artifact file.

  • notify –

    Displays a toast notification if running inside an active Shiny session.

  • notify_error –

    Formats and displays an error toast notification in the active Shiny session.

  • notify_warning –

    Displays a warning toast notification in the active Shiny session.

  • serialize_artifact –

    Serializes item into bytes for download. Defaults to calling item.save() if defined.

  • server –

    Binds server logic, state lifecycle, declarative BaseBinders, and subcomponents to a session.

  • summary_ui –

    Optional status/badge block rendered below the dropdown.

  • sync_version –

    Updates the recorded version for key without reporting a change.

  • ui –

    Constructs and namespaces the UI tree for this component, adding a resource-gate container if requires is set.

  • unmet_requirements –

    Reactively returns the list of currently unmet Requirement rules.

  • val –

    Safely reads self.input[id_]() with fallback default, optional reactive isolation, and string stripping.

Source code in src/shiny_kit/component.py
def __init__(self) -> None:
    self._input: Inputs | None = None
    self._output: Outputs | None = None
    self._session: Session | None = None
    self._state: S | None = None
    self.module_id: str = ""
    self._version_tracker: dict[str, Any] = {}

has_session property

has_session: bool

Whether this component is currently bound to an active Shiny session.

registry property

registry: ArtifactRegistry[T]

Returns the target ArtifactRegistry on self.state.

active_for_download

active_for_download() -> T | None

Returns the artifact instance to serialize when downloading (defaults to self.registry.active).

Source code in src/shiny_kit/explorers.py
def active_for_download(self) -> T | None:
    """Returns the artifact instance to serialize when downloading (defaults to `self.registry.active`)."""
    return self.registry.active

choice_label

choice_label(name: str, item: T) -> str

Formats the dropdown label for item.

Source code in src/shiny_kit/explorers.py
def choice_label(self, name: str, item: T) -> str:
    """Formats the dropdown label for `item`."""
    return name

download_filename

download_filename(item: T | None) -> str

Returns a sanitized filename with self.file_extension for the active artifact.

Source code in src/shiny_kit/explorers.py
def download_filename(self, item: T | None) -> str:
    """Returns a sanitized filename with `self.file_extension` for the active artifact."""
    ext = self.file_extension if self.file_extension.startswith(".") else f".{self.file_extension}"
    if item is None:
        return f"{self.registry_kind.rstrip('s')}{ext}"
    raw_name = (
        getattr(item, "name", None) or getattr(item, "display_name", None) or self.registry.active_key or "artifact"
    )
    safe = "".join(c if c.isalnum() or c in ("-", "_") else "_" for c in str(raw_name)).strip("_")
    return f"{safe or 'artifact'}{ext}"

empty_ui

empty_ui() -> TagChild

Rendered when the registry has 0 items.

Source code in src/shiny_kit/explorers.py
def empty_ui(self) -> TagChild:
    """Rendered when the registry has 0 items."""
    return shiny_ui.div()

extra_buttons_ui

extra_buttons_ui(item: T) -> Sequence[TagChild]

Optional extra action buttons placed alongside the Save/Delete controls.

Source code in src/shiny_kit/explorers.py
def extra_buttons_ui(self, item: T) -> Sequence[TagChild]:
    """Optional extra action buttons placed alongside the Save/Delete controls."""
    return ()

has_changed

has_changed(key: str, version: Any, *, initial: Any = 0) -> bool

Tracks version under key and returns True when version differs from the previously recorded value.

Source code in src/shiny_kit/component.py
def has_changed(self, key: str, version: Any, *, initial: Any = 0) -> bool:
    """Tracks `version` under `key` and returns `True` when `version` differs from the previously recorded value."""
    prev = self._version_tracker.get(key, initial)
    if version != prev:
        self._version_tracker[key] = version
        return True
    return False

is_unlocked

is_unlocked() -> bool

Reactively checks whether all declarative requires rules are satisfied.

Source code in src/shiny_kit/component.py
def is_unlocked(self) -> bool:
    """Reactively checks whether all declarative `requires` rules are satisfied."""
    if not self.requires or self._state is None:
        return True
    return all(r.is_met(self._state) for r in self.requires)

load_artifact_from_path

load_artifact_from_path(path: Path, filename: str) -> T

Override when enable_upload = True to deserialize an uploaded artifact file.

Source code in src/shiny_kit/explorers.py
def load_artifact_from_path(self, path: Path, filename: str) -> T:
    """Override when `enable_upload = True` to deserialize an uploaded artifact file."""
    raise NotImplementedError

notify

notify(
    message: str,
    *,
    type_: Literal["message", "warning", "error", "default"] = "message",
    duration: int | None = 5,
) -> None

Displays a toast notification if running inside an active Shiny session.

Source code in src/shiny_kit/component.py
def notify(
    self,
    message: str,
    *,
    type_: Literal["message", "warning", "error", "default"] = "message",
    duration: int | None = 5,
) -> None:
    """Displays a toast notification if running inside an active Shiny session."""
    shiny_ui.notification_show(message, type=type_, duration=duration)

notify_error

notify_error(
    error: BaseException | str, *, context: str | None = None, duration: int | None = 8
) -> None

Formats and displays an error toast notification in the active Shiny session.

Source code in src/shiny_kit/component.py
def notify_error(
    self,
    error: BaseException | str,
    *,
    context: str | None = None,
    duration: int | None = 8,
) -> None:
    """Formats and displays an error toast notification in the active Shiny session."""
    detail = str(error).strip() or error.__class__.__name__
    msg = f"{context}: {detail}" if context else detail
    self.notify(msg, type_="error", duration=duration)

notify_warning

notify_warning(message: str, *, duration: int | None = 8) -> None

Displays a warning toast notification in the active Shiny session.

Source code in src/shiny_kit/component.py
def notify_warning(self, message: str, *, duration: int | None = 8) -> None:
    """Displays a warning toast notification in the active Shiny session."""
    clean = str(message).strip()
    if clean:
        self.notify(clean, type_="warning", duration=duration)

serialize_artifact

serialize_artifact(item: T) -> bytes | None

Serializes item into bytes for download. Defaults to calling item.save() if defined.

Source code in src/shiny_kit/explorers.py
def serialize_artifact(self, item: T) -> bytes | None:
    """Serializes `item` into bytes for download. Defaults to calling `item.save()` if defined."""
    if hasattr(item, "save") and callable(item.save):
        try:
            raw = item.save(path=None)
        except TypeError:
            raw = item.save()
        if isinstance(raw, bytes):
            return raw
    return None

server

server(id_: str) -> Any

Binds server logic, state lifecycle, declarative BaseBinders, and subcomponents to a session.

Source code in src/shiny_kit/component.py
def server(self, id_: str) -> Any:
    """Binds server logic, state lifecycle, declarative `BaseBinder`s, and subcomponents to a session."""
    self.module_id = id_

    @module.server
    def _server(input: Inputs, output: Outputs, session: Session) -> None:
        self._input = input
        self._output = output
        self._session = session
        self._state = self.state_cls.from_session(session)

        for name in self._registered_binders:
            binder: BaseBinder = getattr(self.__class__, name)
            binder.bind(self, input, output, session)

        for sub_name in self._registered_subcomponents:
            sub_desc: subcomponent[Any] = getattr(self.__class__, sub_name)
            child_inst: Component[Any] = getattr(self, sub_name)
            child_inst.server(sub_desc.child_id)

        if self.requires:
            self._bind_resource_gate(input, output, session)

        self._register_server()

    return _server(id_)

summary_ui

summary_ui(item: T) -> TagChild

Optional status/badge block rendered below the dropdown.

Source code in src/shiny_kit/explorers.py
def summary_ui(self, item: T) -> TagChild:
    """Optional status/badge block rendered below the dropdown."""
    return None

sync_version

sync_version(key: str, version: Any) -> None

Updates the recorded version for key without reporting a change.

Source code in src/shiny_kit/component.py
def sync_version(self, key: str, version: Any) -> None:
    """Updates the recorded version for `key` without reporting a change."""
    self._version_tracker[key] = version

ui classmethod

ui(id_: str, *args: Any, **kwargs: Any) -> Any

Constructs and namespaces the UI tree for this component, adding a resource-gate container if requires is set.

Source code in src/shiny_kit/component.py
@classmethod
def ui(cls, id_: str, *args: Any, **kwargs: Any) -> Any:
    """Constructs and namespaces the UI tree for this component, adding a resource-gate container if `requires` is set."""

    @module.ui
    def _ui() -> Any:
        content = cls._render_ui(*args, **kwargs)
        if not cls.requires:
            return content
        return shiny_ui.TagList(
            shiny_ui.output_ui("_gate_callout"),
            shiny_ui.div(content, id=module.resolve_id("_tab_content_wrapper")),
        )

    return _ui(id_)

unmet_requirements

unmet_requirements() -> list[Requirement]

Reactively returns the list of currently unmet Requirement rules.

Source code in src/shiny_kit/component.py
def unmet_requirements(self) -> list[Requirement]:
    """Reactively returns the list of currently unmet `Requirement` rules."""
    if not self.requires or self._state is None:
        return []
    return [r for r in self.requires if not r.is_met(self._state)]

val

val(id_: str, default: Any = None, *, isolate: bool = False, strip: bool = True) -> Any

Safely reads self.input[id_]() with fallback default, optional reactive isolation, and string stripping.

Source code in src/shiny_kit/component.py
def val(
    self,
    id_: str,
    default: Any = None,
    *,
    isolate: bool = False,
    strip: bool = True,
) -> Any:
    """Safely reads `self.input[id_]()` with fallback `default`, optional reactive isolation, and string stripping."""
    if self._input is None:
        return default

    def _read() -> Any:
        if self._input is None or id_ not in self._input:
            return default
        raw = self._input[id_]()
        if raw is None:
            return default
        if isinstance(raw, str):
            cleaned = raw.strip() if strip else raw
            if not cleaned and default is not None:
                return default
            return cleaned
        return raw

    if isolate:
        with reactive.isolate():
            return _read()
    return _read()

BasePlotterExplorer

BasePlotterExplorer()

              flowchart TD
              shiny_kit.explorers.BasePlotterExplorer[BasePlotterExplorer]
              shiny_kit.component.Component[Component]

                              shiny_kit.component.Component --> shiny_kit.explorers.BasePlotterExplorer
                


              click shiny_kit.explorers.BasePlotterExplorer href "" "shiny_kit.explorers.BasePlotterExplorer"
              click shiny_kit.component.Component href "" "shiny_kit.component.Component"
            

Generic interactive plotter explorer with WidgetSelector-introspected controls.

Subclasses bind a plotter_registry mapping plot keys to (label, plotter_cls) pairs, configure widget_selector_cls, implement active_subject(), and bind a chart output renderer (such as WidgetPlotterExplorer).

Attributes:

  • plotter_registry (ClassVar[Mapping[str, tuple[str, type[Any]]]]) –

    Mapping of {plot_key: (label, plotter_cls)}.

  • widget_selector_cls (ClassVar[type[WidgetSelector]]) –

    WidgetSelector subclass used to introspect plotters.

  • default_plot_key (ClassVar[str]) –

    Default selected plot key when available.

  • param_prefix (ClassVar[str]) –

    Input ID prefix for introspected plotter widgets.

  • sidebar_width (ClassVar[int]) –

    Width of the plot controls sidebar in pixels.

  • chart_height (ClassVar[str]) –

    CSS height of the chart output widget.

  • chart_output_id (ClassVar[str]) –

    Output ID of the main chart widget. Defaults to "main_chart".

Methods:

  • active_subject –

    Returns the active domain model to plot, or None if unavailable.

  • available_choices –

    Returns {plot_key: label} choices available for subject.

  • build_chart –

    Invokes plotter.plot(subject) (override for plotters requiring special arguments).

  • has_changed –

    Tracks version under key and returns True when version differs from the previously recorded value.

  • is_unlocked –

    Reactively checks whether all declarative requires rules are satisfied.

  • notify –

    Displays a toast notification if running inside an active Shiny session.

  • notify_error –

    Formats and displays an error toast notification in the active Shiny session.

  • notify_warning –

    Displays a warning toast notification in the active Shiny session.

  • render_chart_widget –

    Override in subclasses to render the output container widget for self.chart_output_id.

  • render_extra_controls –

    Optional plot-specific controls rendered between Plot Type and Plot Parameters.

  • resolve_selected_plot –

    Selects the active plot key given choices and the previous current_plot.

  • resolved_plot_key –

    Resolves the currently selected valid plot key for subject.

  • selector_header_ui –

    Optional status or unlock hint badge rendered above the Plot Type dropdown.

  • server –

    Binds server logic, state lifecycle, declarative BaseBinders, and subcomponents to a session.

  • sync_version –

    Updates the recorded version for key without reporting a change.

  • ui –

    Constructs and namespaces the UI tree for this component, adding a resource-gate container if requires is set.

  • unmet_requirements –

    Reactively returns the list of currently unmet Requirement rules.

  • val –

    Safely reads self.input[id_]() with fallback default, optional reactive isolation, and string stripping.

Source code in src/shiny_kit/explorers.py
def __init__(self) -> None:
    super().__init__()
    self._plotter_selector = self.widget_selector_cls.default()

has_session property

has_session: bool

Whether this component is currently bound to an active Shiny session.

active_subject

active_subject() -> M | None

Returns the active domain model to plot, or None if unavailable.

Source code in src/shiny_kit/explorers.py
def active_subject(self) -> M | None:
    """Returns the active domain model to plot, or None if unavailable."""
    raise NotImplementedError

available_choices

available_choices(subject: M) -> dict[str, str]

Returns {plot_key: label} choices available for subject.

Source code in src/shiny_kit/explorers.py
def available_choices(self, subject: M) -> dict[str, str]:
    """Returns `{plot_key: label}` choices available for `subject`."""
    return {key: label for key, (label, _) in self.plotter_registry.items()}

build_chart

build_chart(plotter: Any, subject: M, plot_key: str) -> Any | None

Invokes plotter.plot(subject) (override for plotters requiring special arguments).

Source code in src/shiny_kit/explorers.py
def build_chart(self, plotter: Any, subject: M, plot_key: str) -> Any | None:
    """Invokes `plotter.plot(subject)` (override for plotters requiring special arguments)."""
    return plotter.plot(subject)

has_changed

has_changed(key: str, version: Any, *, initial: Any = 0) -> bool

Tracks version under key and returns True when version differs from the previously recorded value.

Source code in src/shiny_kit/component.py
def has_changed(self, key: str, version: Any, *, initial: Any = 0) -> bool:
    """Tracks `version` under `key` and returns `True` when `version` differs from the previously recorded value."""
    prev = self._version_tracker.get(key, initial)
    if version != prev:
        self._version_tracker[key] = version
        return True
    return False

is_unlocked

is_unlocked() -> bool

Reactively checks whether all declarative requires rules are satisfied.

Source code in src/shiny_kit/component.py
def is_unlocked(self) -> bool:
    """Reactively checks whether all declarative `requires` rules are satisfied."""
    if not self.requires or self._state is None:
        return True
    return all(r.is_met(self._state) for r in self.requires)

notify

notify(
    message: str,
    *,
    type_: Literal["message", "warning", "error", "default"] = "message",
    duration: int | None = 5,
) -> None

Displays a toast notification if running inside an active Shiny session.

Source code in src/shiny_kit/component.py
def notify(
    self,
    message: str,
    *,
    type_: Literal["message", "warning", "error", "default"] = "message",
    duration: int | None = 5,
) -> None:
    """Displays a toast notification if running inside an active Shiny session."""
    shiny_ui.notification_show(message, type=type_, duration=duration)

notify_error

notify_error(
    error: BaseException | str, *, context: str | None = None, duration: int | None = 8
) -> None

Formats and displays an error toast notification in the active Shiny session.

Source code in src/shiny_kit/component.py
def notify_error(
    self,
    error: BaseException | str,
    *,
    context: str | None = None,
    duration: int | None = 8,
) -> None:
    """Formats and displays an error toast notification in the active Shiny session."""
    detail = str(error).strip() or error.__class__.__name__
    msg = f"{context}: {detail}" if context else detail
    self.notify(msg, type_="error", duration=duration)

notify_warning

notify_warning(message: str, *, duration: int | None = 8) -> None

Displays a warning toast notification in the active Shiny session.

Source code in src/shiny_kit/component.py
def notify_warning(self, message: str, *, duration: int | None = 8) -> None:
    """Displays a warning toast notification in the active Shiny session."""
    clean = str(message).strip()
    if clean:
        self.notify(clean, type_="warning", duration=duration)

render_chart_widget

render_chart_widget() -> TagChild

Override in subclasses to render the output container widget for self.chart_output_id.

Source code in src/shiny_kit/explorers.py
def render_chart_widget(self) -> TagChild:
    """Override in subclasses to render the output container widget for `self.chart_output_id`."""
    raise NotImplementedError

render_extra_controls

render_extra_controls(subject: M, plot_key: str) -> TagChild

Optional plot-specific controls rendered between Plot Type and Plot Parameters.

Source code in src/shiny_kit/explorers.py
def render_extra_controls(self, subject: M, plot_key: str) -> TagChild:
    """Optional plot-specific controls rendered between Plot Type and Plot Parameters."""
    return shiny_ui.div()

resolve_selected_plot

resolve_selected_plot(
    subject: M, choices: dict[str, str], current_plot: str | None
) -> str | None

Selects the active plot key given choices and the previous current_plot.

Source code in src/shiny_kit/explorers.py
def resolve_selected_plot(
    self,
    subject: M,
    choices: dict[str, str],
    current_plot: str | None,
) -> str | None:
    """Selects the active plot key given `choices` and the previous `current_plot`."""
    if not choices:
        return None
    fallback = self.default_plot_key if self.default_plot_key in choices else next(iter(choices.keys()), None)
    return str(current_plot) if current_plot in choices else fallback

resolved_plot_key

resolved_plot_key(subject: M) -> str | None

Resolves the currently selected valid plot key for subject.

Source code in src/shiny_kit/explorers.py
def resolved_plot_key(self, subject: M) -> str | None:
    """Resolves the currently selected valid plot key for `subject`."""
    choices = self.available_choices(subject)
    if not choices:
        return None
    fallback = self.default_plot_key if self.default_plot_key in choices else next(iter(choices.keys()))
    cur = self.val("plot_type", default=fallback)
    return str(cur) if cur in choices else fallback

selector_header_ui

selector_header_ui(subject: M) -> TagChild

Optional status or unlock hint badge rendered above the Plot Type dropdown.

Source code in src/shiny_kit/explorers.py
def selector_header_ui(self, subject: M) -> TagChild:
    """Optional status or unlock hint badge rendered above the Plot Type dropdown."""
    return None

server

server(id_: str) -> Any

Binds server logic, state lifecycle, declarative BaseBinders, and subcomponents to a session.

Source code in src/shiny_kit/component.py
def server(self, id_: str) -> Any:
    """Binds server logic, state lifecycle, declarative `BaseBinder`s, and subcomponents to a session."""
    self.module_id = id_

    @module.server
    def _server(input: Inputs, output: Outputs, session: Session) -> None:
        self._input = input
        self._output = output
        self._session = session
        self._state = self.state_cls.from_session(session)

        for name in self._registered_binders:
            binder: BaseBinder = getattr(self.__class__, name)
            binder.bind(self, input, output, session)

        for sub_name in self._registered_subcomponents:
            sub_desc: subcomponent[Any] = getattr(self.__class__, sub_name)
            child_inst: Component[Any] = getattr(self, sub_name)
            child_inst.server(sub_desc.child_id)

        if self.requires:
            self._bind_resource_gate(input, output, session)

        self._register_server()

    return _server(id_)

sync_version

sync_version(key: str, version: Any) -> None

Updates the recorded version for key without reporting a change.

Source code in src/shiny_kit/component.py
def sync_version(self, key: str, version: Any) -> None:
    """Updates the recorded version for `key` without reporting a change."""
    self._version_tracker[key] = version

ui classmethod

ui(id_: str, *args: Any, **kwargs: Any) -> Any

Constructs and namespaces the UI tree for this component, adding a resource-gate container if requires is set.

Source code in src/shiny_kit/component.py
@classmethod
def ui(cls, id_: str, *args: Any, **kwargs: Any) -> Any:
    """Constructs and namespaces the UI tree for this component, adding a resource-gate container if `requires` is set."""

    @module.ui
    def _ui() -> Any:
        content = cls._render_ui(*args, **kwargs)
        if not cls.requires:
            return content
        return shiny_ui.TagList(
            shiny_ui.output_ui("_gate_callout"),
            shiny_ui.div(content, id=module.resolve_id("_tab_content_wrapper")),
        )

    return _ui(id_)

unmet_requirements

unmet_requirements() -> list[Requirement]

Reactively returns the list of currently unmet Requirement rules.

Source code in src/shiny_kit/component.py
def unmet_requirements(self) -> list[Requirement]:
    """Reactively returns the list of currently unmet `Requirement` rules."""
    if not self.requires or self._state is None:
        return []
    return [r for r in self.requires if not r.is_met(self._state)]

val

val(id_: str, default: Any = None, *, isolate: bool = False, strip: bool = True) -> Any

Safely reads self.input[id_]() with fallback default, optional reactive isolation, and string stripping.

Source code in src/shiny_kit/component.py
def val(
    self,
    id_: str,
    default: Any = None,
    *,
    isolate: bool = False,
    strip: bool = True,
) -> Any:
    """Safely reads `self.input[id_]()` with fallback `default`, optional reactive isolation, and string stripping."""
    if self._input is None:
        return default

    def _read() -> Any:
        if self._input is None or id_ not in self._input:
            return default
        raw = self._input[id_]()
        if raw is None:
            return default
        if isinstance(raw, str):
            cleaned = raw.strip() if strip else raw
            if not cleaned and default is not None:
                return default
            return cleaned
        return raw

    if isolate:
        with reactive.isolate():
            return _read()
    return _read()

TableExplorer

TableExplorer()

              flowchart TD
              shiny_kit.explorers.TableExplorer[TableExplorer]
              shiny_kit.component.Component[Component]

                              shiny_kit.component.Component --> shiny_kit.explorers.TableExplorer
                


              click shiny_kit.explorers.TableExplorer href "" "shiny_kit.explorers.TableExplorer"
              click shiny_kit.component.Component href "" "shiny_kit.component.Component"
            

Generic multi-view DataFrame and CSV export explorer.

Subclasses implement active_subject(), table_views(subject), and resolve_table(subject, mode) to provide a reactive radio-button view switcher, optional status badges, a filterable render.DataGrid, and one-click CSV export.

Attributes:

  • empty_icon (ClassVar[str]) –

    Lucide icon name shown when active_subject() is None.

  • empty_title (ClassVar[str]) –

    Empty-state heading shown when active_subject() is None.

  • empty_message (ClassVar[str]) –

    Empty-state description shown when active_subject() is None.

  • csv_prefix (ClassVar[str]) –

    Filename prefix for exported .csv files.

  • grid_height (ClassVar[str]) –

    CSS height of the rendered DataGrid.

Methods:

  • active_subject –

    Returns the active domain object backing the table views (or None if unavailable).

  • has_changed –

    Tracks version under key and returns True when version differs from the previously recorded value.

  • is_unlocked –

    Reactively checks whether all declarative requires rules are satisfied.

  • notify –

    Displays a toast notification if running inside an active Shiny session.

  • notify_error –

    Formats and displays an error toast notification in the active Shiny session.

  • notify_warning –

    Displays a warning toast notification in the active Shiny session.

  • resolve_selected_mode –

    Selects the active radio button mode given choices and the previous current_mode.

  • resolve_table –

    Returns the tabular object for subject and mode.

  • serialize_csv –

    Serializes df to UTF-8 CSV bytes. Supports Polars (write_csv) and Pandas (to_csv) out of the box.

  • server –

    Binds server logic, state lifecycle, declarative BaseBinders, and subcomponents to a session.

  • sync_version –

    Updates the recorded version for key without reporting a change.

  • table_views –

    Returns {view_key: label} choices available for subject.

  • toolbar_badges_ui –

    Optional status badges displayed on the left of the table toolbar.

  • ui –

    Constructs and namespaces the UI tree for this component, adding a resource-gate container if requires is set.

  • unmet_requirements –

    Reactively returns the list of currently unmet Requirement rules.

  • val –

    Safely reads self.input[id_]() with fallback default, optional reactive isolation, and string stripping.

Source code in src/shiny_kit/component.py
def __init__(self) -> None:
    self._input: Inputs | None = None
    self._output: Outputs | None = None
    self._session: Session | None = None
    self._state: S | None = None
    self.module_id: str = ""
    self._version_tracker: dict[str, Any] = {}

has_session property

has_session: bool

Whether this component is currently bound to an active Shiny session.

active_subject

active_subject() -> M | None

Returns the active domain object backing the table views (or None if unavailable).

Source code in src/shiny_kit/explorers.py
def active_subject(self) -> M | None:
    """Returns the active domain object backing the table views (or None if unavailable)."""
    raise NotImplementedError

has_changed

has_changed(key: str, version: Any, *, initial: Any = 0) -> bool

Tracks version under key and returns True when version differs from the previously recorded value.

Source code in src/shiny_kit/component.py
def has_changed(self, key: str, version: Any, *, initial: Any = 0) -> bool:
    """Tracks `version` under `key` and returns `True` when `version` differs from the previously recorded value."""
    prev = self._version_tracker.get(key, initial)
    if version != prev:
        self._version_tracker[key] = version
        return True
    return False

is_unlocked

is_unlocked() -> bool

Reactively checks whether all declarative requires rules are satisfied.

Source code in src/shiny_kit/component.py
def is_unlocked(self) -> bool:
    """Reactively checks whether all declarative `requires` rules are satisfied."""
    if not self.requires or self._state is None:
        return True
    return all(r.is_met(self._state) for r in self.requires)

notify

notify(
    message: str,
    *,
    type_: Literal["message", "warning", "error", "default"] = "message",
    duration: int | None = 5,
) -> None

Displays a toast notification if running inside an active Shiny session.

Source code in src/shiny_kit/component.py
def notify(
    self,
    message: str,
    *,
    type_: Literal["message", "warning", "error", "default"] = "message",
    duration: int | None = 5,
) -> None:
    """Displays a toast notification if running inside an active Shiny session."""
    shiny_ui.notification_show(message, type=type_, duration=duration)

notify_error

notify_error(
    error: BaseException | str, *, context: str | None = None, duration: int | None = 8
) -> None

Formats and displays an error toast notification in the active Shiny session.

Source code in src/shiny_kit/component.py
def notify_error(
    self,
    error: BaseException | str,
    *,
    context: str | None = None,
    duration: int | None = 8,
) -> None:
    """Formats and displays an error toast notification in the active Shiny session."""
    detail = str(error).strip() or error.__class__.__name__
    msg = f"{context}: {detail}" if context else detail
    self.notify(msg, type_="error", duration=duration)

notify_warning

notify_warning(message: str, *, duration: int | None = 8) -> None

Displays a warning toast notification in the active Shiny session.

Source code in src/shiny_kit/component.py
def notify_warning(self, message: str, *, duration: int | None = 8) -> None:
    """Displays a warning toast notification in the active Shiny session."""
    clean = str(message).strip()
    if clean:
        self.notify(clean, type_="warning", duration=duration)

resolve_selected_mode

resolve_selected_mode(
    subject: M, choices: dict[str, str], current_mode: str | None
) -> str

Selects the active radio button mode given choices and the previous current_mode.

Source code in src/shiny_kit/explorers.py
def resolve_selected_mode(self, subject: M, choices: dict[str, str], current_mode: str | None) -> str:
    """Selects the active radio button mode given `choices` and the previous `current_mode`."""
    if current_mode and current_mode in choices:
        return current_mode
    return next(iter(choices.keys()), "")

resolve_table

resolve_table(subject: M, mode: str) -> Any | None

Returns the tabular object for subject and mode.

Source code in src/shiny_kit/explorers.py
def resolve_table(self, subject: M, mode: str) -> Any | None:
    """Returns the tabular object for `subject` and `mode`."""
    raise NotImplementedError

serialize_csv

serialize_csv(df: Any) -> bytes

Serializes df to UTF-8 CSV bytes. Supports Polars (write_csv) and Pandas (to_csv) out of the box.

Source code in src/shiny_kit/explorers.py
def serialize_csv(self, df: Any) -> bytes:
    """Serializes `df` to UTF-8 CSV bytes. Supports Polars (`write_csv`) and Pandas (`to_csv`) out of the box."""
    if hasattr(df, "write_csv") and callable(df.write_csv):
        return str(df.write_csv()).encode("utf-8")
    if hasattr(df, "to_csv") and callable(df.to_csv):
        return str(df.to_csv(index=False)).encode("utf-8")
    raise NotImplementedError(f"Override serialize_csv() to export {type(df).__name__} to CSV.")

server

server(id_: str) -> Any

Binds server logic, state lifecycle, declarative BaseBinders, and subcomponents to a session.

Source code in src/shiny_kit/component.py
def server(self, id_: str) -> Any:
    """Binds server logic, state lifecycle, declarative `BaseBinder`s, and subcomponents to a session."""
    self.module_id = id_

    @module.server
    def _server(input: Inputs, output: Outputs, session: Session) -> None:
        self._input = input
        self._output = output
        self._session = session
        self._state = self.state_cls.from_session(session)

        for name in self._registered_binders:
            binder: BaseBinder = getattr(self.__class__, name)
            binder.bind(self, input, output, session)

        for sub_name in self._registered_subcomponents:
            sub_desc: subcomponent[Any] = getattr(self.__class__, sub_name)
            child_inst: Component[Any] = getattr(self, sub_name)
            child_inst.server(sub_desc.child_id)

        if self.requires:
            self._bind_resource_gate(input, output, session)

        self._register_server()

    return _server(id_)

sync_version

sync_version(key: str, version: Any) -> None

Updates the recorded version for key without reporting a change.

Source code in src/shiny_kit/component.py
def sync_version(self, key: str, version: Any) -> None:
    """Updates the recorded version for `key` without reporting a change."""
    self._version_tracker[key] = version

table_views

table_views(subject: M) -> dict[str, str]

Returns {view_key: label} choices available for subject.

Source code in src/shiny_kit/explorers.py
def table_views(self, subject: M) -> dict[str, str]:
    """Returns `{view_key: label}` choices available for `subject`."""
    raise NotImplementedError

toolbar_badges_ui

toolbar_badges_ui(subject: M) -> Sequence[TagChild]

Optional status badges displayed on the left of the table toolbar.

Source code in src/shiny_kit/explorers.py
def toolbar_badges_ui(self, subject: M) -> Sequence[TagChild]:
    """Optional status badges displayed on the left of the table toolbar."""
    return ()

ui classmethod

ui(id_: str, *args: Any, **kwargs: Any) -> Any

Constructs and namespaces the UI tree for this component, adding a resource-gate container if requires is set.

Source code in src/shiny_kit/component.py
@classmethod
def ui(cls, id_: str, *args: Any, **kwargs: Any) -> Any:
    """Constructs and namespaces the UI tree for this component, adding a resource-gate container if `requires` is set."""

    @module.ui
    def _ui() -> Any:
        content = cls._render_ui(*args, **kwargs)
        if not cls.requires:
            return content
        return shiny_ui.TagList(
            shiny_ui.output_ui("_gate_callout"),
            shiny_ui.div(content, id=module.resolve_id("_tab_content_wrapper")),
        )

    return _ui(id_)

unmet_requirements

unmet_requirements() -> list[Requirement]

Reactively returns the list of currently unmet Requirement rules.

Source code in src/shiny_kit/component.py
def unmet_requirements(self) -> list[Requirement]:
    """Reactively returns the list of currently unmet `Requirement` rules."""
    if not self.requires or self._state is None:
        return []
    return [r for r in self.requires if not r.is_met(self._state)]

val

val(id_: str, default: Any = None, *, isolate: bool = False, strip: bool = True) -> Any

Safely reads self.input[id_]() with fallback default, optional reactive isolation, and string stripping.

Source code in src/shiny_kit/component.py
def val(
    self,
    id_: str,
    default: Any = None,
    *,
    isolate: bool = False,
    strip: bool = True,
) -> Any:
    """Safely reads `self.input[id_]()` with fallback `default`, optional reactive isolation, and string stripping."""
    if self._input is None:
        return default

    def _read() -> Any:
        if self._input is None or id_ not in self._input:
            return default
        raw = self._input[id_]()
        if raw is None:
            return default
        if isinstance(raw, str):
            cleaned = raw.strip() if strip else raw
            if not cleaned and default is not None:
                return default
            return cleaned
        return raw

    if isolate:
        with reactive.isolate():
            return _read()
    return _read()

WidgetPlotterExplorer

WidgetPlotterExplorer()

              flowchart TD
              shiny_kit.explorers.WidgetPlotterExplorer[WidgetPlotterExplorer]
              shiny_kit.explorers.BasePlotterExplorer[BasePlotterExplorer]
              shiny_kit.component.Component[Component]

                              shiny_kit.explorers.BasePlotterExplorer --> shiny_kit.explorers.WidgetPlotterExplorer
                                shiny_kit.component.Component --> shiny_kit.explorers.BasePlotterExplorer
                



              click shiny_kit.explorers.WidgetPlotterExplorer href "" "shiny_kit.explorers.WidgetPlotterExplorer"
              click shiny_kit.explorers.BasePlotterExplorer href "" "shiny_kit.explorers.BasePlotterExplorer"
              click shiny_kit.component.Component href "" "shiny_kit.component.Component"
            

Interactive widget plotter explorer with PlotterWidgetSelector controls and container auto-sizing.

Lazily uses shinywidgets.output_widget and bind_widget so shiny-kit remains free of charting library dependencies while providing a ready-to-use base class for Altair/Plotly/widget plotter explorers.

Methods:

  • active_subject –

    Returns the active domain model to plot, or None if unavailable.

  • available_choices –

    Returns {plot_key: label} choices available for subject.

  • build_chart –

    Invokes plotter.plot(subject) (override for plotters requiring special arguments).

  • has_changed –

    Tracks version under key and returns True when version differs from the previously recorded value.

  • is_unlocked –

    Reactively checks whether all declarative requires rules are satisfied.

  • notify –

    Displays a toast notification if running inside an active Shiny session.

  • notify_error –

    Formats and displays an error toast notification in the active Shiny session.

  • notify_warning –

    Displays a warning toast notification in the active Shiny session.

  • render_chart_widget –

    Renders the shinywidgets.output_widget container for self.chart_output_id.

  • render_extra_controls –

    Optional plot-specific controls rendered between Plot Type and Plot Parameters.

  • render_main_chart –

    Reactively renders self.current_chart() fitted to its container.

  • resolve_selected_plot –

    Selects the active plot key given choices and the previous current_plot.

  • resolved_plot_key –

    Resolves the currently selected valid plot key for subject.

  • selector_header_ui –

    Optional status or unlock hint badge rendered above the Plot Type dropdown.

  • server –

    Binds server logic, state lifecycle, declarative BaseBinders, and subcomponents to a session.

  • sync_version –

    Updates the recorded version for key without reporting a change.

  • ui –

    Constructs and namespaces the UI tree for this component, adding a resource-gate container if requires is set.

  • unmet_requirements –

    Reactively returns the list of currently unmet Requirement rules.

  • val –

    Safely reads self.input[id_]() with fallback default, optional reactive isolation, and string stripping.

Attributes:

  • has_session (bool) –

    Whether this component is currently bound to an active Shiny session.

Source code in src/shiny_kit/explorers.py
def __init__(self) -> None:
    super().__init__()
    self._plotter_selector = self.widget_selector_cls.default()

has_session property

has_session: bool

Whether this component is currently bound to an active Shiny session.

active_subject

active_subject() -> M | None

Returns the active domain model to plot, or None if unavailable.

Source code in src/shiny_kit/explorers.py
def active_subject(self) -> M | None:
    """Returns the active domain model to plot, or None if unavailable."""
    raise NotImplementedError

available_choices

available_choices(subject: M) -> dict[str, str]

Returns {plot_key: label} choices available for subject.

Source code in src/shiny_kit/explorers.py
def available_choices(self, subject: M) -> dict[str, str]:
    """Returns `{plot_key: label}` choices available for `subject`."""
    return {key: label for key, (label, _) in self.plotter_registry.items()}

build_chart

build_chart(plotter: Any, subject: M, plot_key: str) -> Any | None

Invokes plotter.plot(subject) (override for plotters requiring special arguments).

Source code in src/shiny_kit/explorers.py
def build_chart(self, plotter: Any, subject: M, plot_key: str) -> Any | None:
    """Invokes `plotter.plot(subject)` (override for plotters requiring special arguments)."""
    return plotter.plot(subject)

has_changed

has_changed(key: str, version: Any, *, initial: Any = 0) -> bool

Tracks version under key and returns True when version differs from the previously recorded value.

Source code in src/shiny_kit/component.py
def has_changed(self, key: str, version: Any, *, initial: Any = 0) -> bool:
    """Tracks `version` under `key` and returns `True` when `version` differs from the previously recorded value."""
    prev = self._version_tracker.get(key, initial)
    if version != prev:
        self._version_tracker[key] = version
        return True
    return False

is_unlocked

is_unlocked() -> bool

Reactively checks whether all declarative requires rules are satisfied.

Source code in src/shiny_kit/component.py
def is_unlocked(self) -> bool:
    """Reactively checks whether all declarative `requires` rules are satisfied."""
    if not self.requires or self._state is None:
        return True
    return all(r.is_met(self._state) for r in self.requires)

notify

notify(
    message: str,
    *,
    type_: Literal["message", "warning", "error", "default"] = "message",
    duration: int | None = 5,
) -> None

Displays a toast notification if running inside an active Shiny session.

Source code in src/shiny_kit/component.py
def notify(
    self,
    message: str,
    *,
    type_: Literal["message", "warning", "error", "default"] = "message",
    duration: int | None = 5,
) -> None:
    """Displays a toast notification if running inside an active Shiny session."""
    shiny_ui.notification_show(message, type=type_, duration=duration)

notify_error

notify_error(
    error: BaseException | str, *, context: str | None = None, duration: int | None = 8
) -> None

Formats and displays an error toast notification in the active Shiny session.

Source code in src/shiny_kit/component.py
def notify_error(
    self,
    error: BaseException | str,
    *,
    context: str | None = None,
    duration: int | None = 8,
) -> None:
    """Formats and displays an error toast notification in the active Shiny session."""
    detail = str(error).strip() or error.__class__.__name__
    msg = f"{context}: {detail}" if context else detail
    self.notify(msg, type_="error", duration=duration)

notify_warning

notify_warning(message: str, *, duration: int | None = 8) -> None

Displays a warning toast notification in the active Shiny session.

Source code in src/shiny_kit/component.py
def notify_warning(self, message: str, *, duration: int | None = 8) -> None:
    """Displays a warning toast notification in the active Shiny session."""
    clean = str(message).strip()
    if clean:
        self.notify(clean, type_="warning", duration=duration)

render_chart_widget

render_chart_widget() -> TagChild

Renders the shinywidgets.output_widget container for self.chart_output_id.

Source code in src/shiny_kit/explorers.py
def render_chart_widget(self) -> TagChild:
    """Renders the `shinywidgets.output_widget` container for `self.chart_output_id`."""
    shinywidgets = importlib.import_module("shinywidgets")
    return shinywidgets.output_widget(
        self.chart_output_id,
        width="100%",
        height=self.chart_height,
        fill=True,
        fillable=True,
    )

render_extra_controls

render_extra_controls(subject: M, plot_key: str) -> TagChild

Optional plot-specific controls rendered between Plot Type and Plot Parameters.

Source code in src/shiny_kit/explorers.py
def render_extra_controls(self, subject: M, plot_key: str) -> TagChild:
    """Optional plot-specific controls rendered between Plot Type and Plot Parameters."""
    return shiny_ui.div()

render_main_chart

render_main_chart() -> Any

Reactively renders self.current_chart() fitted to its container.

Source code in src/shiny_kit/explorers.py
@bind_widget(id_attr="chart_output_id")
def render_main_chart(self) -> Any:
    """Reactively renders `self.current_chart()` fitted to its container."""
    chart = self.current_chart()
    req(chart is not None)
    assert chart is not None
    return chart

resolve_selected_plot

resolve_selected_plot(
    subject: M, choices: dict[str, str], current_plot: str | None
) -> str | None

Selects the active plot key given choices and the previous current_plot.

Source code in src/shiny_kit/explorers.py
def resolve_selected_plot(
    self,
    subject: M,
    choices: dict[str, str],
    current_plot: str | None,
) -> str | None:
    """Selects the active plot key given `choices` and the previous `current_plot`."""
    if not choices:
        return None
    fallback = self.default_plot_key if self.default_plot_key in choices else next(iter(choices.keys()), None)
    return str(current_plot) if current_plot in choices else fallback

resolved_plot_key

resolved_plot_key(subject: M) -> str | None

Resolves the currently selected valid plot key for subject.

Source code in src/shiny_kit/explorers.py
def resolved_plot_key(self, subject: M) -> str | None:
    """Resolves the currently selected valid plot key for `subject`."""
    choices = self.available_choices(subject)
    if not choices:
        return None
    fallback = self.default_plot_key if self.default_plot_key in choices else next(iter(choices.keys()))
    cur = self.val("plot_type", default=fallback)
    return str(cur) if cur in choices else fallback

selector_header_ui

selector_header_ui(subject: M) -> TagChild

Optional status or unlock hint badge rendered above the Plot Type dropdown.

Source code in src/shiny_kit/explorers.py
def selector_header_ui(self, subject: M) -> TagChild:
    """Optional status or unlock hint badge rendered above the Plot Type dropdown."""
    return None

server

server(id_: str) -> Any

Binds server logic, state lifecycle, declarative BaseBinders, and subcomponents to a session.

Source code in src/shiny_kit/component.py
def server(self, id_: str) -> Any:
    """Binds server logic, state lifecycle, declarative `BaseBinder`s, and subcomponents to a session."""
    self.module_id = id_

    @module.server
    def _server(input: Inputs, output: Outputs, session: Session) -> None:
        self._input = input
        self._output = output
        self._session = session
        self._state = self.state_cls.from_session(session)

        for name in self._registered_binders:
            binder: BaseBinder = getattr(self.__class__, name)
            binder.bind(self, input, output, session)

        for sub_name in self._registered_subcomponents:
            sub_desc: subcomponent[Any] = getattr(self.__class__, sub_name)
            child_inst: Component[Any] = getattr(self, sub_name)
            child_inst.server(sub_desc.child_id)

        if self.requires:
            self._bind_resource_gate(input, output, session)

        self._register_server()

    return _server(id_)

sync_version

sync_version(key: str, version: Any) -> None

Updates the recorded version for key without reporting a change.

Source code in src/shiny_kit/component.py
def sync_version(self, key: str, version: Any) -> None:
    """Updates the recorded version for `key` without reporting a change."""
    self._version_tracker[key] = version

ui classmethod

ui(id_: str, *args: Any, **kwargs: Any) -> Any

Constructs and namespaces the UI tree for this component, adding a resource-gate container if requires is set.

Source code in src/shiny_kit/component.py
@classmethod
def ui(cls, id_: str, *args: Any, **kwargs: Any) -> Any:
    """Constructs and namespaces the UI tree for this component, adding a resource-gate container if `requires` is set."""

    @module.ui
    def _ui() -> Any:
        content = cls._render_ui(*args, **kwargs)
        if not cls.requires:
            return content
        return shiny_ui.TagList(
            shiny_ui.output_ui("_gate_callout"),
            shiny_ui.div(content, id=module.resolve_id("_tab_content_wrapper")),
        )

    return _ui(id_)

unmet_requirements

unmet_requirements() -> list[Requirement]

Reactively returns the list of currently unmet Requirement rules.

Source code in src/shiny_kit/component.py
def unmet_requirements(self) -> list[Requirement]:
    """Reactively returns the list of currently unmet `Requirement` rules."""
    if not self.requires or self._state is None:
        return []
    return [r for r in self.requires if not r.is_met(self._state)]

val

val(id_: str, default: Any = None, *, isolate: bool = False, strip: bool = True) -> Any

Safely reads self.input[id_]() with fallback default, optional reactive isolation, and string stripping.

Source code in src/shiny_kit/component.py
def val(
    self,
    id_: str,
    default: Any = None,
    *,
    isolate: bool = False,
    strip: bool = True,
) -> Any:
    """Safely reads `self.input[id_]()` with fallback `default`, optional reactive isolation, and string stripping."""
    if self._input is None:
        return default

    def _read() -> Any:
        if self._input is None or id_ not in self._input:
            return default
        raw = self._input[id_]()
        if raw is None:
            return default
        if isinstance(raw, str):
            cleaned = raw.strip() if strip else raw
            if not cleaned and default is not None:
                return default
            return cleaned
        return raw

    if isolate:
        with reactive.isolate():
            return _read()
    return _read()

Introspective Widgets (shiny_kit.widgets)

Introspective widget builder (WidgetSelector) and docstring parser (DocstringParser).

Classes:

  • DocstringParser –

    Extracts parameter documentation from Google-style (Args:) and NumPy-style (Parameters) docstrings.

  • NumericBounds –

    Declarative numeric input constraints (min_val, max_val, step) for WidgetSelector.

  • ParameterSpec –

    Introspected metadata for a single __init__ parameter.

  • PlotterWidgetSelector –

    Specialized WidgetSelector for plotter classes with color palettes, hex color pickers, and grouped styling.

  • WidgetSelector –

    Introspects class parameters and maps them to PyShiny input widgets decorated with help tooltips.

DocstringParser

Extracts parameter documentation from Google-style (Args:) and NumPy-style (Parameters) docstrings.

Methods:

  • discover_subclasses –

    Recursively discovers all concrete (non-abstract) subclasses of base_cls.

  • display_label –

    Derives a clean human-readable display label from a class docstring or class name.

  • parse_parameters –

    Parses and caches parameter descriptions from a class and its MRO hierarchy docstrings.

discover_subclasses staticmethod

discover_subclasses[T](
    base_cls: type[T], *, exclude_names: set[str] | None = None
) -> dict[str, type[T]]

Recursively discovers all concrete (non-abstract) subclasses of base_cls.

Parameters:

  • base_cls

    (type[T]) –

    Abstract or base class whose concrete subclasses should be discovered.

  • exclude_names

    (set[str] | None, default: None ) –

    Optional set of subclass names to exclude from the returned mapping.

Returns:

  • dict[str, type[T]] –

    Dictionary mapping concrete subclass __name__ to the subclass type.

Source code in src/shiny_kit/widgets.py
@staticmethod
def discover_subclasses[T](
    base_cls: type[T],
    *,
    exclude_names: set[str] | None = None,
) -> dict[str, type[T]]:
    """Recursively discovers all concrete (non-abstract) subclasses of `base_cls`.

    Args:
        base_cls: Abstract or base class whose concrete subclasses should be discovered.
        exclude_names: Optional set of subclass names to exclude from the returned mapping.

    Returns:
        Dictionary mapping concrete subclass `__name__` to the subclass type.
    """
    excluded = exclude_names or set()
    found: dict[str, type[T]] = {}
    queue = list(base_cls.__subclasses__())
    while queue:
        sub = queue.pop(0)
        queue.extend(sub.__subclasses__())
        if (
            sub.__name__ not in excluded
            and not inspect.isabstract(sub)
            and not getattr(sub, "__abstractmethods__", None)
        ):
            found[sub.__name__] = sub
    return found

display_label staticmethod

display_label(
    target_cls: type[Any], strip_suffix: str = "", *, inherit_doc: bool = True
) -> str

Derives a clean human-readable display label from a class docstring or class name.

Parameters:

  • target_cls

    (type[Any]) –

    Class to inspect.

  • strip_suffix

    (str, default: '' ) –

    Optional class-name suffix to strip when falling back to __name__.

  • inherit_doc

    (bool, default: True ) –

    If True, inspects MRO via inspect.getdoc(target_cls); if False, only inspects target_cls.__dict__.get("__doc__").

Returns:

  • str –

    Human-readable label string.

Source code in src/shiny_kit/widgets.py
@staticmethod
def display_label(target_cls: type[Any], strip_suffix: str = "", *, inherit_doc: bool = True) -> str:
    """Derives a clean human-readable display label from a class docstring or class name.

    Args:
        target_cls: Class to inspect.
        strip_suffix: Optional class-name suffix to strip when falling back to `__name__`.
        inherit_doc: If `True`, inspects MRO via `inspect.getdoc(target_cls)`; if `False`,
            only inspects `target_cls.__dict__.get("__doc__")`.

    Returns:
        Human-readable label string.
    """
    doc = inspect.getdoc(target_cls) if inherit_doc else target_cls.__dict__.get("__doc__")
    if doc:
        first_line = doc.strip().splitlines()[0].strip().rstrip(".")
        if first_line:
            return first_line
    return target_cls.__name__.removesuffix(strip_suffix) if strip_suffix else target_cls.__name__

parse_parameters classmethod

parse_parameters(target_cls: type[Any]) -> dict[str, str]

Parses and caches parameter descriptions from a class and its MRO hierarchy docstrings.

Parameters:

  • target_cls

    (type[Any]) –

    Target class whose MRO docstrings should be inspected.

Returns:

  • dict[str, str] –

    Dictionary mapping parameter names to extracted one-line or multi-line descriptions.

Source code in src/shiny_kit/widgets.py
@classmethod
def parse_parameters(cls, target_cls: type[Any]) -> dict[str, str]:
    """Parses and caches parameter descriptions from a class and its MRO hierarchy docstrings.

    Args:
        target_cls: Target class whose MRO docstrings should be inspected.

    Returns:
        Dictionary mapping parameter names to extracted one-line or multi-line descriptions.
    """
    cached = cls._CACHE.get(target_cls)
    if cached is not None:
        return dict(cached)

    docs_to_search: list[str | None] = []
    for base in target_cls.__mro__:
        if base is object:
            continue
        docs_to_search.append(inspect.getdoc(base))
        if "__init__" in base.__dict__:
            docs_to_search.append(inspect.getdoc(base.__init__))

    params: dict[str, str] = {}
    for doc in docs_to_search:
        if not doc:
            continue

        if g_match := cls._GOOGLE_SECTION_RE.search(doc):
            section = g_match.group("content")
            for item in cls._GOOGLE_ITEM_RE.finditer(section):
                name = item.group("name")
                first = item.group("first_line").strip()
                cont_lines = [line.strip() for line in (item.group("cont") or "").splitlines() if line.strip()]
                desc = " ".join([first, *cont_lines]).strip()
                if desc and name not in params:
                    params[name] = desc

        if n_match := cls._NUMPY_SECTION_RE.search(doc):
            section = n_match.group("content")
            for item in cls._NUMPY_ITEM_RE.finditer(section):
                name = item.group("name")
                desc = " ".join(line.strip() for line in item.group("desc").splitlines() if line.strip())
                if desc and name not in params:
                    params[name] = desc

    cls._CACHE[target_cls] = params
    return dict(params)

NumericBounds dataclass

NumericBounds(
    min_val: float | int | None = None,
    max_val: float | int | None = None,
    step: float | int = 1,
)

Declarative numeric input constraints (min_val, max_val, step) for WidgetSelector.

ParameterSpec dataclass

ParameterSpec(
    name: str,
    annotation: Any,
    default: Any,
    docstring: str = "",
    is_optional: bool = False,
    bounds: NumericBounds | None = None,
)

Introspected metadata for a single __init__ parameter.

Attributes:

  • name (str) –

    Parameter name.

  • annotation (Any) –

    Unwrapped type annotation.

  • default (Any) –

    Default value from inspect.signature, or inspect.Parameter.empty.

  • docstring (str) –

    Extracted help text from Google or NumPy docstrings.

  • is_optional (bool) –

    Whether None is an allowed value for this parameter.

  • bounds (NumericBounds | None) –

    Optional resolved numeric bounds for integer/float inputs.

PlotterWidgetSelector

PlotterWidgetSelector()

              flowchart TD
              shiny_kit.widgets.PlotterWidgetSelector[PlotterWidgetSelector]
              shiny_kit.widgets.WidgetSelector[WidgetSelector]

                              shiny_kit.widgets.WidgetSelector --> shiny_kit.widgets.PlotterWidgetSelector
                


              click shiny_kit.widgets.PlotterWidgetSelector href "" "shiny_kit.widgets.PlotterWidgetSelector"
              click shiny_kit.widgets.WidgetSelector href "" "shiny_kit.widgets.WidgetSelector"
            

Specialized WidgetSelector for plotter classes with color palettes, hex color pickers, and grouped styling.

Methods:

  • build_form –

    Builds Plotter input widgets, grouping low-level styling & dimension controls into a collapsible accordion.

  • default –

    Returns the process-wide shared default selector instance for cls.

  • extract_kwargs –

    Extracts and casts values from Shiny's Inputs or a mapping to pass into target_cls(**kwargs).

  • introspect –

    Introspects and caches __init__ parameters, walking the MRO when **kwargs is forwarded to superclasses.

  • register –

    Decorator to register a custom widget builder rule (evaluated first).

Source code in src/shiny_kit/widgets.py
def __init__(self) -> None:
    super().__init__()
    self._register_plotter_rules()

build_form

build_form(
    target_cls: type[Any],
    *,
    exclude: set[str] | None = None,
    choices: Mapping[str, Sequence[str] | Mapping[str, str]] | None = None,
    prefix: str = "",
    group_styling: bool = True,
) -> list[TagChild]

Builds Plotter input widgets, grouping low-level styling & dimension controls into a collapsible accordion.

Source code in src/shiny_kit/widgets.py
def build_form(
    self,
    target_cls: type[Any],
    *,
    exclude: set[str] | None = None,
    choices: Mapping[str, Sequence[str] | Mapping[str, str]] | None = None,
    prefix: str = "",
    group_styling: bool = True,
) -> list[TagChild]:
    """Builds Plotter input widgets, grouping low-level styling & dimension controls into a collapsible accordion."""
    specs = self.introspect(target_cls, exclude=exclude)
    if not group_styling:
        return [self._build_single_widget(spec, choices=choices, prefix=prefix) for spec in specs]

    primary_widgets: list[TagChild] = []
    styling_widgets: list[TagChild] = []

    for spec in specs:
        w = self._build_single_widget(spec, choices=choices, prefix=prefix)
        if self._is_styling_param(spec):
            styling_widgets.append(w)
        else:
            primary_widgets.append(w)

    if styling_widgets:
        accordion_id = f"{prefix}styling_accordion" if prefix else "plotter_styling_accordion"
        primary_widgets.append(
            shiny_ui.accordion(
                shiny_ui.accordion_panel(
                    "Styling & Dimensions",
                    *styling_widgets,
                    value="styling_panel",
                ),
                id=accordion_id,
                open=False,
                class_="mt-2",
            )
        )

    return primary_widgets

default classmethod

default() -> Self

Returns the process-wide shared default selector instance for cls.

Source code in src/shiny_kit/widgets.py
@classmethod
def default(cls) -> Self:
    """Returns the process-wide shared default selector instance for `cls`."""
    inst = cls._DEFAULT_INSTANCES.get(cls)
    if not isinstance(inst, cls):
        inst = cls()
        cls._DEFAULT_INSTANCES[cls] = inst
    return inst

extract_kwargs

extract_kwargs(
    target_cls: type[Any],
    inputs: Inputs | Mapping[str, Any],
    *,
    exclude: set[str] | None = None,
    prefix: str = "",
) -> dict[str, Any]

Extracts and casts values from Shiny's Inputs or a mapping to pass into target_cls(**kwargs).

Source code in src/shiny_kit/widgets.py
def extract_kwargs(
    self,
    target_cls: type[Any],
    inputs: Inputs | Mapping[str, Any],
    *,
    exclude: set[str] | None = None,
    prefix: str = "",
) -> dict[str, Any]:
    """Extracts and casts values from Shiny's `Inputs` or a mapping to pass into `target_cls(**kwargs)`."""
    specs = self.introspect(target_cls, exclude=exclude)
    kwargs: dict[str, Any] = {}

    for spec in specs:
        input_id = f"{prefix}{spec.name}" if prefix else spec.name
        if input_id not in inputs:
            continue

        entry = inputs[input_id]
        raw_val = entry() if callable(entry) else entry

        if (raw_val is None or raw_val == "") and spec.is_optional:
            kwargs[spec.name] = None
        elif isinstance(spec.annotation, type) and issubclass(spec.annotation, Enum):
            if isinstance(raw_val, Enum):
                kwargs[spec.name] = raw_val
            elif str(raw_val) in spec.annotation.__members__:
                kwargs[spec.name] = spec.annotation[str(raw_val)]
            else:
                kwargs[spec.name] = spec.annotation(raw_val)
        elif spec.annotation is bool or isinstance(spec.default, bool):
            kwargs[spec.name] = bool(raw_val)
        elif spec.annotation is int or (isinstance(spec.default, int) and not isinstance(spec.default, bool)):
            kwargs[spec.name] = int(raw_val) if raw_val is not None and raw_val != "" else spec.default
        elif spec.annotation is float or isinstance(spec.default, float):
            kwargs[spec.name] = float(raw_val) if raw_val is not None and raw_val != "" else spec.default
        else:
            kwargs[spec.name] = raw_val

    return kwargs

introspect

introspect(
    target_cls: type[Any], *, exclude: set[str] | None = None
) -> list[ParameterSpec]

Introspects and caches __init__ parameters, walking the MRO when **kwargs is forwarded to superclasses.

Source code in src/shiny_kit/widgets.py
def introspect(self, target_cls: type[Any], *, exclude: set[str] | None = None) -> list[ParameterSpec]:
    """Introspects and caches `__init__` parameters, walking the MRO when `**kwargs` is forwarded to superclasses."""
    cls_excluded: set[str] = set()
    cls_constraints: dict[str, NumericBounds] = dict(self.numeric_constraints)
    for base in reversed(target_cls.__mro__):
        raw_excl = base.__dict__.get("__widget_exclude__")
        if isinstance(raw_excl, (set, frozenset, list, tuple)):
            cls_excluded.update(str(x) for x in raw_excl)
        raw_constr = base.__dict__.get("__widget_constraints__")
        if isinstance(raw_constr, Mapping):
            for k, v in raw_constr.items():
                coerced = self._coerce_numeric_bounds(v)
                if coerced is not None:
                    cls_constraints[str(k)] = coerced

    excluded = frozenset(self.default_exclude | cls_excluded | (exclude or set()) | {"self", "args", "kwargs"})
    cache_key = (type(self), target_cls, excluded)
    cached = self._INTROSPECT_CACHE.get(cache_key)
    if cached is not None:
        return list(cached)

    docs = DocstringParser.parse_parameters(target_cls)
    specs: list[ParameterSpec] = []
    seen_names: set[str] = set()

    for cls in target_cls.__mro__:
        if cls is object:
            break
        sig = inspect.signature(cls.__init__)
        has_var_keyword = False
        for name, param in sig.parameters.items():
            if param.kind == inspect.Parameter.VAR_KEYWORD:
                has_var_keyword = True
                continue
            if param.kind == inspect.Parameter.VAR_POSITIONAL:
                continue
            if name in excluded or name in seen_names:
                continue
            seen_names.add(name)
            annotation, is_optional = self._unwrap_annotation(param.annotation, param.default)
            specs.append(
                ParameterSpec(
                    name=name,
                    annotation=annotation,
                    default=param.default,
                    docstring=docs.get(name, ""),
                    is_optional=is_optional,
                    bounds=cls_constraints.get(name),
                )
            )
        if not has_var_keyword:
            break

    self._INTROSPECT_CACHE[cache_key] = tuple(specs)
    return specs

register

register(predicate: TypePredicate) -> Callable[[WidgetBuilder], WidgetBuilder]

Decorator to register a custom widget builder rule (evaluated first).

Source code in src/shiny_kit/widgets.py
def register(self, predicate: TypePredicate) -> Callable[[WidgetBuilder], WidgetBuilder]:
    """Decorator to register a custom widget builder rule (evaluated first)."""

    def decorator(builder: WidgetBuilder) -> WidgetBuilder:
        self._rules.insert(0, (predicate, builder))
        return builder

    return decorator

WidgetSelector

WidgetSelector()

Introspects class parameters and maps them to PyShiny input widgets decorated with help tooltips.

Target classes may optionally declare class attributes __widget_exclude__: ClassVar[Collection[str]] and __widget_constraints__: ClassVar[Mapping[str, NumericBounds | tuple[float | int | None, float | int | None, float | int]]] across their MRO to customize parameter exclusions and numeric input bounds without importing shiny_kit.

Attributes:

  • default_exclude (ClassVar[set[str]]) –

    Parameter names excluded by default during introspection.

  • non_ui_types (ClassVar[tuple[type[Any], ...]]) –

    Parameter types (such as RNG instances) that should be automatically omitted from generated UI forms.

Examples:

from shiny_kit.widgets import WidgetSelector

class ModelConfig:
    def __init__(self, alpha: float = 0.05, method: str = "exact") -> None:
        self.alpha = alpha
        self.method = method

selector = WidgetSelector.default()
widgets = selector.build_form(ModelConfig, prefix="cfg_")

Methods:

  • build_form –

    Builds a collection of Shiny input widgets for the target class.

  • default –

    Returns the process-wide shared default selector instance for cls.

  • extract_kwargs –

    Extracts and casts values from Shiny's Inputs or a mapping to pass into target_cls(**kwargs).

  • introspect –

    Introspects and caches __init__ parameters, walking the MRO when **kwargs is forwarded to superclasses.

  • register –

    Decorator to register a custom widget builder rule (evaluated first).

Source code in src/shiny_kit/widgets.py
def __init__(self) -> None:
    """Initializes `WidgetSelector` and registers default type-to-widget builder rules."""
    self._rules: list[tuple[TypePredicate, WidgetBuilder]] = []
    self._register_default_rules()

build_form

build_form(
    target_cls: type[Any],
    *,
    exclude: set[str] | None = None,
    choices: Mapping[str, Sequence[str] | Mapping[str, str]] | None = None,
    prefix: str = "",
) -> list[TagChild]

Builds a collection of Shiny input widgets for the target class.

Source code in src/shiny_kit/widgets.py
def build_form(
    self,
    target_cls: type[Any],
    *,
    exclude: set[str] | None = None,
    choices: Mapping[str, Sequence[str] | Mapping[str, str]] | None = None,
    prefix: str = "",
) -> list[TagChild]:
    """Builds a collection of Shiny input widgets for the target class."""
    specs = self.introspect(target_cls, exclude=exclude)
    return [self._build_single_widget(spec, choices=choices, prefix=prefix) for spec in specs]

default classmethod

default() -> Self

Returns the process-wide shared default selector instance for cls.

Source code in src/shiny_kit/widgets.py
@classmethod
def default(cls) -> Self:
    """Returns the process-wide shared default selector instance for `cls`."""
    inst = cls._DEFAULT_INSTANCES.get(cls)
    if not isinstance(inst, cls):
        inst = cls()
        cls._DEFAULT_INSTANCES[cls] = inst
    return inst

extract_kwargs

extract_kwargs(
    target_cls: type[Any],
    inputs: Inputs | Mapping[str, Any],
    *,
    exclude: set[str] | None = None,
    prefix: str = "",
) -> dict[str, Any]

Extracts and casts values from Shiny's Inputs or a mapping to pass into target_cls(**kwargs).

Source code in src/shiny_kit/widgets.py
def extract_kwargs(
    self,
    target_cls: type[Any],
    inputs: Inputs | Mapping[str, Any],
    *,
    exclude: set[str] | None = None,
    prefix: str = "",
) -> dict[str, Any]:
    """Extracts and casts values from Shiny's `Inputs` or a mapping to pass into `target_cls(**kwargs)`."""
    specs = self.introspect(target_cls, exclude=exclude)
    kwargs: dict[str, Any] = {}

    for spec in specs:
        input_id = f"{prefix}{spec.name}" if prefix else spec.name
        if input_id not in inputs:
            continue

        entry = inputs[input_id]
        raw_val = entry() if callable(entry) else entry

        if (raw_val is None or raw_val == "") and spec.is_optional:
            kwargs[spec.name] = None
        elif isinstance(spec.annotation, type) and issubclass(spec.annotation, Enum):
            if isinstance(raw_val, Enum):
                kwargs[spec.name] = raw_val
            elif str(raw_val) in spec.annotation.__members__:
                kwargs[spec.name] = spec.annotation[str(raw_val)]
            else:
                kwargs[spec.name] = spec.annotation(raw_val)
        elif spec.annotation is bool or isinstance(spec.default, bool):
            kwargs[spec.name] = bool(raw_val)
        elif spec.annotation is int or (isinstance(spec.default, int) and not isinstance(spec.default, bool)):
            kwargs[spec.name] = int(raw_val) if raw_val is not None and raw_val != "" else spec.default
        elif spec.annotation is float or isinstance(spec.default, float):
            kwargs[spec.name] = float(raw_val) if raw_val is not None and raw_val != "" else spec.default
        else:
            kwargs[spec.name] = raw_val

    return kwargs

introspect

introspect(
    target_cls: type[Any], *, exclude: set[str] | None = None
) -> list[ParameterSpec]

Introspects and caches __init__ parameters, walking the MRO when **kwargs is forwarded to superclasses.

Source code in src/shiny_kit/widgets.py
def introspect(self, target_cls: type[Any], *, exclude: set[str] | None = None) -> list[ParameterSpec]:
    """Introspects and caches `__init__` parameters, walking the MRO when `**kwargs` is forwarded to superclasses."""
    cls_excluded: set[str] = set()
    cls_constraints: dict[str, NumericBounds] = dict(self.numeric_constraints)
    for base in reversed(target_cls.__mro__):
        raw_excl = base.__dict__.get("__widget_exclude__")
        if isinstance(raw_excl, (set, frozenset, list, tuple)):
            cls_excluded.update(str(x) for x in raw_excl)
        raw_constr = base.__dict__.get("__widget_constraints__")
        if isinstance(raw_constr, Mapping):
            for k, v in raw_constr.items():
                coerced = self._coerce_numeric_bounds(v)
                if coerced is not None:
                    cls_constraints[str(k)] = coerced

    excluded = frozenset(self.default_exclude | cls_excluded | (exclude or set()) | {"self", "args", "kwargs"})
    cache_key = (type(self), target_cls, excluded)
    cached = self._INTROSPECT_CACHE.get(cache_key)
    if cached is not None:
        return list(cached)

    docs = DocstringParser.parse_parameters(target_cls)
    specs: list[ParameterSpec] = []
    seen_names: set[str] = set()

    for cls in target_cls.__mro__:
        if cls is object:
            break
        sig = inspect.signature(cls.__init__)
        has_var_keyword = False
        for name, param in sig.parameters.items():
            if param.kind == inspect.Parameter.VAR_KEYWORD:
                has_var_keyword = True
                continue
            if param.kind == inspect.Parameter.VAR_POSITIONAL:
                continue
            if name in excluded or name in seen_names:
                continue
            seen_names.add(name)
            annotation, is_optional = self._unwrap_annotation(param.annotation, param.default)
            specs.append(
                ParameterSpec(
                    name=name,
                    annotation=annotation,
                    default=param.default,
                    docstring=docs.get(name, ""),
                    is_optional=is_optional,
                    bounds=cls_constraints.get(name),
                )
            )
        if not has_var_keyword:
            break

    self._INTROSPECT_CACHE[cache_key] = tuple(specs)
    return specs

register

register(predicate: TypePredicate) -> Callable[[WidgetBuilder], WidgetBuilder]

Decorator to register a custom widget builder rule (evaluated first).

Source code in src/shiny_kit/widgets.py
def register(self, predicate: TypePredicate) -> Callable[[WidgetBuilder], WidgetBuilder]:
    """Decorator to register a custom widget builder rule (evaluated first)."""

    def decorator(builder: WidgetBuilder) -> WidgetBuilder:
        self._rules.insert(0, (predicate, builder))
        return builder

    return decorator

UI Primitives (shiny_kit.ui)

Stateless UI layout primitives (Icon, EmptyState, KPICard, KPIRow), reactive StagedList, and app footer/CLI helpers.

Classes:

  • EcosystemPillarSpec –

    Specification for an ecosystem pillar pill in package_footer().

  • EmptyState –

    Declarative empty-state placeholder card with a Lucide icon, title, and muted description.

  • Icon –

    Renders Lucide SVG icons wrapped in htmltools.HTML for PyShiny UI elements.

  • KPICard –

    Declarative summary metric card inside a KPIRow.

  • KPIRow –

    Responsive Bootstrap row of KPICard summary metrics.

  • PackageMetadata –

    Resolved Python distribution metadata extracted from importlib.metadata or pyproject.toml.

  • StagedList –

    Per-instance reactive list of staged items with UI badge and button helpers.

Functions:

  • build_cli_parser –

    Constructs a standard CLI argument parser for a shiny-kit web application.

  • package_footer –

    Renders a 2-zone glassmorphic application footer populated from PackageMetadata and optional ecosystem pills.

  • run_cli –

    Parses CLI arguments and launches the PyShiny application via shiny._main.run_app.

EcosystemPillarSpec dataclass

EcosystemPillarSpec(
    name: str,
    slug: str,
    icon: str,
    url: str,
    description: str,
    is_active: bool = False,
    color: str = "#7c3aed",
    dark_color: str | None = None,
)

Specification for an ecosystem pillar pill in package_footer().

Attributes:

  • name (str) –

    Display name of the pillar.

  • slug (str) –

    CSS modifier slug (e.g. "seroverse", "seroref", "kbank", "serogen", "seroepi").

  • icon (str) –

    Lucide icon identifier.

  • url (str) –

    Documentation or landing page URL.

  • description (str) –

    Tooltip summary of the pillar's role.

  • is_active (bool) –

    Whether this pillar corresponds to the running application.

  • color (str) –

    Light-mode accent color for the pillar icon/active badge.

  • dark_color (str | None) –

    Optional dark-mode accent color override.

EmptyState dataclass

EmptyState(
    icon: str,
    title: str,
    message: str,
    dashed: bool = False,
    compact: bool = False,
    icon_size: int = 36,
)

Declarative empty-state placeholder card with a Lucide icon, title, and muted description.

Attributes:

  • icon (str) –

    Lucide icon name.

  • title (str) –

    Primary heading text.

  • message (str) –

    Secondary explanatory paragraph text.

  • dashed (bool) –

    Whether to render a dashed border instead of a solid card background.

  • compact (bool) –

    Whether to use tighter vertical padding.

  • icon_size (int) –

    Pixel size of the Lucide icon. Defaults to 36.

Examples:

from shiny_kit.ui import EmptyState

placeholder = EmptyState(
    icon="database",
    title="No Dataset Loaded",
    message="Upload or select a benchmark cohort in the sidebar.",
)
tag = placeholder.ui()

Methods:

  • ui –

    Renders the empty-state Bootstrap container.

ui

ui() -> Tag

Renders the empty-state Bootstrap container.

Returns:

  • Tag –

    Bootstrap div tag containing the icon, title, and message.

Source code in src/shiny_kit/ui.py
def ui(self) -> Tag:
    """Renders the empty-state Bootstrap container.

    Returns:
        Bootstrap `div` tag containing the icon, title, and message.
    """
    pad_cls = "py-3 my-2" if self.compact else "py-5 my-3"
    border_cls = "border-dashed" if self.dashed else "bg-body-tertiary rounded border"
    return shiny_ui.div(
        Icon.lucide(self.icon, size=self.icon_size, class_="text-muted mb-2"),
        shiny_ui.h6(self.title, class_="text-secondary mt-1 mb-1"),
        shiny_ui.p(
            self.message,
            class_="text-muted small mb-0",
            style="max-width: 560px; margin: 0 auto;",
        ),
        class_=f"text-center {pad_cls} px-3 {border_cls}",
    )

Icon

Renders Lucide SVG icons wrapped in htmltools.HTML for PyShiny UI elements.

Examples:

from shiny_kit.ui import Icon

svg = Icon.lucide("database", size=16)

Methods:

  • github –

    Returns the official GitHub Octocat mark as an inline SVG inheriting currentColor.

  • lucide –

    Render a Lucide SVG icon wrapped in htmltools.HTML for Shiny UI integration.

  • pypi –

    Returns the official PyPI isometric cube mark as an inline SVG inheriting currentColor.

github classmethod

github(size: int = 14) -> HTML

Returns the official GitHub Octocat mark as an inline SVG inheriting currentColor.

Source code in src/shiny_kit/ui.py
@classmethod
def github(cls, size: int = 14) -> HTML:
    """Returns the official GitHub Octocat mark as an inline SVG inheriting `currentColor`."""
    return HTML(
        f'<svg xmlns="http://www.w3.org/2000/svg" width="{size}" height="{size}" '
        'viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">'
        '<path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 '
        "0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 "
        "1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 "
        "0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 "
        "1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 "
        "0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57 "
        'C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg>'
    )

lucide classmethod

lucide(
    name: str | None, size: int = 18, class_: str = "me-1", **kwargs: Any
) -> HTML | None

Render a Lucide SVG icon wrapped in htmltools.HTML for Shiny UI integration.

Parameters:

  • name

    (str | None) –

    Lucide icon identifier (e.g. "database", "shield-check"), or None.

  • size

    (int, default: 18 ) –

    Width and height of the rendered SVG in pixels. Defaults to 18.

  • class_

    (str, default: 'me-1' ) –

    CSS classes appended to "lucide-icon". Defaults to "me-1".

  • **kwargs

    (Any, default: {} ) –

    Additional SVG attributes forwarded to lucide._render_icon.

Returns:

  • HTML | None –

    htmltools.HTML containing the <svg> markup, or None if name is None.

Raises:

  • ValueError –

    If name does not match a valid Lucide icon.

Source code in src/shiny_kit/ui.py
@classmethod
def lucide(cls, name: str | None, size: int = 18, class_: str = "me-1", **kwargs: Any) -> HTML | None:
    """Render a Lucide SVG icon wrapped in `htmltools.HTML` for Shiny UI integration.

    Args:
        name: Lucide icon identifier (e.g. `"database"`, `"shield-check"`), or `None`.
        size: Width and height of the rendered SVG in pixels. Defaults to `18`.
        class_: CSS classes appended to `"lucide-icon"`. Defaults to `"me-1"`.
        **kwargs: Additional SVG attributes forwarded to `lucide._render_icon`.

    Returns:
        `htmltools.HTML` containing the `<svg>` markup, or `None` if `name` is `None`.

    Raises:
        ValueError: If `name` does not match a valid Lucide icon.
    """
    if name is None:
        return None

    icon_name = str(name).strip().lower().replace("_", "-")
    combined_class = f"lucide-icon {class_}".strip()
    try:
        if not kwargs:
            svg_str = cls._render_cached_svg(icon_name, size, combined_class)
        else:
            svg_str = lucide._render_icon(icon_name, size, **{"class": combined_class, **kwargs})
            if "xmlns=" not in svg_str.lower():
                svg_str = svg_str.replace("<svg ", '<svg xmlns="http://www.w3.org/2000/svg" ', 1)
    except (lucide.IconDoesNotExist, FileNotFoundError, AttributeError, ValueError) as err:
        raise ValueError(f"Icon '{name}' (normalized as '{icon_name}') does not exist in Lucide icon set.") from err

    return HTML(svg_str)

pypi classmethod

pypi(size: int = 14) -> HTML

Returns the official PyPI isometric cube mark as an inline SVG inheriting currentColor.

Source code in src/shiny_kit/ui.py
@classmethod
def pypi(cls, size: int = 14) -> HTML:
    """Returns the official PyPI isometric cube mark as an inline SVG inheriting `currentColor`."""
    return HTML(
        f'<svg xmlns="http://www.w3.org/2000/svg" width="{size}" height="{size}" '
        'viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" '
        'stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">'
        '<path d="M12 2L3 7l9 5 9-5-9-5z"/>'
        '<path d="M3 17l9 5 9-5"/>'
        '<path d="M3 12l9 5 9-5"/>'
        '<path d="M12 12v10"/>'
        '<path d="M3 7v10"/>'
        '<path d="M21 7v10"/>'
        "</svg>"
    )

KPICard dataclass

KPICard(
    label: str,
    value: str,
    subtitle: str = "",
    col_width: int = 3,
    value_class: str = "fs-5",
    subtitle_class: str = "text-muted",
    truncate_subtitle: bool = False,
    tooltip: str | None = None,
)

Declarative summary metric card inside a KPIRow.

Attributes:

  • label (str) –

    Muted top label describing the metric.

  • value (str) –

    Bold primary value text.

  • subtitle (str) –

    Secondary caption beneath the primary value.

  • col_width (int) –

    Bootstrap 12-column grid span (col-md-{col_width}). Defaults to 3.

  • value_class (str) –

    Additional CSS classes applied to the primary value element.

  • subtitle_class (str) –

    Additional CSS classes applied to the subtitle element.

  • truncate_subtitle (bool) –

    Whether to apply text-truncate and a hover title attribute to the subtitle.

  • tooltip (str | None) –

    Optional hover tooltip text for the subtitle.

Methods:

  • ui –

    Renders the responsive Bootstrap column and bordered metric card.

ui

ui() -> Tag

Renders the responsive Bootstrap column and bordered metric card.

Returns:

  • Tag –

    Bootstrap div column containing the formatted KPI card.

Source code in src/shiny_kit/ui.py
def ui(self) -> Tag:
    """Renders the responsive Bootstrap column and bordered metric card.

    Returns:
        Bootstrap `div` column containing the formatted KPI card.
    """
    sub_classes = f"small {self.subtitle_class}" + (" text-truncate" if self.truncate_subtitle else "")
    sub_kwargs: dict[str, Any] = {"class_": sub_classes}
    if self.tooltip or self.truncate_subtitle:
        sub_kwargs["title"] = self.tooltip or self.subtitle

    return shiny_ui.div(
        shiny_ui.div(
            shiny_ui.div(self.label, class_="text-muted small"),
            shiny_ui.div(self.value, class_=f"fw-bold {self.value_class}"),
            shiny_ui.div(self.subtitle, **sub_kwargs) if self.subtitle else None,
            class_="p-2 bg-body-tertiary rounded border h-100",
        ),
        class_=f"col-md-{self.col_width}",
    )

KPIRow dataclass

KPIRow(*cards: KPICard)

Responsive Bootstrap row of KPICard summary metrics.

Attributes:

  • cards (tuple[KPICard, ...]) –

    Tuple of KPICard specifications rendered across the row.

Parameters:

  • *cards

    (KPICard, default: () ) –

    Variable number of KPICard instances.

Methods:

  • ui –

    Renders the row g-2 mb-3 grid of KPI cards.

Source code in src/shiny_kit/ui.py
def __init__(self, *cards: KPICard) -> None:
    """Initializes `KPIRow` with one or more `KPICard` items.

    Args:
        *cards: Variable number of `KPICard` instances.
    """
    object.__setattr__(self, "cards", tuple(cards))

ui

ui() -> Tag

Renders the row g-2 mb-3 grid of KPI cards.

Returns:

  • Tag –

    Bootstrap div.row containing all rendered KPICard columns.

Source code in src/shiny_kit/ui.py
def ui(self) -> Tag:
    """Renders the `row g-2 mb-3` grid of KPI cards.

    Returns:
        Bootstrap `div.row` containing all rendered `KPICard` columns.
    """
    return shiny_ui.div(*(card.ui() for card in self.cards), class_="row g-2 mb-3")

PackageMetadata dataclass

PackageMetadata(
    name: str,
    version: str,
    summary: str,
    author: str,
    author_email: str,
    author_url: str,
    homepage: str,
    documentation: str,
    repository: str,
    bug_tracker: str,
    changelog: str,
    pypi_url: str,
    license_spdx: str = "GPL-3.0",
)

Resolved Python distribution metadata extracted from importlib.metadata or pyproject.toml.

Attributes:

  • name (str) –

    Distribution package name.

  • version (str) –

    Full PEP 440 version string (including any .dev or +g... suffix).

  • summary (str) –

    One-line package summary description.

  • author (str) –

    Primary author name.

  • author_email (str) –

    Primary author contact email address.

  • author_url (str) –

    Author website URL.

  • homepage (str) –

    Canonical documentation / homepage URL.

  • documentation (str) –

    Documentation site URL.

  • repository (str) –

    Source code GitHub repository URL.

  • bug_tracker (str) –

    Issue tracker URL.

  • changelog (str) –

    Changelog URL.

  • pypi_url (str) –

    PyPI package project URL.

  • license_spdx (str) –

    Short license identifier ("GPL-3.0").

Methods:

  • load –

    Loads distribution metadata via importlib.metadata with a pyproject.toml fallback.

short_version property

short_version: str

Returns the version string with any local +g... git build suffix removed for compact display.

load cached classmethod

load(
    package_name: str = "shiny-kit", *, pyproject_path: Path | None = None
) -> PackageMetadata

Loads distribution metadata via importlib.metadata with a pyproject.toml fallback.

Parameters:

  • package_name

    (str, default: 'shiny-kit' ) –

    Installed Python distribution name to inspect.

  • pyproject_path

    (Path | None, default: None ) –

    Optional explicit path to pyproject.toml when uninstalled.

Returns:

Source code in src/shiny_kit/ui.py
@classmethod
@lru_cache(maxsize=8)
def load(
    cls,
    package_name: str = "shiny-kit",
    *,
    pyproject_path: Path | None = None,
) -> "PackageMetadata":
    """Loads distribution metadata via `importlib.metadata` with a `pyproject.toml` fallback.

    Args:
        package_name: Installed Python distribution name to inspect.
        pyproject_path: Optional explicit path to `pyproject.toml` when uninstalled.

    Returns:
        Populated `PackageMetadata` instance.
    """
    name = package_name
    version = "0.1.0"
    summary = f"Interactive PyShiny application for {package_name}"
    author = ""
    author_email = ""
    author_url = ""
    urls: dict[str, str] = {}

    mod_name = package_name.replace("-", "_")
    try:
        ver_mod = importlib.import_module(f"{mod_name}._version")
        vcs_version = getattr(ver_mod, "__version__", None)
        if isinstance(vcs_version, str) and vcs_version:
            version = vcs_version
    except Exception:
        pass

    try:
        meta = importlib.metadata.metadata(package_name)
        name = meta.get("Name") or name
        version = meta.get("Version") or version
        summary = meta.get("Summary") or summary

        raw_author = meta.get("Author")
        raw_author_email = meta.get("Author-email")
        if raw_author_email:
            parsed_name, parsed_email = parseaddr(raw_author_email)
            if parsed_name:
                author = parsed_name
            if parsed_email:
                author_email = parsed_email
        if raw_author:
            author = raw_author

        project_urls = meta.get_all("Project-URL") or []
        for entry in project_urls:
            if "," in entry:
                label, url_val = entry.split(",", 1)
                urls[label.strip().lower()] = url_val.strip()
    except importlib.metadata.PackageNotFoundError:
        candidate = pyproject_path or (Path.cwd() / "pyproject.toml")
        if candidate.is_file():
            try:
                data = tomllib.loads(candidate.read_text(encoding="utf-8"))
                proj = data.get("project", {})
                name = str(proj.get("name", name))
                summary = str(proj.get("description", summary))
                authors = proj.get("authors", [])
                if authors and isinstance(authors[0], dict):
                    author = str(authors[0].get("name", author))
                    author_email = str(authors[0].get("email", author_email))
                    author_url = str(authors[0].get("url", author_url))
                for label, url_val in proj.get("urls", {}).items():
                    urls[str(label).strip().lower()] = str(url_val).strip()
            except Exception:
                pass

    repo_url = urls.get("repository", "")
    homepage_url = urls.get("homepage", repo_url or "#")
    docs_url = urls.get("documentation", homepage_url)
    bugs_url = urls.get("bug tracker", f"{repo_url}/issues" if repo_url else "#")
    changelog_url = urls.get("changelog", f"{repo_url}/blob/master/CHANGELOG.md" if repo_url else "#")
    resolved_author_url = (
        urls.get("author")
        or urls.get("author url")
        or author_url
        or (f"mailto:{author_email}" if author_email else "#")
    )

    return cls(
        name=name,
        version=version,
        summary=summary,
        author=author,
        author_email=author_email,
        author_url=resolved_author_url,
        homepage=homepage_url,
        documentation=docs_url,
        repository=repo_url or "#",
        bug_tracker=bugs_url,
        changelog=changelog_url,
        pypi_url=f"https://pypi.org/project/{name}/",
    )

StagedList

StagedList(initial: Iterable[T] = ())

Per-instance reactive list of staged items with UI badge and button helpers.

Methods:

  • action_buttons –

    Renders a compact horizontal toolbar of Add / Remove Last / Clear action buttons.

  • add –

    Appends item to the staged collection.

  • clear –

    Clears all staged items.

  • pop –

    Removes and returns the last staged item, if any.

  • remove_at –

    Removes the item at index if in range.

  • render_badges –

    Renders the staged items using formatter(index, item) or an empty caption.

  • set –

    Replaces the staged items.

Attributes:

  • is_empty (bool) –

    Reactively returns True when no items are staged.

  • items (list[T]) –

    Reactively returns the staged items as a list.

Source code in src/shiny_kit/ui.py
def __init__(self, initial: Iterable[T] = ()) -> None:
    self._val: reactive.Value[tuple[T, ...]] = reactive.value(tuple(initial))

is_empty property

is_empty: bool

Reactively returns True when no items are staged.

items property

items: list[T]

Reactively returns the staged items as a list.

action_buttons staticmethod

action_buttons(
    add_id: str,
    *,
    add_label: str = "Add",
    pop_id: str | None = None,
    pop_label: str = "Remove Last",
    clear_id: str | None = None,
    clear_label: str = "Clear",
) -> Tag

Renders a compact horizontal toolbar of Add / Remove Last / Clear action buttons.

Source code in src/shiny_kit/ui.py
@staticmethod
def action_buttons(
    add_id: str,
    *,
    add_label: str = "Add",
    pop_id: str | None = None,
    pop_label: str = "Remove Last",
    clear_id: str | None = None,
    clear_label: str = "Clear",
) -> Tag:
    """Renders a compact horizontal toolbar of Add / Remove Last / Clear action buttons."""
    btns: list[TagChild] = [
        shiny_ui.input_action_button(
            add_id,
            add_label,
            icon=Icon.lucide("plus", size=14),
            class_="btn-sm btn-outline-primary flex-fill",
        )
    ]
    if pop_id:
        btns.append(
            shiny_ui.input_action_button(
                pop_id,
                pop_label,
                class_="btn-sm btn-outline-secondary",
            )
        )
    if clear_id:
        btns.append(
            shiny_ui.input_action_button(
                clear_id,
                clear_label,
                icon=Icon.lucide("x", size=14),
                class_="btn-sm btn-outline-danger",
            )
        )
    return shiny_ui.div(*btns, class_="d-flex gap-1 mt-1 mb-2")

add

add(item: T, *, deduplicate: bool = False) -> None

Appends item to the staged collection.

Source code in src/shiny_kit/ui.py
def add(self, item: T, *, deduplicate: bool = False) -> None:
    """Appends `item` to the staged collection."""
    with reactive.isolate():
        cur = list(self._val.get())
    if deduplicate and item in cur:
        cur = [x for x in cur if x != item]
    cur.append(item)
    self._val.set(tuple(cur))

clear

clear() -> None

Clears all staged items.

Source code in src/shiny_kit/ui.py
def clear(self) -> None:
    """Clears all staged items."""
    self._val.set(())

pop

pop() -> T | None

Removes and returns the last staged item, if any.

Source code in src/shiny_kit/ui.py
def pop(self) -> T | None:
    """Removes and returns the last staged item, if any."""
    with reactive.isolate():
        cur = list(self._val.get())
    if not cur:
        return None
    removed = cur.pop()
    self._val.set(tuple(cur))
    return removed

remove_at

remove_at(index: int) -> None

Removes the item at index if in range.

Source code in src/shiny_kit/ui.py
def remove_at(self, index: int) -> None:
    """Removes the item at `index` if in range."""
    with reactive.isolate():
        cur = list(self._val.get())
    if 0 <= index < len(cur):
        cur.pop(index)
        self._val.set(tuple(cur))

render_badges

render_badges(
    formatter: Callable[[int, T], TagChild],
    *,
    empty_text: str = "No items staged yet.",
    wrapper_class: str = "",
) -> TagChild

Renders the staged items using formatter(index, item) or an empty caption.

Source code in src/shiny_kit/ui.py
def render_badges(
    self,
    formatter: Callable[[int, T], TagChild],
    *,
    empty_text: str = "No items staged yet.",
    wrapper_class: str = "",
) -> TagChild:
    """Renders the staged items using `formatter(index, item)` or an empty caption."""
    cur = self.items
    if not cur:
        return shiny_ui.div(
            empty_text,
            class_="text-muted small fst-italic p-2 border rounded bg-body-tertiary",
        )
    return shiny_ui.div(
        *(formatter(idx, item) for idx, item in enumerate(cur, start=1)),
        class_=wrapper_class or None,
    )

set

set(items: Iterable[T]) -> None

Replaces the staged items.

Source code in src/shiny_kit/ui.py
def set(self, items: Iterable[T]) -> None:
    """Replaces the staged items."""
    self._val.set(tuple(items))

build_cli_parser

build_cli_parser(
    prog: str,
    *,
    package_name: str | None = None,
    description: str = "Launch the interactive PyShiny web application.",
    version: str | None = None,
    default_host: str = "127.0.0.1",
    default_port: int = 8000,
) -> ArgumentParser

Constructs a standard CLI argument parser for a shiny-kit web application.

Parameters:

  • prog

    (str) –

    CLI program name (e.g. "seroepi-app").

  • package_name

    (str | None, default: None ) –

    Optional distribution name used to resolve --version via PackageMetadata.load().

  • description

    (str, default: 'Launch the interactive PyShiny web application.' ) –

    CLI help description string.

  • version

    (str | None, default: None ) –

    Optional explicit version string overriding PackageMetadata.load(package_name).version.

  • default_host

    (str, default: '127.0.0.1' ) –

    Default bind host address. Defaults to "127.0.0.1".

  • default_port

    (int, default: 8000 ) –

    Default bind port number. Defaults to 8000.

Returns:

Source code in src/shiny_kit/ui.py
def build_cli_parser(
    prog: str,
    *,
    package_name: str | None = None,
    description: str = "Launch the interactive PyShiny web application.",
    version: str | None = None,
    default_host: str = "127.0.0.1",
    default_port: int = 8000,
) -> argparse.ArgumentParser:
    """Constructs a standard CLI argument parser for a `shiny-kit` web application.

    Args:
        prog: CLI program name (e.g. `"seroepi-app"`).
        package_name: Optional distribution name used to resolve `--version` via `PackageMetadata.load()`.
        description: CLI help description string.
        version: Optional explicit version string overriding `PackageMetadata.load(package_name).version`.
        default_host: Default bind host address. Defaults to `"127.0.0.1"`.
        default_port: Default bind port number. Defaults to `8000`.

    Returns:
        Configured `argparse.ArgumentParser`.
    """
    resolved_ver = version
    if resolved_ver is None and package_name:
        resolved_ver = PackageMetadata.load(package_name).version
    resolved_ver = resolved_ver or "0.1.0"

    parser = argparse.ArgumentParser(prog=prog, description=description)
    parser.add_argument(
        "-V",
        "--version",
        action="version",
        version=f"%(prog)s {resolved_ver}",
    )
    parser.add_argument(
        "-H",
        "--host",
        type=str,
        default=default_host,
        help=f"Host interface to bind the server to (default: {default_host}).",
    )
    parser.add_argument(
        "-p",
        "--port",
        type=int,
        default=default_port,
        help=f"Port number to bind the server to (default: {default_port}; use 0 for a random available port).",
    )
    parser.add_argument(
        "-r",
        "--reload",
        action=argparse.BooleanOptionalAction,
        default=False,
        help="Enable or disable auto-reload on source file changes (default: disabled).",
    )
    parser.add_argument(
        "-b",
        "--launch-browser",
        action=argparse.BooleanOptionalAction,
        default=True,
        help="Automatically open the application in the default web browser (default: enabled).",
    )
    return parser
package_footer(
    package_name: str = "shiny-kit",
    *,
    brand_title: str | None = None,
    brand_icon: str = "chart-network",
    author_url: str | None = None,
    meta: PackageMetadata | None = None,
    pillars: Sequence[EcosystemPillarSpec] = (),
) -> Tag

Renders a 2-zone glassmorphic application footer populated from PackageMetadata and optional ecosystem pills.

Parameters:

  • package_name

    (str, default: 'shiny-kit' ) –

    Distribution name used when meta is omitted.

  • brand_title

    (str | None, default: None ) –

    Display title for the brand link. Defaults to meta.name.

  • brand_icon

    (str, default: 'chart-network' ) –

    Lucide icon name for the brand mark. Defaults to "chart-network".

  • author_url

    (str | None, default: None ) –

    Optional explicit author profile URL overriding meta.author_url.

  • meta

    (PackageMetadata | None, default: None ) –

    Optional pre-loaded PackageMetadata instance.

  • pillars

    (Sequence[EcosystemPillarSpec], default: () ) –

    Optional sequence of EcosystemPillarSpec items rendered in the right zone.

Returns:

  • Tag –

    shiny.ui.Tag <footer> element suitable for shiny.ui.page_navbar(footer=...).

Source code in src/shiny_kit/ui.py
def package_footer(
    package_name: str = "shiny-kit",
    *,
    brand_title: str | None = None,
    brand_icon: str = "chart-network",
    author_url: str | None = None,
    meta: PackageMetadata | None = None,
    pillars: Sequence[EcosystemPillarSpec] = (),
) -> Tag:
    """Renders a 2-zone glassmorphic application footer populated from `PackageMetadata` and optional ecosystem pills.

    Args:
        package_name: Distribution name used when `meta` is omitted.
        brand_title: Display title for the brand link. Defaults to `meta.name`.
        brand_icon: Lucide icon name for the brand mark. Defaults to `"chart-network"`.
        author_url: Optional explicit author profile URL overriding `meta.author_url`.
        meta: Optional pre-loaded `PackageMetadata` instance.
        pillars: Optional sequence of `EcosystemPillarSpec` items rendered in the right zone.

    Returns:
        `shiny.ui.Tag` `<footer>` element suitable for `shiny.ui.page_navbar(footer=...)`.
    """
    pkg = meta or PackageMetadata.load(package_name)
    display_brand = brand_title or pkg.name
    resolved_author_url = author_url or pkg.author_url

    left_zone = shiny_ui.div(
        shiny_ui.a(
            Icon.lucide(brand_icon, size=16),
            shiny_ui.span(display_brand),
            href=pkg.homepage,
            target="_blank",
            rel="noopener",
            title=pkg.summary,
            class_="sv-footer-pkg-brand",
        ),
        shiny_ui.a(
            f"v{pkg.short_version}",
            href=pkg.changelog,
            target="_blank",
            rel="noopener",
            title=f"{pkg.name} v{pkg.version} ({pkg.license_spdx} License) — View Changelog",
            class_="sv-footer-version-badge",
        ),
        shiny_ui.span("·", class_="sv-footer-meta-sep"),
        shiny_ui.span(
            "by ",
            shiny_ui.a(
                pkg.author,
                href=resolved_author_url,
                target="_blank",
                rel="noopener",
                title=f"{pkg.author} ({pkg.author_email})",
            ),
            class_="sv-footer-author",
        ),
        shiny_ui.span("·", class_="sv-footer-meta-sep"),
        shiny_ui.div(
            shiny_ui.a(
                Icon.lucide("book-open", size=13),
                shiny_ui.span("Docs"),
                href=pkg.documentation,
                target="_blank",
                rel="noopener",
                title=f"Documentation ({pkg.documentation})",
                class_="sv-footer-icon-link",
            ),
            shiny_ui.a(
                Icon.github(size=13),
                shiny_ui.span("GitHub"),
                href=pkg.repository,
                target="_blank",
                rel="noopener",
                title=f"GitHub Repository ({pkg.repository})",
                class_="sv-footer-icon-link",
            ),
            shiny_ui.a(
                Icon.pypi(size=13),
                shiny_ui.span("PyPI"),
                href=pkg.pypi_url,
                target="_blank",
                rel="noopener",
                title=f"PyPI Package ({pkg.pypi_url})",
                class_="sv-footer-icon-link",
            ),
            shiny_ui.a(
                Icon.lucide("bug", size=13),
                shiny_ui.span("Issues"),
                href=pkg.bug_tracker,
                target="_blank",
                rel="noopener",
                title=f"Bug Tracker ({pkg.bug_tracker})",
                class_="sv-footer-icon-link",
            ),
            class_="sv-footer-icon-links",
        ),
        class_="sv-footer-left",
    )

    pillar_nodes = [
        shiny_ui.a(
            Icon.lucide(pillar.icon, size=13),
            shiny_ui.span(pillar.name),
            href=pillar.url,
            target="_blank",
            rel="noopener",
            title=pillar.description,
            style=f"--pill-color: {pillar.color}; --pill-dark-color: {pillar.dark_color or pillar.color};",
            class_=(
                f"sv-footer-eco-pill sv-footer-eco-pill--{pillar.slug}" + (" is-active" if pillar.is_active else "")
            ),
        )
        for pillar in pillars
    ]

    right_zone = (
        shiny_ui.div(
            shiny_ui.div(*pillar_nodes, class_="sv-footer-eco-pills"),
            class_="sv-footer-right",
        )
        if pillar_nodes
        else None
    )

    return shiny_ui.tags.footer(
        shiny_ui.head_content(shiny_ui.tags.style(FOOTER_CSS)),
        shiny_ui.div(
            left_zone,
            right_zone,
            class_="sv-app-footer-inner",
        ),
        class_="sv-app-footer",
    )

run_cli

run_cli(
    app_target: str,
    *,
    parser: ArgumentParser | None = None,
    prog: str = "shiny-kit-app",
    package_name: str | None = None,
    description: str = "Launch the interactive PyShiny web application.",
    reload_dirs: Sequence[str | Path] | None = None,
    app_dir: str | Path | None = None,
    argv: Sequence[str] | None = None,
) -> int

Parses CLI arguments and launches the PyShiny application via shiny._main.run_app.

Parameters:

  • app_target

    (str) –

    Module/attribute target string passed to run_app (e.g. "seroepi.app.app:app").

  • parser

    (ArgumentParser | None, default: None ) –

    Optional pre-built argparse.ArgumentParser.

  • prog

    (str, default: 'shiny-kit-app' ) –

    CLI program name when parser is not supplied.

  • package_name

    (str | None, default: None ) –

    Optional distribution name when parser is not supplied.

  • description

    (str, default: 'Launch the interactive PyShiny web application.' ) –

    CLI description when parser is not supplied.

  • reload_dirs

    (Sequence[str | Path] | None, default: None ) –

    Optional directories watched when --reload is active.

  • app_dir

    (str | Path | None, default: None ) –

    Optional root directory added to sys.path by run_app.

  • argv

    (Sequence[str] | None, default: None ) –

    Optional sequence of command-line arguments (defaults to sys.argv[1:]).

Returns:

  • int –

    Exit status code 0.

Source code in src/shiny_kit/ui.py
def run_cli(
    app_target: str,
    *,
    parser: argparse.ArgumentParser | None = None,
    prog: str = "shiny-kit-app",
    package_name: str | None = None,
    description: str = "Launch the interactive PyShiny web application.",
    reload_dirs: Sequence[str | Path] | None = None,
    app_dir: str | Path | None = None,
    argv: Sequence[str] | None = None,
) -> int:
    """Parses CLI arguments and launches the PyShiny application via `shiny._main.run_app`.

    Args:
        app_target: Module/attribute target string passed to `run_app` (e.g. `"seroepi.app.app:app"`).
        parser: Optional pre-built `argparse.ArgumentParser`.
        prog: CLI program name when `parser` is not supplied.
        package_name: Optional distribution name when `parser` is not supplied.
        description: CLI description when `parser` is not supplied.
        reload_dirs: Optional directories watched when `--reload` is active.
        app_dir: Optional root directory added to `sys.path` by `run_app`.
        argv: Optional sequence of command-line arguments (defaults to `sys.argv[1:]`).

    Returns:
        Exit status code `0`.
    """
    eff_parser = parser or build_cli_parser(prog, package_name=package_name, description=description)
    args = eff_parser.parse_args(list(argv) if argv is not None else None)
    run_app(
        app_target,
        host=args.host,
        port=args.port,
        reload=bool(args.reload),
        reload_dirs=[str(d) for d in reload_dirs] if reload_dirs else None,
        app_dir=str(app_dir) if app_dir is not None else None,
        launch_browser=bool(args.launch_browser),
    )
    return 0

Caching (shiny_kit.caching)

Pure-stdlib two-tier content-addressable cache with LRU memory and pluggable disk persistence.

Classes:

  • BaseCache –

    Thread-safe two-tier content-addressable cache (in-memory LRU + pluggable disk tier).

BaseCache

BaseCache(
    cache_dir: str | Path | None = None,
    *,
    max_memory_items: int = 64,
    persist_to_disk: bool = True,
)

Thread-safe two-tier content-addressable cache (in-memory LRU + pluggable disk tier).

Provides deterministic SHA-256 fingerprinting across primitives, paths, dataclasses, collections, and objects implementing __cache_fingerprint__(), alongside single-flight per-key locking so concurrent callers computing the same key wait on the first computation.

Subclasses can override _update_custom_hasher() to fingerprint domain-specific types (e.g. DataFrames or ML estimators) and _dump_to_disk() / _load_from_disk() for custom serialization formats (e.g. joblib).

Attributes:

  • disk_extension (ClassVar[str]) –

    File extension used for disk cache entries. Defaults to ".pkl".

  • env_prefix (ClassVar[str]) –

    Environment variable prefix ({env_prefix}_CACHE_DIR, {env_prefix}_HOME).

  • default_home_dirname (ClassVar[str]) –

    Default home directory folder name. Defaults to ".shiny-kit".

Parameters:

  • cache_dir

    (str | Path | None, default: None ) –

    Optional directory path for persistent disk storage.

  • max_memory_items

    (int, default: 64 ) –

    Maximum number of items retained in the in-memory LRU tier.

  • persist_to_disk

    (bool, default: True ) –

    Whether to persist cached values to disk.

Methods:

  • acall_or_load –

    Asynchronously executes await fn(*args, **kwargs) with single-flight fingerprint caching.

  • call_or_load –

    Executes fn(*args, **kwargs) with thread-safe two-tier fingerprint caching.

  • clear –

    Clears both the in-memory LRU cache and persistent disk artifacts.

  • compute_fit_key –

    Computes a composite cache key for fitting estimator on (X, y).

  • fingerprint –

    Computes a deterministic 32-character SHA-256 hex digest for obj.

  • fit_or_load –

    Returns a cached fitted clone of estimator on (X, y), or fits and caches it on miss.

  • get –

    Looks up a cached artifact by fingerprint key across memory and disk tiers.

  • get_default_cache_dir –

    Resolves the persistent cache directory from environment variables or ~/.shiny-kit/cache.

  • put –

    Stores an artifact in the in-memory LRU and optional disk tier atomically.

Source code in src/shiny_kit/caching.py
def __init__(
    self,
    cache_dir: str | Path | None = None,
    *,
    max_memory_items: int = 64,
    persist_to_disk: bool = True,
) -> None:
    """Initializes `BaseCache` with an in-memory LRU and optional disk directory.

    Args:
        cache_dir: Optional directory path for persistent disk storage.
        max_memory_items: Maximum number of items retained in the in-memory LRU tier.
        persist_to_disk: Whether to persist cached values to disk.
    """
    self.cache_dir = Path(cache_dir) if cache_dir is not None else self.get_default_cache_dir()
    self.max_memory_items = max_memory_items
    self.persist_to_disk = persist_to_disk
    self._lru: OrderedDict[str, Any] = OrderedDict()
    self._lock = threading.RLock()
    self._inflight_locks: weakref.WeakValueDictionary[str, _SyncKeyLock] = weakref.WeakValueDictionary()
    self._async_inflight_locks: weakref.WeakValueDictionary[tuple[int, str], asyncio.Lock] = (
        weakref.WeakValueDictionary()
    )
    self.hits: int = 0
    self.misses: int = 0

acall_or_load async

acall_or_load[R](
    fn: Callable[..., Awaitable[R]],
    *args: Any,
    force_refresh: bool = False,
    **kwargs: Any,
) -> R

Asynchronously executes await fn(*args, **kwargs) with single-flight fingerprint caching.

Parameters:

  • fn

    (Callable[..., Awaitable[R]]) –

    Async callable to await on cache miss.

  • *args

    (Any, default: () ) –

    Positional arguments forwarded to fn.

  • force_refresh

    (bool, default: False ) –

    If True, bypasses cached values and recomputes fn.

  • **kwargs

    (Any, default: {} ) –

    Keyword arguments forwarded to fn.

Returns:

  • R –

    Cached or freshly computed return value R.

Source code in src/shiny_kit/caching.py
async def acall_or_load[R](
    self,
    fn: Callable[..., Awaitable[R]],
    *args: Any,
    force_refresh: bool = False,
    **kwargs: Any,
) -> R:
    """Asynchronously executes `await fn(*args, **kwargs)` with single-flight fingerprint caching.

    Args:
        fn: Async callable to await on cache miss.
        *args: Positional arguments forwarded to `fn`.
        force_refresh: If `True`, bypasses cached values and recomputes `fn`.
        **kwargs: Keyword arguments forwarded to `fn`.

    Returns:
        Cached or freshly computed return value `R`.
    """
    key = self._call_key(fn, args, kwargs)
    if not force_refresh:
        with self._lock:
            hit, cached = self._lru_get(key)
            if hit:
                self.hits += 1
                return cached

    async_lock = self._get_async_key_lock(key)
    async with async_lock:
        if not force_refresh:
            hit, cached = self.get(key)
            if hit:
                return cached
        res = await fn(*args, **kwargs)
        return self.put(key, res)

call_or_load

call_or_load[R](
    fn: Callable[..., R], *args: Any, force_refresh: bool = False, **kwargs: Any
) -> R

Executes fn(*args, **kwargs) with thread-safe two-tier fingerprint caching.

Parameters:

  • fn

    (Callable[..., R]) –

    Callable to execute on cache miss.

  • *args

    (Any, default: () ) –

    Positional arguments forwarded to fn.

  • force_refresh

    (bool, default: False ) –

    If True, bypasses cached values and recomputes fn.

  • **kwargs

    (Any, default: {} ) –

    Keyword arguments forwarded to fn.

Returns:

  • R –

    Cached or freshly computed return value R.

Source code in src/shiny_kit/caching.py
def call_or_load[R](
    self,
    fn: Callable[..., R],
    *args: Any,
    force_refresh: bool = False,
    **kwargs: Any,
) -> R:
    """Executes `fn(*args, **kwargs)` with thread-safe two-tier fingerprint caching.

    Args:
        fn: Callable to execute on cache miss.
        *args: Positional arguments forwarded to `fn`.
        force_refresh: If `True`, bypasses cached values and recomputes `fn`.
        **kwargs: Keyword arguments forwarded to `fn`.

    Returns:
        Cached or freshly computed return value `R`.
    """
    key = self._call_key(fn, args, kwargs)
    if not force_refresh:
        with self._lock:
            hit, cached = self._lru_get(key)
            if hit:
                self.hits += 1
                return cached

    key_lock = self._get_key_lock(key)
    with key_lock:
        if not force_refresh:
            hit, cached = self.get(key)
            if hit:
                return cached
        res = fn(*args, **kwargs)
        return self.put(key, res)

clear

clear() -> None

Clears both the in-memory LRU cache and persistent disk artifacts.

Source code in src/shiny_kit/caching.py
def clear(self) -> None:
    """Clears both the in-memory LRU cache and persistent disk artifacts."""
    with self._lock:
        self._lru.clear()
        self._inflight_locks.clear()
        self._async_inflight_locks.clear()
        self.hits = 0
        self.misses = 0
        artifacts_dir = self.cache_dir / "artifacts"
        if artifacts_dir.exists():
            shutil.rmtree(artifacts_dir, ignore_errors=True)

compute_fit_key

compute_fit_key(estimator: Any, X: Any, y: Any = None) -> str

Computes a composite cache key for fitting estimator on (X, y).

Parameters:

  • estimator

    (Any) –

    Unfitted or configured estimator instance.

  • X

    (Any) –

    Input data object.

  • y

    (Any, default: None ) –

    Optional target argument.

Returns:

  • str –

    32-character SHA-256 fingerprint string.

Source code in src/shiny_kit/caching.py
def compute_fit_key(self, estimator: Any, X: Any, y: Any = None) -> str:
    """Computes a composite cache key for fitting `estimator` on `(X, y)`.

    Args:
        estimator: Unfitted or configured estimator instance.
        X: Input data object.
        y: Optional target argument.

    Returns:
        32-character SHA-256 fingerprint string.
    """
    return self.fingerprint(("fit", estimator, X, y))

fingerprint classmethod

fingerprint(obj: Any) -> str

Computes a deterministic 32-character SHA-256 hex digest for obj.

Parameters:

  • obj

    (Any) –

    Arbitrary Python object, dataclass, mapping, sequence, or domain object.

Returns:

  • str –

    32-character hexadecimal SHA-256 fingerprint string.

Source code in src/shiny_kit/caching.py
@classmethod
def fingerprint(cls, obj: Any) -> str:
    """Computes a deterministic 32-character SHA-256 hex digest for `obj`.

    Args:
        obj: Arbitrary Python object, dataclass, mapping, sequence, or domain object.

    Returns:
        32-character hexadecimal SHA-256 fingerprint string.
    """
    hasher = hashlib.sha256()
    cls._update_hasher(hasher, obj)
    return hasher.hexdigest()[:32]

fit_or_load

fit_or_load[E](estimator: E, X: Any = None, y: Any = None) -> E

Returns a cached fitted clone of estimator on (X, y), or fits and caches it on miss.

Uses per-key single-flight locking so concurrent sessions fitting the same model and dataset wait on the first fit and share its cached output without redundant execution. Captures and re-emits user-facing warnings via WarningCatcher.

Parameters:

  • estimator

    (E) –

    Unfitted or configured estimator implementing .fit(X) / .fit(X, y).

  • X

    (Any, default: None ) –

    Input data (or None when wrapping pre-fitted models).

  • y

    (Any, default: None ) –

    Optional target argument.

Returns:

  • E –

    The fitted estimator instance.

Source code in src/shiny_kit/caching.py
def fit_or_load[E](self, estimator: E, X: Any = None, y: Any = None) -> E:
    """Returns a cached fitted clone of `estimator` on `(X, y)`, or fits and caches it on miss.

    Uses per-key single-flight locking so concurrent sessions fitting the same model and
    dataset wait on the first fit and share its cached output without redundant execution.
    Captures and re-emits user-facing warnings via `WarningCatcher`.

    Args:
        estimator: Unfitted or configured estimator implementing `.fit(X)` / `.fit(X, y)`.
        X: Input data (or `None` when wrapping pre-fitted models).
        y: Optional target argument.

    Returns:
        The fitted estimator instance.
    """
    key = self.compute_fit_key(estimator, X, y)
    with self._lock:
        hit, cached = self._lru_get(key)
        if hit:
            self.hits += 1
            WarningCatcher.emit_messages(getattr(cached, "warnings_", ()), stacklevel=2)
            return cached

    key_lock = self._get_key_lock(key)
    with key_lock:
        hit, cached = self.get(key)
        if hit:
            WarningCatcher.emit_messages(getattr(cached, "warnings_", ()), stacklevel=2)
            return cached

        fitted: E = self._clone_estimator(estimator)
        with WarningCatcher() as catcher:
            if y is not None:
                fitted.fit(X, y)  # ty: ignore[unresolved-attribute]
            else:
                fitted.fit(X)  # ty: ignore[unresolved-attribute]
        inner_warnings = getattr(getattr(fitted, "estimator_", None), "warnings_", ())
        own_warnings = getattr(fitted, "warnings_", ())
        merged_warnings = WarningCatcher.merge(inner_warnings, own_warnings, catcher.messages)
        try:
            fitted.warnings_ = merged_warnings  # ty: ignore[unresolved-attribute]
        except Exception:
            pass
        stored = self.put(key, fitted)
        WarningCatcher.emit_messages(merged_warnings, stacklevel=2)
        return stored

get

get(key: str) -> tuple[bool, Any]

Looks up a cached artifact by fingerprint key across memory and disk tiers.

Parameters:

  • key

    (str) –

    32-character hexadecimal fingerprint key.

Returns:

  • tuple[bool, Any] –

    Tuple of (hit, value) where hit is True if found in memory or disk cache.

Source code in src/shiny_kit/caching.py
def get(self, key: str) -> tuple[bool, Any]:
    """Looks up a cached artifact by fingerprint key across memory and disk tiers.

    Args:
        key: 32-character hexadecimal fingerprint key.

    Returns:
        Tuple of `(hit, value)` where `hit` is `True` if found in memory or disk cache.
    """
    with self._lock:
        hit, val = self._lru_get(key)
        if hit:
            self.hits += 1
            return True, val

    if self.persist_to_disk:
        path = self._disk_path(key)
        if path.exists():
            try:
                val = self._load_from_disk(path)
                with self._lock:
                    self._lru_put(key, val)
                    self.hits += 1
                return True, val
            except Exception:
                path.unlink(missing_ok=True)

    with self._lock:
        self.misses += 1
    return False, None

get_default_cache_dir classmethod

get_default_cache_dir() -> Path

Resolves the persistent cache directory from environment variables or ~/.shiny-kit/cache.

Source code in src/shiny_kit/caching.py
@classmethod
def get_default_cache_dir(cls) -> Path:
    """Resolves the persistent cache directory from environment variables or `~/.shiny-kit/cache`."""
    if custom := os.environ.get(f"{cls.env_prefix}_CACHE_DIR"):
        return Path(custom).expanduser().resolve()
    base = Path(os.environ.get(f"{cls.env_prefix}_HOME", f"~/{cls.default_home_dirname}")).expanduser().resolve()
    return base / "cache"

put

put(key: str, value: Any) -> Any

Stores an artifact in the in-memory LRU and optional disk tier atomically.

Parameters:

  • key

    (str) –

    32-character hexadecimal fingerprint key.

  • value

    (Any) –

    Artifact to cache.

Returns:

  • Any –

    The stored value.

Source code in src/shiny_kit/caching.py
def put(self, key: str, value: Any) -> Any:
    """Stores an artifact in the in-memory LRU and optional disk tier atomically.

    Args:
        key: 32-character hexadecimal fingerprint key.
        value: Artifact to cache.

    Returns:
        The stored `value`.
    """
    self._lru_put(key, value)
    if self.persist_to_disk:
        path = self._disk_path(key)
        path.parent.mkdir(parents=True, exist_ok=True)
        tmp_path = path.with_suffix(f".tmp.{os.getpid()}.{uuid4().hex[:8]}")
        try:
            self._dump_to_disk(value, tmp_path)
            tmp_path.replace(path)
        except Exception:
            tmp_path.unlink(missing_ok=True)
            raise
    return value

Progress Reporting (shiny_kit.progress)

Zero-dependency, context-propagated hierarchical progress reporting.

Classes:

  • LazyShinyProgress –

    Thread-safe lazy bridge from ProgressReporter to shiny.ui.Progress.

  • ProgressReporter –

    Hierarchical, rate-throttled progress reporter with sub-range composition.

Functions:

  • current_progress –

    Returns the context-local ProgressReporter (or a zero-cost no-op reporter if unbound).

  • progress_context –

    Binds a root ProgressReporter to the current execution context (propagates into asyncio.to_thread).

LazyShinyProgress

LazyShinyProgress(
    session: Session | None,
    *,
    default_message: str = "Working...",
    grace_sec: float = 0.15,
    loop: AbstractEventLoop | None = None,
    progress_factory: Callable[[], Any] | None = None,
)

Thread-safe lazy bridge from ProgressReporter to shiny.ui.Progress.

Suppresses opening the Shiny progress notification if the task finishes within grace_sec (default 150ms), so instant BaseCache hits or trivial operations never flash a progress bar. If the task is still running after grace_sec, opens and updates shiny.ui.Progress on the main asyncio event loop.

Methods:

  • close –

    Cancels any pending grace timer and closes the Shiny progress bar if opened.

  • update –

    Records the latest milestone and dispatches to the Shiny event loop if past grace period.

Source code in src/shiny_kit/progress.py
def __init__(
    self,
    session: Session | None,
    *,
    default_message: str = "Working...",
    grace_sec: float = 0.15,
    loop: asyncio.AbstractEventLoop | None = None,
    progress_factory: Callable[[], Any] | None = None,
) -> None:
    self._session = session
    self._grace_sec = max(0.0, float(grace_sec))
    self._loop = loop
    self._progress_factory = progress_factory
    self._start_time = time.monotonic()
    self._latest: tuple[float, str | None, str | None] = (0.0, default_message, None)
    self._progress: Any | None = None
    self._timer: asyncio.TimerHandle | None = None
    self._closed: bool = False

    if self._loop is not None and (self._session is not None or self._progress_factory is not None):
        if self._grace_sec <= 0.0:
            self._flush_on_loop()
        else:
            self._timer = self._loop.call_later(self._grace_sec, self._flush_on_loop)

close

close() -> None

Cancels any pending grace timer and closes the Shiny progress bar if opened.

Source code in src/shiny_kit/progress.py
def close(self) -> None:
    """Cancels any pending grace timer and closes the Shiny progress bar if opened."""
    if self._closed:
        return
    self._closed = True
    if self._timer is not None:
        self._timer.cancel()
        self._timer = None
    if self._progress is not None:
        try:
            self._progress.close()
        finally:
            self._progress = None

update

update(fraction: float, message: str | None, detail: str | None) -> None

Records the latest milestone and dispatches to the Shiny event loop if past grace period.

Source code in src/shiny_kit/progress.py
def update(self, fraction: float, message: str | None, detail: str | None) -> None:
    """Records the latest milestone and dispatches to the Shiny event loop if past grace period."""
    if self._closed:
        return
    prev_msg = self._latest[1]
    self._latest = (fraction, message if message is not None else prev_msg, detail)
    if self._loop is None or (self._session is None and self._progress_factory is None):
        return
    elapsed = time.monotonic() - self._start_time
    if self._progress is not None or elapsed >= self._grace_sec:
        try:
            self._loop.call_soon_threadsafe(self._flush_on_loop)
        except RuntimeError:
            pass

ProgressReporter dataclass

ProgressReporter(
    _shared: _SharedProgressState = (lambda: _SharedProgressState(callback=None))(),
    start: float = 0.0,
    end: float = 1.0,
    default_message: str | None = None,
    detail_prefix: str | None = None,
)

Hierarchical, rate-throttled progress reporter with sub-range composition.

Maps local progress fractions in [0.0, 1.0] into [start, end] of the root task, ensuring nested estimators and cross-validation folds advance monotonically from 0% to 100%.

Attributes:

  • start (float) –

    Lower bound of this reporter's progress interval within [0.0, 1.0].

  • end (float) –

    Upper bound of this reporter's progress interval within [0.0, 1.0].

  • default_message (str | None) –

    Default primary status message when report() omits message.

  • detail_prefix (str | None) –

    Optional hierarchical prefix prepended to detail strings.

Examples:

from shiny_kit.progress import current_progress, progress_context

events = []
with progress_context(lambda frac, msg, det: events.append((frac, msg, det))):
    prog = current_progress()
    prog.report(0.2, message="Loading data")
    with prog.subrange(0.2, 1.0, detail_prefix="Fold 1"):
        current_progress().report(0.5, detail="Fitting")

Methods:

  • report –

    Emits a progress update mapped into [self.start, self.end].

  • subrange –

    Scopes nested current_progress() calls into [start, end] of the current range.

is_active property

is_active: bool

Whether an active listener callback is bound to this reporter.

Returns:

  • bool –

    True if a ProgressCallback is attached, False for the default no-op reporter.

report

report(
    fraction: float,
    message: str | None = None,
    detail: str | None = None,
    *,
    force: bool = False,
) -> None

Emits a progress update mapped into [self.start, self.end].

Parameters:

  • fraction

    (float) –

    Local completion fraction in [0.0, 1.0].

  • message

    (str | None, default: None ) –

    Optional primary status heading.

  • detail

    (str | None, default: None ) –

    Optional secondary detail string.

  • force

    (bool, default: False ) –

    If True, bypasses rate-limiting min_interval_sec throttling.

Source code in src/shiny_kit/progress.py
def report(
    self,
    fraction: float,
    message: str | None = None,
    detail: str | None = None,
    *,
    force: bool = False,
) -> None:
    """Emits a progress update mapped into `[self.start, self.end]`.

    Args:
        fraction: Local completion fraction in `[0.0, 1.0]`.
        message: Optional primary status heading.
        detail: Optional secondary detail string.
        force: If `True`, bypasses rate-limiting `min_interval_sec` throttling.
    """
    cb = self._shared.callback
    if cb is None:
        return

    if message is not None and self.detail_prefix is None:
        self.default_message = message

    clamped = min(1.0, max(0.0, float(fraction)))
    span = max(0.0, self.end - self.start)
    mapped = min(1.0, max(self._shared.max_fraction, self.start + clamped * span))

    now = time.monotonic()
    if (
        not force
        and self._shared.min_interval_sec > 0.0
        and clamped < 1.0
        and (now - self._shared.last_emit_time) < self._shared.min_interval_sec
    ):
        return

    self._shared.last_emit_time = now
    self._shared.max_fraction = mapped

    if self.detail_prefix is not None:
        eff_message = self.default_message if self.default_message is not None else message
    else:
        eff_message = message if message is not None else self.default_message
    if self.detail_prefix and detail:
        eff_detail: str | None = f"{self.detail_prefix} · {detail}"
    elif self.detail_prefix:
        eff_detail = self.detail_prefix
    else:
        eff_detail = detail

    cb(mapped, eff_message, eff_detail)

subrange

subrange(
    start: float,
    end: float,
    *,
    message: str | None = None,
    detail_prefix: str | None = None,
) -> Generator[ProgressReporter]

Scopes nested current_progress() calls into [start, end] of the current range.

Parameters:

  • start

    (float) –

    Relative start fraction in [0.0, 1.0] within the parent reporter's range.

  • end

    (float) –

    Relative end fraction in [start, 1.0] within the parent reporter's range.

  • message

    (str | None, default: None ) –

    Optional override for default_message inside the subrange.

  • detail_prefix

    (str | None, default: None ) –

    Optional prefix prepended to nested detail strings.

Yields:

Source code in src/shiny_kit/progress.py
@contextmanager
def subrange(
    self,
    start: float,
    end: float,
    *,
    message: str | None = None,
    detail_prefix: str | None = None,
) -> Generator["ProgressReporter"]:
    """Scopes nested `current_progress()` calls into `[start, end]` of the current range.

    Args:
        start: Relative start fraction in `[0.0, 1.0]` within the parent reporter's range.
        end: Relative end fraction in `[start, 1.0]` within the parent reporter's range.
        message: Optional override for `default_message` inside the subrange.
        detail_prefix: Optional prefix prepended to nested `detail` strings.

    Yields:
        Scoped child `ProgressReporter` bound to `_ACTIVE_PROGRESS`.
    """
    if not self.is_active:
        yield self
        return

    s_clamped = min(1.0, max(0.0, float(start)))
    e_clamped = min(1.0, max(s_clamped, float(end)))
    span = max(0.0, self.end - self.start)
    child_start = self.start + s_clamped * span
    child_end = self.start + e_clamped * span

    if self.detail_prefix and detail_prefix:
        combined_prefix: str | None = f"{self.detail_prefix} · {detail_prefix}"
    else:
        combined_prefix = detail_prefix or self.detail_prefix

    child = ProgressReporter(
        _shared=self._shared,
        start=child_start,
        end=child_end,
        default_message=message if message is not None else self.default_message,
        detail_prefix=combined_prefix,
    )
    token = _ACTIVE_PROGRESS.set(child)
    try:
        yield child
    finally:
        _ACTIVE_PROGRESS.reset(token)

current_progress

current_progress() -> ProgressReporter

Returns the context-local ProgressReporter (or a zero-cost no-op reporter if unbound).

Returns:

  • ProgressReporter –

    Active ProgressReporter for the current execution context.

Source code in src/shiny_kit/progress.py
def current_progress() -> ProgressReporter:
    """Returns the context-local `ProgressReporter` (or a zero-cost no-op reporter if unbound).

    Returns:
        Active `ProgressReporter` for the current execution context.
    """
    active = _ACTIVE_PROGRESS.get()
    return active if active is not None else _NOOP_REPORTER

progress_context

progress_context(
    callback: ProgressCallback,
    *,
    message: str | None = None,
    min_interval_sec: float = 0.0,
) -> Generator[ProgressReporter]

Binds a root ProgressReporter to the current execution context (propagates into asyncio.to_thread).

Parameters:

  • callback

    (ProgressCallback) –

    Listener callable (fraction, message, detail) -> None invoked on progress updates.

  • message

    (str | None, default: None ) –

    Optional initial default status message.

  • min_interval_sec

    (float, default: 0.0 ) –

    Minimum elapsed seconds between consecutive non-final progress emissions.

Yields:

Source code in src/shiny_kit/progress.py
@contextmanager
def progress_context(
    callback: ProgressCallback,
    *,
    message: str | None = None,
    min_interval_sec: float = 0.0,
) -> Generator[ProgressReporter]:
    """Binds a root `ProgressReporter` to the current execution context (propagates into `asyncio.to_thread`).

    Args:
        callback: Listener callable `(fraction, message, detail) -> None` invoked on progress updates.
        message: Optional initial default status message.
        min_interval_sec: Minimum elapsed seconds between consecutive non-final progress emissions.

    Yields:
        Root `ProgressReporter` bound to the current `contextvars` context.
    """
    shared = _SharedProgressState(callback=callback, min_interval_sec=min_interval_sec)
    reporter = ProgressReporter(_shared=shared, start=0.0, end=1.0, default_message=message)
    token = _ACTIVE_PROGRESS.set(reporter)
    try:
        yield reporter
    finally:
        _ACTIVE_PROGRESS.reset(token)

Warning Capture (shiny_kit.warnings)

Context manager for intercepting, filtering, and deduplicating user-facing warnings.

Classes:

  • WarningCatcher –

    Context manager that intercepts, filters, deduplicates, and optionally re-emits user-facing warnings.

WarningCatcher

WarningCatcher(*, reemit: bool = False)

Context manager that intercepts, filters, deduplicates, and optionally re-emits user-facing warnings.

Attributes:

  • reemit_on_exit (bool) –

    Whether captured warnings should be re-emitted via warnings.warn on __exit__.

  • messages (tuple[str, ...]) –

    Deduplicated tuple of captured warning strings after context exit.

Examples:

import warnings
from shiny_kit.warnings import WarningCatcher

with WarningCatcher() as catcher:
    warnings.warn("Unpadded strata detected", UserWarning)
assert catcher.messages == ("Unpadded strata detected",)

Parameters:

  • reemit

    (bool, default: False ) –

    Whether to re-emit captured user warnings when exiting the with block.

Methods:

  • __enter__ –

    Enters the warnings.catch_warnings(record=True) context.

  • __exit__ –

    Exits the warning capture context, populates self.messages, and optionally re-emits.

  • emit_messages –

    Emits each message in messages as a standard UserWarning.

  • is_relevant –

    Returns True if w is an explicit UserWarning or originates from non-library application code.

  • merge –

    Deduplicates warning messages across iterables while preserving first-seen order.

Source code in src/shiny_kit/warnings.py
def __init__(self, *, reemit: bool = False) -> None:
    """Initializes a `WarningCatcher` context manager.

    Args:
        reemit: Whether to re-emit captured user warnings when exiting the `with` block.
    """
    self.reemit_on_exit = reemit
    self.messages: tuple[str, ...] = ()
    self._catch_ctx: Any = None
    self._recorded: list[warnings.WarningMessage] | None = None

__enter__

__enter__() -> Self

Enters the warnings.catch_warnings(record=True) context.

Source code in src/shiny_kit/warnings.py
def __enter__(self) -> Self:
    """Enters the `warnings.catch_warnings(record=True)` context."""
    self._catch_ctx = warnings.catch_warnings(record=True)
    self._recorded = self._catch_ctx.__enter__()
    warnings.simplefilter("always")
    return self

__exit__

__exit__(
    exc_type: type[BaseException] | None,
    exc_val: BaseException | None,
    exc_tb: TracebackType | None,
) -> None

Exits the warning capture context, populates self.messages, and optionally re-emits.

Source code in src/shiny_kit/warnings.py
def __exit__(
    self,
    exc_type: type[BaseException] | None,
    exc_val: BaseException | None,
    exc_tb: TracebackType | None,
) -> None:
    """Exits the warning capture context, populates `self.messages`, and optionally re-emits."""
    if self._catch_ctx is not None:
        self._catch_ctx.__exit__(exc_type, exc_val, exc_tb)
    raw_msgs = [str(w.message).strip() for w in (self._recorded or []) if self.is_relevant(w)]
    self.messages = self.merge(raw_msgs)
    if self.reemit_on_exit and exc_type is None and self.messages:
        self.emit_messages(self.messages, stacklevel=3)

emit_messages staticmethod

emit_messages(messages: Iterable[str], *, stacklevel: int = 2) -> None

Emits each message in messages as a standard UserWarning.

Parameters:

  • messages

    (Iterable[str]) –

    Iterable of warning strings to emit.

  • stacklevel

    (int, default: 2 ) –

    Stack level passed to warnings.warn. Defaults to 2.

Source code in src/shiny_kit/warnings.py
@staticmethod
def emit_messages(messages: Iterable[str], *, stacklevel: int = 2) -> None:
    """Emits each message in `messages` as a standard `UserWarning`.

    Args:
        messages: Iterable of warning strings to emit.
        stacklevel: Stack level passed to `warnings.warn`. Defaults to `2`.
    """
    for msg in WarningCatcher.merge(messages):
        warnings.warn(msg, UserWarning, stacklevel=stacklevel)

is_relevant staticmethod

is_relevant(w: WarningMessage) -> bool

Returns True if w is an explicit UserWarning or originates from non-library application code.

Parameters:

  • w

    (WarningMessage) –

    Recorded warnings.WarningMessage instance.

Returns:

  • bool –

    True if the warning should be surfaced to the user, False otherwise.

Source code in src/shiny_kit/warnings.py
@staticmethod
def is_relevant(w: warnings.WarningMessage) -> bool:
    """Returns True if `w` is an explicit `UserWarning` or originates from non-library application code.

    Args:
        w: Recorded `warnings.WarningMessage` instance.

    Returns:
        `True` if the warning should be surfaced to the user, `False` otherwise.
    """
    if issubclass(w.category, (DeprecationWarning, PendingDeprecationWarning, ResourceWarning, ImportWarning)):
        return False
    if issubclass(w.category, UserWarning):
        return True
    norm_file = (w.filename or "").replace("\\", "/")
    return bool(norm_file and "/site-packages/" not in norm_file and "/lib/python" not in norm_file)

merge staticmethod

merge(*message_iters: Iterable[str]) -> tuple[str, ...]

Deduplicates warning messages across iterables while preserving first-seen order.

Parameters:

  • *message_iters

    (Iterable[str], default: () ) –

    Variable number of iterables yielding warning strings.

Returns:

  • tuple[str, ...] –

    Deduplicated tuple of stripped, non-empty warning messages.

Source code in src/shiny_kit/warnings.py
@staticmethod
def merge(*message_iters: Iterable[str]) -> tuple[str, ...]:
    """Deduplicates warning messages across iterables while preserving first-seen order.

    Args:
        *message_iters: Variable number of iterables yielding warning strings.

    Returns:
        Deduplicated tuple of stripped, non-empty warning messages.
    """
    seen: dict[str, None] = {}
    for it in message_iters:
        for msg in it:
            clean = str(msg).strip()
            if clean and clean not in seen:
                seen[clean] = None
    return tuple(seen.keys())