No project description provided
Project description
Schorle
(pronounced as ˈʃɔʁlə) is a server-driven UI kit for Python with
async support.
Note: This project is in an early stage of development. It is not ready for production use.
Installation
pip install schorle
Usage
Take a look at the examples directory.
Concepts
Elements
Elements are the building blocks of a UI. The entrypoint to build a UI is the Page
class.
from schorle.elements.page import Page
class MyPage(Page):
pass
Elements can be nested.
from schorle.elements.button import Button
from schorle.elements.html import Div, Paragraph
from schorle.elements.page import Page
from schorle.reactives.text import Text
class Container(Div):
p1: Paragraph = Paragraph(text=Text("Hello"))
p2: Paragraph = Paragraph(text=Text("World"))
class MyPage(Page):
button: Button = Button.factory(text=Text("Click me!"))
container: Container = Container.factory()
There can also be dynamic elements:
from schorle.elements.base.element import Reactive
from schorle.elements.button import Button
from schorle.elements.html import Div, Paragraph
from schorle.elements.page import Page
from schorle.reactives.text import Text
import random
class MyPage(Page):
button: Button = Button.factory(text=Text("Click me!"))
reactive_element: Reactive = Reactive.factory()
def __init__(self, **data):
super().__init__(**data)
self.button.add_callback("click", self.on_click)
async def on_click(self):
if random.random() > 0.5:
self.dynamic.update(Paragraph(text=Text("Clicked!")))
else:
await self.dynamic.update(Div(text=Text("Not clicked!")))
As well as dynamic collections:
from schorle.elements.button import Button
from schorle.elements.html import Div
from schorle.elements.page import Page
from schorle.reactives.text import Text
from schorle.elements.base.element import Collection
import random
class MyPage(Page):
button: Button = Button.factory(text=Text("Click me!"))
paragraphs: Collection[Div] = Collection.factory()
def __init__(self, **data):
super().__init__(**data)
self.button.add_callback("click", self.on_click)
async def on_click(self):
if random.random() > 0.5:
new_paragraphs = [Div(text=Text("Clicked!"))]
else:
new_paragraphs = []
await self.paragraphs.update(new_paragraphs)
Dynamic collections and elements can also be nested.
Reacting to client-side events
To call something in response to a client-side event, use either @reactive
or add_callback
.
- React to a client-side event with
@reactive
:
from schorle.elements.button import Button
from schorle.utils import reactive
class MyButton(Button):
@reactive("click")
async def on_click(self):
await self.text.update("Clicked!")
- React to a client-side event with
add_callback
:
from schorle.elements.button import Button
from schorle.elements.html import Paragraph
from schorle.elements.page import Page
from schorle.reactives.text import Text
class MyPage(Page):
button: Button = Button.factory(text=Text("Click me!"))
p: Paragraph = Paragraph(text=Text("Not clicked!"))
def __init__(self, **data):
super().__init__(**data)
self.button.add_callback("click", self.on_click)
async def on_click(self):
await self.p.text.update("Clicked!")
Server-side effects
To call some updates from the server-side, use @effector
decorator in combination with subscribe
method:
from __future__ import annotations
from schorle.app import Schorle
from schorle.effector import effector
from schorle.elements.button import Button
from schorle.elements.page import Page
from schorle.reactives.classes import Classes
from schorle.reactives.state import ReactiveModel
from schorle.reactives.text import Text
from schorle.utils import reactive
app = Schorle()
class Counter(ReactiveModel):
value: int = 0
@effector # effector transforms a method into an effect generating function
async def increment(self):
self.value += 1
class ButtonWithCounter(Button):
text: Text = Text("Click me!")
counter: Counter = Counter.factory()
@reactive("click")
async def handle(self):
await self.counter.increment()
async def _on_increment(self, counter: Counter):
await self.text.update(f"Clicked {counter.value} times")
await self.classes.toggle("btn-success")
async def before_render(self):
await self.counter.increment.subscribe(self._on_increment) # subscribe to the effect
class PageWithButton(Page):
classes: Classes = Classes("flex flex-col justify-center items-center h-screen w-screen")
first_button: ButtonWithCounter = ButtonWithCounter.factory()
second_button: ButtonWithCounter = ButtonWithCounter.factory()
@app.get("/")
def get_page():
return PageWithButton()
Running the application
Schorle application is a thin wrapper around FastAPI. To run the application,
use uvicorn
:
uvicorn examples.simple:app --reload
Tech stack
- FastAPI - web framework
- HTMX - client-side library for dynamic HTML
- Tailwind CSS - CSS framework
- DaisyUI - Component library for Tailwind CSS
- Pydantic - classes and utilities for elements
Roadmap
- Add more elements
- Introduce
suspense
for loading states - Add more examples
- Add tests
- Add CI/CD
- Add documentation
- Add support for Plotly-based charts
- Refactor the imports
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.