Package: brutalpywebui
IntroBrutalPyWebUI 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 focusedBrutalPyWebUI 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 dependenciesBrutalPyWebUI depends only on:hypercorn (the ASGI server),Quart (a Flask replacement for asyncio)Light on assetsBrutalPyWebUI bundles only:a default favicon (the Pyton logo)a default font (JetBrains Mono Regular)a default css reset (Normalize.css)Note that you can disable all three separately.Flexible UI designThere are three main approaches in regards to actually
building the user interface:UtilitarianWrite 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 thewui.el_*()orwui.pg_*()utility functions.Embrace ModernityUse 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 usingbase_jsin 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 useasset_handlerto serve your scripts along with other files.Anything in the middle / anything you can think ofSelf-explanatory.The idea is to give the developer a sturdy platform to
build upon, expanding freely as needed, without
highly specific enforced patterns.InstallationpipinstallbrutalpywebuiBasic usageimporttypingastfrombrutalpywebuiimportBrutalPyWebUIexample_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=Noneticker=0asyncdefon_background():globaltickerticker+=1awaitwui.el_set_text(["#txt_ticker"],str(ticker))asyncdefon_event(event:str,data:t.Any):matchevent:case"btn_press":awaitwui.el_set_text(["#txt_result"],data)case_:passasyncdefon_init():# this uses Jinja2 templatesawaitwui.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,# secondsbase_css=example_css,)# these are the default host and port# specified here for the sake of the examplewui.run(host="localhost",port=7865)Technical noteThe 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__asyncdefon_init():passasyncdefon_event(name:str,data:t.Any):passasyncdefon_asset(asset_name:str):# served at yourhost:port/assets/<asset_name>asset_bytes,content_type=mock_read_asset(asset_name)returnasset_bytes,content_typewui=BrutalPyWebUI(page_title="MyApp",init_handler=on_init,event_handler=on_event,asset_handler=on_asset,background_handler=on_background,background_interval=5.2,# secondsbase_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_textAppend text to the current el.innerText of one or more targets.
Affects all connected instances.awaitwui.el_set_text(['#my_element'],'Hello')awaitwui.el_append_text(['#my_element'],' there')selectors(list[str])-- querySelectorAll selectors for targeting.content(str)-- a string containing any text.el_append_valueAppend text to the current el.value of one or more targets.
Affects all connected instances.awaitwui.el_set_value(['#my_input'],'Hello')awaitwui.el_append_value(['#my_input'],' there')selectors(list[str])-- querySelectorAll selectors for targeting.content(str)-- a string containing any text.el_class_addAdd a class to el.classList on one or more targets.
Affects all connected instances.awaitwui.el_class_add(['#my_element'],'my_custom_class')selectors(list[str])-- querySelectorAll selectors for targeting.name(str)-- name of the class.el_class_removeRemove a class from el.classList on one or more targets.
Affects all connected instances.awaitwui.el_class_remove(['#my_element'],'my_custom_class')selectors(list[str])-- querySelectorAll selectors for targeting.name(str)-- name of the class.el_disableDisable an element using el.disabled.
Affects all connected instances.awaitwui.el_disable(['#some_input','#some_button'])selectors(list[str])-- querySelectorAll selectors for targeting.el_enableEnable an element disabled using el.disabled.
Affects all connected instances.awaitwui.el_enable(['#some_input','#some_button'])selectors(list[str])-- querySelectorAll selectors for targeting.el_set_attributeUse el.setAttribute on one or more targets.
Affects all connected instances.awaitwui.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_templEvaluate a Jinja2 template string and set the el.innerHTML of one or more targets.
Affects all connected instances.awaitwui.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_unsafeDirectly set the el.innerHTML of one or more targets.
Affects all connected instances.awaitwui.el_set_html_unsafe(['#my_element'],'<div>Stuff</div>')selectors(list[str])-- querySelectorAll selectors for targeting.content(str)-- a string containing html.el_set_styleSet a property on el.style on one or more targets.
Affects all connected instances.awaitwui.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_textDirectly set the el.innerText of one or more targets.
Affects all connected instances.awaitwui.el_set_text(['#my_element'],'Hello there')selectors(list[str])-- querySelectorAll selectors for targeting.content(str)-- a string containing raw text.el_set_valueSet the el.value of one or more targets.
Affects all connected instances.awaitwui.el_set_value(['#my_input'],'Hello')selectors(list[str])-- querySelectorAll selectors for targeting.content(str)-- a string containing any text.pg_evalEval a js string on the frontend.
Affects all connected instances.awaitwui.pg_eval('call_some_javascript()')content(str)-- the js stringpg_set_titleSet the title of the page dynamically.
Affects all connected instances.awaitwui.pg_set_title('A New Title')content(str)-- the page titlerunRun the BrutalPyWebUI app at host on port.awaitwui.run(host="localhost",port=7865)host(str)-- hostname.port(int)-- port number.JavaScript reference_wuiEventSend an event to yourevent_handleron the backend._wuiEvent('my_event',['some','data'])name(string)-- name of your eventdata(any)-- json-compatible object to send with your event_wuiValReturnsel.valueof an element._wuiVal('#my_input')selector(string)-- querySelector selector_wuiCheckedReturnsel.checkedof an element._wuiChecked('#my_input')selector(string)-- querySelector selector_wuiSelectedReturnsel.selectedof an element._wuiSelected('#my_input')selector(string)-- querySelector selector