diff --git a/docs/tutorial/typer-command.md b/docs/tutorial/typer-command.md
index 803b44d0f5..802237e9c7 100644
--- a/docs/tutorial/typer-command.md
+++ b/docs/tutorial/typer-command.md
@@ -215,6 +215,32 @@ Hello Camila
+### Run a package or module with specified function
+
+You can also specify a function directly using the colon syntax:
+
+
+
+```console
+$ typer my_package.main:my_function run --name Camila
+
+Hello Camila
+```
+
+
+
+or with the file path
+
+
+
+```console
+$ typer main.py:my_function run --name Camila
+
+Hello Camila
+```
+
+
+
## Options
You can specify one of the following **CLI options**:
@@ -222,6 +248,8 @@ You can specify one of the following **CLI options**:
* `--app`: the name of the variable with a `Typer()` object to run as the main app.
* `--func`: the name of the variable with a function that would be used with `typer.run()`.
+Alternatively, you can specify the function directly using the syntax `module:function`.
+
### Defaults
When your run a script with the `typer` command it will use the app from the following priority:
diff --git a/tests/test_cli/test_multi_func.py b/tests/test_cli/test_multi_func.py
index 8b4b43a94d..62f6c8b362 100644
--- a/tests/test_cli/test_multi_func.py
+++ b/tests/test_cli/test_multi_func.py
@@ -85,6 +85,26 @@ def test_script_func_not_function():
assert "Not a function:" in result.stderr
+def test_script_colon_not_function():
+ result = subprocess.run(
+ [
+ sys.executable,
+ "-m",
+ "coverage",
+ "run",
+ "-m",
+ "typer",
+ "tests/assets/cli/multi_func.py:message",
+ "run",
+ "--name",
+ "Camila",
+ ],
+ capture_output=True,
+ encoding="utf-8",
+ )
+ assert "Not a function:" in result.stderr
+
+
def test_script_func():
result = subprocess.run(
[
@@ -104,3 +124,41 @@ def test_script_func():
)
assert "Hello" not in result.stdout
assert "Stuff" in result.stdout
+
+
+def test_script_module_colon_func():
+ result = subprocess.run(
+ [
+ sys.executable,
+ "-m",
+ "coverage",
+ "run",
+ "-m",
+ "typer",
+ "tests.assets.cli.multi_func:say_stuff",
+ "run",
+ ],
+ capture_output=True,
+ encoding="utf-8",
+ )
+ assert "Hello" not in result.stdout
+ assert "Stuff" in result.stdout
+
+
+def test_script_file_colon_func():
+ result = subprocess.run(
+ [
+ sys.executable,
+ "-m",
+ "coverage",
+ "run",
+ "-m",
+ "typer",
+ "tests/assets/cli/multi_func.py:say_stuff",
+ "run",
+ ],
+ capture_output=True,
+ encoding="utf-8",
+ )
+ assert "Hello" not in result.stdout
+ assert "Stuff" in result.stdout
diff --git a/typer/cli.py b/typer/cli.py
index 3fe3d3ee7f..82166be0cb 100644
--- a/typer/cli.py
+++ b/typer/cli.py
@@ -43,6 +43,10 @@ def __init__(self) -> None:
def maybe_update_state(ctx: click.Context) -> None:
path_or_module = ctx.params.get("path_or_module")
if path_or_module:
+ if ":" in path_or_module:
+ module_part, func_part = path_or_module.rsplit(":", 1)
+ path_or_module = module_part
+ ctx.params.update({"func": func_part})
file_path = Path(path_or_module)
if file_path.exists() and file_path.is_file():
state.file = file_path