Skip to content

Add plotly run command #3329

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

Open
wants to merge 6 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 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
3 changes: 3 additions & 0 deletions dash/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from ._cli import cli

cli()
171 changes: 171 additions & 0 deletions dash/_cli.py
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to use that in the CI for some test to avoid regression?

Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
import argparse
import importlib
import sys
from typing import Any, Dict

from dash import Dash


def load_app(app_path: str) -> Dash:
"""
Load a Dash app instance from a string like "module:variable".

:param app_path: The import path to the Dash app instance.
:return: The loaded Dash app instance.
"""
app_split = app_path.split(":")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

check that len(app_split) == 2 here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I see that further down you're allowing users to provide just the module name, then using the first Dash you find in that module.

module_str = app_split[0]

if not module_str:
raise ValueError(f"Invalid app path: '{app_path}'. ")

try:
module = importlib.import_module(module_str)
except ImportError as e:
raise ImportError(f"Could not import module '{module_str}'.") from e

if len(app_split) == 2:
app_str = app_split[1]
try:
app_instance = getattr(module, app_str)
except AttributeError as e:
raise AttributeError(
f"Could not find variable '{app_str}' in module '{module_str}'."
) from e
else:
for module_var in vars(module).values():
if isinstance(module_var, Dash):
app_instance = module_var
break

if not isinstance(app_instance, Dash):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if I run with just the module name, but the module doesn't define a Dash, I think app_instance will be uninitialized at the point of the isinstance() check.

raise TypeError(f"'{app_path}' did not resolve to a Dash app instance.")

return app_instance


def create_parser() -> argparse.ArgumentParser:
"""Create the argument parser for the Plotly CLI."""
parser = argparse.ArgumentParser(
description="A command line interface for Plotly Dash."
)
subparsers = parser.add_subparsers(dest="command", required=True)

# --- `run` command ---
run_parser = subparsers.add_parser(
"run",
help="Run a Dash app.",
description="Run a local development server for a Dash app.",
)

run_parser.add_argument(
"app",
help='The Dash app to run, in the format "module:variable" '
'or just "module" to find the app instance automatically. (eg: plotly run app)',
)

# Server options
run_parser.add_argument(
"--host",
type=str,
help='Host IP used to serve the application (Default: "127.0.0.1").',
)
run_parser.add_argument(
"--port",
"-p",
type=int,
help='Port used to serve the application (Default: "8050").',
)
run_parser.add_argument(
"--proxy",
type=str,
help='Proxy configuration string, e.g., "http://0.0.0.0:8050::https://my.domain.com".',
)

run_parser.add_argument(
"--debug",
"-d",
action="store_true",
help="Enable/disable Flask debug mode and dev tools.",
)

# Dev Tools options
dev_tools_group = run_parser.add_argument_group("dev tools options")
dev_tools_group.add_argument(
"--dev-tools-ui",
action="store_true",
help="Enable/disable the dev tools UI.",
)
dev_tools_group.add_argument(
"--dev-tools-props-check",
action="store_true",
help="Enable/disable component prop validation.",
)
dev_tools_group.add_argument(
"--dev-tools-serve-dev-bundles",
action="store_true",
help="Enable/disable serving of dev bundles.",
)
dev_tools_group.add_argument(
"--dev-tools-hot-reload",
action="store_true",
help="Enable/disable hot reloading.",
)
dev_tools_group.add_argument(
"--dev-tools-hot-reload-interval",
type=float,
help="Interval in seconds for hot reload polling (Default: 3).",
)
dev_tools_group.add_argument(
"--dev-tools-hot-reload-watch-interval",
type=float,
help="Interval in seconds for server-side file watch polling (Default: 0.5).",
)
dev_tools_group.add_argument(
"--dev-tools-hot-reload-max-retry",
type=int,
help="Max number of failed hot reload requests before failing (Default: 8).",
)
dev_tools_group.add_argument(
"--dev-tools-silence-routes-logging",
action="store_true",
help="Enable/disable silencing of Werkzeug's route logging.",
)
dev_tools_group.add_argument(
"--dev-tools-disable-version-check",
action="store_true",
help="Enable/disable the Dash version upgrade check.",
)
dev_tools_group.add_argument(
"--dev-tools-prune-errors",
action="store_true",
help="Enable/disable pruning of tracebacks to user code only.",
)

return parser


def cli():
"""The main entry point for the Plotly CLI."""
sys.path.insert(0, ".")
parser = create_parser()
args = parser.parse_args()

try:
if args.command == "run":
app = load_app(args.app)

# Collect arguments to pass to the app.run() method.
# Only include arguments that were actually provided on the CLI
# or have a default value in the parser.
run_options: Dict[str, Any] = {
key: value
for key, value in vars(args).items()
if value is not None and key not in ["command", "app"]
}

app.run(**run_options)

except (ValueError, ImportError, AttributeError, TypeError) as e:
print(f"Error: {e}", file=sys.stderr)
sys.exit(1)
37 changes: 20 additions & 17 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
from setuptools import setup, find_packages

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


def read_req_file(req_type):
Expand All @@ -21,10 +22,10 @@ def read_req_file(req_type):
include_package_data=True,
license="MIT",
description=(
"A Python framework for building reactive web-apps. "
"Developed by Plotly."
"A Python framework for building reactive web-apps. " "Developed by Plotly."
),
long_description=io.open("README.md", encoding="utf-8").read(), # pylint: disable=consider-using-with
# pylint: disable=consider-using-with
long_description=io.open("README.md", encoding="utf-8").read(),
long_description_content_type="text/markdown",
install_requires=read_req_file("install"),
python_requires=">=3.8",
Expand All @@ -34,14 +35,14 @@ def read_req_file(req_type):
"testing": read_req_file("testing"),
"celery": read_req_file("celery"),
"diskcache": read_req_file("diskcache"),
"compress": read_req_file("compress")
"compress": read_req_file("compress"),
},
entry_points={
"console_scripts": [
"dash-generate-components = "
"dash.development.component_generator:cli",
"dash-generate-components = dash.development.component_generator:cli",
"renderer = dash.development.build_process:renderer",
"dash-update-components = dash.development.update_components:cli"
"dash-update-components = dash.development.update_components:cli",
"plotly = dash._cli:cli",
],
"pytest11": ["dash = dash.testing.plugin"],
},
Expand Down Expand Up @@ -78,16 +79,18 @@ def read_req_file(req_type):
],
data_files=[
# like `jupyter nbextension install --sys-prefix`
("share/jupyter/nbextensions/dash", [
"dash/nbextension/main.js",
]),
(
"share/jupyter/nbextensions/dash",
[
"dash/nbextension/main.js",
],
),
# like `jupyter nbextension enable --sys-prefix`
("etc/jupyter/nbconfig/notebook.d", [
"dash/nbextension/dash.json"
]),
("etc/jupyter/nbconfig/notebook.d", ["dash/nbextension/dash.json"]),
# Place jupyterlab extension in extension directory
("share/jupyter/lab/extensions", [
"dash/labextension/dist/dash-jupyterlab.tgz"
]),
(
"share/jupyter/lab/extensions",
["dash/labextension/dist/dash-jupyterlab.tgz"],
),
],
)