mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
handle missing dev
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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>"]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
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)
|
||||
with redirect_stderr(redirector):
|
||||
with redirect_stdout(redirector): # type: ignore
|
||||
await run_process_messages()
|
||||
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,7 +1212,8 @@ class App(Generic[ReturnType], DOMNode):
|
||||
self._registry.discard(widget)
|
||||
|
||||
async def _disconnect_devtools(self):
|
||||
await self.devtools.disconnect()
|
||||
if self.devtools is not None:
|
||||
await self.devtools.disconnect()
|
||||
|
||||
def _start_widget(self, parent: Widget, widget: Widget) -> None:
|
||||
"""Start a widget (run it's task) so that it can receive messages.
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -15,24 +15,15 @@ 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"""
|
||||
import aiohttp
|
||||
import msgpack
|
||||
from aiohttp import (
|
||||
ClientConnectorError,
|
||||
ClientResponseError,
|
||||
ClientWebSocketResponse,
|
||||
)
|
||||
|
||||
|
||||
try:
|
||||
import aiohttp
|
||||
import msgpack
|
||||
from aiohttp import (
|
||||
ClientConnectorError,
|
||||
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
|
||||
LOG_QUEUE_MAXSIZE = 512
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user