├── .gitignore ├── LICENSE.txt ├── README.md ├── examples ├── __init__.py ├── basic_async.py ├── bot_with_gui.py ├── progress_bar.py └── simple_example.py ├── pyproject.toml └── src └── dearpygui_async ├── __about__.py ├── __init__.py └── dearpygui_async.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Global directories 2 | __pycache__/ 3 | 4 | # Global files 5 | *.py[cod] 6 | *.dll 7 | *.so 8 | *.log 9 | *.swp 10 | 11 | # Root directories 12 | /.benchmarks/ 13 | /.cache/ 14 | /.venv/ 15 | /.env/ 16 | /.idea/ 17 | /.mypy_cache/ 18 | /.pytest_cache/ 19 | /.ruff_cache/ 20 | /.vscode/ 21 | /backend/dist/ 22 | /dist/ 23 | /site/ 24 | /.direnv 25 | 26 | # Root files 27 | /.coverage* 28 | /.env -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024-present CasuallyCalm 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dearpygui_async 2 | 3 | [![PyPI - Version](https://img.shields.io/pypi/v/dearpygui-async.svg)](https://pypi.org/project/dearpygui-async) 4 | [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/dearpygui-async.svg)](https://pypi.org/project/dearpygui-async) 5 | 6 | A simple way to integrate some async functionality into your dearpygui application. 7 | 8 | ## Key Features 9 | 10 | * Ease of use 11 | * Async callbacks 12 | * Setup & Teardown functions for use with other async applications 13 | 14 | ## Installation 15 | 16 | ```console 17 | pip install dearpygui-async 18 | ``` 19 | ### Note: you will need to install dearpygui separately in order to use this! 20 | 21 | 22 | ## Simple Example 23 | 24 | ```py 25 | import asyncio 26 | import dearpygui.dearpygui as dpg 27 | from dearpygui_async import DearPyGuiAsync # import 28 | 29 | dpg_async = DearPyGuiAsync() # initialize 30 | 31 | async def save_callback(): 32 | await asyncio.sleep(3) 33 | print("Save Clicked") 34 | 35 | dpg.create_context() 36 | dpg.create_viewport() 37 | dpg.setup_dearpygui() 38 | 39 | with dpg.window(label="Example Window"): 40 | dpg.add_text("Hello world") 41 | dpg.add_button(label="Save", callback=save_callback) 42 | dpg.add_input_text(label="string") 43 | dpg.add_slider_float(label="float") 44 | 45 | dpg.show_viewport() 46 | dpg_async.run() # run; replaces `dpg.start_dearpygui()` 47 | dpg.destroy_context() 48 | 49 | ``` 50 | 51 | ## License 52 | 53 | `dearpygui-async` is distributed under the terms of the [MIT](https://spdx.org/licenses/MIT.html) license. 54 | -------------------------------------------------------------------------------- /examples/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CasuallyCalm/dearpygui-async/ffb4e6ec86babe1f35cf356897ee2e55533ecb90/examples/__init__.py -------------------------------------------------------------------------------- /examples/basic_async.py: -------------------------------------------------------------------------------- 1 | '''Basic example with a background task and a button that starts another process without blocking the gui''' 2 | 3 | import asyncio 4 | 5 | import dearpygui.dearpygui as dpg 6 | from dearpygui_async import DearPyGuiAsync 7 | 8 | HEIGHT = 400 9 | WIDTH = 600 10 | 11 | 12 | async def save_callback(): 13 | _input = dpg.get_value('input') 14 | print(f"Saved input: {_input}") 15 | await asyncio.sleep(3) 16 | _new_input = dpg.get_value('input') 17 | if _input != _new_input: 18 | print(f"Your input changed to: {_new_input}") 19 | 20 | 21 | 22 | def dpg_start(): 23 | 24 | dpg.create_context() 25 | dpg.create_viewport(width=WIDTH, height=HEIGHT) 26 | dpg.setup_dearpygui() 27 | 28 | with dpg.window(label="Async Example Window", tag="Window", width=WIDTH, height=HEIGHT): 29 | dpg.add_text("Controls") 30 | dpg.add_button(label="Save", callback=save_callback) 31 | dpg.add_input_text(label="string", tag="input") 32 | dpg.add_slider_float(label="float", tag="slider") 33 | 34 | dpg.show_viewport() 35 | 36 | 37 | def dpg_stop(): 38 | dpg.destroy_context() 39 | 40 | 41 | dpg_async = DearPyGuiAsync() 42 | 43 | 44 | async def coro(): 45 | while True: 46 | print("background task") 47 | await asyncio.sleep(3) 48 | 49 | 50 | async def hook(): 51 | asyncio.create_task(coro()) 52 | 53 | 54 | dpg_async.setup = hook 55 | 56 | if __name__ == "__main__": 57 | dpg_start() 58 | dpg_async.run() 59 | dpg_stop() 60 | -------------------------------------------------------------------------------- /examples/bot_with_gui.py: -------------------------------------------------------------------------------- 1 | ''' 2 | A more useful use case to add a GUI to a discord bot using discord.py (https://discordpy.readthedocs.io) 3 | ''' 4 | 5 | import os 6 | 7 | import dearpygui.dearpygui as dpg 8 | import discord 9 | from basic_async import dpg_start, dpg_stop 10 | from dearpygui_async import DearPyGuiAsync 11 | 12 | client = discord.Client(intents=discord.Intents.all()) 13 | 14 | dpg_async = DearPyGuiAsync(loop=client.loop) 15 | 16 | 17 | async def send_message_from_gui_on_send(): 18 | channel = client.get_channel(1088861021126541394) # replace with your guild ID 19 | msg = dpg.get_value("input") 20 | print(f'Sent Message: {msg}') 21 | await channel.send(msg) 22 | 23 | 24 | def get_roles(): 25 | guild = client.get_guild(365633712945102848) # replace with your channel ID 26 | for role in guild.roles: 27 | dpg.add_text(f"{role.name}:{role.id}", parent="Window") 28 | 29 | 30 | @client.event 31 | async def on_ready(): 32 | print("logged in") 33 | 34 | @client.event 35 | async def on_message(message:discord.Message): 36 | slider_value = dpg.get_value('slider') 37 | dpg.set_value('slider', slider_value+1) 38 | 39 | 40 | async def setup_hook(): 41 | await dpg_async.start() 42 | 43 | 44 | async def teardown(): 45 | await client.close() 46 | await dpg_async.stop() 47 | 48 | dpg_async.teardown = teardown 49 | 50 | client.setup_hook = setup_hook 51 | 52 | dpg_start() 53 | 54 | dpg.add_button( 55 | label="Send Message", 56 | parent="Window", 57 | callback=send_message_from_gui_on_send, 58 | before="input", 59 | ) 60 | 61 | dpg.add_button(label="Get Roles", parent="Window", callback=get_roles) 62 | 63 | client.run(os.environ.get("TOKEN")) 64 | dpg_stop() 65 | -------------------------------------------------------------------------------- /examples/progress_bar.py: -------------------------------------------------------------------------------- 1 | # reference: https://github.com/my1e5/dpg-examples/blob/main/threading/progress_bar.py 2 | 3 | import asyncio 4 | import time 5 | 6 | import dearpygui.dearpygui as dpg 7 | from dearpygui_async import DearPyGuiAsync 8 | 9 | dpg_async = DearPyGuiAsync() 10 | 11 | dpg.create_context() 12 | 13 | running = False 14 | paused = False 15 | progress = 0 16 | 17 | async def sleep(seconds:float): 18 | '''An asyncio sleep. 19 | 20 | On Windows this achieves a better granularity than asyncio.sleep 21 | 22 | Args: 23 | seconds (float): Seconds to sleep for. 24 | 25 | ''' 26 | await asyncio.get_running_loop().run_in_executor(None, time.sleep, seconds) 27 | 28 | # This task is blocking due to time.sleep. Using asyncio.sleep would allow you to do this 29 | # without blocking the event loop, but adds additional time overhead due to the nature of asyncio 30 | # To convert this to not block the event loop are commented 31 | # Due to how asyncio.sleep currently (3.12) works on windows, a different sleep is being used to get more accurate time 32 | def run_task(): 33 | # async def run_task(): 34 | start = time.time() 35 | global running 36 | global paused 37 | global progress 38 | print("Running...") 39 | 40 | for i in range(1,101): 41 | while paused: 42 | # await sleep(0.1) 43 | time.sleep(0.1) 44 | if not running: 45 | return 46 | progress = i 47 | print(i) 48 | dpg.set_value(progress_bar, 1/100 * (i)) 49 | dpg.configure_item(progress_bar, overlay=f"{i}%") 50 | time.sleep(0.05) 51 | # await sleep(0.05) 52 | 53 | print("Finished") 54 | running = False 55 | dpg.set_item_label(start_pause_resume_button, "Finished") 56 | dpg.disable_item(start_pause_resume_button) 57 | dpg.show_item(reset_button) 58 | print(f'Total time: {time.time()-start}') 59 | 60 | def start_stop_callback(): 61 | global running 62 | global paused 63 | if not running: 64 | print("Started") 65 | running = True 66 | paused = False 67 | # these two lines perform essentially the same way to run blocking 68 | # code without blocking the main event loop 69 | #-------------------------------- 70 | dpg_async.loop.run_in_executor(None, run_task) 71 | # asyncio.create_task(asyncio.to_thread(run_task)) 72 | #----------------------------- 73 | # asyncio.create_task(run_task()) # uncomment to run an the non-blocking version and comment out the previous lines to run the blocking version 74 | dpg.set_item_label(start_pause_resume_button, "Pause") 75 | else: 76 | if not paused: 77 | print("Paused...") 78 | paused = True 79 | dpg.set_item_label(start_pause_resume_button, "Resume") 80 | dpg.show_item(reset_button) 81 | return 82 | print("Resuming...") 83 | paused = False 84 | dpg.set_item_label(start_pause_resume_button, "Pause") 85 | dpg.hide_item(reset_button) 86 | 87 | def reset_callback(): 88 | global running 89 | global paused 90 | global progress 91 | running = False 92 | paused = False 93 | progress = 0 94 | dpg.set_value(progress_bar, 0) 95 | dpg.configure_item(progress_bar, overlay="0%") 96 | dpg.set_item_label(start_pause_resume_button, "Start") 97 | dpg.enable_item(start_pause_resume_button) 98 | dpg.hide_item(reset_button) 99 | 100 | with dpg.window() as primary_window: 101 | with dpg.group(horizontal=True): 102 | start_pause_resume_button = dpg.add_button(label="Start", width=70, callback=start_stop_callback) 103 | reset_button = dpg.add_button(label="Reset", width=70, callback=reset_callback) 104 | dpg.hide_item(reset_button) 105 | progress_bar = dpg.add_progress_bar(default_value=0, width=-1, overlay="0%") 106 | 107 | dpg.set_primary_window(primary_window, True) 108 | dpg.create_viewport(width=400, height=300, title="Progress Bar with Pause/Resume") 109 | dpg.setup_dearpygui() 110 | dpg.show_viewport() 111 | dpg_async.run() 112 | dpg.destroy_context() -------------------------------------------------------------------------------- /examples/simple_example.py: -------------------------------------------------------------------------------- 1 | '''simple example shown in the readme, slight modification from the example shown on dearpygui github''' 2 | 3 | import asyncio 4 | 5 | import dearpygui.dearpygui as dpg 6 | from dearpygui_async import DearPyGuiAsync 7 | 8 | dpg_async = DearPyGuiAsync() 9 | 10 | async def save_callback(): 11 | print("Save Clicked") 12 | await asyncio.sleep(3) 13 | print("Save Clicked after 3 more seconds") 14 | 15 | 16 | dpg.create_context() 17 | dpg.create_viewport() 18 | dpg.setup_dearpygui() 19 | 20 | with dpg.window(label="Example Window"): 21 | dpg.add_text("Hello world") 22 | dpg.add_button(label="Save", callback=save_callback) 23 | dpg.add_input_text(label="string") 24 | dpg.add_slider_float(label="float") 25 | 26 | dpg.show_viewport() 27 | dpg_async.run() 28 | dpg.destroy_context() 29 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["hatchling"] 3 | build-backend = "hatchling.build" 4 | 5 | [project] 6 | name = "dearpygui-async" 7 | dynamic = ["version"] 8 | description = 'An async helper for dearpygui' 9 | readme = "README.md" 10 | requires-python = ">=3.8" 11 | license = "MIT" 12 | keywords = ['dearpygui', 'async', 'python', 'gui','desktop','asyncio'] 13 | authors = [ 14 | { name = "CasuallyCalm", email = "void@some.where" }, 15 | ] 16 | classifiers = [ 17 | "Development Status :: 4 - Beta", 18 | "Programming Language :: Python", 19 | "Programming Language :: Python :: 3.8", 20 | "Programming Language :: Python :: 3.9", 21 | "Programming Language :: Python :: 3.10", 22 | "Programming Language :: Python :: 3.11", 23 | "Programming Language :: Python :: 3.12", 24 | "Programming Language :: Python :: Implementation :: CPython", 25 | "Programming Language :: Python :: Implementation :: PyPy", 26 | "Framework :: AsyncIO" 27 | ] 28 | dependencies = [] 29 | 30 | [project.urls] 31 | Documentation = "https://github.com/CasuallyCalm/dearpygui-async#readme" 32 | Issues = "https://github.com/CasuallyCalm/dearpygui-async/issues" 33 | Source = "https://github.com/CasuallyCalm/dearpygui-async" 34 | 35 | [tool.hatch.version] 36 | path = "src/dearpygui_async/__about__.py" 37 | 38 | [tool.hatch.envs.default] 39 | dependencies = [ 40 | "coverage[toml]>=6.5", 41 | "pytest", 42 | ] 43 | 44 | [tool.hatch.envs.examples] 45 | dependencies = ['dearpygui','discord.py'] 46 | 47 | [tool.hatch.envs.default.scripts] 48 | test = "pytest {args:tests}" 49 | test-cov = "coverage run -m pytest {args:tests}" 50 | cov-report = [ 51 | "- coverage combine", 52 | "coverage report", 53 | ] 54 | cov = [ 55 | "test-cov", 56 | "cov-report", 57 | ] 58 | 59 | [[tool.hatch.envs.all.matrix]] 60 | python = ["3.8", "3.9", "3.10", "3.11", "3.12"] 61 | 62 | [tool.hatch.envs.types] 63 | dependencies = [ 64 | "mypy>=1.0.0", 65 | ] 66 | [tool.hatch.envs.types.scripts] 67 | check = "mypy --install-types --non-interactive {args:src/dearpygui_async tests}" 68 | 69 | [tool.coverage.run] 70 | source_pkgs = ["dearpygui_async", "tests"] 71 | branch = true 72 | parallel = true 73 | omit = [ 74 | "src/dearpygui_async/__about__.py", 75 | ] 76 | 77 | [tool.coverage.paths] 78 | dearpygui_async = ["src/dearpygui_async", "*/dearpygui-async/src/dearpygui_async"] 79 | tests = ["tests", "*/dearpygui-async/tests"] 80 | 81 | [tool.coverage.report] 82 | exclude_lines = [ 83 | "no cov", 84 | "if __name__ == .__main__.:", 85 | "if TYPE_CHECKING:", 86 | ] 87 | -------------------------------------------------------------------------------- /src/dearpygui_async/__about__.py: -------------------------------------------------------------------------------- 1 | __version__ = "0.1.2" 2 | -------------------------------------------------------------------------------- /src/dearpygui_async/__init__.py: -------------------------------------------------------------------------------- 1 | from .dearpygui_async import DearPyGuiAsync # noqa 2 | -------------------------------------------------------------------------------- /src/dearpygui_async/dearpygui_async.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import time 3 | 4 | import dearpygui.dearpygui as dpg 5 | 6 | 7 | async def _sleep(seconds:float): 8 | '''An asyncio sleep. 9 | 10 | On Windows this achieves a better granularity than asyncio.sleep 11 | 12 | Args: 13 | seconds (float): Seconds to sleep for. 14 | 15 | ''' 16 | await asyncio.get_running_loop().run_in_executor(None, time.sleep, seconds) 17 | 18 | class DearPyGuiAsync: 19 | 20 | __callback_task:asyncio.Task 21 | 22 | def __init__(self, loop=None): 23 | self.loop = loop or asyncio.get_event_loop() 24 | 25 | async def setup(self): 26 | ''' 27 | Special method that runs when starting 28 | This is helpful for running code that has special setup behavior that may be asynchronous 29 | ''' 30 | pass 31 | 32 | async def teardown(self): 33 | ''' 34 | Special method that runs when shutting down. 35 | This is helpful for running code that has special shutdown behavior that may be asynchronous 36 | ''' 37 | pass 38 | 39 | async def run_callbacks(self, jobs): 40 | ''' 41 | Run the callbacks that were added 42 | ''' 43 | if jobs is None: 44 | pass 45 | else: 46 | for job in jobs: 47 | if job[0] is None: 48 | pass 49 | else: 50 | sig = dpg.inspect.signature(job[0]) 51 | args = [] 52 | for arg in range(len(sig.parameters)): 53 | args.append(job[arg + 1]) 54 | if asyncio.iscoroutinefunction( 55 | job[0] 56 | ) or asyncio.iscoroutinefunction(job[0].__call__): 57 | try: 58 | await job[0](*args) 59 | except Exception as e: 60 | print(e) 61 | else: 62 | job[0](*args) 63 | 64 | 65 | async def callback_loop(self): 66 | ''' 67 | |coro| 68 | Processes the the callbacks asynchronously 69 | This will configure the app to manually manage the callbacks so overwrite this if you want to do something else 70 | ''' 71 | dpg.configure_app(manual_callback_management=True) 72 | while dpg.is_dearpygui_running(): 73 | asyncio.create_task(self.run_callbacks(dpg.get_callback_queue())) 74 | dpg.render_dearpygui_frame() 75 | await _sleep(0.0095) 76 | await self.teardown() 77 | 78 | async def start(self): 79 | ''' 80 | |coro| 81 | For starting the gui in an async context 82 | Usually to add a gui to another async process 83 | ''' 84 | await self.setup() 85 | self._callback_task = asyncio.create_task(self.callback_loop()) 86 | 87 | async def __start(self): 88 | await self.setup() 89 | await self.callback_loop() 90 | 91 | async def stop(self): 92 | ''' 93 | |coro| 94 | Manually cancel the callback processing task 95 | ''' 96 | self._callback_task.cancel() 97 | await self.teardown() 98 | 99 | def run(self): 100 | ''' 101 | |blocking| 102 | Run DearPyGui with async compatibility 103 | Use this in place of `dpg.start_gui()` 104 | 105 | ''' 106 | self.loop.run_until_complete(self.__start()) 107 | --------------------------------------------------------------------------------