mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
Add --port option to textual console. (#2258)
* Add --port option to textual console. * Changelog. * Address review feedback. * Mark unpredictable test as xfail. This test gets an xfail mark until #2254 is open. * Make DEVTOOLS_PORT a constant. Related review: https://github.com/Textualize/textual/pull/2258\#discussion_r1165210395 * Factor logic into function. Related review: https://github.com/Textualize/textual/pull/2258\#discussion_r1165298259 * Remove dead import.
This commit is contained in:
committed by
GitHub
parent
e32cdbb390
commit
bb2c31ba35
@@ -16,14 +16,15 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
|||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Added `DataTable.remove_row` method https://github.com/Textualize/textual/pull/2253
|
- Added `DataTable.remove_row` method https://github.com/Textualize/textual/pull/2253
|
||||||
|
- option `--port` to the command `textual console` to specify which port the console should connect to https://github.com/Textualize/textual/pull/2258
|
||||||
- `Widget.scroll_to_center` method to scroll children to the center of container widget https://github.com/Textualize/textual/pull/2255 and https://github.com/Textualize/textual/pull/2276
|
- `Widget.scroll_to_center` method to scroll children to the center of container widget https://github.com/Textualize/textual/pull/2255 and https://github.com/Textualize/textual/pull/2276
|
||||||
- Added `TabActivated` message to `TabbedContent` https://github.com/Textualize/textual/pull/2260
|
- Added `TabActivated` message to `TabbedContent` https://github.com/Textualize/textual/pull/2260
|
||||||
|
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fixed order styles are applied in DataTable - allows combining of renderable styles and component classes https://github.com/Textualize/textual/pull/2272
|
- Fixed order styles are applied in DataTable - allows combining of renderable styles and component classes https://github.com/Textualize/textual/pull/2272
|
||||||
|
|
||||||
|
|
||||||
## [0.19.1] - 2023-04-10
|
## [0.19.1] - 2023-04-10
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
@@ -90,6 +90,20 @@ Multiple groups may be excluded, for example to exclude everything except warnin
|
|||||||
textual console -x SYSTEM -x EVENT -x DEBUG -x INFO
|
textual console -x SYSTEM -x EVENT -x DEBUG -x INFO
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Custom port
|
||||||
|
|
||||||
|
You can use the option `--port` to specify a custom port to run the console on, which comes in handy if you have other software running on the port that Textual uses by default:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
textual console --port 7342
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, use the command `run` with the same `--port` option:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
textual run --dev --port 7342 my_app.py
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
## Textual log
|
## Textual log
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
from ..constants import DEFAULT_DEVTOOLS_PORT, DEVTOOLS_PORT_ENVIRON_VARIABLE
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import click
|
import click
|
||||||
except ImportError:
|
except ImportError:
|
||||||
@@ -21,14 +23,27 @@ def run():
|
|||||||
|
|
||||||
|
|
||||||
@run.command(help="Run the Textual Devtools console.")
|
@run.command(help="Run the Textual Devtools console.")
|
||||||
|
@click.option(
|
||||||
|
"--port",
|
||||||
|
"port",
|
||||||
|
type=int,
|
||||||
|
default=None,
|
||||||
|
metavar="PORT",
|
||||||
|
help=f"Port to use for the development mode console. Defaults to {DEFAULT_DEVTOOLS_PORT}.",
|
||||||
|
)
|
||||||
@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(port: int | None, verbose: bool, exclude: list[str]) -> None:
|
||||||
"""Launch the textual console."""
|
"""Launch the textual console."""
|
||||||
|
import os
|
||||||
|
|
||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
|
|
||||||
from textual.devtools.server import _run_devtools
|
from textual.devtools.server import _run_devtools
|
||||||
|
|
||||||
|
if port is not None:
|
||||||
|
os.environ[DEVTOOLS_PORT_ENVIRON_VARIABLE] = str(port)
|
||||||
|
|
||||||
console = Console()
|
console = Console()
|
||||||
console.clear()
|
console.clear()
|
||||||
console.show_cursor(False)
|
console.show_cursor(False)
|
||||||
@@ -78,6 +93,14 @@ def _post_run_warnings() -> None:
|
|||||||
)
|
)
|
||||||
@click.argument("import_name", metavar="FILE or FILE:APP")
|
@click.argument("import_name", metavar="FILE or FILE:APP")
|
||||||
@click.option("--dev", "dev", help="Enable development mode", is_flag=True)
|
@click.option("--dev", "dev", help="Enable development mode", is_flag=True)
|
||||||
|
@click.option(
|
||||||
|
"--port",
|
||||||
|
"port",
|
||||||
|
type=int,
|
||||||
|
default=None,
|
||||||
|
metavar="PORT",
|
||||||
|
help=f"Port to use for the development mode console. Defaults to {DEFAULT_DEVTOOLS_PORT}.",
|
||||||
|
)
|
||||||
@click.option("--press", "press", help="Comma separated keys to simulate press")
|
@click.option("--press", "press", help="Comma separated keys to simulate press")
|
||||||
@click.option(
|
@click.option(
|
||||||
"--screenshot",
|
"--screenshot",
|
||||||
@@ -86,7 +109,9 @@ def _post_run_warnings() -> None:
|
|||||||
metavar="DELAY",
|
metavar="DELAY",
|
||||||
help="Take screenshot after DELAY seconds",
|
help="Take screenshot after DELAY seconds",
|
||||||
)
|
)
|
||||||
def run_app(import_name: str, dev: bool, press: str, screenshot: int | None) -> None:
|
def run_app(
|
||||||
|
import_name: str, dev: bool, port: int | None, press: str, screenshot: int | None
|
||||||
|
) -> None:
|
||||||
"""Run a Textual app.
|
"""Run a Textual app.
|
||||||
|
|
||||||
The code to run may be given as a path (ending with .py) or as a Python
|
The code to run may be given as a path (ending with .py) or as a Python
|
||||||
@@ -107,7 +132,6 @@ def run_app(import_name: str, dev: bool, press: str, screenshot: int | None) ->
|
|||||||
in quotes:
|
in quotes:
|
||||||
|
|
||||||
textual run "foo.py arg --option"
|
textual run "foo.py arg --option"
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
@@ -116,6 +140,9 @@ def run_app(import_name: str, dev: bool, press: str, screenshot: int | None) ->
|
|||||||
|
|
||||||
from textual.features import parse_features
|
from textual.features import parse_features
|
||||||
|
|
||||||
|
if port is not None:
|
||||||
|
os.environ[DEVTOOLS_PORT_ENVIRON_VARIABLE] = str(port)
|
||||||
|
|
||||||
features = set(parse_features(os.environ.get("TEXTUAL", "")))
|
features = set(parse_features(os.environ.get("TEXTUAL", "")))
|
||||||
if dev:
|
if dev:
|
||||||
features.add("debug")
|
features.add("debug")
|
||||||
|
|||||||
@@ -25,10 +25,28 @@ def get_environ_bool(name: str) -> bool:
|
|||||||
Returns:
|
Returns:
|
||||||
`True` if the env var is "1", otherwise `False`.
|
`True` if the env var is "1", otherwise `False`.
|
||||||
"""
|
"""
|
||||||
has_environ = os.environ.get(name) == "1"
|
has_environ = get_environ(name) == "1"
|
||||||
return has_environ
|
return has_environ
|
||||||
|
|
||||||
|
|
||||||
|
def get_environ_int(name: str, default: int) -> int:
|
||||||
|
"""Retrieves an integer environment variable.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: Name of environment variable.
|
||||||
|
default: The value to use if the value is not set, or set to something other
|
||||||
|
than a valid integer.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The integer associated with the environment variable if it's set to a valid int
|
||||||
|
or the default value otherwise.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return int(get_environ(name, default))
|
||||||
|
except ValueError:
|
||||||
|
return default
|
||||||
|
|
||||||
|
|
||||||
BORDERS = list(BORDER_CHARS)
|
BORDERS = list(BORDER_CHARS)
|
||||||
|
|
||||||
DEBUG: Final[bool] = get_environ_bool("TEXTUAL_DEBUG")
|
DEBUG: Final[bool] = get_environ_bool("TEXTUAL_DEBUG")
|
||||||
@@ -37,3 +55,13 @@ DRIVER: Final[str | None] = get_environ("TEXTUAL_DRIVER", None)
|
|||||||
|
|
||||||
LOG_FILE: Final[str | None] = get_environ("TEXTUAL_LOG", None)
|
LOG_FILE: Final[str | None] = get_environ("TEXTUAL_LOG", None)
|
||||||
"""A last resort log file that appends all logs, when devtools isn't working."""
|
"""A last resort log file that appends all logs, when devtools isn't working."""
|
||||||
|
|
||||||
|
|
||||||
|
DEVTOOLS_PORT_ENVIRON_VARIABLE: Final[str] = "TEXTUAL_CONSOLE_PORT"
|
||||||
|
"""The name of the environment variable that sets the port for the devtools."""
|
||||||
|
DEFAULT_DEVTOOLS_PORT: Final[int] = 8081
|
||||||
|
"""The default port to use for the devtools."""
|
||||||
|
DEVTOOLS_PORT: Final[int] = get_environ_int(
|
||||||
|
DEVTOOLS_PORT_ENVIRON_VARIABLE, DEFAULT_DEVTOOLS_PORT
|
||||||
|
)
|
||||||
|
"""Constant with the port that the devtools will connect to."""
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ from rich.console import Console
|
|||||||
from rich.segment import Segment
|
from rich.segment import Segment
|
||||||
|
|
||||||
from .._log import LogGroup, LogVerbosity
|
from .._log import LogGroup, LogVerbosity
|
||||||
|
from ..constants import DEVTOOLS_PORT
|
||||||
|
|
||||||
DEVTOOLS_PORT = 8081
|
|
||||||
WEBSOCKET_CONNECT_TIMEOUT = 3
|
WEBSOCKET_CONNECT_TIMEOUT = 3
|
||||||
LOG_QUEUE_MAXSIZE = 512
|
LOG_QUEUE_MAXSIZE = 512
|
||||||
|
|
||||||
@@ -88,10 +88,12 @@ class DevtoolsClient:
|
|||||||
|
|
||||||
Args:
|
Args:
|
||||||
host: The host the devtools server is running on, defaults to "127.0.0.1"
|
host: The host the devtools server is running on, defaults to "127.0.0.1"
|
||||||
port: The port the devtools server is accessed via, defaults to 8081
|
port: The port the devtools server is accessed via, `DEVTOOLS_PORT` by default.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, host: str = "127.0.0.1", port: int = DEVTOOLS_PORT) -> None:
|
def __init__(self, host: str = "127.0.0.1", port: int | None = None) -> None:
|
||||||
|
if port is None:
|
||||||
|
port = DEVTOOLS_PORT
|
||||||
self.url: str = f"ws://{host}:{port}"
|
self.url: str = f"ws://{host}:{port}"
|
||||||
self.session: aiohttp.ClientSession | None = None
|
self.session: aiohttp.ClientSession | None = None
|
||||||
self.log_queue_task: Task | None = None
|
self.log_queue_task: Task | None = None
|
||||||
|
|||||||
@@ -46,7 +46,10 @@ def _run_devtools(verbose: bool, exclude: list[str] | None = None) -> None:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
run_app(
|
run_app(
|
||||||
app, port=DEVTOOLS_PORT, print=noop_print, loop=asyncio.get_event_loop()
|
app,
|
||||||
|
port=DEVTOOLS_PORT,
|
||||||
|
print=noop_print,
|
||||||
|
loop=asyncio.get_event_loop(),
|
||||||
)
|
)
|
||||||
except OSError:
|
except OSError:
|
||||||
from rich import print
|
from rich import print
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ from rich.console import ConsoleDimensions
|
|||||||
from rich.panel import Panel
|
from rich.panel import Panel
|
||||||
|
|
||||||
from tests.utilities.render import wait_for_predicate
|
from tests.utilities.render import wait_for_predicate
|
||||||
|
from textual.constants import DEFAULT_DEVTOOLS_PORT
|
||||||
from textual.devtools.client import DevtoolsClient
|
from textual.devtools.client import DevtoolsClient
|
||||||
from textual.devtools.redirect_output import DevtoolsLog
|
from textual.devtools.redirect_output import DevtoolsLog
|
||||||
|
|
||||||
@@ -21,7 +22,7 @@ TIMESTAMP = 1649166819
|
|||||||
|
|
||||||
def test_devtools_client_initialize_defaults():
|
def test_devtools_client_initialize_defaults():
|
||||||
devtools = DevtoolsClient()
|
devtools = DevtoolsClient()
|
||||||
assert devtools.url == "ws://127.0.0.1:8081"
|
assert devtools.url == f"ws://127.0.0.1:{DEFAULT_DEVTOOLS_PORT}"
|
||||||
|
|
||||||
|
|
||||||
async def test_devtools_client_is_connected(devtools):
|
async def test_devtools_client_is_connected(devtools):
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ class MyApp(App[None]):
|
|||||||
|
|
||||||
def compose(self) -> ComposeResult:
|
def compose(self) -> ComposeResult:
|
||||||
with VerticalScroll():
|
with VerticalScroll():
|
||||||
yield Label(("SPAM\n" * 25)[:-1])
|
yield Label(("SPAM\n" * 205)[:-1])
|
||||||
with VerticalScroll():
|
with VerticalScroll():
|
||||||
yield Label(("SPAM\n" * 53)[:-1])
|
yield Label(("SPAM\n" * 53)[:-1])
|
||||||
with VerticalScroll(id="vertical"):
|
with VerticalScroll(id="vertical"):
|
||||||
|
|||||||
@@ -418,6 +418,9 @@ def test_scroll_visible(snap_compare):
|
|||||||
assert snap_compare(SNAPSHOT_APPS_DIR / "scroll_visible.py", press=["t"])
|
assert snap_compare(SNAPSHOT_APPS_DIR / "scroll_visible.py", press=["t"])
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.xfail(
|
||||||
|
reason="Unpredictable while https://github.com/Textualize/textual/issues/2254 is open."
|
||||||
|
)
|
||||||
def test_scroll_to_center(snap_compare):
|
def test_scroll_to_center(snap_compare):
|
||||||
# READ THIS IF THIS TEST FAILS:
|
# READ THIS IF THIS TEST FAILS:
|
||||||
# While https://github.com/Textualize/textual/issues/2254 is open, the snapshot
|
# While https://github.com/Textualize/textual/issues/2254 is open, the snapshot
|
||||||
|
|||||||
Reference in New Issue
Block a user