Repository navigation
Getting Started
wapyt apps run in pyTincture service mode: a Python backend serves the page,
the browser runs your UI module in Pyodide, and backend-for-frontend (BFF)
classes carry data between them. Targets pyTincture 1.0 release candidates and
wapyt 0.2 (0.2.0 is on PyPI); the server needs
Python 3.13 or 3.14.
my_app/
├── service.py # ASGI app; never sent to the browser
├── dashboard.py # browser entrypoint (APP_ENTRYPOINT + MainWindow subclass)
├── dashboard_data.py # @backend_for_frontend class(es)
├── widget.py # literal widgetset metadata
└── wapyt-99.99.99-py3-none-any.whl # the browser wheel (step 3)
/dashboard serves dashboard.py: the URL is the module filename, not the
class name.
pyTincture reads the entrypoint's imports with ast (it never imports them)
looking for literal metadata. A local widget.py is the dependable way:
# widget.py
__widgetset__ = "wapyt"
__version__ = "0.2.0" # the wapyt version you build frompyTincture turns this into a pin (wapyt==0.2.0) and the browser installs
the matching wheel from the app folder, or the 99.99.99 development wheel
from step 3, which it accepts whatever the pin says. Keep it equal to the
version of the wapyt checkout or release you use. From PyPI, development and
release-candidate versions need pip install --pre wapyt.
# dashboard.py
import widget # noqa: F401Importing wapyt directly also works when wapyt is installed in the
server's Python, but only as a regular install: with pyTincture ≤ 1.0.0rc12
an editable (pip install -e) install is not discovered and the app boots with
no widgets.
The browser installs a wheel, not your checkout, and wapyt is neither on PyPI nor in pyTincture's built-in wheel locks. Build it into the app folder:
/path/to/wa_pytincture_widgetset/scripts/dev_wheel.sh /path/to/my_appIt builds at the development version 99.99.99 (pyTincture's
PYTINCTURE_DEV_WHEEL_VERSION), regenerates the hash manifest and copies the
wheel in. Re-run it after any wapyt change and restart the service.
pyTincture's automatic MainWindow detection only recognises dhxpyt's class.
Either name your class after the module (class dashboard(MainWindow) in
dashboard.py) or declare it:
APP_ENTRYPOINT = "Dashboard"Without one of the two, the page answers HTTP 422.
class Dashboard(MainWindow):
def load_ui(self): # never define __init__ to call this
...MainWindow's metaclass calls load_ui() after construction. Calling it from
your own __init__ as well builds everything twice.
from pathlib import Path
from pytincture import PytinctureConfig, create_app
HERE = Path(__file__).resolve().parent
def policy_hook(user, policy, class_name, function_name, **kwargs):
return True # extra checks beyond the claims pyTincture enforces itself
app = create_app(PytinctureConfig(
modules_path=str(HERE),
default_application="dashboard",
# Only needed when an export uses @bff_policy. Register by dotted path:
# set_bff_policy_hook() does not reach an app built by create_app().
environment={"BFF_POLICY_HOOK_PATH": "service.policy_hook"},
))python -m uvicorn service:app --port 8070 # http://127.0.0.1:8070/dashboard# dashboard_data.py
from pytincture.dataclass import backend_for_frontend
@backend_for_frontend
class DashboardData:
def rows(self) -> list[dict]:
return [{"id": "1", "name": "From the server"}]# dashboard.py
import asyncio
from dashboard_data import DashboardData
def load_ui(self):
self.table = self.add_datatable("main", DataTableConfig(...))
asyncio.ensure_future(self.refresh())
async def refresh(self):
self.table.set_rows(await DashboardData().rows_async())Call the generated name_async() companion; the plain name() is a blocking
request. pyTincture re-executes a BFF module for every call, so keep state in
storage, never in module globals. See Events and Async.
pyTincture can also run browser-only pages that install the widgetset from an index. wapyt is not published to one, so wapyt apps use service mode.
wapyt is MIT-licensed · Repository · pyTincture · Issues
Getting started
Widgets
- Layout and MainWindow
- Tree
- DataTable
- Form
- ModalWindow
- Window
- TabWidget
- Toolbar
- Sidebar
- Chat
- Terminal
- CardPanel
- ResourceBoard
- File Transfer
- Message
- ContextMenu
- MenuBar
- Listbox
- Kanban
- ProgressBar
- Pagination
- Popup and tooltips
- Chart
- Scheduler
- MediaPlayer
Guides
Planning
Contributing