diff options
author | Tristan van Berkom <tristan@codethink.co.uk> | 2020-12-21 16:47:41 +0900 |
---|---|---|
committer | Tristan van Berkom <tristan@codethink.co.uk> | 2020-12-21 18:09:59 +0900 |
commit | a15c16cb47cd558634025824ef244c4a3e991ded (patch) | |
tree | c644a98e62d5fa5345fbe2c28eaeb749396c497c /src/buildstream/_messenger.py | |
parent | ba5664fff47ad0e0a2614c1bf893ae5c31d747e7 (diff) | |
download | buildstream-a15c16cb47cd558634025824ef244c4a3e991ded.tar.gz |
_messenger.py: Adding (almost) full pep484 type annotations.
This omits the type annotation for the message handler callback, as
this callback contains a keyword argument and can only be annotated
using `Protocol` type, which will only be available in python >= 3.8.
Added a FIXME comment so that we can recitify this when dropping
support for python 3.7
Diffstat (limited to 'src/buildstream/_messenger.py')
-rw-r--r-- | src/buildstream/_messenger.py | 176 |
1 files changed, 104 insertions, 72 deletions
diff --git a/src/buildstream/_messenger.py b/src/buildstream/_messenger.py index f18d3dc92..0a420abdd 100644 --- a/src/buildstream/_messenger.py +++ b/src/buildstream/_messenger.py @@ -21,17 +21,19 @@ import os import datetime import threading from contextlib import contextmanager +from typing import Optional, Callable, Iterator, TextIO from . import _signals from ._exceptions import BstError from ._message import Message, MessageType +from ._state import State, Task -_RENDER_INTERVAL = datetime.timedelta(seconds=1) +_RENDER_INTERVAL: datetime.timedelta = datetime.timedelta(seconds=1) # Time in seconds for which we decide that we want to display subtask information -_DISPLAY_LIMIT = datetime.timedelta(seconds=3) +_DISPLAY_LIMIT: datetime.timedelta = datetime.timedelta(seconds=3) # If we're in the test suite, we need to ensure that we don't set a limit if "BST_TEST_SUITE" in os.environ: _DISPLAY_LIMIT = datetime.timedelta(seconds=0) @@ -42,36 +44,65 @@ if "BST_TEST_SUITE" in os.environ: class _TimeData: __slots__ = ["start_time"] - def __init__(self, start_time): - self.start_time = start_time + def __init__(self, start_time: datetime.datetime) -> None: + self.start_time: datetime.datetime = start_time +# _MessengerLocal +# +# Thread local storage for the messenger +# +class _MessengerLocal(threading.local): + def __init__(self) -> None: + super().__init__() + + # The callback to call when propagating messages + # + # FIXME: The message handler is currently not strongly typed, + # as it uses a kwarg, we cannot declare it with Callable. + # We can use `Protocol` to strongly type this with python >= 3.8 + self.message_handler = None + + # The open file handle for this task + self.log_handle: Optional[TextIO] = None + + # The filename for this task + self.log_filename: Optional[str] = None + + # Level of silent messages depth in this task + self.silence_scope_depth: int = 0 + + +# Messenger() +# +# The messenger object. +# +# This is used to propagate messages either from the main context or +# from task contexts in such a way that messages are propagated to +# the frontend and also optionally recorded to a task log file when +# the message is issued from a task context. +# class Messenger: - def __init__(self): - self._state = None - self._next_render = None # A Time object - self._active_simple_tasks = 0 - self._render_status_cb = None - - self._locals = threading.local() - self._locals.message_handler = None - self._locals.log_handle = None - self._locals.log_filename = None - self._locals.silence_scope_depth = 0 + def __init__(self) -> None: + self._state: Optional[State] = None # The State object + + # + # State related to simple tasks, these drive the status bar + # when ongoing activities occur outside of an active scheduler + # + self._active_simple_tasks: int = 0 # Number of active simple tasks + self._next_render: Optional[datetime.datetime] = None # The time of the next render + self._render_status_cb: Optional[Callable[[], None]] = None # The render callback + + # Thread local storage + self._locals: _MessengerLocal = _MessengerLocal() # set_message_handler() # # Sets the handler for any status messages propagated through - # the context. - # - # The handler should have the signature: - # - # def handler( - # message: _message.Message, # The message to send. - # is_silenced: bool, # Whether messages are currently being silenced. - # ) -> None + # the messenger. # - def set_message_handler(self, handler): + def set_message_handler(self, handler) -> None: self._locals.message_handler = handler # set_state() @@ -79,9 +110,9 @@ class Messenger: # Sets the State object within the Messenger # # Args: - # state (State): The state to set + # state: The state to set # - def set_state(self, state): + def set_state(self, state: State) -> None: self._state = state # set_render_status_cb() @@ -89,22 +120,11 @@ class Messenger: # Sets the callback to use to render status # # Args: - # callback (function): The Callback to be notified + # callback: The Callback to be notified # - # Callback Args: - # There are no arguments to the callback - # - def set_render_status_cb(self, callback): + def set_render_status_cb(self, callback: Callable[[], None]) -> None: self._render_status_cb = callback - # _silent_messages(): - # - # Returns: - # (bool): Whether messages are currently being silenced - # - def _silent_messages(self): - return self._locals.silence_scope_depth > 0 - # message(): # # Proxies a message back to the caller, this is the central @@ -113,7 +133,7 @@ class Messenger: # Args: # message: A Message object # - def message(self, message): + def message(self, message: Message) -> None: # If we are recording messages, dump a copy into the open log file. self._record_message(message) @@ -132,19 +152,19 @@ class Messenger: # _message.unconditional_messages will be silenced. # # Args: - # actually_silence (bool): Whether to actually do the silencing, if - # False then this context manager does not - # affect anything. + # actually_silence: Whether to actually do the silencing, if + # False then this context manager does not + # affect anything. # @contextmanager - def silence(self, *, actually_silence=True): + def silence(self, *, actually_silence: bool = True) -> Iterator[None]: if not actually_silence: - yield + yield None return self._locals.silence_scope_depth += 1 try: - yield + yield None finally: assert self._locals.silence_scope_depth > 0 self._locals.silence_scope_depth -= 1 @@ -154,20 +174,22 @@ class Messenger: # Context manager for performing timed activities and logging those # # Args: - # activity_name (str): The name of the activity - # detail (str): An optional detailed message, can be multiline output - # silent_nested (bool): If True, all nested messages are silenced except for unconditionaly ones + # activity_name: The name of the activity + # detail: An optional detailed message, can be multiline output + # silent_nested: If True, all nested messages are silenced except for unconditionaly ones # kwargs: Remaining Message() constructor keyword arguments. # @contextmanager - def timed_activity(self, activity_name, *, detail=None, silent_nested=False, **kwargs): + def timed_activity( + self, activity_name: str, *, detail: str = None, silent_nested: bool = False, **kwargs + ) -> Iterator[None]: with self.timed_suspendable() as timedata: try: # Push activity depth for status messages message = Message(MessageType.START, activity_name, detail=detail, **kwargs) self.message(message) with self.silence(actually_silence=silent_nested): - yield + yield None except BstError: # Note the failure in status messages and reraise, the scheduler @@ -186,21 +208,23 @@ class Messenger: # Context manager for creating a task to report progress to. # # Args: - # activity_name (str): The name of the activity - # task_name (str): Optionally, the task name for the frontend during this task - # detail (str): An optional detailed message, can be multiline output - # silent_nested (bool): If True, all nested messages are silenced except for unconditionaly ones + # activity_name: The name of the activity + # task_name: Optionally, the task name for the frontend during this task + # detail: An optional detailed message, can be multiline output + # silent_nested: If True, all nested messages are silenced except for unconditionaly ones # kwargs: Remaining Message() constructor keyword arguments. # # Yields: # Task: A Task object that represents this activity, principally used to report progress # @contextmanager - def simple_task(self, activity_name, *, task_name=None, detail=None, silent_nested=False, **kwargs): + def simple_task( + self, activity_name: str, *, task_name: str = None, detail: str = None, silent_nested: bool = False, **kwargs + ) -> Iterator[Optional[Task]]: # Bypass use of State when none exists (e.g. tests) if not self._state: with self.timed_activity(activity_name, detail=detail, silent_nested=silent_nested, **kwargs): - yield + yield None return if not task_name: @@ -254,17 +278,17 @@ class Messenger: # Messenger.get_log_filename() API. # # Args: - # filename (str): A logging directory relative filename, - # the pid and .log extension will be automatically - # appended + # filename: A logging directory relative filename, + # the pid and .log extension will be automatically + # appended # - # logdir (str) : The path to the log file directory. + # logdir: The path to the log file directory. # # Yields: - # (str): The fully qualified log filename + # The fully qualified log filename # @contextmanager - def recorded_messages(self, filename, logdir): + def recorded_messages(self, filename: str, logdir: str) -> Iterator[str]: # We dont allow recursing in this context manager, and # we also do not allow it in the main process. assert not hasattr(self._locals, "log_handle") or self._locals.log_handle is None @@ -308,9 +332,9 @@ class Messenger: # manager is active # # Returns: - # (file): The active logging file handle, or None + # The active logging file handle, or None # - def get_log_handle(self): + def get_log_handle(self) -> Optional[TextIO]: return self._locals.log_handle # get_log_filename() @@ -320,9 +344,9 @@ class Messenger: # manager is active # # Returns: - # (str): The active logging filename, or None + # The active logging filename, or None # - def get_log_filename(self): + def get_log_filename(self) -> Optional[str]: return self._locals.log_filename # timed_suspendable() @@ -331,10 +355,10 @@ class Messenger: # adjust for clock drift caused by suspending # # Yields: - # TimeData: An object that contains the time the activity started + # An object that contains the time the activity started # @contextmanager - def timed_suspendable(self): + def timed_suspendable(self) -> Iterator[_TimeData]: # Note: timedata needs to be in a namedtuple so that values can be # yielded that will change timedata = _TimeData(start_time=datetime.datetime.now()) @@ -351,14 +375,22 @@ class Messenger: with _signals.suspendable(stop_time, resume_time): yield timedata + # _silent_messages(): + # + # Returns: + # (bool): Whether messages are currently being silenced + # + def _silent_messages(self) -> bool: + return self._locals.silence_scope_depth > 0 + # _record_message() # # Records the message if recording is enabled # # Args: - # message (Message): The message to record + # message: The message to record # - def _record_message(self, message): + def _record_message(self, message: Message) -> None: if self._locals.log_handle is None: return @@ -411,7 +443,7 @@ class Messenger: # Calls the render status callback set in the messenger, but only if a # second has passed since it last rendered. # - def _render_status(self): + def _render_status(self) -> None: assert self._next_render # self._render_status_cb() |