Merge branch 'main' into expose-node-tree

This commit is contained in:
Dave Pearson
2023-04-28 13:58:04 +01:00
7 changed files with 54 additions and 40 deletions

View File

@@ -11,6 +11,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Added `TreeNode.tree` as a read-only public attribute https://github.com/Textualize/textual/issues/2413 - Added `TreeNode.tree` as a read-only public attribute https://github.com/Textualize/textual/issues/2413
## [0.22.1] - 2023-04-28
### Fixed
- Fixed timer issue https://github.com/Textualize/textual/issues/2416
- Fixed `textual run` issue https://github.com/Textualize/textual/issues/2391
## [0.22.0] - 2023-04-27 ## [0.22.0] - 2023-04-27
### Fixed ### Fixed

View File

@@ -8,7 +8,7 @@
Textual comes with a command line application of the same name. The `textual` command is a super useful tool that will help you to build apps. Textual comes with a command line application of the same name. The `textual` command is a super useful tool that will help you to build apps.
Take a moment to look through the available sub-commands. There will be even more helpful tools here in the future. Take a moment to look through the available subcommands. There will be even more helpful tools here in the future.
```bash ```bash
textual --help textual --help
@@ -17,25 +17,48 @@ textual --help
## Run ## Run
You can run Textual apps with the `run` subcommand. If you supply a path to a Python file it will load and run the application. The `run` sub-command runs Textual apps. If you supply a path to a Python file it will load and run the app.
```bash ```bash
textual run my_app.py textual run my_app.py
``` ```
The `run` sub-command will first look for a `App` instance called `app` in the global scope of your Python file. If there is no `app`, it will create an instance of the first `App` class it finds and run that. This is equivalent to running `python my_app.py` from the command prompt, but will allow you to set various switches which can help you debug, such as `--dev` which enable the [Console](#console).
Alternatively, you can add the name of an `App` instance or class after a colon to run a specific app in the Python file. Here's an example: See the `run` subcommand's help for details:
```bash ```bash
textual run my_app.py:alternative_app textual run --help
```
You can also run Textual apps from a python import.
The following command would import `music.play` and run a Textual app in that module:
```bash
textual run music.play
```
This assumes you have a Textual app instance called `app` in `music.play`.
If your app has a different name, you can append it after a colon:
```bash
textual run music.play:MusicPlayerApp
``` ```
!!! note !!! note
If the Python file contains a call to app.run() then you can launch the file as you normally would any other Python program. Running your app via `textual run` will give you access to a few Textual features such as live editing of CSS files. This works for both Textual app *instances* and *classes*.
### Running from commands
If your app is installed as a command line script, you can use the `-c` switch to run it.
For instance, the following will run the `textual colors` command:
```bash
textual run -c textual colors
```
## Live editing ## Live editing
If you combine the `run` command with the `--dev` switch your app will run in *development mode*. If you combine the `run` command with the `--dev` switch your app will run in *development mode*.

View File

@@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "textual" name = "textual"
version = "0.22.0" version = "0.22.1"
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>"]

View File

@@ -17,7 +17,11 @@ from typing import NoReturn, Sequence
EXEC_SCRIPT = Template( EXEC_SCRIPT = Template(
"""\ """\
from textual.app import App from textual.app import App
from $MODULE import $APP as app; try:
from $MODULE import $APP as app;
except ImportError:
raise SystemExit("Unable to import '$APP' from module '$MODULE'") from None
if isinstance(app, App): if isinstance(app, App):
# If we imported an app, run it # If we imported an app, run it
app.run() app.run()
@@ -111,25 +115,6 @@ def exec_command(
os.execvpe(command, [command, *args], environment) os.execvpe(command, [command, *args], environment)
def check_import(module_name: str, app_name: str) -> bool:
"""Check if a symbol can be imported.
Args:
module_name: Name of the module
app_name: Name of the app.
Returns:
True if the app may be imported from the module.
"""
try:
sys.path.insert(0, "")
module = importlib.import_module(module_name)
except ImportError as error:
return False
return hasattr(module, app_name)
def exec_import( def exec_import(
import_name: str, args: Sequence[str], environment: dict[str, str] import_name: str, args: Sequence[str], environment: dict[str, str]
) -> NoReturn: ) -> NoReturn:
@@ -147,9 +132,6 @@ def exec_import(
module, _colon, app = import_name.partition(":") module, _colon, app = import_name.partition(":")
app = app or "app" app = app or "app"
if not check_import(module, app):
raise ExecImportError(f"Unable to import {app!r} from {import_name!r}")
script = EXEC_SCRIPT.substitute(MODULE=module, APP=app) script = EXEC_SCRIPT.substitute(MODULE=module, APP=app)
# Compiling the script will raise a SyntaxError if there are any invalid symbols # Compiling the script will raise a SyntaxError if there are any invalid symbols
compile(script, "textual-exec", "exec") compile(script, "textual-exec", "exec")

View File

@@ -147,16 +147,16 @@ def _run_app(
) -> 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 app to run may be given as a path (ending with .py) which will be equivalent to running the
import, which will load the code and run an app called "app". You may optionally script with python, or as a Python import which will import and run an app called "app".
add a colon plus the class or class instance you want to run.
In the case of an import, you can import and run an alternative app by appending a colon followed
by the name of the app instance or class.
Here are some examples: Here are some examples:
textual run foo.py textual run foo.py
textual run foo.py:MyApp
textual run module.foo textual run module.foo
textual run module.foo:MyApp textual run module.foo:MyApp

View File

@@ -131,16 +131,16 @@ class Timer:
wait_time = max(0, next_timer - now) wait_time = max(0, next_timer - now)
await sleep(wait_time) await sleep(wait_time)
count += 1 count += 1
try:
await self._tick(next_timer=next_timer, count=count)
except EventTargetGone:
break
await self._active.wait() await self._active.wait()
if self._reset: if self._reset:
start = _time.get_time() start = _time.get_time()
count = 0 count = 0
self._reset = False self._reset = False
continue continue
try:
await self._tick(next_timer=next_timer, count=count)
except EventTargetGone:
break
async def _tick(self, *, next_timer: float, count: int) -> None: async def _tick(self, *, next_timer: float, count: int) -> None:
"""Triggers the Timer's action: either call its callback, or sends an event to its target""" """Triggers the Timer's action: either call its callback, or sends an event to its target"""

View File

@@ -404,7 +404,9 @@ class Tabs(Widget, can_focus=True):
active_tab = self.query_one(f"#tabs-list > #{active}", Tab) active_tab = self.query_one(f"#tabs-list > #{active}", Tab)
self.query("#tabs-list > Tab.-active").remove_class("-active") self.query("#tabs-list > Tab.-active").remove_class("-active")
active_tab.add_class("-active") active_tab.add_class("-active")
self._highlight_active(animate=previously_active != "") self.call_after_refresh(
self._highlight_active, animate=previously_active != ""
)
self.post_message(self.TabActivated(self, active_tab)) self.post_message(self.TabActivated(self, active_tab))
else: else:
underline = self.query_one(Underline) underline = self.query_one(Underline)