Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updated docs for app.run #2414

Merged
merged 5 commits into from
Apr 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).

## [0.22.1] - 2023-04-28

### Fixed

- Fixed `textual run` issue https://github.com/Textualize/textual/issues/2391

## [0.22.0] - 2023-04-27

### Fixed
Expand Down
35 changes: 29 additions & 6 deletions docs/guide/devtools.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

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
textual --help
Expand All @@ -17,24 +17,47 @@ textual --help

## 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
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
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

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

Expand Down
28 changes: 5 additions & 23 deletions src/textual/cli/_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@
EXEC_SCRIPT = Template(
"""\
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 we imported an app, run it
app.run()
Expand Down Expand Up @@ -111,25 +115,6 @@ def exec_command(
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(
import_name: str, args: Sequence[str], environment: dict[str, str]
) -> NoReturn:
Expand All @@ -147,9 +132,6 @@ def exec_import(
module, _colon, app = import_name.partition(":")
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)
# Compiling the script will raise a SyntaxError if there are any invalid symbols
compile(script, "textual-exec", "exec")
Expand Down
10 changes: 5 additions & 5 deletions src/textual/cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,16 +147,16 @@ def _run_app(
) -> None:
"""Run a Textual app.

The code to run may be given as a path (ending with .py) or as a Python
import, which will load the code and run an app called "app". You may optionally
add a colon plus the class or class instance you want to run.
The app to run may be given as a path (ending with .py) which will be equivalent to running the
script with python, or as a Python import which will import and run an app called "app".

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:

textual run foo.py

textual run foo.py:MyApp

textual run module.foo

textual run module.foo:MyApp
Expand Down
4 changes: 3 additions & 1 deletion src/textual/widgets/_tabs.py
Original file line number Diff line number Diff line change
Expand Up @@ -404,7 +404,9 @@ def watch_active(self, previously_active: str, active: str) -> None:
active_tab = self.query_one(f"#tabs-list > #{active}", Tab)
self.query("#tabs-list > Tab.-active").remove_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))
else:
underline = self.query_one(Underline)
Expand Down