Pydrud
Build Python UIs with a Flutter-inspired widget API.
Run host-side with the toolchain-free Pydash preview, or compile a real Android APK with Chaquopy.
Congratulations, Android users — Pydash is officially live!
Download the preview client from the Pydash releases page. iOS is coming soon.
Pydrud is a declarative UI framework for building native Android apps in Python — a Flutter-inspired, radically simpler alternative to writing Java or Kotlin, with Material Design 3 (Material You) built in. It is a Python GUI framework for Android: write the interface once as a Python widget tree, and a generated renderer turns it into native Android Views at runtime. There is no XML layout and no Java to write, and the widget, theme and styling layers stay platform-neutral, so the same screen code renders through any adapter.
Pydrud ships with a platform-neutral widget tree, a keyed diff engine, and a versioned render protocol. The same screen code drives the host preview and the native Android renderer.
It ships the Material 3 design system end to end: HCT-generated colour with all 36 semantic roles, the M3 type scale, shape and elevation systems, the component catalogue from buttons to side sheets, and the M3 motion tokens — all of it expressed once in Python and translated by the renderer.
Search terms this project answers to: Python Android UI framework · declarative Python UI · Material Design 3 in Python · Material You Python · Chaquopy app · native Android Views from Python · Flutter alternative for Python · build an Android app without Java · PSS stylesheets.
- 📦 PyPI: pypi.org/project/pydrud
- 📚 Docs: docs/ARCHITECTURE.md · docs/PSS_STYLESHEETS.md
- 🐛 Issues: github.com/MurShidM01/Pydrud/issues
- 📝 Changes: CHANGELOG.md
Quick start
pip install pydrud # install the framework
pydrud create my_app --org com.example # scaffold a project (Pydash runtime)
cd my_app
pydrud dev # run host Python, print a preview QR/URI
For an installable Android app, add the native platform and run it:
pydrud init android # standalone app with embedded Python (Chaquopy)
pydrud run # build, install and launch on a device/emulator
The generated APK boots your Python on device and runs fully offline. Pass
--runtime chaquopy to pydrud create to scaffold the Android project
directly.
Runtimes
| Runtime | What runs where | Build requirements | Select with |
|---|---|---|---|
| Pydash (default) | Python and state stay on the host; a preview renderer receives widget snapshots/patches and returns events. No APK. | Host Python only — no Android SDK, NDK, Gradle or ADB. | pydrud create <name> or --runtime pydash |
| Android standalone (Chaquopy) | CPython is bundled in the generated project; widgets render as native Android Views, fully offline. | Android SDK/NDK, JDK, Gradle; a device/emulator to install. | pydrud init android, or pydrud create <name> --runtime chaquopy |
An Android target can also be built with no embedded interpreter:
| Backend | On the device | Use it when |
|---|---|---|
chaquopy (default) |
CPython bundled; runs fully offline | You want a self-contained, publishable APK |
host |
No interpreter — the app dials out to Python on your machine | You want your real Python app without embedding a runtime |
none |
A pure renderer with no runtime | You only need the native shell / protocol |
pydrud init android --backend host|none|chaquopy
pydrud dev --bridge # serve the host bridge (runs `adb reverse` for you)
All three share the same widget tree, keyed diff and protocol-v2 render transactions; only the transport differs.
Platform-neutral by design
Android is the only renderer Pydrud ships — there is no iOS, Linux, Windows or web backend to configure or maintain — but the UI layer itself is renderer-agnostic, so the same screen code is portable:
- One widget vocabulary.
pydrud/widgets/imports nothing frompydrud/platforms/android/, the generated Java templates orandroid.*. - One wire format. A plain JSON node dict (
type,key,style,props,children,events) with no Android types in it. - Renderer facts as data. A
RendererProfilesupplies platform-specific style facts to the PSS parser/resolver, so neither ever imports a platform adapter implicitly. - Capability negotiation. A renderer advertises the widgets and services it supports, and unsupported widgets are substituted with documented fallbacks instead of being dropped.
Adding a platform later means adding one adapter under pydrud/platforms/
plus its transport — not forking the design system. See
docs/ARCHITECTURE.md.
Installation
pip install pydrud # from PyPI
git clone https://github.com/MurShidM01/Pydrud.git
cd Pydrud && pip install -e ".[dev]" # from source
Behind a proxy, export http_proxy / https_proxy before pydrud build;
the values are forwarded to Gradle automatically.
What you get
| Area | Description |
|---|---|
| Declarative UI | 130+ widgets and presets: layout, Material 3 components, form fields, chips, charts, media, gestures and animations |
| Reactive state | State<T> re-renders the tree on change; Store / Computed / ReactiveList for app-level state |
| Styling | Inline styles plus platform-neutral .pss stylesheets with the full CSS authoring surface (see below) |
| Design system | Theme + Tokens — colours, radii, sizes, depth, motion and type in Python; light/dark and Material You |
| Responsive | Live window metrics (size, insets, text scale, orientation) drive breakpoints, percent units and adaptive widgets |
| Rendering | A transport-neutral widget tree and protocol-v2 transactions; Chaquopy renders native Android Views |
| CLI toolchain | Runtime-aware create / init / dev / sync / analyze / doctor / pip, plus Android run / build / watch / icons / keygen / permissions / capabilities |
| Data layer | SQLite Database, Model ORM with migrations, and a TTL Cache |
| Native services | Dialogs, storage, files, share, notifications, location, haptics, camera, sensors, biometrics and more |
| Async by default | Thread pool, timers and a non-blocking HTTP client, so the UI never freezes |
| Testable | pydrud.testing.AppTester runs your whole app in CI without a device or emulator |
Examples
Two complete apps live in examples/:
| Example | Demonstrates |
|---|---|
todo_app.py |
Store state, keyed lists, swipe-to-delete, search + segmented filtering, persistence, a confirm dialog |
weather_app.py |
The non-blocking HTTP client, pull-to-refresh, permissions + GPS, a Chart, skeleton loading states, bottom navigation |
Run either headless with python examples/todo_app.py --tree, or drop it into
a project as src/app/main.py.
PSS stylesheets
Pydrud Style Sheets (PSS) keep presentation out of Python while using the same
platform-neutral vocabulary. Files named *.pss under src/ are discovered
automatically and re-resolved without re-running Python modules.
:root { --brand: #FF4F46E5; }
.card {
background: var(--brand);
border-radius: 16;
padding: 24; /* all four sides */
shadow: { color: #40000000; offsetY: 8; blur: 24 };
font: { size: 16, weight: 600 };
}
@media (max-width: 360px) {
.card { padding: 16; }
}
@keyframes pulse {
from { transform: scale(1); opacity: 1; }
to { transform: scale(1.4); opacity: .4; }
}
.badge { animation: pulse 1.2s ease-in-out infinite; }
.badge:active { transform: scale(.92); }
Supported today: custom properties (--x / var() with fallback, inherited),
the universal selector *, structural and state pseudo-classes
(:first-child, :nth-child(), :not(), :active, :hover, :focus, …),
@media queries, @keyframes + the animation shorthand, transition, and
the CSS value language — clamp(), calc(), min()/max(),
env(safe-area-inset-*) and the viewport units, resolved against the live
window. CSS long-hands fold into Pydrud composites (font-size → font.size,
padding-top → padding.top, background → bg, transform: scale() →
scale), and web-only declarations are accepted then ignored with one
warning, so a stylesheet copied from the web drops in nearly verbatim.
$token references resolve against the active palette — bg: $surface,
color: $text, border-radius: $radius_card — so one stylesheet follows
light/dark mode and re-tints after Theme.seed().
See docs/PSS_STYLESHEETS.md for the selector, cascade, value, diagnostics and reload contract, and docs/examples/heartbeat-css-parity.pss for a full stylesheet translated from a web prototype.
Widget catalogue
The API is Flutter-shaped. Every widget serialises to a platform-neutral node; the renderer decides how to draw it.
Layout
| Widget | Description |
|---|---|
Container, Padding, SizedBox, Center, Spacer |
Boxes, padding and fixed/centred space |
Row, Column, Flex |
Linear layouts (Flex is either axis by direction) |
Stack, Positioned |
Overlapping and absolutely-placed children |
Scaffold, AppBar, FloatingActionButton |
Page chrome and the Material action button |
ListView, GridView, PageView, Carousel, SingleChildScrollView, Wrap |
Scrolling and paging |
Card, Divider, Drawer, Tabs, BottomNavigationBar, NavigationRail |
Surfaces and navigation |
AdaptiveLayout, LayoutBuilder, ResponsiveBuilder, ResponsiveGrid, SafeArea, ShowWhen |
Responsive composition |
Controls
| Widget | Description |
|---|---|
Text, Heading, Title, Subtitle, Label, Caption, Link, RichText |
Typography |
Button, TextButton, ElevatedButton, IconButton, SegmentedButton |
Actions |
TextField, NumberField, PasswordField, SearchBar, Form, FormField |
Input and validation |
Checkbox, Switch, Radio, Slider, RangeSlider, Dropdown, Stepper, Rating |
Selection |
Chip, Badge, Avatar, Banner, Tooltip, PopupMenu, ListTile, ExpansionTile |
Material 3 components |
ProgressBar, CircularProgress, Skeleton, EmptyState, ErrorState, LoadingState |
Feedback and placeholders |
AlertDialog, ModalBottomSheet |
Declarative modals — add to the tree to show, drop to dismiss |
Chart, Canvas, Image, SvgPicture, WebView, VideoPlayer, MapView, NativeView |
Media, painting and escape hatches |
pydrud docs --serve generates a searchable HTML reference for every widget,
prop and service.
Events, state and async
widget.on_click(callback) # any widget — callback(data)
widget.on_change(callback) # inputs, sliders, page views
widget.on("select", callback) # any named event
from pydrud import State, Store, Computed
count = State(0)
Text(lambda: f"{count.value}") # rebuilds when count changes
page.run_task(heavy_work) # thread pool; `async def` also works
page.every(1.0, tick) # repeating timer
page.http.get("/posts").then(render).catch(show_error)
CLI reference
Every command uses the same branded terminal UI (auto-disabled when output is
redirected; override with NO_COLOR / PYDRUD_COLOR). pydrud analyze --json
stays plain JSON.
| Command | Description |
|---|---|
pydrud create <name> |
Scaffold a Pydash project (no Android toolchain) |
pydrud create <name> --runtime chaquopy |
Scaffold the standalone Android APK project |
pydrud init android [--backend …] |
Add the Android platform to an existing project |
pydrud dev [--bridge] [--host] [--port] |
Run host Python and serve the preview / device bridge |
pydrud build [--debug|--release] |
Build an APK (Android platform only) |
pydrud run [--debug|--release] |
Build, install and launch on a device |
pydrud watch |
Android Hot Reload runner |
pydrud devices |
List connected Android devices |
pydrud analyze [--path src] [--json] |
Static analysis of Python UI code and PSS stylesheets |
pydrud sync |
Refresh the generated Android layer |
pydrud clean |
Clean Android build artifacts |
pydrud doctor |
Check host and Android toolchain requirements |
pydrud pip add|remove|list|search|sync |
Manage project packages |
pydrud permissions add|remove <name> |
Update YAML and the Android manifest |
pydrud capabilities add|remove <name> |
Enable Android feature bundles |
pydrud icons [--source logo.png] |
Generate launcher icons |
pydrud keygen |
Create the Play Store upload keystore |
pydrud docs [--serve] |
Offline HTML API reference |
pydrud inspect [--tree] [--watch] |
Inspect the widget tree or Android bridge traffic |
Android project configuration
Applies once a project has the Android platform. pydrud.yaml is the
source of truth for generated Android files — change it and run
pydrud sync.
app_name: "Taskflow"
package: "com.example.taskflow"
runtime: "chaquopy"
version_code: 2
version_name: "1.1.0"
min_sdk: 24
target_sdk: 36
compile_sdk: 36
scheme: "taskflow"
permissions: ["CAMERA"]
capabilities: ["haptics", "notifications"]
camera: false
python_version: "3.11"
protocol_version: 2
| Key | Purpose |
|---|---|
runtime |
chaquopy, host or none |
capabilities |
Feature bundles (camera, microphone, location, contacts, notifications, haptics, …). pydrud capabilities list --all describes each |
permissions |
Individual Android permissions (dangerous ones still need a runtime request) |
camera |
Bundle the CameraX + ML Kit stack (~10 MB). Off by default; pydrud permissions add camera enables it. Without it, CameraPreview renders its fallback and camera calls explain the fix |
Runtime dangerous permissions always require page.permissions.request(...)
and explicit user consent — Pydrud never fakes or bypasses the Android prompt.
Architecture
Both runtimes share the transport-neutral widget tree, keyed diff engine and protocol-v2 transactions:
Pydash (pydrud dev)
host Python App ── token-authenticated preview handshake ──► renderer
source, state and handlers stay on the host
Chaquopy (pydrud run / APK)
embedded Python App ── local NDJSON bridge ──► generated BridgeService
└─ native Android Views
App(target=main)builds a widget tree; PSS rules resolve on the Python side.page.update()computes keyed patches from the last renderer-confirmed tree.- Python sends one
RenderTransaction(full snapshot or patch batch) with a strictly increasing revision. - The renderer applies it atomically and ACKs; a NACK triggers a full resync.
- Events travel back through the same bridge to Python callbacks.
Source layout
No file in the repository exceeds 1,000 lines.
| Package | Contents |
|---|---|
pydrud/widgets/ |
Widget library: basic, layout, forms, advanced, canvas, gestures, animation, conditional, responsive, presets, plus material/ and theme/ |
pydrud/runtime/ |
App, Page and the bridge event loop, split into focused mixins |
pydrud/core/ |
Platform-neutral engine: diff/RenderTransaction, state, bridge/protocol, responsive, watcher, and styles/ (the PSS engine) |
pydrud/services/ |
http and native/ platform services |
pydrud/commands/ |
The CLI (project/, builder, analyzer, doctor, docs, …) |
pydrud/android/templates/ |
Jinja2 templates for the generated Android project |
Native renderer (Chaquopy)
A thin ViewFactory orchestrator plus cohesive collaborators:
| Class | Responsibility |
|---|---|
ViewFactory |
Orchestrator — view/node maps, public API, collaborator wiring |
BuiltinViews |
createView dispatch and built-in widget factories |
ViewStyler |
applyStyle, borders, gradients, typography, dimensions |
LayoutEngine |
Linear/grid layout params, spacing, child attach/removal |
TreePatcher |
applyPatch / updateProps |
EventBinder |
Click/change binding, popup menus, touch feedback |
ViewAnimator |
Entrance animations, keyframes, transitions |
ImageLoader · NativeViewFactory |
Image pipeline · user NativeView classes |
MaterialViews + MaterialNavigationViews |
Material 3 components |
BridgeService · PydrudTheme |
TCP/NDJSON server · generated design system |
Requirements
Host workflow (default)
Python 3.10+ and the Pydrud package. pydrud dev needs no Java, Android SDK,
NDK, Gradle or ADB.
Android APK workflow
| Tool | Default | Notes |
|---|---|---|
| Python | 3.11 | Used by the Chaquopy build/runtime configuration |
| Java (JDK) | 17+ | Checked by pydrud doctor |
| Android SDK | API 36 compile/target | Set ANDROID_HOME or ANDROID_SDK_ROOT |
| Android NDK | 28.2.13676358 |
Generated project default |
| Android Gradle Plugin | 8.13.2 |
Generated project default |
| Gradle | 8.14.4 |
Wrapper generated with the project |
| Chaquopy | 17.0.0 |
Embedded Python runtime (MIT since 12.0.1) |
Projects target Android API 24+ and default to arm64-v8a, armeabi-v7a and
x86_64. ADB is needed for pydrud run, not for pydrud dev or the tests.
Setting up the Android SDK
Windows (PowerShell):
[System.Environment]::SetEnvironmentVariable('ANDROID_HOME', 'C:\Android\Sdk', 'User')
[System.Environment]::SetEnvironmentVariable('ANDROID_SDK_ROOT', 'C:\Android\Sdk', 'User')
Linux / macOS:
export ANDROID_HOME=$HOME/Android/Sdk
export ANDROID_SDK_ROOT=$HOME/Android/Sdk
Restart your terminal and run pydrud doctor to verify.
Development
pip install -e ".[dev]"
python -m pytest tests/ -v
ruff check . # the same lint CI runs
The suite covers widget/state/protocol unit tests, keyed-diff and PSS
parsing/resolution, scaffold generation for both project shapes, generated
Java template parsing, and end-to-end runtime tests via pydrud.testing —
all without a device or emulator.
tests/test_neutrality.py additionally proves the UI layer is
renderer-neutral: every module under pydrud/widgets/ is imported in a fresh
interpreter and asserted to reach no Android adapter and no pyjnius, so the
portability claim above cannot regress unnoticed.
Roadmap
| Version | Focus |
|---|---|
| v2.1.4 | Per-ABI APKs rebuilt with a product flavor dimension (Chaquopy rejects splits.abi); pydrud build collects the flavor APKs |
| v2.1.3 | pydrud build discovers per-ABI APKs (prints them all, returns the universal one); --output copies every split |
| v2.1.2 | Opt-in per-ABI APK splits (abi_splits) — one APK per ABI plus a universal APK for smaller downloads |
| v2.1.1 | PSS as a full CSS-style language, declarative @keyframes/transition/:active, pluggable runtime backends, Heartbeat starter, content blur, animated theming |
| v2.1.0 | create/init android CLI, pluggable runtime backends, PSS engine, Heartbeat starter, runtime-aware tooling |
| 2.1.5 | Material 3 Complete: HCT colour, 36 roles, the M3 component catalogue, M3 typography/shape/motion — Android renderers, platform-neutral UI layer |
| Next | Material Symbols + dynamic colour, expressive motion (springs), container transform, predictive back |
| Later | Further M3 Expressive work, then additional platform backends on top of the neutral UI layer |
See CHANGELOG.md for the complete itemised history.
Contributing
- Fork the repository.
- Create a branch:
git checkout -b feature/amazing-feature. - Commit your changes and push the branch.
- Open a Pull Request.
License
MIT © Pydrud Contributors. See LICENSE for details.
Pydrud — Pythonic. Portable. Simple.
Metadata
Release files for pydrud 2.1.5
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| pydrud-2.1.5.tar.gz | 1.1 MB | Without provenance | Details for pydrud-2.1.5.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| pydrud-2.1.5-py3-none-any.whl | Python 3 | none | any | Details for pydrud-2.1.5-py3-none-any.whl |
Total release size: 2.0 MB
Release files / pydrud-2.1.5.tar.gz
| Download URL | pydrud-2.1.5.tar.gz |
|---|---|
| Size | 1.1 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
cbe1098a11e8b719948376104f2c63a5c210475c4ba132269d93c3f26db3e02e
|
|
BLAKE2b-256 checksum How to use checksums |
b961dd0eac23a51451fe36075e04c1b8f49ff074661df7a481c0e709dbfba9b8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.10
|
Release files / pydrud-2.1.5-py3-none-any.whl
| Download URL | pydrud-2.1.5-py3-none-any.whl |
|---|---|
| Size | 932.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
88f8675a98ec38cdb351d5c26be8090a1a4433c0776b029eca268340cd0223ff
|
|
BLAKE2b-256 checksum How to use checksums |
fab0d4f0705434898274fdc80e2aca50c4ad9d645c586ec84708b191af46621e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.10
|
Security for pydrud 2.1.5
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.