diff --git a/src/textual/_log.py b/src/textual/_log.py index 73c549a6d..07c51f4d1 100644 --- a/src/textual/_log.py +++ b/src/textual/_log.py @@ -2,6 +2,7 @@ from enum import Enum, auto class LogGroup(Enum): + UNDEFINED = 0 EVENT = auto() DEBUG = auto() INFO = auto() diff --git a/src/textual/app.py b/src/textual/app.py index f6066c3f7..2a437f390 100644 --- a/src/textual/app.py +++ b/src/textual/app.py @@ -511,10 +511,10 @@ class App(Generic[ReturnType], DOMNode): try: if len(objects) == 1 and not kwargs: self.devtools.log( + DevtoolsLog(objects, caller=_textual_calling_frame), group, verbosity, severity, - DevtoolsLog(objects, caller=_textual_calling_frame), ) else: output = " ".join(str(arg) for arg in objects) @@ -524,10 +524,10 @@ class App(Generic[ReturnType], DOMNode): ) output = f"{output} {key_values}" if output else key_values self.devtools.log( + DevtoolsLog(output, caller=_textual_calling_frame), group, verbosity, severity, - DevtoolsLog(output, caller=_textual_calling_frame), ) except Exception as error: self.on_exception(error) diff --git a/src/textual/devtools/client.py b/src/textual/devtools/client.py index 32218d471..991f740fd 100644 --- a/src/textual/devtools/client.py +++ b/src/textual/devtools/client.py @@ -139,7 +139,7 @@ class DevtoolsClient: log_queue = self.log_queue websocket = self.websocket - async def update_console(): + async def update_console() -> None: """Coroutine function scheduled as a Task, which listens on the websocket for updates from the server regarding any changes in the server Console dimensions. When the client learns of this @@ -153,7 +153,7 @@ class DevtoolsClient: payload = message_json["payload"] self.console.width = payload["width"] self.console.height = payload["height"] - self.verbose = payload["verbose"] + self.verbose = payload.get("verbose", False) async def send_queued_logs(): """Coroutine function which is scheduled as a Task, which consumes @@ -215,10 +215,10 @@ class DevtoolsClient: def log( self, - group: LogGroup, - verbosity: LogVerbosity, - severity: LogSeverity, log: DevtoolsLog, + group: LogGroup = LogGroup.UNDEFINED, + verbosity: LogVerbosity = LogVerbosity.NORMAL, + severity: LogSeverity = LogSeverity.NORMAL, ) -> None: """Queue a log to be sent to the devtools server for display. diff --git a/src/textual/devtools/redirect_output.py b/src/textual/devtools/redirect_output.py index e18cf872b..3122da9ec 100644 --- a/src/textual/devtools/redirect_output.py +++ b/src/textual/devtools/redirect_output.py @@ -98,8 +98,8 @@ class StdoutRedirector: batched_log = "".join(cast(str, log.objects_or_string) for log in log_batch) batched_log = batched_log.rstrip() self.devtools.log( + DevtoolsLog(batched_log, caller=log_batch[-1].caller), LogGroup.PRINT, LogVerbosity.NORMAL, LogSeverity.NORMAL, - DevtoolsLog(batched_log, caller=log_batch[-1].caller), ) diff --git a/src/textual/devtools/server.py b/src/textual/devtools/server.py index b2bddf821..167696563 100644 --- a/src/textual/devtools/server.py +++ b/src/textual/devtools/server.py @@ -36,7 +36,7 @@ async def _on_startup(app: Application) -> None: await service.start() -def _run_devtools(verbose: bool, exclude: list[str]) -> None: +def _run_devtools(verbose: bool, exclude: list[str] | None = None) -> None: app = _make_devtools_aiohttp_app(verbose=verbose, exclude=exclude) def noop_print(_: str): @@ -46,9 +46,9 @@ def _run_devtools(verbose: bool, exclude: list[str]) -> None: def _make_devtools_aiohttp_app( - exclude: list[str], size_change_poll_delay_secs: float = DEFAULT_SIZE_CHANGE_POLL_DELAY_SECONDS, verbose: bool = False, + exclude: list[str] | None = None, ) -> Application: app = Application() diff --git a/src/textual/devtools/service.py b/src/textual/devtools/service.py index da5419150..7d8ef9a21 100644 --- a/src/textual/devtools/service.py +++ b/src/textual/devtools/service.py @@ -100,7 +100,7 @@ class DevtoolsService: { "type": "server_info", "payload": { - "width": self.console.width - 4, + "width": self.console.width, "height": self.console.height, "verbose": self.verbose, }, @@ -181,7 +181,7 @@ class ClientHandler: type = message["type"] if type == "client_log": payload = message["payload"] - if LogGroup(payload["group"]).name in self.service.exclude: + if LogGroup(payload.get("group", 0)).name in self.service.exclude: continue encoded_segments = payload["segments"] segments = pickle.loads(encoded_segments) @@ -198,9 +198,9 @@ class ClientHandler: path=payload["path"], line_number=payload["line_number"], unix_timestamp=payload["timestamp"], - group=payload["group"], - verbosity=payload["verbosity"], - severity=payload["severity"], + group=payload.get("group", 0), + verbosity=payload.get("verbosity", 0), + severity=payload.get("severity", 0), ) ) last_message_time = message_time diff --git a/tests/devtools/test_devtools.py b/tests/devtools/test_devtools.py index f66766445..b8c65bfd1 100644 --- a/tests/devtools/test_devtools.py +++ b/tests/devtools/test_devtools.py @@ -37,6 +37,9 @@ def test_log_message_render(console): path="abc/hello.py", line_number=123, unix_timestamp=TIMESTAMP, + group=0, + verbosity=0, + severity=0, ) table = next(iter(message.__rich_console__(console, console.options))) @@ -52,7 +55,7 @@ def test_log_message_render(console): local_time = datetime.fromtimestamp(TIMESTAMP) string_timestamp = local_time.time() - assert left == f"[dim]{string_timestamp}" + assert left.plain == f"[{string_timestamp}] UNDEFINED" assert right.align == "right" assert "hello.py:123" in right.renderable diff --git a/tests/devtools/test_devtools_client.py b/tests/devtools/test_devtools_client.py index 84a57b3c6..0f5f6e490 100644 --- a/tests/devtools/test_devtools_client.py +++ b/tests/devtools/test_devtools_client.py @@ -36,15 +36,20 @@ async def test_devtools_log_places_encodes_and_queues_message(devtools): queued_log = await devtools.log_queue.get() queued_log_data = msgpack.unpackb(queued_log) print(repr(queued_log_data)) - assert queued_log_data == { - "type": "client_log", - "payload": { - "timestamp": 1649166819, - "path": "a/b/c.py", - "line_number": 123, - "segments": b"\x80\x04\x95B\x00\x00\x00\x00\x00\x00\x00]\x94(\x8c\x0crich.segment\x94\x8c\x07Segment\x94\x93\x94\x8c\rHello, world!\x94NN\x87\x94\x81\x94h\x03\x8c\x01\n\x94NN\x87\x94\x81\x94e.", - }, - } + + +{ + "type": "client_log", + "payload": { + "group": 0, + "verbosity": 0, + "severity": 0, + "timestamp": 1649166819, + "path": "a/b/c.py", + "line_number": 123, + "segments": b"\x80\x04\x95@\x00\x00\x00\x00\x00\x00\x00]\x94(\x8c\x0crich.segment\x94\x8c\x07Segment\x94\x93\x94\x8c\x0bhello world\x94NN\x87\x94\x81\x94h\x03\x8c\x01\n\x94NN\x87\x94\x81\x94e.", + }, +} @time_machine.travel(datetime.utcfromtimestamp(TIMESTAMP)) @@ -57,6 +62,9 @@ async def test_devtools_log_places_encodes_and_queues_many_logs_as_string(devtoo assert queued_log_data == { "type": "client_log", "payload": { + "group": 0, + "verbosity": 0, + "severity": 0, "timestamp": 1649166819, "path": "a/b/c.py", "line_number": 123, diff --git a/tests/devtools/test_redirect_output.py b/tests/devtools/test_redirect_output.py index 349c2aa69..7292f3e87 100644 --- a/tests/devtools/test_redirect_output.py +++ b/tests/devtools/test_redirect_output.py @@ -14,7 +14,7 @@ TIMESTAMP = 1649166819 async def test_print_redirect_to_devtools_only(devtools): await devtools._stop_log_queue_processing() - with redirect_stdout(StdoutRedirector(devtools, None)): # type: ignore + with redirect_stdout(StdoutRedirector(devtools)): # type: ignore print("Hello, world!") assert devtools.log_queue.qsize() == 1 @@ -32,28 +32,26 @@ async def test_print_redirect_to_devtools_only(devtools): ) -async def test_print_redirect_to_logfile_only(devtools, in_memory_logfile): +async def test_print_redirect_to_logfile_only(devtools): await devtools.disconnect() - with redirect_stdout(StdoutRedirector(devtools, in_memory_logfile)): # type: ignore + with redirect_stdout(StdoutRedirector(devtools)): # type: ignore print("Hello, world!") - assert in_memory_logfile.getvalue() == "Hello, world!\n" -async def test_print_redirect_to_devtools_and_logfile(devtools, in_memory_logfile): +async def test_print_redirect_to_devtools_and_logfile(devtools): await devtools._stop_log_queue_processing() - with redirect_stdout(StdoutRedirector(devtools, in_memory_logfile)): # type: ignore + with redirect_stdout(StdoutRedirector(devtools)): # type: ignore print("Hello, world!") assert devtools.log_queue.qsize() == 1 - assert in_memory_logfile.getvalue() == "Hello, world!\n" @time_machine.travel(datetime.fromtimestamp(TIMESTAMP)) -async def test_print_without_flush_not_sent_to_devtools(devtools, in_memory_logfile): +async def test_print_without_flush_not_sent_to_devtools(devtools): await devtools._stop_log_queue_processing() - with redirect_stdout(StdoutRedirector(devtools, in_memory_logfile)): # type: ignore + with redirect_stdout(StdoutRedirector(devtools)): # type: ignore # End is no longer newline character, so print will no longer # flush the output buffer by default. print("Hello, world!", end="") @@ -62,44 +60,19 @@ async def test_print_without_flush_not_sent_to_devtools(devtools, in_memory_logf @time_machine.travel(datetime.fromtimestamp(TIMESTAMP)) -async def test_print_forced_flush_sent_to_devtools(devtools, in_memory_logfile): +async def test_print_forced_flush_sent_to_devtools(devtools): await devtools._stop_log_queue_processing() - with redirect_stdout(StdoutRedirector(devtools, in_memory_logfile)): # type: ignore + with redirect_stdout(StdoutRedirector(devtools)): # type: ignore print("Hello, world!", end="", flush=True) assert devtools.log_queue.qsize() == 1 @time_machine.travel(datetime.fromtimestamp(TIMESTAMP)) -async def test_print_multiple_args_batched_as_one_log(devtools, in_memory_logfile): +async def test_print_multiple_args_batched_as_one_log(devtools): await devtools._stop_log_queue_processing() - - with redirect_stdout(StdoutRedirector(devtools, in_memory_logfile)): # type: ignore - # We call print with multiple arguments here, but it - # results in a single log added to the log queue. - print("Hello", "world", "multiple") - - assert devtools.log_queue.qsize() == 1 - - queued_log = await devtools.log_queue.get() - queued_log_json = json.loads(queued_log) - payload = queued_log_json["payload"] - - assert queued_log_json["type"] == "client_log" - assert payload["timestamp"] == TIMESTAMP - assert ( - payload["encoded_segments"] - == "gANdcQAoY3JpY2guc2VnbWVudApTZWdtZW50CnEBWBQAAABIZWxsbyB3b3JsZCBtdWx0aXBsZXECTk6HcQOBcQRoAVgBAAAACnEFTk6HcQaBcQdlLg==" - ) - assert len(payload["path"]) > 0 - assert payload["line_number"] != 0 - - -@time_machine.travel(datetime.fromtimestamp(TIMESTAMP)) -async def test_print_multiple_args_batched_as_one_log(devtools, in_memory_logfile): - await devtools._stop_log_queue_processing() - redirector = StdoutRedirector(devtools, in_memory_logfile) + redirector = StdoutRedirector(devtools) with redirect_stdout(redirector): # type: ignore # This print adds 3 messages to the buffer that can be batched print("The first", "batch", "of logs", end="") @@ -111,10 +84,10 @@ async def test_print_multiple_args_batched_as_one_log(devtools, in_memory_logfil @time_machine.travel(datetime.fromtimestamp(TIMESTAMP)) -async def test_print_strings_containing_newline_flushed(devtools, in_memory_logfile): +async def test_print_strings_containing_newline_flushed(devtools): await devtools._stop_log_queue_processing() - with redirect_stdout(StdoutRedirector(devtools, in_memory_logfile)): # type: ignore + with redirect_stdout(StdoutRedirector(devtools)): # type: ignore # Flushing is disabled since end="", but the first # string will be flushed since it contains a newline print("Hel\nlo", end="") @@ -127,7 +100,7 @@ async def test_print_strings_containing_newline_flushed(devtools, in_memory_logf async def test_flush_flushes_buffered_logs(devtools, in_memory_logfile): await devtools._stop_log_queue_processing() - redirector = StdoutRedirector(devtools, in_memory_logfile) + redirector = StdoutRedirector(devtools) with redirect_stdout(redirector): # type: ignore print("x", end="") diff --git a/tests/utilities/test_app.py b/tests/utilities/test_app.py index e23952b23..64eb637cb 100644 --- a/tests/utilities/test_app.py +++ b/tests/utilities/test_app.py @@ -25,20 +25,11 @@ from textual.geometry import Size, Region class AppTest(App): - def __init__( - self, - *, - test_name: str, - size: Size, - log_verbosity: int = 2, - ): + def __init__(self, *, test_name: str, size: Size): # Tests will log in "/tests/test.[test name].log": log_path = Path(__file__).parent.parent / f"test.{test_name}.log" super().__init__( driver_class=DriverTest, - log_path=log_path, - log_verbosity=log_verbosity, - log_color_system="256", ) # Let's disable all features by default