# Copyright 2026 HUBzero Foundation, LLC.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# HUBzero is a registered trademark of Purdue University.
# Authors:
# Daniel Mejia (denphi), Purdue University (denphi@denphi.com)
"""
dash_env - IPython magic commands for configuring Dash environment on nanoHUB.
This module provides the %set_dash_env magic command that configures
environment variables needed to run Dash applications behind the
nanoHUB weber proxy.
Usage in Jupyter:
%load_ext nanohubdash
%set_dash_env --port 8001 --host 0.0.0.0
"""
import os
from IPython.core.magic import magics_class, Magics, line_magic
from IPython.core.magic_arguments import magic_arguments, argument, parse_argstring
def _normalize(p: str) -> str:
"""
Normalize a URL path to ensure it starts and ends with '/'.
Args:
p: Path string to normalize.
Returns:
Normalized path with leading and trailing slashes.
"""
if not p.startswith("/"):
p = "/" + p
if not p.endswith("/"):
p += "/"
return p
def _get_session():
"""
Retrieve session information from environment variables.
Returns:
tuple: (session_id, session_directory)
Raises:
RuntimeError: If SESSION/SESSIONDIR environment variables are not set.
"""
session = os.getenv("SESSION") or os.getenv("SESSION_ID")
sessiondir = os.getenv("SESSIONDIR") or os.getenv("SESSION_DIR")
if not session or not sessiondir:
raise RuntimeError("SESSION / SESSIONDIR not set")
return session, sessiondir
def _get_requests_prefix_and_url(dash_port: int):
"""
Parse nanoHUB resources file and construct proxy URL information.
Reads the session's resources file to extract hub_url, filexfer_port,
and filexfer_cookie, then constructs the appropriate weber proxy paths.
Args:
dash_port: The port number where Dash will be running.
Returns:
tuple: (requests_prefix, browser_url, proxy_url)
- requests_prefix: Path for DASH_REQUESTS_PATHNAME_PREFIX
- browser_url: Full URL to access the Dash app in browser
- proxy_url: Base proxy URL (https://proxy.<hub_host>)
Raises:
RuntimeError: If required fields cannot be parsed from resources file.
"""
session, sessiondir = _get_session()
fn = os.path.join(sessiondir, "resources")
url = fxp = fxc = None
with open(fn) as f:
for line in f:
if line.startswith("hub_url"):
url = line.split()[1]
elif line.startswith("filexfer_port"):
fxp = str(int(line.split()[1]) % 1000)
elif line.startswith("filexfer_cookie"):
fxc = line.split()[1]
if not (url and fxp and fxc):
raise RuntimeError("Could not parse hub_url/filexfer_port/filexfer_cookie from resources")
weber = f"/weber/{session}/{fxc}/{fxp}/"
proxy_seg = f"proxy/{dash_port}/" # appended AFTER weber path
requests_prefix = _normalize(weber + proxy_seg)
proxy_url = "https://proxy." + url.split("//", 1)[1]
browser_url = proxy_url + requests_prefix
return requests_prefix, browser_url, proxy_url
[docs]
@magics_class
class SetDashEnvMagic(Magics):
"""
IPython magic class for setting up Dash environment on nanoHUB.
Provides the %set_dash_env line magic that configures environment
variables needed for Dash applications running behind the nanoHUB
weber proxy.
"""
@magic_arguments()
@argument("--port", type=int, default=8001, help="Dash port (default 8001)")
@argument("--host", type=str, default="0.0.0.0", help="Dash bind host (default 0.0.0.0)")
@line_magic
def set_dash_env(self, line):
"""
Configure environment variables for running Dash on nanoHUB.
Usage:
%set_dash_env [--port 8001] [--host 0.0.0.0]
This magic command sets the following environment variables:
- DASH_REQUESTS_PATHNAME_PREFIX: Weber proxy path for requests
- DASH_ROUTES_PATHNAME_PREFIX: Set to '/'
- DASH_HOST: The host address for the Dash server
- DASH_PORT: The port number for the Dash server
- DASH_BASE_PROXY: Base proxy URL (https://proxy.<hub_host>)
After execution, prints the browser URL where the Dash app
will be accessible.
Args:
line: Command line arguments string.
"""
args = parse_argstring(self.set_dash_env, line)
req_prefix, browser_url, proxy_url = _get_requests_prefix_and_url(args.port)
os.environ["DASH_REQUESTS_PATHNAME_PREFIX"] = req_prefix
os.environ["DASH_ROUTES_PATHNAME_PREFIX"] = "/"
os.environ["DASH_HOST"] = args.host
os.environ["DASH_PORT"] = str(args.port)
os.environ["DASH_BASE_PROXY"] = proxy_url
print("Dash environment variables loaded.")
[docs]
def load_ipython_extension(ipython):
"""
Register the SetDashEnvMagic with IPython.
This function is called automatically when the extension is loaded
via %load_ext nanohubdash.
Args:
ipython: The IPython shell instance.
"""
ipython.register_magics(SetDashEnvMagic)