Skip to main content

async library that works on top of tkinter's event loop

Project description

AsyncTkinter

Youtube

asynctkinter is an async library that saves you from ugly callback-based code, just like most of async libraries do. Let's say you want to do:

  1. print('A')
  2. wait for 1sec
  3. print('B')
  4. wait for a label to be pressed
  5. print('C')

in that order. Your code would look like this:

def what_you_want_to_do(label):
    bind_id = None
    print('A')

    def one_sec_later(__):
        nonlocal bind_id
        print('B')
        bind_id = label.bind('<Button>', on_press, '+')
    label.after(1000, one_sec_later)

    def on_press(event):
        label.unbind('<Button>', bind_id)
        print('C')

It's barely readable and not easy to understand. If you use asynctkinter, the code above will become like this:

import asynctkinter as at

async def what_you_want_to_do(label):
    print('A')
    await at.sleep(1000, after=label.after)
    print('B')
    await at.event(label, '<Button>')
    print('C')

Installation

pip install asynctkinter

Pin the minor version

If you use this module, it's recommended to pin the minor version, because if it changed, it usually means some breaking changes occurred.

Usage

from tkinter import Tk, Label
import asynctkinter as at
at.patch_unbind()

root = Tk()
label = Label(root, text='Hello', font=('', 60))
label.pack()

async def some_task(label):
    label['text'] = 'start heavy task'

    # wait for a label to be pressed
    event = await at.event(label, '<Button>')
    print(f"pos: {event.x}, {event.y}")

    # wait for 2sec
    await at.sleep(2000, after=label.after)

at.start(some_task(label))
root.mainloop()

wait for the completion/cancellation of multiple tasks simultaneously

async def some_task(label):
    from functools import partial
    import asynctkinter as at
    sleep = partial(at.sleep, after=label.after)
    # wait until EITEHR a label is pressed OR 5sec passes
    tasks = await at.or_(
        at.event(label, '<Button>'),
        sleep(5000),
    )
    print("The label was pressed" if tasks[0].done else "5sec passed")

    # wait until BOTH a label is pressed AND 5sec passes"
    tasks = await at.and_(
        at.event(label, '<Button>'),
        sleep(5000),
    )

synchronization primitive

There is a Trio's Event equivalent.

import asynctkinter as at

async def task_A(e):
    print('A1')
    await e.wait()
    print('A2')
async def task_B(e):
    print('B1')
    await e.wait()
    print('B2')

e = at.Event()
at.start(task_A(e))
# A1
at.start(task_B(e))
# B1
e.set()
# A2
# B2

threading

asynctkinter doesn't have any I/O primitives like Trio and asyncio do, thus threads are the only way to perform them without blocking the main-thread:

from concurrent.futures import ThreadPoolExecuter
import asynctkinter as at

executer = ThreadPoolExecuter()

async def some_task(widget):
    # create a new thread, run a function inside it, then
    # wait for the completion of that thread
    r = await at.run_in_thread(
        thread_blocking_operation, after=widget.after)
    print("return value:", r)

    # run a function inside a ThreadPoolExecuter, and wait for the completion
    r = await at.run_in_executer(
        thread_blocking_operation, executer, after=widget.after)
    print("return value:", r)

Exceptions(not BaseExceptions) are propagated to the caller, so you can handle them like you do in synchronous code:

import requests
from requests.exceptions import Timeout
import asynctkinter as at

async def some_task(widget):
    try:
        r = await at.run_in_thread(
            lambda: requests.get('htt...', timeout=10), after=widget.after)
    except Timeout:
        print("TIMEOUT!")
    else:
        print('GOT A RESPONSE')

Structured Concurrency

Both asynctkinter.and_() and asynctkinter.or_() follow the concept of "structured concurrency". What does that mean? They promise two things:

  • The tasks passed into them never outlive them.
  • Exceptions occured in the tasks are propagated to the caller.

Read this post if you are curious to the concept.

Misc

  • Why is patch_unbind() necessary? Take a look at this.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

asynctkinter-0.2.0.tar.gz (5.3 kB view hashes)

Uploaded Source

Built Distribution

asynctkinter-0.2.0-py3-none-any.whl (5.0 kB view hashes)

Uploaded Python 3

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page