Skip to content

tdfs4ds.app_launcher

tdfs4ds.app_launcher

Start the tdfs4ds admin app from a notebook and get a link to open it.

launch_app() starts python -m tdfs4ds.serve in a separate process (the notebook's own connection is untouched, closing the notebook does not stop it), waits until it answers, and shows a link. In a Jupyter notebook (SageMaker notebook instance, JupyterHub, Teradata AI Studio) the app is reached through Jupyter's own proxy, <jupyter base>/proxy/absolute/<port>/, so nothing has to be exposed and access is protected by the Jupyter login; the app's path prefix (TDFS4DS_SERVE_ROOT_PATH) is set to match. Outside Jupyter the link is http://localhost:<port>/.

app_status() and stop_app() read the state file the launch writes (~/.tdfs4ds/app_<port>.json), so they work from another notebook or after a kernel restart. sagemaker_lifecycle_script() returns the "Start notebook" script that starts the app with the instance.

The proxy is the jupyter-server-proxy extension of the Jupyter SERVER (on SageMaker it lives in the server's own environment, not the kernel's), so it is checked by asking the Jupyter server through it, not by importing it.

__all__ module-attribute

__all__ = ['launch_app', 'stop_app', 'app_status', 'sagemaker_lifecycle_script', 'connection_settings', 'dotenv_candidates']

DEFAULT_PORT module-attribute

DEFAULT_PORT = 8000

_STATE_DIR module-attribute

_STATE_DIR = home() / '.tdfs4ds'

_CONN_ATTRS module-attribute

_CONN_ATTRS = ('TD_HOST', 'TD_USERNAME', 'TD_PASSWORD', 'TD_ENCRYPT', 'TD_LOGMECH', 'TD_LOGDATA', 'TD_DATABASE')

_CONN_LABELS module-attribute

_CONN_LABELS = {'TD_HOST': 'host', 'TD_USERNAME': 'username', 'TD_PASSWORD': 'password', 'TD_ENCRYPT': 'encrypt', 'TD_LOGMECH': 'logon mechanism', 'TD_LOGDATA': 'token (logdata)', 'TD_DATABASE': 'default database'}

_CONN_SECRETS module-attribute

_CONN_SECRETS = ('TD_PASSWORD', 'TD_LOGDATA')

_SHELL_ENV module-attribute

_SHELL_ENV = '~/.tdfs4ds/env.sh'

_BASHRC_MARK module-attribute

_BASHRC_MARK = '# tdfs4ds: TDFS4DS_* settings of the env file'

_in_notebook

_in_notebook() -> bool

True inside a Jupyter kernel (a terminal IPython is not a notebook).

_runtime_dirs

_runtime_dirs() -> list

_pid_alive

_pid_alive(pid) -> bool

_ancestor_pids

_ancestor_pids(limit: int = 32) -> set

Process ids of this process's ancestors (Linux /proc; the parent elsewhere).

_norm_base

_norm_base(base: Optional[str]) -> str

_url_path

_url_path(url: str) -> str

jupyter_server

jupyter_server() -> Optional[dict]

The running Jupyter server this kernel belongs to: {base_url, url, token}, or None.

Read from Jupyter's runtime files (jpserver-*.json / nbserver-*.json), which is what list_running_servers reads, so it works even when the kernel's environment has no jupyter_server package (SageMaker).

Other services write files there too (seen on SageMaker Studio: a jpserver file for http://localhost:3001/mcp), so the newest file is only the last resort. The choice is, in order: the server that is an ancestor of this kernel, then a server whose URL path equals its base path (a Jupyter server's always does), then the newest.

_sagemaker_metadata

_sagemaker_metadata() -> dict

_studio_base

_studio_base() -> Optional[str]

/jupyterlab/default/ in a SageMaker Studio JupyterLab space, else None.

proxy_path

proxy_path(port: int = DEFAULT_PORT, base_url: Optional[str] = None) -> str

<base>proxy/absolute/<port> (no trailing slash): the app's path prefix behind Jupyter.

_sagemaker_host

_sagemaker_host() -> Optional[str]

https://<name>.notebook.<region>.sagemaker.aws on a SageMaker notebook instance, else None.

None on SageMaker Studio: its address (https://<id>.studio.<region>...) cannot be worked out from inside the space.

_state_file

_state_file(port: int) -> Path

_read_state

_read_state(port: int) -> dict

_health

_health(port: int, timeout: float = 2.0) -> Optional[dict]

The app's /health answer on this machine, or None.

_is_app_404

_is_app_404(err: HTTPError) -> bool

A 404 written by the app itself (FastAPI's {"detail": ...}), not by Jupyter.

_proxy_reaches_app

_proxy_reaches_app(server: dict, path: str, timeout: float = 5.0)

Does the Jupyter server's proxy reach the app?

True, False (Jupyter answered: no proxy), "prefix" (the proxy reached the app but the app was started with another path prefix), or None when it cannot be asked.

A JupyterHub single-user server (AI Studio) refuses the token of its own runtime file, and jupyter-server-proxy answers an unauthenticated request with a 404: that 404 says nothing about the proxy, so the answer is None when the server's API refuses the token too.

_log_tail

_log_tail(path, n: int = 25) -> str

_show

_show(url: str, label: str, notes: list)

A clickable link in the notebook, the URL and notes printed otherwise.

_p

_p(text: str = '') -> None

_same_file

_same_file(a, b) -> bool

dotenv_candidates

dotenv_candidates(env_file: Optional[str] = None) -> list

The .env files the app or load_dotenv() could read, existing ones only except env_file: env_file, ./.env, ~/.tdfs4ds/.env, and the one python-dotenv's find_dotenv(usecwd=True) finds (searching up from here).

_app_dotenv

_app_dotenv(env_file: Optional[str]) -> Optional[str]

The .env the app process will read, or None.

_resolve_connection

_resolve_connection(env_file: Optional[str] = None) -> dict

{"values", "sources", "file", "missing", "invalid"} as the app will see them (raw values).

_gaps

_gaps(values: dict) -> dict

{"missing", "invalid", "token"} of a set of connection values (the one rule).

_shown

_shown(attr, value) -> str

connection_settings

connection_settings(env_file: Optional[str] = None) -> dict

The database connection settings the app would use, never a secret value.

{"file": the .env the app reads (or None), "variables": {TDFS4DS_TD_X: {"value" (secrets hidden), "source"}}, "missing": [...], "invalid": {...}, "complete": bool, "candidates": [.env files found]}. TDFS4DS_TD_ENCRYPT is reported missing when unset, though the app then runs unencrypted.

_logon_kwargs

_logon_kwargs(values: dict) -> Optional[dict]

_probe

_probe(values: dict)

(True | False | None, detail) - a logon with the raw driver; this session is untouched.

_menu

_menu(question: str, options: list, default: int = 1) -> int

A numbered choice; returns the 1-based index (the default when unanswered).

_ask_values

_ask_values(values: dict, wanted: list, token: bool) -> dict

Ask for wanted (plus the logon mechanism when unset); returns the answers.

_private

_private(path: str) -> bool

A file under ~/.tdfs4ds (a per-user directory, never a project's).

_save_values

_save_values(entered: dict, env_file: Optional[str], app_file: Optional[str]) -> Optional[str]

Offer to write entered into a .env for the next launches; returns the file written, or None.

_connection_dialogue

_connection_dialogue(env_file: Optional[str], ask: Optional[bool], check_logon: bool, preset: Optional[dict] = None) -> dict

Make sure the app can connect; ask what is missing. Returns {"env_file", "entered", "saved_to", "logon", "notes"} (entered holds raw values for the app's environment).

_resolve_schema

_resolve_schema(env_file: Optional[str], entered: dict) -> tuple

(database or None, source) of the feature store the app will open.

_raw_rows

_raw_rows(values: dict)

run(sql) -> rows over one raw teradatasql connection (this session is untouched), or None.

_discover_stores

_discover_stores(values: dict) -> Optional[list]

[{"database", "complete", "missing"}] visible with values, or None when not readable.

_query_band_notes

_query_band_notes(conn: dict) -> None

Whether this user can tag runs and read DBQL (the Execution log's data); notes only, nothing asked: the app works without it, the figures are what is missing.

_store_dialogue

_store_dialogue(conn: dict, ask: Optional[bool]) -> dict

Make sure the app opens a feature store; ask when none is set. Updates conn.

_child_env

_child_env(base: dict, env_file: Optional[str], entered: dict, saved_to: Optional[str])

(environment, env_file for --env) of the app process.

Values typed in the dialogue reach the app through its environment. --env loads its file OVER the environment, so when that file still holds a value the user replaced without saving it there, the file is merged here instead (file over environment, typed values over the file) and --env is dropped.

_token_logon

_token_logon(token: str) -> dict

The database logon settings for a session token.

_token_settings

_token_settings(token: str) -> dict

Everything a session token replaces for the app: logon, Vector Store, MCP gateway.

_env_file_credentials

_env_file_credentials(env_file: Optional[str]) -> dict

TDFS4DS_TD_* for the MCP server, with the app's precedence: the env file over the session.

(python -m tdfs4ds.serve --env loads its file with override=True.)

launch_app

launch_app(env_file: Optional[str] = None, port: int = DEFAULT_PORT, *, through_jupyter: Optional[bool] = None, timeout: float = 300, show: bool = True, log_file: Optional[str] = None, root_path: Optional[str] = None, mcp: bool = False, mcp_port: int = 8001, mcp_profile: Optional[str] = 'eda', ask: Optional[bool] = None, check_logon: bool = True, token: Optional[str] = None) -> dict

Start the tdfs4ds admin app in the background and show a link to open it.

Parameters

env_file : str, optional A .env with the connection and models (TDFS4DS_TD_*, ...); it wins over the environment. Without it the app reads the first of ./.env and ~/.tdfs4ds/.env (the environment winning). port : int The app's port on this machine (8000). through_jupyter : bool, optional Reach the app through Jupyter's proxy (<base>/proxy/absolute/<port>/). Default: True inside a Jupyter notebook, False elsewhere (http://localhost:<port>/). timeout : float Seconds to wait for the app to answer (the first start connects to the database; the vector index is then built in the background, shown in the app's status bar). show : bool Display the link (a button in a notebook, the URL in a terminal). log_file : str, optional Where the app writes its log (~/.tdfs4ds/app_<port>.log). root_path : str, optional The app's path prefix, when the one worked out from Jupyter is wrong (e.g. '/jupyterlab/default/proxy/absolute/8000'). mcp : bool Also start a local Teradata MCP server with the TDFS4DS_TD_* credentials (:func:tdfs4ds.start_mcp_server) and give its address to the app, which then offers the MCP skills. Needs teradata-mcp-server installed; a failure is reported in the notes and the app starts without it. mcp_port, mcp_profile : int, str The MCP server's port (8001) and tool profile ('eda'). ask : bool, optional Before starting, check that the app has a database host, username, password (or a JWT token) and TDFS4DS_TD_ENCRYPT, resolved the way the app reads them (:func:connection_settings). When one is missing, or the database refuses them, ask what to do: type them (the password hidden; the logon is tested with the raw driver, this session is not touched), use another .env found, or start without them (Connect page). Typed values can then be saved in a .env found (:func:dotenv_candidates) or a new one; the password is asked for separately. None (default) = ask when someone can answer (a terminal or a notebook); False = never ask (missing settings are listed in the notes). Not asked when an app already runs on port. check_logon : bool Test the logon before starting (one connection with the raw driver). token : str, optional A session token. The app logs on with it and gives it to the Vector Store and the MCP gateway, over what the .env holds; nothing is written to the file. Not needed on AI Studio: when the .env logs on with a token, the app takes the current one from the platform (as %gen_token does) and renews it itself. Elsewhere it expires with the session: launch again with a fresh one.

Returns

dict url (the link), local_url, pid, log, reused (an app was already running on that port), ready, proxy_ok (the Jupyter proxy reaches the app: True / False / "prefix" = it reaches an app started with another prefix / None = could not be checked), mcp (the MCP server's start report, when asked).

app_status

app_status(port: int = DEFAULT_PORT) -> dict

Is an app running on port? Its URL, pid, log and connection (from /health).

stop_app

stop_app(port: int = DEFAULT_PORT, timeout: float = 15) -> bool

Stop the app started by :func:launch_app on port. True when it is stopped.

sagemaker_lifecycle_script

sagemaker_lifecycle_script(env_file: str = '/home/ec2-user/SageMaker/tdfs4ds.env', port: int = DEFAULT_PORT, version: Optional[str] = None, conda_env: str = 'python3', start_app: bool = True, mcp: bool = False, mcp_port: int = 8001, share_env: bool = True) -> str

The "Start notebook" lifecycle script of a SageMaker notebook instance.

It installs tdfs4ds (the latest, or version) in the conda environment behind the conda_python3 kernel, so notebooks get it too, and (with start_app) starts the app behind Jupyter's proxy with env_file when it exists. With mcp it also installs teradata-mcp-server and starts it on 127.0.0.1:<mcp_port> with the env file's TDFS4DS_TD_* credentials (:func:tdfs4ds.start_mcp_server), for the app's MCP skills. Everything runs in the background as ec2-user: a lifecycle script must finish within 5 minutes.

share_env (0.3.3.15): the env file's settings also reach the notebooks and the terminals, not only the app.

  • Notebooks: ~/.tdfs4ds/.env is linked to env_file, and import tdfs4ds reads that file when the working directory has no .env of its own. A kernel does not read shell profiles, so exporting variables (in /etc/profile.d or ~/.bashrc) would not reach it.
  • Terminals: ~/.tdfs4ds/env.sh holds the TDFS4DS_* variables as quoted export lines, written by tdfs4ds's own .env parser (so a value with spaces, quotes or JSON is read the same way), and ~/.bashrc sources it.

The env file holds credentials: it and env.sh are made readable by ec2-user only (/etc/profile.d would make them readable by every account). Both are rebuilt at each start (the home directory outside SageMaker/ is reset when the instance stops), so an edited env file is taken into account at the next start; a running terminal sees it after source ~/.bashrc.