Shiny for Python

repository·main·Indexed 23 days ago

https://github.com/posit-dev/py-shiny

A web development framework for building fast, interactive, and scalable web applications using Python. It supports rapid prototyping and large-scale applications, featuring a CLI for app scaffolding and execution, a specialized express workflow with decorators like @expressify and @hold, and integrated OpenTelemetry support for internal telemetry collection.

Tokens
19.2K
Snippets
41
Records
128
Agent score
82%

What's inside shiny

  1. Develop a custom Shiny component with React and TypeScript

    main

    This template demonstrates how to structure a Python package that includes a custom JavaScript/React component for Shiny. The project is split into a TypeScript source directory for the component logic and a Python package directory that bundles the compiled JavaScript assets.

    Project Structure

    • srcts/index.ts: Defines the input component using TypeScript.
    • custom_component/custom_component.py: Contains the Python interface for the component.
    • custom_component/__init__.py: Defines the Python package exports.
    • custom_component/distjs/: The destination folder for bundled JavaScript files.
    • example-app/app.py: A sample Shiny application demonstrating the component.
  2. Develop a custom Shiny input component package

    main

    This repository template demonstrates how to structure a Python package that defines a custom input component for Shiny, combining TypeScript for the frontend logic and Python for the backend interface.

    Project Structure

    • package.json: Manages JavaScript dependencies for building component assets.
    • srcts/index.ts: Contains the TypeScript definition for the input component.
    • custom_component/custom_component.py: Contains the Python functions and classes for the input component.
    • custom_component/__init__.py: Defines the Python package exports.
    • custom_component/distjs/: The destination folder for bundled JavaScript files.
    • example-app/app.py: A sample Shiny application demonstrating the custom component.
  3. How bookmark state components work

    main

    Bookmark state is composed of two distinct parts:

    1. Input Values

    By default, all input values are captured. To prevent specific inputs from being saved, add their IDs to the session.bookmark.exclude list.

    Note: Input values are only restored during UI rendering and server-side dynamic UI. They are provided as a courtesy during on_restore and on_restored callbacks.

    2. Custom Values

    You can store arbitrary data in the values dictionary of the BookmarkState object. Unlike inputs, custom values are never restored via the UI automatically; they are only available within the server via @session.on_restore or @session.on_restored hooks. You must manually update the UI with these values if needed.

    # Exclude an input
    session.bookmark.exclude.append("input_id")
    
    # Store a custom value
    @session.bookmark.on_bookmark
    async def _(state: BookmarkState):
        state.values["custom_data"] = "some value"
  4. Understand the Shiny bookmarking lifecycle

    main

    The bookmarking process follows a specific sequence of callbacks and execution stages:

    1. Restoration (When a user opens a bookmarked URL)

    1. @session.bookmark.on_restore: These callbacks execute first. Use them to call ui.update_input(name, value) using values from the state object.
    2. Reactive Expressions: All reactive expressions run.
    3. @session.bookmark.on_restored: These callbacks execute last, after all reactive expressions have finished.

    2. Creation (When a user requests a bookmark)

    1. @session.bookmark.on_bookmark: These callbacks execute first. Use them to set the values that will be saved in state.values.
    2. Saving: The bookmark state is saved to the URL or disk.
    3. @session.bookmark.on_bookmarked: These callbacks execute last. This is where you can handle the resulting URL (e.g., updating the browser's query string).
    # --- RESTORATION EXAMPLE ---
    @session.bookmark.on_restore
    def _(state):
        # Use bookmark values to update inputs
        ui.update_input("input_id", state.values["some_value"])
    
    @session.bookmark.on_restored
    def _(state):
        # Handle any post-restoration tasks
        pass
    
    # --- CREATION EXAMPLE ---
    @session.bookmark.on_bookmark
    def _(state):
        # Set bookmark values
        state.values["some_value"] = compute_value()
    
    @session.bookmark.on_bookmarked
    async def _(url):
        # Handle the bookmark URL
        await session.bookmark.update_query_string(url)
  5. Working with htmltools in Shiny components

    main

    When implementing components using htmltools, follow these patterns:

    • Mutability: Tag objects are mutable by default. Use copy.copy() if you need to modify a tag without affecting the original.
    • Attributes and Classes: Use .add_class(), .add_style(), and .attrs to manipulate CSS classes, styles, and HTML attributes.
    • Tag Creation: Create new tags using ui.tags and add attributes or children directly. For example: ui.tags.div(class_='fw-bold', ...).
    • Inline Styles: Use the css() helper for inline styles.
    • Children: Tag children can be strings, Tags, TagLists, or None.
  6. Structure of a custom Shiny component package

    main

    A custom Shiny component package typically consists of a Python package containing the logic and a JavaScript/TypeScript source directory for the UI components. The standard structure is:

    • package.json: Contains dependencies for building the JavaScript components.
    • srcts/: Contains the TypeScript source files (e.g., index.ts) where the component is defined.
    • custom_component/: The Python package directory.
      • custom_component.py: Contains the Python functions/classes for the component.
      • __init__.py: Defines the Python package exports.
      • distjs/: The destination for bundled JavaScript files.
    • example-app/: Contains an app.py to demonstrate the component.
    package.json        # Contains the dependencies needed to build the components javascript
    srcts/              # Source Typescript files
      index.ts          # Where we define the input component
    custom_component/
      custom_component.py   # Python functions for the input component
      __init__.py       # Used to define exports for python package.
      distjs/           # Where the bundled js files are put
    example-app/
      app.py            # Example app for the custom-input component
  7. Add a Playwright controller for input components

    main

    If the ported component is an input component, you must create a Playwright controller for end-to-end testing in shiny/playwright/controller/_input_fields.py (or a new file).

    Implementation Steps:

    1. Create a class inheriting from appropriate mixins (e.g., _SetTextM, _ExpectTextInputValueM).
    2. Implement __init__, set, and interaction methods.
    3. Implement expectation methods (e.g., expect_value, expect_placeholder).
    4. Export the new controller class in shiny/playwright/controller/__init__.py by adding it to __all__.
    class InputSubmitTextarea(
        _SetTextM,
        WidthContainerStyleM,
        _ExpectTextInputValueM,
        _ExpectPlaceholderAttrM,
        _ExpectRowsAttrM,
        UiWithLabel,
    ):
        """Controller for :func:`shiny.ui.input_submit_textarea`."""
    
        loc_button: Locator
    
        def __init__(self, page: Page, id: str) -> None:
            super().__init__(
                page,
                id=id,
                loc=f"textarea#{id}.form-control",
            )
            self.loc_button = self.loc_container.locator(".bslib-submit-textarea-btn")
    
        def set(self, value: str, *, submit: bool = False, timeout: Timeout = None) -> None:
            # Implementation
            pass
  8. Build the Shiny for Python API documentation

    main

    To generate and view the Shiny for Python API documentation locally, you must first have Quarto installed on your system. The build process involves installing Python dependencies, generating .qmd files via quartodoc, and then rendering the site with Quarto.

    Follow these steps in order:

    1. Install dependencies: Run make deps to install the necessary Python packages and Quarto extensions.
    2. Generate API files: Run make quartodoc to build the .qmd files into the api/ directory.
    3. Build and serve: Use make serve to build the site and start a local server that watches for changes to the .qmd files.

    If you only want to build the site once without a continuous watch mode, use make site.

    # Install build dependencies
    make deps
    
    # Build the .qmd files for Shiny
    make quartodoc
    
    # Build the docs, serve them locally, and watch for changes
    make serve
    
    # Alternatively, build the site just once
    make site
  9. Identify core source files in bslib for porting

    main

    When studying a bslib feature to port it to py-shiny, locate these specific file types in the bslib repository:

    • R implementation: R/[feature-name].R (The main component functions).
    • TypeScript bindings: srcts/src/components/[featureName].ts (Client-side behavior).
    • SCSS styles: inst/components/scss/[feature_name].scss (Component styles).
    • Unit tests: tests/testthat/test-[feature-name].R (R unit tests).

    Ignore generated files like compiled JS/CSS or documentation; focus on the source files listed above.

  10. Write Playwright smoke tests for Shiny for Python

    main

    To test Shiny for Python applications, use Playwright in conjunction with shiny.pytest.create_app_fixture and the shiny.playwright.controller module.

    Core Testing Patterns

    • Assert → Act → Assert: Always verify the initial state of inputs and outputs, perform an action (like set() or click()), and then verify the final state.
    • Use Controllers: Never use page.locator() to find elements. Instead, use the official controllers provided by shiny.playwright.controller (e.g., controller.InputSlider(page, "id")).
    • Relative Paths: When using create_app_fixture, always provide a relative path from the test file to the app file. Never use absolute paths.
    • Keyword-Only Arguments: When calling controller methods, always pass arguments as keywords (e.g., expect_cell(value="0", row=1, col=2)).
    • String Assertions: All assertions must use string values, even for numbers (e.g., expect_max("15")).

    Limitations and Exclusions

    • Icons: Do not test icon functionality (e.g., avoid expect_icon()).
    • Plots: Do not test OutputPlot content or functionality; avoid using the OutputPlot controller.
    • Unique IDs: Only test Shiny components that have unique IDs.
  11. Create your first R Shiny app

    main

    A basic Shiny application consists of three core components:

    1. UI function: Defines the visual layout and user interface.
    2. Server function: Contains the logic and reactive calculations.
    3. shinyApp(): A function used to combine the UI and Server components into a runnable application.

    For simple applications, you can define these inline. For larger projects, it is recommended to use separate ui.R and server.R files. You can execute your application using runApp().