Skip to content

Commit 2137408

Browse files
committed
feat: module:func syntax for run command; docs and tests
1 parent 3b17788 commit 2137408

File tree

3 files changed

+90
-0
lines changed

3 files changed

+90
-0
lines changed

docs/tutorial/typer-command.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,13 +215,41 @@ Hello Camila
215215

216216
</div>
217217

218+
### Run a package or module with specified function
219+
220+
You can also specify a function directly using the colon syntax:
221+
222+
<div class="termy">
223+
224+
```console
225+
$ typer my_package.main:my_function run --name Camila
226+
227+
Hello Camila
228+
```
229+
230+
</div>
231+
232+
or with the file path
233+
234+
<div class="termy">
235+
236+
```console
237+
$ typer main.py:my_function run --name Camila
238+
239+
Hello Camila
240+
```
241+
242+
</div>
243+
218244
## Options
219245

220246
You can specify one of the following **CLI options**:
221247

222248
* `--app`: the name of the variable with a `Typer()` object to run as the main app.
223249
* `--func`: the name of the variable with a function that would be used with `typer.run()`.
224250

251+
Alternatively, you can specify the function directly using the syntax `module:function`.
252+
225253
### Defaults
226254

227255
When your run a script with the `typer` command it will use the app from the following priority:

tests/test_cli/test_multi_func.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,26 @@ def test_script_func_not_function():
8585
assert "Not a function:" in result.stderr
8686

8787

88+
def test_script_colon_not_function():
89+
result = subprocess.run(
90+
[
91+
sys.executable,
92+
"-m",
93+
"coverage",
94+
"run",
95+
"-m",
96+
"typer",
97+
"tests/assets/cli/multi_func.py:message",
98+
"run",
99+
"--name",
100+
"Camila",
101+
],
102+
capture_output=True,
103+
encoding="utf-8",
104+
)
105+
assert "Not a function:" in result.stderr
106+
107+
88108
def test_script_func():
89109
result = subprocess.run(
90110
[
@@ -104,3 +124,41 @@ def test_script_func():
104124
)
105125
assert "Hello" not in result.stdout
106126
assert "Stuff" in result.stdout
127+
128+
129+
def test_script_module_colon_func():
130+
result = subprocess.run(
131+
[
132+
sys.executable,
133+
"-m",
134+
"coverage",
135+
"run",
136+
"-m",
137+
"typer",
138+
"tests.assets.cli.multi_func:say_stuff",
139+
"run",
140+
],
141+
capture_output=True,
142+
encoding="utf-8",
143+
)
144+
assert "Hello" not in result.stdout
145+
assert "Stuff" in result.stdout
146+
147+
148+
def test_script_file_colon_func():
149+
result = subprocess.run(
150+
[
151+
sys.executable,
152+
"-m",
153+
"coverage",
154+
"run",
155+
"-m",
156+
"typer",
157+
"tests/assets/cli/multi_func.py:say_stuff",
158+
"run",
159+
],
160+
capture_output=True,
161+
encoding="utf-8",
162+
)
163+
assert "Hello" not in result.stdout
164+
assert "Stuff" in result.stdout

typer/cli.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ def __init__(self) -> None:
4343
def maybe_update_state(ctx: click.Context) -> None:
4444
path_or_module = ctx.params.get("path_or_module")
4545
if path_or_module:
46+
if ":" in path_or_module:
47+
module_part, func_part = path_or_module.rsplit(":", 1)
48+
path_or_module = module_part
49+
ctx.params.update({"func": func_part})
4650
file_path = Path(path_or_module)
4751
if file_path.exists() and file_path.is_file():
4852
state.file = file_path

0 commit comments

Comments
 (0)