Changelog¶
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
Highlightsfor the most attractive new features.BREAKINGfor breaking changes.Addedfor new features.Changedfor changes in existing functionality.Deprecatedfor soon-to-be removed features.Removedfor now removed features.Fixedfor any bug fixes.Securityin case of vulnerabilities.Docsfor documentation changes.YANKEDfor deprecated releases.Internalfor internal changes. Only for maintainers.
Tip
This homepage is used to provide a blog-like changelog and BREAKING CHANGE migration guide.
You can expand sub-projects to view detailed changelogs.
Unreleased¶
0.7.2¶
Fixed¶
-
#253 - fix(pytauri): support deferred evaluation type hint in
@command.
Docs¶
- #254 - docs(fix): add note on "patching install_name of libpython" for macOS standalone
0.7.1¶
Fixed¶
- #240 - fix(pytauri): include
pytauri_utilsin package. Yankpy/pytauri/v0.7.0release.
0.7.0¶
Highlights¶
App State Management and AsyncTools utils¶
- #208 - feat(pytauri)!: app state management and
AsyncToolsutils.
See:
- State Management: https://pytauri.github.io/pytauri/0.7/usage/tutorial/state-management
AsyncTools: https://pytauri.github.io/pytauri/0.7/usage/concepts/async
Debugging Tutorial¶
- #182 - feat!: debugging tutorial.
See: https://pytauri.github.io/pytauri/0.7/usage/tutorial/debugging
Also, we added tauri::{IS_DEV, RESTART_EXIT_CODE, VERSION, webview_version} for obtaining pytauri runtime configuration.
Generate TypeScript Client for IPC¶
- #179 - feat(pytauri): generate typescript client for IPC.
See: https://pytauri.github.io/pytauri/0.7/usage/tutorial/gen-ts
automatically generates the following TypeScript client code:
export async function greetToPerson(
body: Commands["greet_to_person"]["input"],
options?: InvokeOptions
): Promise<Commands["greet_to_person"]["output"]> {
return await pyInvoke("greet_to_person", body, options);
}
Support using arbitrary types in command¶
- #171 - feat(pytauri): support using arbitrary types in
command.
from pydantic import RootModel
from pytauri import Commands
commands = Commands()
RootModelStr = RootModel[str]
# 😫 ❌
@commands.command()
async def old(body: RootModelStr) -> bytes:
return b"null"
# 😇 ✔
@commands.command()
async def new(body: str) -> None:
return
BREAKING¶
-
#208 - feat(pytauri)!: app state management and
AsyncToolsutils.The following parameters have been removed:
pytauri.Commandsset_command(check_signature)parse_parameters(check_signature)
-
#178 - feat(plugin-api)!: remove
rawPyInvokeandChannel.Migration
Before
v0.7, IPC between Python and the frontend always usedbytes/ArrayBufferfor data transfer.pyInvokeandChannel.addJsonListenerwould force serialization and deserialization of input and output, whilerawPyInvokeandChannel.onmessagewould directly send and receive rawArrayBufferdata. This mechanism has been improved inv0.7:-
Now,
pyInvokeautomatically chooses whether to serialize based on your input type:ArrayBuffer/Uint8Arraywill be sent directly to the backend.- Other types (
any) will be converted toArrayBufferusingJSON.stringifyandTextEncoderbefore being sent to the backend.
Only the backend can decide whether to deserialize or accept the raw bytes data.
-
Only the backend can decide whether to return deserialized JSON data or raw
ArrayBufferdata:- If
Commandreturns abytestype or Channel.send sends abytestype, the frontend will receive anArrayBuffer. - If
Commandreturns other types (BaseModel/Any) and Channel.send sends astrtype, the frontend will receive automatically deserialized JSON data.
- If
Therefore:
pyInvokereplacesrawPyInvokeimport { Channel } from "@tauri-apps/api/core"replacesimport { Channel } from "tauri-plugin-pytauri-api".
-
0.6.1¶
Highlights¶
Rebranding¶
#173 - docs: rebranding.
A branding overhaul with an updated logo and colors:
The shape is similar to shapes found in the Tauri logo.
We have verified with the Tauri board and they are OK with this logo.
Comparison
The updated brand color is a mix of the Python and Tauri blue.
Q: Changing the logo and branding color might confuse users that they have entered the wrong site
While this might be true, we think PyTauri is not large enough yet to have this be a big issue. Either way I would suggest leaving this issue #170 up for a week and adding the banner above to the website to prepare users for the transition. An argument could also be made that a more professional looking icon attracts more users. Anyway, we will not change the logo (at least the color) again in the future.
Fixed¶
-
#175 - fix(pytauri): bump
tauri-plugin-*to fix rust docs build failures ondocs.rs.See tauri-apps/tauri#13597 for details.
tauri-plugin-opener = { version = "2.3.0" }tauri-plugin-clipboard-manager = { version = "2.2.3" }tauri-plugin-dialog = { version = "2.2.2" }tauri-plugin-fs = { version = "2.3.0" }tauri-plugin-notification = { version = "2.2.3" }
0.6.0¶
Highlights¶
create-pytauri-app template generator¶
- #169 - docs: update docs for v0.6.0 .
Since version 0.6, create-pytauri-app is the recommended way to start a new PyTauri project, even if it is still in development.
Usage
Refer to uv and copier, run the following command:
This will initialize the project in the form of an interactive questionnaire:
However, we still recommend reading the entire "Tutorial" section, as it will help you understand all the details of pytauri.
tauri-plugin-dialog bingings¶
- #163 - feat(plugin): implement
tauri-plugin-dialogbindings.
tauri-plugin-dialog has now been integrated into pytauri as the plugin-dialog gated feature.
Usage
from pytauri_plugins.dialog import DialogExt, MessageDialogButtons, MessageDialogKind
@commands.command()
async def greet(
app_handle: AppHandle, webview_window: WebviewWindow
) -> bytes:
file_dialog_builder = DialogExt.file(app_handle)
file_dialog_builder.pick_files(
lambda files: print(f"Files selected: {files}"),
add_filter=("markdown", ["md"]),
set_parent=webview_window,
set_title="Select a Markdown file",
)
message_dialog_builder = DialogExt.message(app_handle, "Hello!")
message_dialog_builder.show(
lambda is_ok: print(f"Dialog closed with: {is_ok}"),
parent=webview_window,
buttons=MessageDialogButtons.OkCancelCustom("ok", "cancel"),
kind=MessageDialogKind.Info,
)
return b"null"
Integrate plugins as features¶
- #160 - feat(pytauri)!: integrate
plugin-notificationas a gated-feature ofpytauri.
The rs/pytauri-plugin-notification crate and the py/pytauri-plugin-notification package have been removed. Instead, use the plugin-notification feature of the rs/pytauri crate.
For details, compare the v0.5 and v0.6 "tutorial/using-plugins" documentation.
Migration
# src-tauri/Cargo.toml
[dependencies]
-pytauri-plugin-notification = ...
+pytauri = { version = "...", features = ["plugin-notification"] }
BREAKING¶
-
#166 - fix(standalone)!: standalone binary not working on MacOS.
Patch
install_nameforlibpython3.*.dylibofpython-build-standaloneSee: https://github.com/pytauri/pytauri/issues/99#issuecomment-2704556726.
The
install_nameoflibpython3.*.dylibbuilt bypython-build-standalonedoes not include@rpath, which makes therpathset for the executable ineffective.Migration
Until this is fixed upstream in
python-build-standalone, you need to manually patch theinstall_name:Do not create more than one
AppinstanceSee: https://github.com/tauri-apps/tauri/issues/12934
tauridoes not allow creating more than oneAppinstance per process. Previously, we were unaware of this limitation and suggested creating asample_appto obtain theresource_dir, which subsequently caused a panic inAppin__init__.py.Migration
-
#161 - refactor(pytauri)!: refactor
BuilderArgstoTypedDict. -
#157 - feat(pytauri)!:
Position.Physical(x, y)->Position.Physical((x, y)).These APIs have changed:
Position.PhysicalPosition.LogicalSize.PhysicalSize.Logical
Migration
from pytauri import Position, PositionType, Size, SizeType def foo(pos: PositionType, size: SizeType) -> None: match pos: - case Position.Physical(x, y): + case Position.Physical((x, y)): print(f"Physical position: {x}, {y}") - case Position.Logical(x, y): + case Position.Logical((x, y)): print(f"Logical position: {x}, {y}") match size: - case Size.Physical(w, h): + case Size.Physical((w, h)): print(f"Physical size: {w}, {h}") - case Size.Logical(w, h): + case Size.Logical((w, h)): print(f"Logical size: {w}, {h}") -foo(pos=Position.Physical(1, 2), size=Size.Physical(3, 4)) +foo(pos=Position.Physical((1, 2)), size=Size.Physical((3, 4)))
Added¶
-
#157 - feat(pytauri): fully implement
tauri::RunEventbindings.-
mod tauri::ThemeCloseRequestApiExitRequestApiDragDropEvent(DragDropEventType)WebviewEvent(WebviewEventType)WindowEvent(WindowEventType)RunEvent::{ExitRequested::api, WebviewEvent::event, WindowEvent::event}fieldswebview::WebviewWindow::{on_webview_event, on_window_event}methodsAppHandle::set_themeandwebview::WebviewWindow::{set_theme, theme}methods
-
add
_NonExhaustivefield to all#[non_exhaustive]enums
Usage
from pytauri import AppHandle, Manager, WindowEvent, WindowEventType def register_window_event_handler(app_handle: AppHandle): webview_window = Manager.get_webview_window(app_handle, "main") assert webview_window is not None close_requested = False def window_event_handler(event: WindowEventType) -> None: nonlocal close_requested match event: case WindowEvent.CloseRequested(api=api): if not close_requested: print("Preventing window closing") api.prevent_close() close_requested = True case WindowEvent.Focused(focused): print(f"Window focused: {focused}") case WindowEvent.Moved((x, y)): print(f"Moved to ({x}, {y})") case _: pass webview_window.on_window_event(window_event_handler) -
-
#158 - chore: bump
pyo3to0.25.
0.5.0¶
Highlights¶
Wheel on Windows Arm64¶
- #139 - feat: add support for the
windows-11-armplatform wheel.
You can now install pytauri-wheel from PyPI on Windows 11 Arm64.
Using Unreleased Commits¶
- #147 - feat: support installation from git repository.
See: https://pytauri.github.io/pytauri/0.5/usage/using-unreleased-commits/
Accessing the request headers¶
- #136 - feat(pytauri): accessing the request headers in
Commands.
See: https://pytauri.github.io/pytauri/0.5/usage/concepts/ipc/#accessing-request-headers
Protect source code¶
- #134 - docs: add tutorial on using
Cythonto protect source code.
See: https://pytauri.github.io/pytauri/0.5/usage/tutorial/build-standalone-cython/
BREAKING¶
- #136 -
tauri v2.5requires upgrading@tauri-apps/api: ^2.5andtauri-plugin-pytauri-api: ^0.5. - #141 - feat(pytauri)!:
pytauri.path.PathResolvernow returns apathlib.Pathobject instead of astr. -
#133 - fix(pytauri)!: make
BuilderArgs.invoke_handleras required parameter for #110.If you do not specify
invoke_handler,pytauriwill not register thetauri-plugin-pytauriplugin, which means you cannot usepyInvokein the frontend to callCommands(you will receive an error like "plugin pytauri not found"). If this is indeed the behavior you expect, explicitly passNone.
Added¶
- #141 - chore: bump
pyo3to0.24.1. - #124 - #136 - chore: bump
tauridependencies:taurito2.5.1tauri-utilsto~2.4tauri-buildto2.2tauri-pluginto2.2tauri-plugin-notificationto2.2tauri-plugin-openerto2.2@tauri-apps/apito^2.5@tauri-apps/plugin-openerto^2.2@tauri-apps/plugin-dialogto^2.2
Docs¶
- #124 - docs: update example to use
App.run_returninstead ofApp.run, allowing cleanup tasks to be performed after the app exits (e.g., shutting down theniceguiserver) and retrieving the exit code.
0.4.0¶
Highlights¶
Precompiled python wheel (goodbye Rust compiler)¶
In v0.4.0, we have introduced an exciting new feature: precompiled Python wheel support! 🎉
This means you can use PyTauri without writing any Rust code or needing a Rust compiler.
This allows you to perform full-stack development in pure Python (like pywebview but battery-included 🤓).
Please refer to the PyTauri Wheel documentation for more information.
New logo for PyTauri¶
Thanks to @ISOR3X in #111! PyTauri now has its own logo 🎉:

It is indeed a snake, but not eating a bean, or perhaps it was not intended to be. It is more so a combination of the Tauri logo (two dots with rings around them) and the Python logo (the snake, specifically the head). The left part is more intended to visualize the snake curling around. Perhaps it is a bit too abstract.
Changed¶
- #63 - chore: bump
tauritov2.3.
Internal¶
- #119 - ci(rs/release): add
--no-verifytocargo publishso that we can release parallelly. - #113 - ci: add
macos-latestos inlint-testCI. - #111 - docs: added PyTauri logo and updated documentation colors
- #103 - chore: transfer repo to
pytauriorg.
0.3.0¶
Highlights¶
Changed¶
- #79 - bump
rust-version = "1.82"
Docs¶
- #88 - docs: add rust api reference section.
- #85 - docs: add concepts
IPCandusing multiprocessingsections. - #80 -
example/nicegui-app:- Use
BuilderArgs.setupfor initialization instead of listening to theRunEvent.Readyevent. - Rewrite the
FrontServerstartup/shutdownevent hook logic. - Modularize the code.
- Use
- #79 -
example/nicegui-app:- use
trayandmenufeature - use
python3.10matchstatement instead ofif-elsestatement - bump
requires-python = ">=3.10"
- use
Internal¶
- #81 - ci: add
clear-cache.ymlworkflow.
0.2.0¶
BREAKING¶
- #70 - feat(notification): removed
NotificationBuilderArgs. SeeCHANGELOG.mdofpy/pytauri-plugin-notificationfor how to migrate. - #57 - refactor(py/pytauri): remove
RunEventEnum, use matchedRunEventdirectly. SeeCHANGELOG.mdofpy/pytaurifor how to migrate. - #56 - perf(pytauri): all IPC methods that previously accepted
bytearrayas a parameter now only acceptbytesas a parameter. - #52 - refactor(standalone)!: new API for preparing python interpreter.
The
pytauri::standalonemodule has been completely rewritten. Previously, you usedprepare_freethreaded_python_with_executableandappend_ext_mod. Now, you need to usePythonInterpreterBuilder. See thepytauricrate rust API docs and tutorial (examples/tauri-app)main.rscode for more information on how to migrate.
Docs¶
- #60 - update
examplesmain.rsto removeresource_dir()UNC path prefix\\?\forPythonInterpreterEnv::Standalone. Fix pallets/jinja#1675 fornicegui-appstandalone example. - #55 - Add
integrate with niceguiexamplenicegui-app. Seeexamples/nicegui-app. - #52 - update
examples/tauri-appmain.rsfor new API to prepare python interpreter. - #52 - add the usage of
multiprocessing.freeze_supportinexamples/tauri-app__main__.py.
Changed¶
- #46 - bump
tauritov2.2
Internal¶
- #83 - chore: bump
pyo3tov0.23.4inCargo.lockto fix PyO3/pyo3#4828. - #64 - test: add integration tests for
commandandchannelipc