summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTristan van Berkom <tristan@codethink.co.uk>2020-12-21 16:47:41 +0900
committerTristan van Berkom <tristan@codethink.co.uk>2020-12-21 18:09:59 +0900
commita15c16cb47cd558634025824ef244c4a3e991ded (patch)
treec644a98e62d5fa5345fbe2c28eaeb749396c497c
parentba5664fff47ad0e0a2614c1bf893ae5c31d747e7 (diff)
downloadbuildstream-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
-rw-r--r--src/buildstream/_messenger.py176
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()