Show HN: New Python GUI Framework (better than others?)
4 months ago
8
A ridiculously Pythonic and powerful framework for building beautiful desktop applications.
WinUp is a modern UI framework for Python that wraps the power of PySide6 (Qt) in a simple, declarative, and developer-friendly API. It's designed to let you build applications faster, write cleaner code, and enjoy the development process.
Why WinUp? (Instead of raw PySide6 or Tkinter)
Desktop development in Python can feel clunky. WinUp was built to fix that.
Non-existent. Requires manual on_change handlers to update state and UI.
Developer Tools
Built-in Hot Reloading, code profiler, and window tools out of the box.
Non-existent. Restart the entire app for every single UI change.
Code Structure
Reusable, self-contained components with @component.
Often leads to large, monolithic classes or procedural scripts.
In short, WinUp provides the "killer features" of modern web frameworks (like React or Vue) for the desktop, saving you time and letting you focus on what matters: your application's logic.
Feature
WinUp (with your improvements)
PyEdifice
🧱 Architecture
React-style w/ plugins + state
React-style + state
🌐 Built-in Routing
✅ Yes (Router(routes={...}))
❌ No built-in routing
♻️ Lifecycle Hooks
✅ on_mount, on_unmount, etc.
⚠️ Limited (did_mount, etc.)
🎨 Theming / Styling System
✅ Global & Scoped themes
❌ Manual CSS injection
🔲 Layout Options
✅ Row, Column, Grid, Stack, Flexbox
⚠️ Mostly Box & HBox/VBox
🎞️ Animations
✅ Built-in (fade, scale, etc.)
❌ None built-in
🔁 Hot Reloading (LHR)
✅ Stable + fast (loadup dev)
⚠️ Experimental, limited support
📦 Packaging
✅ With LoadUp (PyInstaller-based)
❌ Must integrate PyInstaller manually
🧩 Component Reusability
✅ High, declarative
✅ High
🛠 Developer Tooling
✅ DevTools planned, Inspector soon
❌ None yet
📱 Mobile Support
❌ Not yet
❌ Not supported
🧠 Learning Curve
✅ Easy for Python+React users
✅ Easy but less tooling
✅ = Built-in or robust
⚠️ = Partial or limited
❌ = Missing entirely
Declarative & Pythonic UI: Build complex layouts with simple Row and Column objects instead of clunky box layouts.
Component-Based Architecture: Use the @component decorator to create modular and reusable UI widgets from simple functions.
Powerful Styling System: Style your widgets with simple Python dictionaries using props. Create global "CSS-like" classes with style.add_style_dict.
Full Application Shell: Build professional applications with a declarative API for MenuBar, ToolBar, StatusBar, and SystemTrayIcon.
Asynchronous Task Runner: Run long-running operations in the background without freezing your UI using the simple @tasks.run decorator.
Performance by Default: Includes an opt-in @memo decorator to cache component renders and prevent needless re-computation.
Advanced Extensibility:
Widget Factory: Replace any default widget with your own custom implementation (e.g., C++ based) using ui.register_widget().
Multiple Windows: Create and manage multiple independent windows for complex applications like tool palettes or music players.
Reactive State Management:
One-Way Binding: Automatically update your UI when your data changes with state.bind().
Two-Way Binding: Effortlessly sync input widgets with your state using state.bind_two_way().
Subscriptions: Trigger any function in response to state changes with state.subscribe().
Developer-Friendly Tooling:
Hot Reloading: See your UI changes instantly without restarting your app.
Profiler: Easily measure the performance of any function with the @profiler.measure() decorator.
Window Tools: Center, flash, or manage your application window with ease.
Built-in Routing: Easily create multi-page applications with an intuitive, state-driven router.
Flexible Data Layer: Includes simple, consistent connectors for SQLite, PostgreSQL, MySQL, MongoDB, and Firebase.
The watchdog library is required for the Hot Reloading feature.
This makes an app template ready for use, if LoadUp doesn't work, use PyInstaller instead.
Getting Started: Hello, WinUp!
Creating an application is as simple as defining a main component and running it.
# hello_world.pyimportwinupfromwinupimportui# The @component decorator is optional for the main component, but good practice.@winup.componentdefApp():
"""This is our main application component."""returnui.Column(
props={
"alignment": "AlignCenter",
"spacing": 20
},
children=[
ui.Label("👋 Hello, WinUp!", props={"font-size": "24px"}),
ui.Button("Click Me!", on_click=lambda: print("Button clicked!"))
]
)
if__name__=="__main__":
winup.run(main_component_path="file_name:App", title="My First WinUp App")
WinUp abstracts away Qt's manual layout system. You build UIs by composing Row and Column components.
Beyond simple Row and Column, WinUp provides more advanced layouts for building complex UIs.
1. Stack Layout
A Stack layout places its children on top of each other, like a deck of cards. Only one child is visible at a time. This is perfect for wizards, tab-less interfaces, or navigation with a RouterView.
defApp():
# The main stack layoutmain_stack=ui.Stack(children=[
ui.Label("Page 1"),
ui.Label("Page 2"),
])
# Controls to switch between pagesreturnui.Column(children=[
main_stack,
ui.Row(children=[
ui.Button("Show Page 1", on_click=lambda: main_stack.set_current_index(0)),
ui.Button("Show Page 2", on_click=lambda: main_stack.set_current_index(1)),
])
])
2. Grid Layout
For form fields, dashboards, or calculator-style interfaces, the Grid layout is ideal. You specify children as tuples containing the widget and its grid position: (widget, row, col). You can also specify row and column spans: (widget, row, col, row_span, col_span).
To create flexible UIs that adapt to window size, you can add a stretch factor to children of a Row or Column. This works like the flex-grow property in CSS. A child with a higher stretch factor will take up more available space.
In this example, the middle label (stretch: 2) will be twice as wide as the other two.
defApp():
returnui.Row(
props={"spacing": 5},
children=[
(ui.Label("Takes up 1 part"), {"stretch": 1}),
(ui.Label("Takes up 2 parts"), {"stretch": 2}),
(ui.Label("Takes up 1 part"), {"stretch": 1}),
]
)
You can style any widget by passing a props dictionary. Props can be CSS-like properties, or special keywords like class and id for use with a global stylesheet.
# Define global styleswinup.style.add_style_dict({
".btn-primary": {
"background-color": "#007bff",
"color": "white",
"border-radius": "5px",
"padding": "10px"
},
".btn-primary:hover": {
"background-color": "#0056b3"
}
})
# Use the class in a componentdefApp():
returnui.Button("Primary Button", props={"class": "btn-primary"})
Theming and Dynamic Styling
WinUp includes a powerful theming system that lets you define and switch between different looks for your application (e.g., light and dark mode) at runtime.
The system is built on a simple concept: theme variables. You define your application's stylesheet using variables (like $primary-color or $text-color). Then, you can define multiple "themes" that provide concrete values for these variables.
1. Using Theme Variables
You can use theme variables in two places:
In a global stylesheet using style.styler.add_style_dict().
WinUp comes with built-in light and dark themes. You can switch between them at any time using style.styler.themes.set_theme().
fromwinupimportstyledeftoggle_theme():
# Access the theme manager through the styler singletoncurrent_theme=style.styler.themes._active_theme_nameifcurrent_theme=="light":
style.styler.themes.set_theme("dark")
else:
style.styler.themes.set_theme("light")
# You can connect this function to a button click or a settings switch.# The entire application will automatically restyle itself.
3. Creating Custom Themes
You can easily define your own themes by providing a dictionary of variable names to color values.
fromwinupimportstyle# Define a custom "matrix" themematrix_theme= {
"primary-color": "#00FF41",
"primary-text-color": "#000000",
"background-color": "#0D0208",
"text-color": "#00FF41",
"border-color": "#008F11",
"hover-color": "#00A62A",
}
# Add it to the theme manager via the stylerstyle.styler.themes.add_theme("matrix", matrix_theme)
# Now you can switch to itstyle.styler.themes.set_theme("matrix")
Creating Reusable Components
While you can use raw ui elements everywhere, the best way to build a maintainable application is to create your own library of reusable components. WinUp provides two main ways to do this.
1. Styled Variants (Recommended)
This is the most common and powerful pattern. You can create a new, reusable component by wrapping a base widget with default props. This is perfect for creating a consistent design system (e.g., PrimaryButton, SecondaryButton, Card, Avatar).
The ui.create_component function is the key to this pattern.
# components.pyfromwinupimportui# Create a PrimaryButton variant with default stylesPrimaryButton=ui.create_component(
ui.Button,
{
"class": "btn-primary", # Target with global stylesheet"padding": "10px 15px",
"font-weight": "bold",
}
)
# Create an AlertLabel variantAlertLabel=ui.create_component(
ui.Label,
{
"background-color": "$error-color",
"color": "$primary-text-color",
"padding": "10px",
"border-radius": "4px",
}
)
# --- In your main application ---# from components import PrimaryButton, AlertLabeldefApp():
returnui.Column(children=[
PrimaryButton("Click me!"),
# You can still override props at the instance levelPrimaryButton("Cancel", props={"background-color": "$disabled-color"}),
AlertLabel("Something went wrong!"),
])
2. Subclassing (For Advanced Behavior)
If you need to add new behavior to a widget (not just styles), you can fall back to standard Python subclassing. This is useful for creating highly specialized components with their own internal logic. After creating your class, you can register it with ui.register_widget to make it available everywhere.
fromwinup.ui.widgets.inputimportInputasDefaultInputclassPasswordInput(DefaultInput):
"""An Input that hides text by default but has a toggle button."""def__init__(self, props: dict=None):
super().__init__(props=props)
# In a real implementation, you would add a button here# and connect it to self.setEchoMode().self.setEchoMode(self.EchoMode.Password)
# In your main script, before winup.run():# This makes `ui.PasswordInput()` available if you register it.# ui.register_widget("PasswordInput", PasswordInput)
Traits System: Adding Behavior without Subclassing
While subclassing is great for creating new kinds of widgets, sometimes you just want to add a small, reusable piece of behavior to an existing widget—like making it draggable or giving it a right-click menu. This is where Traits come in.
Traits are modular behaviors that can be dynamically attached to any widget instance. WinUp comes with several built-in traits:
draggable & droptarget: A powerful, data-driven system for drag-and-drop functionality.
context_menu: Adds a custom right-click context menu.
tooltip: A simple way to add a hover tooltip.
hover_effect: Applies a [hover="true"] style property on mouse-over, which you can target in your stylesheets (e.g., QPushButton[hover="true"]).
highlightable: Makes the text of a widget (like ui.Label) selectable by the user.
You can add a trait to any widget using winup.traits.add_trait().
Example: Advanced Drag-and-Drop
The new drag-and-drop system is data-driven. You make a widget draggable and provide it with data. You make another widget a droptarget and tell it what kind of data it accepts and what to do on_drop.
# dnd_demo.pyimportwinupfromwinupimportui, traits, statedefApp():
# Use state to manage the listsstate.create("list_a", [{"id": 1, "text": "Item A"}])
state.create("list_b", [{"id": 2, "text": "Item B"}])
defmove_item(source_list_key, target_list_key, item_id):
# Find the item and move it between the state listssource_list=state.get(source_list_key)
item_to_move=next((itemforiteminsource_listifitem["id"] ==item_id), None)
ifitem_to_move:
new_source= [iforiinsource_listifi["id"] !=item_id]
state.set(source_list_key, new_source)
state.set(target_list_key, state.get(target_list_key) + [item_to_move])
# A reusable component for our drop zones@winup.componentdefDropList(title, list_key, accepts_type):
list_container=ui.Column(props={"spacing": 5, "min-height": "100px", "background-color": "#f0f0f0", "padding": "10px"})
# 1. Make the container a drop targettraits.add_trait(list_container, "droptarget",
accepts=[accepts_type],
on_drop=lambdadata: move_item(data["source_list"], list_key, data["item_id"])
)
defrender_list(items):
winup.core.hot_reload.clear_layout(list_container.layout())
foriteminitems:
# 2. Make each item draggabledraggable_widget=ui.Label(item["text"], props={"padding": "8px", "background-color": "white"})
traits.add_trait(draggable_widget, "draggable",
data={"type": accepts_type, "item_id": item["id"], "source_list": list_key}
)
list_container.add_child(draggable_widget)
state.subscribe(list_key, render_list)
render_list(state.get(list_key))
returnui.Column([ui.Label(title, props={"font-weight": "bold"}), list_container])
returnui.Row(props={"spacing": 20, "margin": "20px"}, children=[
DropList("List A (drag from here)", "list_a", "list-item"),
DropList("List B (drop here)", "list_b", "list-item"),
])
if__name__=="__main__":
winup.run(main_component_path="file_name:App", title="Drag and Drop Demo")
State Management: The Reactive Core
WinUp includes a powerful state management system that lets you create reactive UIs with minimal boilerplate. The new, recommended approach is object-oriented, making your code safer and more readable.
The New Way: state.create() and bind_to() (Recommended)
Instead of using string keys, you now create dedicated State objects. These objects are the single source of truth for your data and provide methods to interact with that data.
The real power comes from the bind_to() method, which can link one or more state objects to any widget property, using a simple function to format the final value.
1. Simple Counter Example
Here, we create a counter state object and bind it to a label's text property. The lambda function formats the output.
# new_state_demo.pyimportwinupfromwinupimportuidefApp():
# 1. Create a state object with an initial value.counter=winup.state.create("counter", 0)
# 2. Create the UI widgets.label=ui.Label()
# 3. Bind the state to the label's 'text' property.# The lambda function will re-run whenever the counter changes.counter.bind_to(label, 'text', lambdac: f"Counter Value: {c}")
defincrement():
# 4. Use the state object's methods to update the value.counter.set(counter.get() +1)
returnui.Column(children=[
label,
ui.Button("Increment", on_click=increment)
])
if__name__=="__main__":
winup.run(main_component_path="file_name:App", title="New State Demo")
2. Multi-State Binding
Need a widget to react to changes in multiple state values? Use the and_() method to combine them. The formatter lambda will receive the state values as arguments in the same order.
defApp():
# Create two state objectsfirst_name=winup.state.create("first_name", "John")
last_name=winup.state.create("last_name", "Doe")
greeting_label=ui.Label()
# Bind the label's text to BOTH state objectsfirst_name.and_(last_name).bind_to(
greeting_label,
'text',
lambdafn, ln: f"Full Name: {fn}{ln}"
)
# ... widgets to change first_name and last_name
3. Two-Way Binding for Inputs
For the common case of syncing an Input widget with a state, the bind_two_way() helper is still available. It works directly with the state key.
email_input=ui.Input()
# The input's value updates the state, and the state updates the input's value.winup.state.bind_two_way(email_input, 'email_value')
Legacy State Management (Old API)
For backward compatibility, the older, string-based API is still functional.
winup.state.set("key", value): Sets a value in the global store.
winup.state.get("key"): Retrieves a value.
winup.state.bind(widget, "property", "key"): A simple one-way binding to a widget's property.
winup.state.subscribe("key", callback): Calls a function whenever a value changes.
# old_api_demo.pywinup.state.set("legacy_counter", 0)
label=ui.Label()
winup.state.bind(label, "text", "legacy_counter") # Binds to the text propertydefincrement():
winup.state.set("legacy_counter", winup.state.get("legacy_counter") +1)
Asynchronous Task Runner (@tasks.run)
To keep your application responsive, any long-running operation (like a network request or a complex calculation) should be run on a background thread. WinUp makes this incredibly simple with the @tasks.run decorator.
It handles all the complex threading logic for you. You just need to provide callbacks for when the task starts, finishes, or encounters an error.
fromwinupimporttasks, shellimporttime# These callbacks will be executed safely on the main GUI thread.defon_task_start():
shell.StatusBar.show_message("Processing...", -1) # Show message indefinitelydefon_task_finish(result):
print(f"Task finished with result: {result}")
shell.StatusBar.show_message(f"Success: {result}", 5000) # Show for 5sdefon_task_error(error_details):
exception, traceback_str=error_detailsprint(f"Task failed: {exception}")
shell.StatusBar.show_message(f"Error: {exception}", 5000)
# Decorate your long-running function@tasks.run(on_start=on_task_start, on_finish=on_task_finish, on_error=on_task_error)deffetch_data_from_server(url: str):
"""This function runs on a background thread."""print("Fetching data...")
time.sleep(3) # Simulate a network requestif"error"inurl:
raiseConnectionError("Could not connect to server.")
return"Data fetched successfully!"# In your UI, just call the function as you normally would.# WinUp will automatically delegate it to the background thread pool.defApp():
returnui.Row(children=[
ui.Button("Fetch Data", on_click=lambda: fetch_data_from_server("my-api.com/data")),
ui.Button("Trigger Error", on_click=lambda: fetch_data_from_server("my-api.com/error")),
])
The decorator accepts three optional callback arguments:
on_start: A function to call right before the task begins executing.
on_finish: A function that will receive the return value of your function if it completes successfully.
on_error: A function that will receive a (exception, traceback) tuple if your function raises an exception.
Hot Reloading (React-style Fast Refresh):
WinUp now features a fully automatic, intelligent hot reloading system. Simply run your application with dev=True, and the framework will automatically watch all of your project's Python files for changes.
When you save a file, WinUp will instantly reload the relevant parts of your code and re-render your UI without restarting the entire application, preserving your application's state. This provides a seamless development experience similar to modern web frameworks.
How to Use:
Just pass the dev=True flag to the run function. That's it!
# my_app.pyimportwinupfromwinupimportui@winup.componentdefApp():
# Change this text, save the file, and see the UI update instantly.returnui.Label("Hello, Hot Reloading!")
if__name__=="__main__":
# Run in development mode to enable hot reloading.winup.run(main_component_path="my_app:App", title="Hot Reload Demo", dev=True)
This setup allows you to see UI changes instantly just by saving any file in your project.
Performance & Memoization:
For UIs that render large amounts of data, you can significantly improve performance by caching component results. The @winup.memo decorator automatically caches the widget created by a component. If the component is called again with the same arguments, the cached widget is returned instantly instead of being re-created.
importwinupfromwinupimportui# By adding @winup.memo, this component will only be re-created# if the 'color' argument changes.@winup.memodefColorBlock(color: str):
returnui.Frame(props={"background-color": color, "min-height": "20px"})
defApp():
# In this list, ColorBlock('#AABBCC') will only be called once.# The framework will then reuse the cached widget for the other two instances.returnui.Column(children=[
ColorBlock(color="#AABBCC"),
ColorBlock(color="#EEEEEE"),
ColorBlock(color="#AABBCC"),
ColorBlock(color="#AABBCC"),
])
Profiler:
Simply add the @profiler.measure() decorator to any function to measure its execution time. Results are printed to the console when the application closes.
The profiler also automatically tracks the performance of the memoization cache, showing you hits, misses, and the overall hit ratio.
fromwinup.toolsimportprofiler@profiler.measuredefsome_expensive_function():
# ... code to measure ...importtimetime.sleep(1)
WinUp includes a simple yet powerful router that allows you to build applications with multiple pages or views, like a settings screen, a user dashboard, or different application tabs.
The system is built around three core concepts:
Router: An object that holds your application's routes (a mapping of a path like "/home" to a component function) and manages the current state.
RouterView: A special component that acts as a placeholder. It automatically displays the correct component for the current route.
RouterLink: A clickable component (like a hyperlink) that tells the Router to navigate to a different path.
Example: A Simple Multi-Page App
Here's how you can structure a basic application with a "Home" and "Settings" page.
# multi_page_app.pyimportwinupfromwinupimportuifromwinup.routerimportRouter, RouterView, RouterLink# 1. Define your page components@winup.componentdefHomePage():
returnui.Label("Welcome to the Home Page!", props={"font-size": "18px"})
@winup.componentdefSettingsPage():
returnui.Column(children=[
ui.Label("Settings", props={"font-size": "18px"}),
ui.Switch(text="Enable Dark Mode")
])
# 2. Create a router instance with your routesapp_router=Router({
"/": HomePage,
"/settings": SettingsPage,
})
# 3. Build the main application layoutdefApp():
returnui.Column(
props={"spacing": 15, "margin": "10px"},
children=[
# Navigation linksui.Row(
props={"spacing": 20},
children=[
RouterLink(router=app_router, to="/", text="Home"),
RouterLink(router=app_router, to="/settings", text="Settings")
]
),
# The RouterView will render either HomePage or SettingsPageui.Frame(
props={"border": "1px solid #ccc", "padding": "10px"},
children=[
RouterView(router=app_router)
]
)
]
)
if__name__=="__main__":
# You need to create the router files first for this to work.winup.run(main_component_path="file_name:App", title="Multi-Page App Demo")
Component Lifecycle Hooks: on_mount and on_unmount
To manage side effects—like fetching data, setting up timers, or starting animations—WinUp components now have lifecycle hooks. These are special functions you can pass to a component that run at specific times.
on_mount: This function is called exactly once, right after the component's UI has been created and added to the scene (i.e., it has "mounted"). It's the perfect place to load data or start animations.
on_unmount: This function is called exactly once, just before the component is destroyed and removed from the scene. It's essential for cleanup tasks, like canceling network requests, invalidating timers, or saving state.
Example: A Self-Updating Clock
This example demonstrates how to use on_mount to start a timer and on_unmount to clean it up, preventing memory leaks.
# lifecycle_demo.pyimportwinupfromwinupimportui, statefromPySide6.QtCoreimportQTimer@winup.componentdefClock():
# 1. A state to hold the current time stringtime_state=state.create("current_time", "Loading...")
# 2. A variable to hold our QTimer instancetimer=Nonedefupdate_time():
# This function will be called by the timerimportdatetimenow=datetime.datetime.now()
time_state.set(now.strftime("%H:%M:%S"))
defon_mount():
nonlocaltimerprint("Clock Mounted: Starting timer.")
# Create and start the timer when the component appearstimer=QTimer()
timer.timeout.connect(update_time)
timer.start(1000) # Update every 1000 ms (1 second)update_time() # Initial updatedefon_unmount():
nonlocaltimerprint("Clock Unmounted: Stopping timer.")
# Stop the timer when the component disappears to avoid errorsiftimer:
timer.stop()
timer=None# 3. Create a label and bind its text to our statetime_label=ui.Label()
time_state.bind_to(time_label, 'text', lambdat: f"Current Time: {t}")
# 4. Pass the hooks to the component containerreturnui.Frame(
children=[time_label],
on_mount=on_mount,
on_unmount=on_unmount
)
# A simple app to show/hide the clock to test the hooksdefApp():
visibility_state=state.create("clock_visible", True)
deftoggle_clock():
visibility_state.set(notvisibility_state.get())
placeholder=ui.Frame(props={"min-height": "50px"})
defon_visibility_change(is_visible):
clear_layout(placeholder.layout())
ifis_visible:
placeholder.add_child(Clock())
else:
placeholder.add_child(ui.Label("Clock is hidden."))
visibility_state.subscribe(on_visibility_change)
on_visibility_change(visibility_state.get()) # Initial renderreturnui.Column(children=[
ui.Button("Toggle Clock", on_click=toggle_clock),
placeholder
])
if__name__=="__main__":
# You will need to import clear_layout from winup.core.hot_reloadfromwinup.core.hot_reloadimportclear_layoutwinup.run(main_component_path="file_name:App", title="Lifecycle Demo")
Built-in Animations & Effects
WinUp provides a simple API for creating smooth animations, located in the winup.animate.fx module. You can easily fade widgets in and out or animate any of their properties.
Example: Fading and Moving
This example shows how to use fade_in, fade_out, and the generic animate function to move a widget.
# animation_demo.pyimportwinupfromwinupimportuifromwinup.animateimportfx# Import the animation functionsfromPySide6.QtCoreimportQRectdefApp():
animated_label=ui.Label(
"Animate Me!",
props={"padding": "20px", "background-color": "#ADD8E6"}
)
defslide_in():
# Animate the widget's geometry (position and size)start_rect=animated_label.geometry()
end_rect=QRect(20, 20, start_rect.width(), start_rect.height())
fx.animate(animated_label, b"geometry", end_rect, 500)
# Place the label in a container with a null layout for manual positioningcontainer=ui.Frame(
props={"layout": "null"},
children=[animated_label]
)
# Start the label faded outfx.fade_out(animated_label, duration=0)
returnui.Column(props={"margin":"20px", "spacing": 10}, children=[
container,
ui.Row(props={"spacing": 10}, children=[
ui.Button("Fade In", on_click=lambda: fx.fade_in(animated_label, 300)),
ui.Button("Fade Out", on_click=lambda: fx.fade_out(animated_label, 300)),
ui.Button("Slide In", on_click=slide_in),
])
])
if__name__=="__main__":
winup.run(App, title="Animation Demo")
Hot Reloading for Development
WinUp includes a powerful hot-reloading feature to accelerate development. When enabled, any changes you make to your component files will be reflected instantly in your running application without needing a manual restart.
The hot-reloading service monitors your project's Python files. When a change is detected, it intelligently reloads the necessary modules and redraws the UI, preserving the application's state. This is ideal for iterating quickly on UI design and component logic.
To enable hot reloading, you must run your application in development mode. This is done by passing dev=True to the winup.run() function.
You also need to provide the main component as a string path in the format "path.to.module:ComponentName". This allows WinUp to dynamically re-import your component when the source file changes.
Here is how you would structure your main application script to use hot reloading:
# main.pyimportwinupimportsysimportos# Assume 'my_app' is your components packagefrommy_app.my_componentimportAppif__name__=="__main__":
# Add the project root to the path to ensure modules can be foundproject_root=os.path.abspath(os.path.dirname(__file__))
ifproject_rootnotinsys.path:
sys.path.insert(0, project_root)
# Run the app with hot reloading enabledwinup.run(
main_component_path="my_app.my_component:App",
title="My App with Hot Reload",
dev=True
)
Now, when you run python main.py, any changes you make to my_app/my_component.py (or any other component file) will be reflected instantly.
WinUp is an open-source project. Contributions are welcome!
This project is licensed under the Apache 2.0 License.