Skip to content

Modal

modal

Defines the shared Modal deployment lifecycle used across Mirumoji

Both Modal integrations reuse these helpers but keep their app definitions with their owner. Only the shared, app-agnostic lifecycle functions lives here

Server

The server auto-deploys its GPU-offload app (see server/modal_processing) which runs only transcription and conversion on modal while everything else runs locally

Launcher
  • The launcher deploys mirumoji itself as a hosted app, with both backend and frontend running on Modal

  • Its launcher-only host operations (credentials, volume management, URLs, and logs) live in launcher.modal.lifecycle

Authentication

Every call authenticates through the environment's MODAL_TOKEN_ID / MODAL_TOKEN_SECRET variables, so managing apps on the user's own workspace needs nothing but those two tokens

Ownership

Apps deployed through ensure_deployed are tagged managed-by=mirumoji with the deploying package version, so a running install can recognise its own app, keep it current, and never create a duplicate

Import Cost
  • modal is only imported lazily inside each function so that importing this module (which the launcher does on every command) stays cheap

  • modal is a core dependency of the package so importing it here would be safe otherwise

MANAGED_BY_KEY = 'managed-by' module-attribute

Tag key marking a Modal app as managed by Mirumoji

MANAGED_BY_VALUE = 'mirumoji' module-attribute

Tag value marking a Modal app as managed by Mirumoji

NO_WINDOW = getattr(subprocess, 'CREATE_NO_WINDOW', 0) module-attribute

Suppresses the transient console window a console-less GUI process would otherwise flash when spawning the modal CLI on Windows (a no-op elsewhere), matching launcher.core.process

VERSION_KEY = 'mirumoji-version' module-attribute

Tag key recording the package version a Modal app was deployed from

clean_subprocess_error(stderr)

Distils the modal CLI's noisy error output down to one readable line

The modal CLI frames errors with Rich, so its stderr carries ANSI colour codes and box-drawing borders. This strips both and returns the last line that still has content, which is where the CLI puts the actual error

ASCII Borders

Rich falls back to +, -, and | borders on a non-UTF terminal (a Windows cp1252 console), so those are stripped too. Otherwise a border line would survive and be returned as the error

Parameters:

Name Type Description Default
stderr str

The captured subprocess stderr

required

Returns:

Type Description
str

The concise error line, or a generic fallback when none is found

deployed_tags(name)

Returns the tags of a deployed Modalapp, orNone` when it is not deployed

Untagged Apps
  • A deployed app whose tags cannot be read is reported as an empty mapping rather than None

  • This way, callers can tell a non-tagged app apart from one that doesn't exist

Parameters:

Name Type Description Default
name str

The deployed app name

required

Returns:

Type Description
dict[str, str] | None

The app's tag mapping, {} when it has none readable, or None when no app of that name is deployed

Raises:

Type Description
ModalError

If the workspace cannot be reached to look the app up

ensure_authenticated()

Ensures that the Modal API credentials are present in the environment

Raises:

Type Description
ModalError

If either MODAL_TOKEN_ID or MODAL_TOKEN_SECRET is unset

ensure_deployed(app, name, *, version, extra_tags=None, force=False)

Deploys a Modal app under name unless an app of the same name whose tags already match managed_tags(version) and any extra_tags is deployed

Idempotent + Tracked
  • Looks the app up first and returns early when a Mirumoji-managed app of the same version (and matching extra_tags) is already deployed

  • extra_tags are part of the deployed identity: they are compared alongside the version and set on deploy, so a change to them (not only to the version) forces a redeploy. The host uses this so a changed host mode or reservation is never mistaken for the live app

  • If there's no app deployed under name, or that app doesn't have the managed_tags(__version__) tags, the app is deployed with those tags set

  • Redeploys happen in place (Modal updates an app of the same name rather than creating a duplicate), so a package upgrade transparently rolls the deployed app forward

Force
  • When force=True, the app is always redeployed

  • This is needed to roll out a code or image change without a version bump during development

Blocking

Performs network I/O, so a caller on the event loop should run it in a thread

Parameters:

Name Type Description Default
app App

The locally-defined app to deploy

required
name str

The deployed app name

required
version str

The package version being deployed

required
extra_tags dict[str, str] | None

Extra identity tags compared and set alongside the managed tags, so a deploy whose configuration changed (not only its version) is not mistaken for the live one

None
force bool

Redeploy even when the same version is already live

False

Raises:

Type Description
ModalError

If credentials are missing or the deploy fails

managed_tags(version)

Builds the ownership tags applied to every Mirumoji-managed Modal app

Parameters:

Name Type Description Default
version str

The package version deploying the app

required

Returns:

Type Description
dict[str, str]

The managed-by and version tag mapping

modal_cli_argv(*args)

Builds the argv for a modal CLI invocation

Prefers <interpreter> -m modal, falling back to a modal entry point on the PATH when the process has no interpreter to spawn

Parameters:

Name Type Description Default
*args str

The modal sub-command and its arguments

()

Returns:

Type Description
list[str]

The argv to spawn

Raises:

Type Description
ModalError

If neither an interpreter nor a modal executable is found

python_executable()

Returns a real Python interpreter to spawn, or None when there is none

Packaged Builds
  • A pip-installed launcher runs under a genuine interpreter, so sys.executable is used directly

  • Inside a packaged bundle sys.executable is the app binary, so PATH is searched instead and None is returned when the machine has no interpreter at all

Store Alias Stub

On Windows, PATH carries a fake python.exe under Microsoft\WindowsApps (the app execution alias). Running it only prints a Microsoft Store install prompt, so a hit from there is not an interpreter and is skipped

Returns:

Type Description
str | None

A path to a Python interpreter, or None when none is available

stop(name)

Stops a deployed Modal app under name, removing it from the workspace

In-Process First
  • Modal's Python SDK exposes no public call to stop an app, so this drives modal's own command callback in-process and falls back to the modal app stop CLI when that is unavailable

  • The packaged desktop GUI embeds Python in the app binary, so sys.executable is not an interpreter there. Running the callback in-process keeps the stop working without spawning anything

  • Stopping is one-way, so a stopped app is redeployed rather than restarted

Best Effort
  • Never raises, so a server caller (shutdown) can ignore the return

  • The failure reason is both logged and returned, so a CLI / GUI caller can surface it to the user instead of reporting a false success

Blocking

Performs network I/O, so a caller on the event loop should run it in a thread

Parameters:

Name Type Description Default
name str

The deployed app name to stop

required

Returns:

Type Description
str | None

A short error message when the stop failed, or None on success