Skip to main content

Brutalist realtime web-based UI for hassle-free desktop apps.

Project description

Intro

BrutalPyWebUI is a brutalist async Python framework for building utilitarian desktop applications that require a tried-and-true web-based cross-platform interface with realtime capabilities and no hassle.

Simple and focused

BrutalPyWebUI exposes a basic web interface bound to a host and port of your choice, and provides you a selection of very basic utility functions to update the DOM on the frontend from your Python backend, as well as a few global javascript functions for quickly grabbing data from the interface on the frontend and sending it to the backend.

Light on dependencies

BrutalPyWebUI depends only on:

Light on assets

BrutalPyWebUI bundles only:

Note that you can disable all three separately.

Flexible UI design

There are three main approaches in regards to actually building the user interface:

Utilitarian

Write most of your logic in Python and use the UI as a barebones interface that sends events by calling the special global _wuiEvent() function from a script or an onclick attribute, etc., and gets updated on-demand, from your Python code, by calling any of the wui.el_*() or wui.pg_*() utility functions.

Embrace Modernity

Use Python-land as more of a complementary backend and inject heavier javascript and css that can contain anything from logic and styling for basic web components to the bundled code of a full-fledged React app. Note that any js you inject using base_js in the constructor is injected after the definitions for the global _wui*() functions so they will not be overriden and you can call them normally from within your app. You could also just use asset_handler to serve your scripts along with other files.

Anything in the middle / anything you can think of

Self-explanatory.

The idea is to give the developer a sturdy platform to build upon, expanding freely as needed, without highly specific enforced patterns.

Installation

pip install brutalpywebui

Basic usage

import typing as t
from brutalpywebui import BrutalPyWebUI


example_html = '''
<input id="inp_regular_txt" value="Updated value" />
<button onclick="_wuiEvent('btn_press', _wuiVal('#inp_regular_txt'))">{{ button_text }}</button>
<div>Result: <span id="txt_result">Old value</span></div>
<div>Ticker: <span id="txt_ticker">{{ ticker_text }}</span></div>
'''

example_css = '''
body {
    display: flex;
    flex-direction: column;
    align-items: center;
    padding: 30px 20px;
}

body > * {
    width: 100%;
    height: 24px;
    max-width: 300px;
    margin-bottom: 10px;
    padding: 0;
    border: none;
    outline: none;
    box-sizing: border-box;
}

body > input {
    border: 1px solid black;
    padding: 0 3px;
}
'''

wui: BrutalPyWebUI | None = None

ticker = 0


async def on_background():
    global ticker

    ticker += 1
    await wui.el_set_text(["#txt_ticker"], str(ticker))


async def on_event(event: str, data: t.Any):
    match event:
        case "btn_press":
            await wui.el_set_text(["#txt_result"], data)

        case _:
            pass


async def on_init():
    # this uses Jinja2 templates
    await wui.el_set_html_templ(
        ["body"],
        example_html,
        button_text="Press me!",
        ticker_text=str(ticker),
    )


wui = BrutalPyWebUI(
    page_title="MyApp",
    init_handler=on_init,
    event_handler=on_event,
    background_handler=on_background,
    background_interval=5.2,  # seconds
    base_css=example_css,
)

# these are the default host and port
# specified here for the sake of the example
wui.run(host="localhost", port=7865)

Technical note

The run() method uses asyncio.run() and is meant to be run on the main thread. The app will not be served until run() is called.

Python reference

__init__

async def on_init():
    pass

async def on_event(name: str, data: t.Any):
    pass

async def on_asset(asset_name: str):
    # served at yourhost:port/assets/<asset_name>
    asset_bytes, content_type = mock_read_asset(asset_name)
    return asset_bytes, content_type

wui = BrutalPyWebUI(
    page_title="MyApp",
    init_handler=on_init,
    event_handler=on_event,
    asset_handler=on_asset,
    background_handler=on_background,
    background_interval=5.2,  # seconds
    base_css=lambda: mock_read_asset("bundle.css")  # or pass str directly,
    base_js=lambda: mock_read_asset("bundle.js")  # or pass str directly,
    inject_python_favicon=True,
    inject_jetbrains_font=True,
    inject_normalize_css=True,
    page_websocket_use_tls=False,
    page_lang=lambda: "en",  # or pass str directly,
    page_encoding=lambda: "UTF-8",  # or pass str directly,
    page_viewport=lambda: "width=device-width, initial-scale=1.0", # or pass str directly,
    debug=False,  # print debug logs where applicable
)
  • page_title(str|()->str) -- title of the page.
  • init_handler(async()->None) -- coroutine to run on init.
  • event_handler(async(str,Any)->None) -- coroutine to handle your custom events from the frontend.
  • asset_handler(async(str)->(asset_content(Any), content_type(str))) -- coroutine to handle serving your custom assets for the frontend.
  • background_handler(async()->None) -- coroutine called periodically in the background controlled by the background_interval arg.
  • background_interval(float) -- how often (in seconds with subsecond precision) to call the background_handler.
  • base_css(str|()->str) -- css string to inject in the main css file for the frontend.
  • base_js(str|()->str) -- js string to inject into the main js file for the frontend.
  • inject_python_favicon(bool) -- whether to use a default favicon.
  • inject_jetbrains_font(bool) -- whether to use the JetBrains Mono Regular font.
  • inject_normalize_css(bool) -- whether to inject NormalizeCSS into the main css file for the frontend.
  • page_websocket_use_tls(bool) -- whether to use wss instead of ws for the websocket connection on the frontend.
  • page_lang(str) -- the lang attribute for the html tag.
  • page_encoding(str) -- the encoding of the page, normally you should not change this.
  • page_viewport(str) -- the value for the viewport meta tag, normally you should not change this unless having responsive issues with CSS.
  • debug(bool) -- whether to print debug logs where applicable.

el_append_text

Append text to the current el.innerText of one or more targets. Affects all connected instances.

await wui.el_set_text(['#my_element'], 'Hello')
await wui.el_append_text(['#my_element'], ' there')
  • selectors(list[str]) -- querySelectorAll selectors for targeting.
  • content(str) -- a string containing any text.

el_append_value

Append text to the current el.value of one or more targets. Affects all connected instances.

await wui.el_set_value(['#my_input'], 'Hello')
await wui.el_append_value(['#my_input'], ' there')
  • selectors(list[str]) -- querySelectorAll selectors for targeting.
  • content(str) -- a string containing any text.

el_class_add

Add a class to el.classList on one or more targets. Affects all connected instances.

await wui.el_class_add(['#my_element'], 'my_custom_class')
  • selectors(list[str]) -- querySelectorAll selectors for targeting.
  • name(str) -- name of the class.

el_class_remove

Remove a class from el.classList on one or more targets. Affects all connected instances.

await wui.el_class_remove(['#my_element'], 'my_custom_class')
  • selectors(list[str]) -- querySelectorAll selectors for targeting.
  • name(str) -- name of the class.

el_disable

Disable an element using el.disabled. Affects all connected instances.

await wui.el_disable(['#some_input', '#some_button'])
  • selectors(list[str]) -- querySelectorAll selectors for targeting.

el_enable

Enable an element disabled using el.disabled. Affects all connected instances.

await wui.el_enable(['#some_input', '#some_button'])
  • selectors(list[str]) -- querySelectorAll selectors for targeting.

el_set_attribute

Use el.setAttribute on one or more targets. Affects all connected instances.

await wui.el_set_attribute(['#my_element'], 'data-something', 'the value')
  • selectors(list[str]) -- querySelectorAll selectors for targeting.
  • name(str) -- name of the attribute.
  • content(str) -- value of the attribute.

el_set_html_templ

Evaluate a Jinja2 template string and set the el.innerHTML of one or more targets. Affects all connected instances.

await wui.el_set_html_templ(
    ['#my_element'],
    '<div>{{ content }}</div>',
    content='Stuff',
)
  • selectors(list[str]) -- querySelectorAll selectors for targeting.
  • content(str) -- a string containing a Jinja2 template.

el_set_html_unsafe

Directly set the el.innerHTML of one or more targets. Affects all connected instances.

await wui.el_set_html_unsafe(['#my_element'], '<div>Stuff</div>')
  • selectors(list[str]) -- querySelectorAll selectors for targeting.
  • content(str) -- a string containing html.

el_set_style

Set a property on el.style on one or more targets. Affects all connected instances.

await wui.el_set_style(['#my_element'], 'backgroundColor', 'lightgray')
  • selectors(list[str]) -- querySelectorAll selectors for targeting.
  • name(str) -- name of the style attribute (camelCase).
  • content(str) -- value of the attribute.

el_set_text

Directly set the el.innerText of one or more targets. Affects all connected instances.

await wui.el_set_text(['#my_element'], 'Hello there')
  • selectors(list[str]) -- querySelectorAll selectors for targeting.
  • content(str) -- a string containing raw text.

el_set_value

Set the el.value of one or more targets. Affects all connected instances.

await wui.el_set_value(['#my_input'], 'Hello')
  • selectors(list[str]) -- querySelectorAll selectors for targeting.
  • content(str) -- a string containing any text.

pg_eval

Eval a js string on the frontend. Affects all connected instances.

await wui.pg_eval('call_some_javascript()')
  • content(str) -- the js string

pg_set_title

Set the title of the page dynamically. Affects all connected instances.

await wui.pg_set_title('A New Title')
  • content(str) -- the page title

run

Run the BrutalPyWebUI app at host on port.

await wui.run(host="localhost", port=7865)
  • host(str) -- hostname.
  • port(int) -- port number.

JavaScript reference

_wuiEvent

Send an event to your event_handler on the backend.

_wuiEvent('my_event', ['some', 'data'])
  • name(string) -- name of your event
  • data(any) -- json-compatible object to send with your event

_wuiVal

Returns el.value of an element.

_wuiVal('#my_input')
  • selector(string) -- querySelector selector

_wuiChecked

Returns el.checked of an element.

_wuiChecked('#my_input')
  • selector(string) -- querySelector selector

_wuiSelected

Returns el.selected of an element.

_wuiSelected('#my_input')
  • selector(string) -- querySelector selector

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

brutalpywebui-0.1.0.tar.gz (185.3 kB view hashes)

Uploaded Source

Built Distribution

brutalpywebui-0.1.0-py3-none-any.whl (185.5 kB view hashes)

Uploaded Python 3

Supported by

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