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.
|
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:
|
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
|
## Demo
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "textual"
|
name = "textual"
|
||||||
version = "0.2.0b9"
|
version = "0.2.0b11"
|
||||||
homepage = "https://github.com/Textualize/textual"
|
homepage = "https://github.com/Textualize/textual"
|
||||||
description = "Modern Text User Interface framework"
|
description = "Modern Text User Interface framework"
|
||||||
authors = ["Will McGugan <will@textualize.io>"]
|
authors = ["Will McGugan <will@textualize.io>"]
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ class Logger:
|
|||||||
app = active_app.get()
|
app = active_app.get()
|
||||||
except LookupError:
|
except LookupError:
|
||||||
raise LoggerError("Unable to log without an active app.") from None
|
raise LoggerError("Unable to log without an active app.") from None
|
||||||
if not app.devtools.is_connected:
|
if not app.devtools_enabled:
|
||||||
return
|
return
|
||||||
|
|
||||||
previous_frame = inspect.currentframe().f_back
|
previous_frame = inspect.currentframe().f_back
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ from contextlib import redirect_stderr, redirect_stdout
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from pathlib import Path, PurePath
|
from pathlib import Path, PurePath
|
||||||
from time import perf_counter
|
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 weakref import WeakSet, WeakValueDictionary
|
||||||
|
|
||||||
from ._ansi_sequences import SYNC_END, SYNC_START
|
from ._ansi_sequences import SYNC_END, SYNC_START
|
||||||
@@ -36,8 +36,6 @@ from .binding import Binding, Bindings
|
|||||||
from .css.query import NoMatches
|
from .css.query import NoMatches
|
||||||
from .css.stylesheet import Stylesheet
|
from .css.stylesheet import Stylesheet
|
||||||
from .design import ColorSystem
|
from .design import ColorSystem
|
||||||
from .devtools.client import DevtoolsClient, DevtoolsConnectionError, DevtoolsLog
|
|
||||||
from .devtools.redirect_output import StdoutRedirector
|
|
||||||
from .dom import DOMNode
|
from .dom import DOMNode
|
||||||
from .driver import Driver
|
from .driver import Driver
|
||||||
from .drivers.headless_driver import HeadlessDriver
|
from .drivers.headless_driver import HeadlessDriver
|
||||||
@@ -51,6 +49,10 @@ from .renderables.blank import Blank
|
|||||||
from .screen import Screen
|
from .screen import Screen
|
||||||
from .widget import AwaitMount, Widget
|
from .widget import AwaitMount, Widget
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from .devtools.client import DevtoolsClient
|
||||||
|
|
||||||
|
|
||||||
PLATFORM = platform.system()
|
PLATFORM = platform.system()
|
||||||
WINDOWS = PLATFORM == "Windows"
|
WINDOWS = PLATFORM == "Windows"
|
||||||
|
|
||||||
@@ -221,7 +223,15 @@ class App(Generic[ReturnType], DOMNode):
|
|||||||
] = WeakValueDictionary()
|
] = WeakValueDictionary()
|
||||||
self._installed_screens.update(**self.SCREENS)
|
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._return_value: ReturnType | None = None
|
||||||
|
|
||||||
self.css_monitor = (
|
self.css_monitor = (
|
||||||
@@ -275,7 +285,7 @@ class App(Generic[ReturnType], DOMNode):
|
|||||||
bool: True if devtools are enabled.
|
bool: True if devtools are enabled.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return "devtools" in self.features
|
return "devtools" in self.features and self.devtools is not None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def debug(self) -> bool:
|
def debug(self) -> bool:
|
||||||
@@ -448,15 +458,18 @@ class App(Generic[ReturnType], DOMNode):
|
|||||||
verbosity (int, optional): Verbosity level 0-3. Defaults to 1.
|
verbosity (int, optional): Verbosity level 0-3. Defaults to 1.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not self.devtools.is_connected:
|
devtools = self.devtools
|
||||||
|
if devtools is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
if verbosity.value > LogVerbosity.NORMAL.value and not self.devtools.verbose:
|
if verbosity.value > LogVerbosity.NORMAL.value and not devtools.verbose:
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
from .devtools.client import DevtoolsLog
|
||||||
|
|
||||||
if len(objects) == 1 and not kwargs:
|
if len(objects) == 1 and not kwargs:
|
||||||
self.devtools.log(
|
devtools.log(
|
||||||
DevtoolsLog(objects, caller=_textual_calling_frame),
|
DevtoolsLog(objects, caller=_textual_calling_frame),
|
||||||
group,
|
group,
|
||||||
verbosity,
|
verbosity,
|
||||||
@@ -468,7 +481,7 @@ class App(Generic[ReturnType], DOMNode):
|
|||||||
f"{key}={value!r}" for key, value in kwargs.items()
|
f"{key}={value!r}" for key, value in kwargs.items()
|
||||||
)
|
)
|
||||||
output = f"{output} {key_values}" if output else key_values
|
output = f"{output} {key_values}" if output else key_values
|
||||||
self.devtools.log(
|
devtools.log(
|
||||||
DevtoolsLog(output, caller=_textual_calling_frame),
|
DevtoolsLog(output, caller=_textual_calling_frame),
|
||||||
group,
|
group,
|
||||||
verbosity,
|
verbosity,
|
||||||
@@ -987,6 +1000,9 @@ class App(Generic[ReturnType], DOMNode):
|
|||||||
self._set_active()
|
self._set_active()
|
||||||
|
|
||||||
if self.devtools_enabled:
|
if self.devtools_enabled:
|
||||||
|
assert self.devtools is not None
|
||||||
|
from .devtools.client import DevtoolsConnectionError
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await self.devtools.connect()
|
await self.devtools.connect()
|
||||||
self.log.system(f"Connected to devtools ( {self.devtools.url} )")
|
self.log.system(f"Connected to devtools ( {self.devtools.url} )")
|
||||||
@@ -1074,10 +1090,21 @@ class App(Generic[ReturnType], DOMNode):
|
|||||||
if self.is_headless:
|
if self.is_headless:
|
||||||
await run_process_messages()
|
await run_process_messages()
|
||||||
else:
|
else:
|
||||||
redirector = StdoutRedirector(self.devtools)
|
if self.devtools_enabled:
|
||||||
with redirect_stderr(redirector):
|
devtools = self.devtools
|
||||||
with redirect_stdout(redirector): # type: ignore
|
assert devtools is not None
|
||||||
await run_process_messages()
|
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:
|
finally:
|
||||||
driver.stop_application_mode()
|
driver.stop_application_mode()
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
@@ -1085,7 +1112,7 @@ class App(Generic[ReturnType], DOMNode):
|
|||||||
finally:
|
finally:
|
||||||
self._running = False
|
self._running = False
|
||||||
self._print_error_renderables()
|
self._print_error_renderables()
|
||||||
if self.devtools.is_connected:
|
if self.devtools is not None and self.devtools.is_connected:
|
||||||
await self._disconnect_devtools()
|
await self._disconnect_devtools()
|
||||||
|
|
||||||
async def _pre_process(self) -> None:
|
async def _pre_process(self) -> None:
|
||||||
@@ -1185,7 +1212,8 @@ class App(Generic[ReturnType], DOMNode):
|
|||||||
self._registry.discard(widget)
|
self._registry.discard(widget)
|
||||||
|
|
||||||
async def _disconnect_devtools(self):
|
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:
|
def _start_widget(self, parent: Widget, widget: Widget) -> None:
|
||||||
"""Start a widget (run it's task) so that it can receive messages.
|
"""Start a widget (run it's task) so that it can receive messages.
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
import click
|
import click
|
||||||
from importlib_metadata import version
|
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
|
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("-v", "verbose", help="Enable verbose logs.", is_flag=True)
|
||||||
@click.option("-x", "--exclude", "exclude", help="Exclude log group(s)", multiple=True)
|
@click.option("-x", "--exclude", "exclude", help="Exclude log group(s)", multiple=True)
|
||||||
def console(verbose: bool, exclude: list[str]) -> None:
|
def console(verbose: bool, exclude: list[str]) -> None:
|
||||||
|
"""Launch the textual console."""
|
||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
|
from textual.devtools.server import _run_devtools
|
||||||
|
|
||||||
console = Console()
|
console = Console()
|
||||||
console.clear()
|
console.clear()
|
||||||
|
|||||||
@@ -383,8 +383,8 @@ class DemoApp(App):
|
|||||||
webbrowser.open(link)
|
webbrowser.open(link)
|
||||||
|
|
||||||
def action_toggle_sidebar(self) -> None:
|
def action_toggle_sidebar(self) -> None:
|
||||||
|
|
||||||
sidebar = self.query_one(Sidebar)
|
sidebar = self.query_one(Sidebar)
|
||||||
|
self.set_focus(None)
|
||||||
if sidebar.has_class("-hidden"):
|
if sidebar.has_class("-hidden"):
|
||||||
sidebar.remove_class("-hidden")
|
sidebar.remove_class("-hidden")
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -15,24 +15,15 @@ from rich.segment import Segment
|
|||||||
from .._log import LogGroup, LogVerbosity
|
from .._log import LogGroup, LogVerbosity
|
||||||
|
|
||||||
|
|
||||||
class DevtoolsDependenciesMissingError(Exception):
|
import aiohttp
|
||||||
"""Raise when the required devtools dependencies are not installed in the environment"""
|
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
|
DEVTOOLS_PORT = 8081
|
||||||
WEBSOCKET_CONNECT_TIMEOUT = 3
|
WEBSOCKET_CONNECT_TIMEOUT = 3
|
||||||
LOG_QUEUE_MAXSIZE = 512
|
LOG_QUEUE_MAXSIZE = 512
|
||||||
|
|||||||
@@ -1,11 +1,17 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
from aiohttp.web import run_app
|
|
||||||
from aiohttp.web_app import Application
|
try:
|
||||||
from aiohttp.web_request import Request
|
from aiohttp.web import run_app
|
||||||
from aiohttp.web_routedef import get
|
from aiohttp.web_app import Application
|
||||||
from aiohttp.web_ws import WebSocketResponse
|
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.client import DEVTOOLS_PORT
|
||||||
from textual.devtools.service import DevtoolsService
|
from textual.devtools.service import DevtoolsService
|
||||||
|
|||||||
Reference in New Issue
Block a user