Skip to main content

Native desktop GUI framework for Python, powered by iced

Project description

plushie

Build native desktop apps in Python. Pre-1.0

Write your entire application in Python (state, events, UI) and get native windows on Linux, macOS, and Windows. Available on PyPI as plushie. The renderer is built on Iced and ships as a precompiled binary, no Rust toolchain required.

SDKs are also available for Elixir, Gleam, Ruby, and TypeScript.

Quick start

from dataclasses import dataclass, replace

import plushie
from plushie import ui
from plushie.events import Click


@dataclass(frozen=True, slots=True)
class Model:
    count: int = 0


class Counter(plushie.App[Model]):
    def init(self):
        return Model()

    def update(self, model, event):
        match event:
            case Click(id="inc"):
                return replace(model, count=model.count + 1)
            case Click(id="dec"):
                return replace(model, count=model.count - 1)
            case _:
                return model

    def view(self, model):
        return ui.window(
            "main",
            ui.column(
                ui.text("count", f"Count: {model.count}"),
                ui.row(
                    ui.button("inc", "+"),
                    ui.button("dec", "-"),
                    spacing=8,
                ),
                padding=16,
                spacing=8,
            ),
            title="Counter",
        )


if __name__ == "__main__":
    plushie.run(Counter)

Install and run:

pip install plushie
python -m plushie download              # download precompiled binary
python -m plushie run counter:Counter   # run the counter example

The repo includes several examples you can try, from a minimal counter to a full widget catalog. Edit them while the GUI is running and see changes instantly. For complete project demos, including native Rust extensions, see the plushie-demos repo.

How it works

Under the hood, a renderer built on iced handles window drawing and platform integration. Your Python code sends widget trees to the renderer over stdin; the renderer draws native windows and sends user events back over stdout.

You don't need Rust to use plushie. The renderer is a precompiled binary, similar to how your app talks to a database without you writing C. If you ever need custom native rendering, the extension system lets you write Rust for just those parts.

The same protocol works over a local pipe, an SSH connection, or any bidirectional byte stream - your code doesn't need to change.

Features

  • Elm architecture - init, update, view. State lives in Python, pure functions, predictable updates
  • Built-in widgets - layout, input, display, and interactive widgets out of the box
  • Canvas - shapes, paths, gradients, transforms, and interactive elements for custom 2D drawing
  • Themes - dark, light, nord, catppuccin, tokyo night, and more, with custom palettes and per-widget style overrides
  • Animation - renderer-side transitions, springs, and sequences with no wire traffic per frame
  • Multi-window - declare windows in your view; the framework manages the rest
  • Platform effects - native file dialogs, clipboard, OS notifications
  • Accessibility - keyboard navigation, screen readers, and focus management via AccessKit
  • Custom widgets - compose existing widgets in pure Python, draw on the canvas, or extend with native Rust
  • Hot reload - edit code, see changes instantly with full state preservation
  • Remote rendering - app on a server or embedded device, renderer on a display machine over SSH or any byte stream
  • Fault-tolerant - renderer crashes auto-recover; app exceptions are caught and state reverted

Testing and automation

Tests run through the real renderer binary, not mocks. Interact like a user: click, type, find elements, assert on text. Three interchangeable backends:

  • Mock - millisecond tests, no display server
  • Headless - real rendering via tiny-skia, supports screenshots for pixel regression in CI
  • Windowed - real windows with GPU rendering, platform effects, real input
import pytest
from plushie.testing import AppFixture
from todo import TodoApp, Model


@pytest.fixture
def app(plushie_pool):
    with AppFixture(TodoApp, plushie_pool) as f:
        yield f


def test_add_and_complete_todo(app):
    app.type_text("#new-todo", "Buy milk")
    app.submit("#new-todo")

    app.assert_exists("#todo-1")
    assert app.model.items[0].text == "Buy milk"

    app.toggle("#todo-1/done")
    assert app.model.items[0].done is True

    app.select("#filter", "done")
    app.assert_not_exists("#todo-1")
pytest                                    # mock (fast, no display)
PLUSHIE_TEST_BACKEND=headless pytest      # real rendering, no display
PLUSHIE_TEST_BACKEND=windowed pytest      # real windows (needs display)

Status

Pre-1.0. The core works (built-in widgets, event system, themes, multi-window, testing framework, accessibility) but the API is still evolving. Pin to an exact version and read the CHANGELOG when upgrading.

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

plushie-0.6.0.tar.gz (316.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

plushie-0.6.0-py3-none-any.whl (205.2 kB view details)

Uploaded Python 3

File details

Details for the file plushie-0.6.0.tar.gz.

File metadata

  • Download URL: plushie-0.6.0.tar.gz
  • Upload date:
  • Size: 316.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.7

File hashes

Hashes for plushie-0.6.0.tar.gz
Algorithm Hash digest
SHA256 328646036eadc0c980591c24fff3d9cd5aff6934aa29c8a1e2677ad2e6f3fb11
MD5 7f314436249328865f8793f1f8f19c8b
BLAKE2b-256 c036efb030e9fbf55259a8d7f3ddc84550a0a7fd498cbbc6686d24128c817317

See more details on using hashes here.

File details

Details for the file plushie-0.6.0-py3-none-any.whl.

File metadata

  • Download URL: plushie-0.6.0-py3-none-any.whl
  • Upload date:
  • Size: 205.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.7

File hashes

Hashes for plushie-0.6.0-py3-none-any.whl
Algorithm Hash digest
SHA256 4ad7ed5ca469316f3617a8feb8bd266e4b69478798da5e1a4233e3ac17011cbb
MD5 7698723fcf3169a970c1cc2149e8bc42
BLAKE2b-256 93c2bcafc196dd41edd0539b5f0afc73919b2bc1f074d433451500ac8bc08090

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page