"""Utilities for the tools commands.""" import asyncio from asyncio.events import BaseDefaultEventLoopPolicy import os import sys _DEFAULT_LIMIT = 2**16 # 64kb async def stdio(limit=_DEFAULT_LIMIT, loop=None): """Create async standard in/out handlers.""" if loop is None: loop = asyncio.get_event_loop() if sys.platform == "win32": return _win32_stdio(loop) reader = asyncio.StreamReader(limit=limit, loop=loop) reader_protocol = asyncio.StreamReaderProtocol(reader, loop=loop) writer_transport, writer_protocol = await loop.connect_write_pipe( lambda: asyncio.streams.FlowControlMixin(loop=loop), os.fdopen(1, "wb") ) writer = asyncio.streams.StreamWriter(writer_transport, writer_protocol, None, loop) await loop.connect_read_pipe(lambda: reader_protocol, sys.stdin) return reader, writer def patch_stdin_stdout(stdin, stdout): """Patch sys.stdout.write to ensure proper encoding.""" write_orig = stdout.write read_orig = stdin.readline async def decode_read(): """Decode the input line.""" line = await read_orig() return line.decode("UTF-8") def encode_write(line: str): """Encode line before passing to write method.""" write_orig(line.encode("UTF-8")) stdin.readline = decode_read stdout.write = encode_write def _win32_stdio(loop): # no support for asyncio stdio yet on Windows, see https://bugs.python.org/issue26832 # use an executor to read from stdio and write to stdout # note: if nothing ever drains the writer explicitly, no flushing ever takes place! class Win32StdinReader: """Windows standard in Reader.""" def __init__(self): """Init the Win32StdinReader class.""" self.stdin = sys.stdin.buffer async def readline(self): """Read a line.""" # a single call to sys.stdin.readline() is thread-safe return await loop.run_in_executor(None, sys.stdin.readline) class Win32StdoutWriter: """Windows standard out writer.""" def __init__(self): """Init the Win32StdoutWriter class.""" self.buffer = [] self.stdout = sys.stdout.buffer def write(self, data): """Write to standard out.""" self.buffer.append(data) async def drain(self): """Flush the output to standard out.""" data, self.buffer = self.buffer, [] # a single call to sys.stdout.writelines() is thread-safe return await loop.run_in_executor(None, sys.stdout.writelines, data) return Win32StdinReader(), Win32StdoutWriter() def set_loop() -> None: """Attempt to use different loop.""" if sys.platform == "win32": if hasattr(asyncio, "WindowsProactorEventLoopPolicy"): # pylint: disable=no-member policy = asyncio.WindowsProactorEventLoopPolicy() else: class ProactorPolicy(BaseDefaultEventLoopPolicy): """Event loop policy to create proactor loops.""" _loop_factory = asyncio.ProactorEventLoop policy = ProactorPolicy() asyncio.set_event_loop_policy(policy)