Skip to main content

aio_stdout

Asynchronous Input Output - Stdout

The purpose of this package is to provide asynchronous variants of the builtin input and print functions. print is known to be relatively slow compared to other operations. input is even slower because it has to wait for user input. While these slow IO operations are being ran, code using asyncio should be able to continuously run.

PIP Installing

For Unix/macOS:

python3 -m pip install aio-stdout

For Windows:

py -m pip install aio-stdout

ainput and aprint

With aio_stdout, the aio_stdout.ainput and aio_stdout.aprint functions provide easy to use functionality with organized behaviour.

import asyncio
from aio_stdout import ainput, aprint

async def countdown(n: int) -> None:
    """Count down from `n`, taking `n` seconds to run."""
    for i in range(n, 0, -1):
        await aprint(i)
        await asyncio.sleep(1)

async def get_name() -> str:
    """Ask the user for their name."""
    name = await ainput("What is your name? ")
    await aprint(f"Your name is {name}.")
    return name

async def main() -> None:
    await asyncio.gather(countdown(15), get_name())

if __name__ == "__main__":
    asyncio.run(main())

Example output:

15
What is your name? Jane
14
13
12
11
10
9
8
Your name is Jane.
7
6
5
4
3
2
1

Notice that while the prompt "What is your name? " is being waited for, the countdown continues to aprint in the background, without becoming blocked. The countdown does not, however, display its results until the ainput is completed. Instead it waits for the ainput to finish before flushing out all of the queued messages.

It is worth noting that with naive threading, a normal attempt to use print while waiting on an input leads to overlapping messages. Fixing this behavior requires a lot more work than should be needed to use a simple print or input function, which is why this package exists. To remedy this problem, queues are used to store messages until they are ready to be printed.

IO Locks

Although the asynchronization behaviors of ainput and aprint are nice, sometimes we want to be able to synchronize our messages even more. IO locks provide a way to group messages together, locking the global aio_stdout queues until it finishes or yields access.

import asyncio
from aio_stdout import IOLock, ainput, aprint

async def countdown(n: int) -> None:
    """Count down from `n`, taking `n` seconds to run."""
    async with IOLock(n=5) as lock:
        for i in range(n, 0, -1):
            await lock.aprint(i)
            await asyncio.sleep(1)

async def get_name() -> str:
    """Ask the user for their name."""
    async with IOLock() as lock:
        name = await lock.ainput("What is your name? ")
        await lock.aprint(f"Your name is {name}.")
    return name

async def main() -> None:
    await asyncio.gather(countdown(15), get_name())

if __name__ == "__main__":
    asyncio.run(main())

Let's try the example again now using the new locks:

15
14
13
12
11
What is your name? Jane
Your name is Jane.
10
9
8
7
6
5
4
3
2
1

Notice that this time the countdown does not immediately yield to the get_name. Instead, it runs 5 messages before yielding control over to get_name. Now, after the lock.ainput finishes, it does not yield to countdown. Instead, it runs its own lock.aprint first. In the meantime, countdown continues to run in the background and flushes all of its buffered messages afterwards.

Flushing

Since messages may be delayed, it is possible for your asynchronous code to finish running before all messages are displayed, producing confusing results. As such, the best recommended practice is to flush from main before terminating.

from aio_stdout import flush

@flush
async def main() -> None:
    ...

Final Example

Combining all best practices, the final example should look something like this:

import asyncio
from aio_stdout import IOLock, ainput, aprint, flush

async def countdown(n: int) -> None:
    """Count down from `n`, taking `n` seconds to run."""
    for i in range(n, 0, -1):
        await aprint(i)
        await asyncio.sleep(1)

async def get_name() -> str:
    """Ask the user for their name."""
    async with IOLock() as lock:
        name = await lock.ainput("What is your name? ")
        await lock.aprint(f"Your name is {name}.")
    return name

@flush
async def main() -> None:
    await asyncio.gather(countdown(15), get_name())

if __name__ == "__main__":
    asyncio.run(main())

Common Gotchas

  • Using input or print instead of ainput and aprint will push a message immediately to the console, potentially conflicting with ainput or aprint.
  • Using ainput or aprint instead of lock.ainput and lock.aprint may produce deadlock due to having to wait for the lock to release. As such, the lock is equipped with a default timeout limit of 10 seconds to avoid deadlock and explain to users this potential problem.

Metadata

Release files for aio-stdout 0.0.5

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for aio-stdout 0.0.5
File Size Uploaded Provenance
aio-stdout-0.0.5.tar.gz 11.0 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for aio-stdout 0.0.5
File Interpreter ABI Platform
aio_stdout-0.0.5-py3-none-any.whl Python 3 none any Details

Total release size: 23.9 kB

Release files / aio-stdout-0.0.5.tar.gz

Download URL aio-stdout-0.0.5.tar.gz
Size 11.0 kB
Tags Source
SHA-256 checksum
How to use checksums
05dd34e3e234e52e5a2e98648e78f26df9f9c914c3072bf4c43e6daebd844859
BLAKE2b-256 checksum
How to use checksums
abf786efb81abda2ce58b1164c04a43eccabfa27910871f59cca9fc9d210ec53
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/3.7.1 importlib_metadata/4.10.1 pkginfo/1.8.2 requests/2.26.0 requests-toolbelt/0.9.1 tqdm/4.62.3 CPython/3.9.5

Release files / aio_stdout-0.0.5-py3-none-any.whl

Download URL aio_stdout-0.0.5-py3-none-any.whl
Size 13.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
ceda42cd5be4777d99dded369cc5698d4b8bbae484102b7e57685294a93f805a
BLAKE2b-256 checksum
How to use checksums
16d052501da33d9f5f692d499e3d33a2aef34db4223e33d8877a6438a232cb83
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/3.7.1 importlib_metadata/4.10.1 pkginfo/1.8.2 requests/2.26.0 requests-toolbelt/0.9.1 tqdm/4.62.3 CPython/3.9.5

Security for aio-stdout 0.0.5

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page