Skip to content

Commit db1dc8d

Browse files
committed
Add plotly run command
1 parent 9add5c1 commit db1dc8d

File tree

3 files changed

+195
-17
lines changed

3 files changed

+195
-17
lines changed

dash/__main__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from ._cli import cli
2+
3+
cli()

dash/_cli.py

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
import argparse
2+
import importlib
3+
import sys
4+
from typing import Any, Dict
5+
6+
from dash import Dash
7+
8+
9+
def load_app(app_path: str) -> Dash:
10+
"""
11+
Load a Dash app instance from a string like "module:variable".
12+
13+
:param app_path: The import path to the Dash app instance.
14+
:return: The loaded Dash app instance.
15+
"""
16+
if ":" not in app_path:
17+
raise ValueError(
18+
f"Invalid app path: '{app_path}'. "
19+
'The path must be in the format "module:variable".'
20+
)
21+
22+
module_str, app_str = app_path.split(":", 1)
23+
24+
if not module_str or not app_str:
25+
raise ValueError(
26+
f"Invalid app path: '{app_path}'. "
27+
'Both module and variable names are required in "module:variable".'
28+
)
29+
30+
try:
31+
module = importlib.import_module(module_str)
32+
except ImportError as e:
33+
raise ImportError(f"Could not import module '{module_str}'.") from e
34+
35+
try:
36+
app_instance = getattr(module, app_str)
37+
except AttributeError as e:
38+
raise AttributeError(
39+
f"Could not find variable '{app_str}' in module '{module_str}'."
40+
) from e
41+
42+
if not isinstance(app_instance, Dash):
43+
raise TypeError(f"'{app_path}' did not resolve to a Dash app instance.")
44+
45+
return app_instance
46+
47+
48+
def create_parser() -> argparse.ArgumentParser:
49+
"""Create the argument parser for the Plotly CLI."""
50+
parser = argparse.ArgumentParser(
51+
description="A command line interface for Plotly Dash."
52+
)
53+
subparsers = parser.add_subparsers(dest="command", required=True)
54+
55+
# --- `run` command ---
56+
run_parser = subparsers.add_parser(
57+
"run",
58+
help="Run a Dash app.",
59+
description="Run a local development server for a Dash app.",
60+
)
61+
62+
run_parser.add_argument(
63+
"app", help='The Dash app to run, in the format "module:variable".'
64+
)
65+
66+
# Server options
67+
run_parser.add_argument(
68+
"--host",
69+
type=str,
70+
help='Host IP used to serve the application (Default: "127.0.0.1").',
71+
)
72+
run_parser.add_argument(
73+
"--port",
74+
"-p",
75+
type=int,
76+
help='Port used to serve the application (Default: "8050").',
77+
)
78+
run_parser.add_argument(
79+
"--proxy",
80+
type=str,
81+
help='Proxy configuration string, e.g., "http://0.0.0.0:8050::https://my.domain.com".',
82+
)
83+
84+
# Debug flag (supports --debug and --no-debug)
85+
# Note: Requires Python 3.9+
86+
run_parser.add_argument(
87+
"--debug",
88+
"-d",
89+
action=argparse.BooleanOptionalAction,
90+
help="Enable/disable Flask debug mode and dev tools.",
91+
)
92+
93+
# Dev Tools options
94+
dev_tools_group = run_parser.add_argument_group("dev tools options")
95+
dev_tools_group.add_argument(
96+
"--dev-tools-ui",
97+
action=argparse.BooleanOptionalAction,
98+
help="Enable/disable the dev tools UI.",
99+
)
100+
dev_tools_group.add_argument(
101+
"--dev-tools-props-check",
102+
action=argparse.BooleanOptionalAction,
103+
help="Enable/disable component prop validation.",
104+
)
105+
dev_tools_group.add_argument(
106+
"--dev-tools-serve-dev-bundles",
107+
action=argparse.BooleanOptionalAction,
108+
help="Enable/disable serving of dev bundles.",
109+
)
110+
dev_tools_group.add_argument(
111+
"--dev-tools-hot-reload",
112+
action=argparse.BooleanOptionalAction,
113+
help="Enable/disable hot reloading.",
114+
)
115+
dev_tools_group.add_argument(
116+
"--dev-tools-hot-reload-interval",
117+
type=float,
118+
help="Interval in seconds for hot reload polling (Default: 3).",
119+
)
120+
dev_tools_group.add_argument(
121+
"--dev-tools-hot-reload-watch-interval",
122+
type=float,
123+
help="Interval in seconds for server-side file watch polling (Default: 0.5).",
124+
)
125+
dev_tools_group.add_argument(
126+
"--dev-tools-hot-reload-max-retry",
127+
type=int,
128+
help="Max number of failed hot reload requests before failing (Default: 8).",
129+
)
130+
dev_tools_group.add_argument(
131+
"--dev-tools-silence-routes-logging",
132+
action=argparse.BooleanOptionalAction,
133+
help="Enable/disable silencing of Werkzeug's route logging.",
134+
)
135+
dev_tools_group.add_argument(
136+
"--dev-tools-disable-version-check",
137+
action=argparse.BooleanOptionalAction,
138+
help="Enable/disable the Dash version upgrade check.",
139+
)
140+
dev_tools_group.add_argument(
141+
"--dev-tools-prune-errors",
142+
action=argparse.BooleanOptionalAction,
143+
help="Enable/disable pruning of tracebacks to user code only.",
144+
)
145+
146+
return parser
147+
148+
149+
def cli():
150+
"""The main entry point for the Plotly CLI."""
151+
sys.path.insert(0, ".")
152+
parser = create_parser()
153+
args = parser.parse_args()
154+
155+
try:
156+
if args.command == "run":
157+
app = load_app(args.app)
158+
159+
# Collect arguments to pass to the app.run() method.
160+
# Only include arguments that were actually provided on the CLI
161+
# or have a default value in the parser.
162+
run_options: Dict[str, Any] = {
163+
key: value
164+
for key, value in vars(args).items()
165+
if value is not None and key not in ["command", "app"]
166+
}
167+
168+
app.run(**run_options)
169+
170+
except (ValueError, ImportError, AttributeError, TypeError) as e:
171+
print(f"Error: {e}", file=sys.stderr)
172+
sys.exit(1)

setup.py

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
from setuptools import setup, find_packages
44

55
main_ns = {}
6-
exec(open("dash/version.py", encoding="utf-8").read(), main_ns) # pylint: disable=exec-used, consider-using-with
6+
# pylint: disable=exec-used, consider-using-with
7+
exec(open("dash/version.py", encoding="utf-8").read(), main_ns)
78

89

910
def read_req_file(req_type):
@@ -21,10 +22,10 @@ def read_req_file(req_type):
2122
include_package_data=True,
2223
license="MIT",
2324
description=(
24-
"A Python framework for building reactive web-apps. "
25-
"Developed by Plotly."
25+
"A Python framework for building reactive web-apps. " "Developed by Plotly."
2626
),
27-
long_description=io.open("README.md", encoding="utf-8").read(), # pylint: disable=consider-using-with
27+
# pylint: disable=consider-using-with
28+
long_description=io.open("README.md", encoding="utf-8").read(),
2829
long_description_content_type="text/markdown",
2930
install_requires=read_req_file("install"),
3031
python_requires=">=3.8",
@@ -34,14 +35,14 @@ def read_req_file(req_type):
3435
"testing": read_req_file("testing"),
3536
"celery": read_req_file("celery"),
3637
"diskcache": read_req_file("diskcache"),
37-
"compress": read_req_file("compress")
38+
"compress": read_req_file("compress"),
3839
},
3940
entry_points={
4041
"console_scripts": [
41-
"dash-generate-components = "
42-
"dash.development.component_generator:cli",
42+
"dash-generate-components = dash.development.component_generator:cli",
4343
"renderer = dash.development.build_process:renderer",
44-
"dash-update-components = dash.development.update_components:cli"
44+
"dash-update-components = dash.development.update_components:cli",
45+
"plotly = dash._cli:cli",
4546
],
4647
"pytest11": ["dash = dash.testing.plugin"],
4748
},
@@ -78,16 +79,18 @@ def read_req_file(req_type):
7879
],
7980
data_files=[
8081
# like `jupyter nbextension install --sys-prefix`
81-
("share/jupyter/nbextensions/dash", [
82-
"dash/nbextension/main.js",
83-
]),
82+
(
83+
"share/jupyter/nbextensions/dash",
84+
[
85+
"dash/nbextension/main.js",
86+
],
87+
),
8488
# like `jupyter nbextension enable --sys-prefix`
85-
("etc/jupyter/nbconfig/notebook.d", [
86-
"dash/nbextension/dash.json"
87-
]),
89+
("etc/jupyter/nbconfig/notebook.d", ["dash/nbextension/dash.json"]),
8890
# Place jupyterlab extension in extension directory
89-
("share/jupyter/lab/extensions", [
90-
"dash/labextension/dist/dash-jupyterlab.tgz"
91-
]),
91+
(
92+
"share/jupyter/lab/extensions",
93+
["dash/labextension/dist/dash-jupyterlab.tgz"],
94+
),
9295
],
9396
)

0 commit comments

Comments
 (0)