handle missing dev

This commit is contained in:
Will McGugan
2022-10-22 17:04:48 +01:00
parent c5a292aa9c
commit 7e9b296c93
8 changed files with 73 additions and 42 deletions

View File

@@ -25,13 +25,13 @@ You can install Textual via PyPI.
If you plan on developing Textual apps, then you should install `textual[dev]`. The `[dev]` part installs a few extra dependencies for development.
```
pip install "textual[dev]==0.2.0b9"
pip install "textual[dev]==0.2.0b11"
```
If you only plan on _running_ Textual apps, then you can drop the `[dev]` part:
```
pip install textual==0.2.0b9
pip install textual==0.2.0b11
```
## Demo

View File

@@ -1,6 +1,6 @@
[tool.poetry]
name = "textual"
version = "0.2.0b9"
version = "0.2.0b11"
homepage = "https://github.com/Textualize/textual"
description = "Modern Text User Interface framework"
authors = ["Will McGugan <will@textualize.io>"]

View File

@@ -52,7 +52,7 @@ class Logger:
app = active_app.get()
except LookupError:
raise LoggerError("Unable to log without an active app.") from None
if not app.devtools.is_connected:
if not app.devtools_enabled:
return
previous_frame = inspect.currentframe().f_back

View File

@@ -12,7 +12,7 @@ from contextlib import redirect_stderr, redirect_stdout
from datetime import datetime
from pathlib import Path, PurePath
from time import perf_counter
from typing import Any, Generic, Iterable, Type, TypeVar, cast, Union
from typing import Any, Generic, Iterable, Type, TYPE_CHECKING, TypeVar, cast, Union
from weakref import WeakSet, WeakValueDictionary
from ._ansi_sequences import SYNC_END, SYNC_START
@@ -36,8 +36,6 @@ from .binding import Binding, Bindings
from .css.query import NoMatches
from .css.stylesheet import Stylesheet
from .design import ColorSystem
from .devtools.client import DevtoolsClient, DevtoolsConnectionError, DevtoolsLog
from .devtools.redirect_output import StdoutRedirector
from .dom import DOMNode
from .driver import Driver
from .drivers.headless_driver import HeadlessDriver
@@ -51,6 +49,10 @@ from .renderables.blank import Blank
from .screen import Screen
from .widget import AwaitMount, Widget
if TYPE_CHECKING:
from .devtools.client import DevtoolsClient
PLATFORM = platform.system()
WINDOWS = PLATFORM == "Windows"
@@ -221,7 +223,15 @@ class App(Generic[ReturnType], DOMNode):
] = WeakValueDictionary()
self._installed_screens.update(**self.SCREENS)
self.devtools: DevtoolsClient | None = None
try:
from .devtools.client import DevtoolsClient
except ImportError:
# Dev dependencies not installed
self.devtools = None
else:
self.devtools = DevtoolsClient()
self._return_value: ReturnType | None = None
self.css_monitor = (
@@ -275,7 +285,7 @@ class App(Generic[ReturnType], DOMNode):
bool: True if devtools are enabled.
"""
return "devtools" in self.features
return "devtools" in self.features and self.devtools is not None
@property
def debug(self) -> bool:
@@ -448,15 +458,18 @@ class App(Generic[ReturnType], DOMNode):
verbosity (int, optional): Verbosity level 0-3. Defaults to 1.
"""
if not self.devtools.is_connected:
devtools = self.devtools
if devtools is None:
return
if verbosity.value > LogVerbosity.NORMAL.value and not self.devtools.verbose:
if verbosity.value > LogVerbosity.NORMAL.value and not devtools.verbose:
return
try:
from .devtools.client import DevtoolsLog
if len(objects) == 1 and not kwargs:
self.devtools.log(
devtools.log(
DevtoolsLog(objects, caller=_textual_calling_frame),
group,
verbosity,
@@ -468,7 +481,7 @@ class App(Generic[ReturnType], DOMNode):
f"{key}={value!r}" for key, value in kwargs.items()
)
output = f"{output} {key_values}" if output else key_values
self.devtools.log(
devtools.log(
DevtoolsLog(output, caller=_textual_calling_frame),
group,
verbosity,
@@ -987,6 +1000,9 @@ class App(Generic[ReturnType], DOMNode):
self._set_active()
if self.devtools_enabled:
assert self.devtools is not None
from .devtools.client import DevtoolsConnectionError
try:
await self.devtools.connect()
self.log.system(f"Connected to devtools ( {self.devtools.url} )")
@@ -1074,10 +1090,21 @@ class App(Generic[ReturnType], DOMNode):
if self.is_headless:
await run_process_messages()
else:
redirector = StdoutRedirector(self.devtools)
if self.devtools_enabled:
devtools = self.devtools
assert devtools is not None
from .devtools.redirect_output import StdoutRedirector
redirector = StdoutRedirector(devtools)
with redirect_stderr(redirector):
with redirect_stdout(redirector): # type: ignore
await run_process_messages()
else:
null_file = _NullFile()
with redirect_stderr(null_file):
with redirect_stdout(null_file):
await run_process_messages()
finally:
driver.stop_application_mode()
except Exception as error:
@@ -1085,7 +1112,7 @@ class App(Generic[ReturnType], DOMNode):
finally:
self._running = False
self._print_error_renderables()
if self.devtools.is_connected:
if self.devtools is not None and self.devtools.is_connected:
await self._disconnect_devtools()
async def _pre_process(self) -> None:
@@ -1185,6 +1212,7 @@ class App(Generic[ReturnType], DOMNode):
self._registry.discard(widget)
async def _disconnect_devtools(self):
if self.devtools is not None:
await self.devtools.disconnect()
def _start_widget(self, parent: Widget, widget: Widget) -> None:

View File

@@ -1,8 +1,12 @@
from __future__ import annotations
import sys
import click
from importlib_metadata import version
from textual.devtools.server import _run_devtools
from rich.console import Console
from textual._import_app import import_app, AppFail
@@ -16,7 +20,9 @@ def run():
@click.option("-v", "verbose", help="Enable verbose logs.", is_flag=True)
@click.option("-x", "--exclude", "exclude", help="Exclude log group(s)", multiple=True)
def console(verbose: bool, exclude: list[str]) -> None:
"""Launch the textual console."""
from rich.console import Console
from textual.devtools.server import _run_devtools
console = Console()
console.clear()

View File

@@ -383,8 +383,8 @@ class DemoApp(App):
webbrowser.open(link)
def action_toggle_sidebar(self) -> None:
sidebar = self.query_one(Sidebar)
self.set_focus(None)
if sidebar.has_class("-hidden"):
sidebar.remove_class("-hidden")
else:

View File

@@ -15,11 +15,6 @@ from rich.segment import Segment
from .._log import LogGroup, LogVerbosity
class DevtoolsDependenciesMissingError(Exception):
"""Raise when the required devtools dependencies are not installed in the environment"""
try:
import aiohttp
import msgpack
from aiohttp import (
@@ -27,11 +22,7 @@ try:
ClientResponseError,
ClientWebSocketResponse,
)
except ImportError:
# TODO: Add link to documentation on how to install devtools
raise DevtoolsDependenciesMissingError(
"Textual Devtools requires installation of the 'dev' extra dependencies. "
)
DEVTOOLS_PORT = 8081
WEBSOCKET_CONNECT_TIMEOUT = 3

View File

@@ -1,11 +1,17 @@
from __future__ import annotations
import asyncio
try:
from aiohttp.web import run_app
from aiohttp.web_app import Application
from aiohttp.web_request import Request
from aiohttp.web_routedef import get
from aiohttp.web_ws import WebSocketResponse
except ImportError:
raise ImportError(
"Textual Devtools requires installation of the 'dev' extra dependencies."
)
from textual.devtools.client import DEVTOOLS_PORT
from textual.devtools.service import DevtoolsService