Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Asynchronous input in Linux/macOS (patch ainput to reset blocking mode) #22

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/loveletter_cli/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@

import more_itertools as mitt
import valid8
from aioconsole import ainput, aprint
from multimethod import multimethod

import loveletter.game
Expand All @@ -22,6 +21,8 @@
from loveletter_cli.exceptions import Restart
from loveletter_cli.server_process import ServerProcess
from loveletter_cli.ui import (
ainput,
aprint,
async_ask_valid_input,
draw_game,
pause,
Expand Down
45 changes: 39 additions & 6 deletions src/loveletter_cli/ui/input.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import enum
import functools
import os
import sys
import textwrap
from typing import Callable, Tuple, Type, TypeVar

import aioconsole
import more_itertools
import valid8
from aioconsole import ainput, aprint

from .misc import printable_width

Expand All @@ -14,6 +16,41 @@
_DEFAULT = object()


# Define ainput() based on OS: if the OS uses the O_NONBLOCK flag, we have to
# clear it after every call to ainput() to ensure compatibility with blocking
# IO such as print() and input().
if hasattr(os, "set_blocking"):

@functools.wraps(aioconsole.ainput)
async def ainput(*args, **kwargs):
result = await aioconsole.ainput(*args, **kwargs)
os.set_blocking(sys.stdin.fileno(), True)
return result

@functools.wraps(aioconsole.ainput)
async def aprint(*args, **kwargs):
result = await aioconsole.aprint(*args, **kwargs)
os.set_blocking(sys.stdin.fileno(), True)
return result

else:
ainput = aioconsole.ainput
aprint = aioconsole.aprint


def ask_valid_input(*args, **kwargs) -> _T:
error_message, parser, prompt, validation_errors = _ask_valid_input_parse_args(
*args, **kwargs
)

while True:
raw_input = input(prompt)
try:
return _parse_input(raw_input, parser, error_message, validation_errors)
except (valid8.ValidationError, *validation_errors):
continue


async def async_ask_valid_input(*args, **kwargs):
"""Asynchronous version of :func:`ask_valid_input`."""
error_message, parser, prompt, validation_errors = _ask_valid_input_parse_args(
Expand Down Expand Up @@ -152,8 +189,4 @@ def _decorate_prompt(prompt: str) -> str:


async def pause() -> None:
# Using ainput() instead of regular input() sometimes causes trouble:
# the user has to enter twice before input is detected;
# but the asynchronous nature is needed to ensure other events are handled in time
# (e.g. when the connection is lost).
await ainput("Enter something to continue... ")
input("Enter something to continue... ")