"""CFNgin UI manipulation."""
from __future__ import annotations
import logging
import threading
from getpass import getpass
from typing import TYPE_CHECKING, Any, ContextManager, Optional, TextIO, Type, Union
if TYPE_CHECKING:
from types import TracebackType
LOGGER = logging.getLogger(__name__)
[docs]class UI(ContextManager["UI"]):
"""Used internally from terminal output in a multithreaded environment.
Ensures that two threads don't write over each other while asking a user
for input (e.g. in interactive mode).
"""
[docs] def __init__(self) -> None:
"""Instantiate class."""
self._lock = threading.RLock()
[docs] def log(
self,
lvl: int,
msg: Union[Exception, str],
*args: Any,
logger: Union[logging.Logger, logging.LoggerAdapter[Any]] = LOGGER,
**kwargs: Any,
) -> None:
"""Log the message if the current thread owns the underlying lock.
Args:
lvl: Log level.
msg: String template or exception to use for the log record.
logger: Specific logger to log to.
"""
with self:
return logger.log(lvl, msg, *args, **kwargs)
[docs] def info(
self,
msg: str,
*args: Any,
logger: Union[logging.Logger, logging.LoggerAdapter[Any]] = LOGGER,
**kwargs: Any,
) -> None:
"""Log the line if the current thread owns the underlying lock.
Args:
msg: String template or exception to use
for the log record.
logger: Specific logger to log to.
"""
kwargs["logger"] = logger
self.log(logging.INFO, msg, *args, **kwargs)
[docs] def ask(self, message: str) -> str:
"""Collect input from a user in a multithreaded environment.
This wraps the built-in input function to ensure that only 1
thread is asking for input from the user at a give time. Any process
that tries to log output to the terminal will be blocked while the
user is being prompted.
"""
with self:
return get_raw_input(message)
[docs] def getpass(self, prompt: str, stream: Optional[TextIO] = None) -> str:
"""Wrap getpass to lock the UI."""
with self:
return getpass(prompt, stream)
[docs] def __enter__(self) -> UI:
"""Enter the context manager."""
self._lock.__enter__()
return self
[docs] def __exit__(
self,
exc_type: Optional[Type[BaseException]],
exc_value: Optional[BaseException],
traceback: Optional[TracebackType],
) -> None:
"""Exit the context manager."""
self._lock.__exit__(exc_type, exc_value, traceback)
# Global UI object for other modules to use.
ui = UI()