mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-09-26 19:45:00 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e3907743cc | ||
|
|
4d5bb3c26e | ||
|
|
ab851822c5 |
@@ -32,6 +32,8 @@ def test(config: pathlib.Path, port: int, tag: str, verbose: bool):
|
|||||||
docker_compose=None,
|
docker_compose=None,
|
||||||
port=port,
|
port=port,
|
||||||
watch=False,
|
watch=False,
|
||||||
|
debugger_port=None,
|
||||||
|
debugger_base_url=f"http://127.0.0.1:{port}",
|
||||||
postgres_uri=None,
|
postgres_uri=None,
|
||||||
api_version=None,
|
api_version=None,
|
||||||
image=tag,
|
image=tag,
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
__version__ = "0.4.32"
|
__version__ = "0.4.32.dev0"
|
||||||
|
|||||||
@@ -48,6 +48,9 @@ def get_anonymized_params(
|
|||||||
if kwargs.get("docker_compose"):
|
if kwargs.get("docker_compose"):
|
||||||
params["docker_compose"] = True
|
params["docker_compose"] = True
|
||||||
|
|
||||||
|
if kwargs.get("debugger_port"):
|
||||||
|
params["debugger_port"] = True
|
||||||
|
|
||||||
if kwargs.get("postgres_uri"):
|
if kwargs.get("postgres_uri"):
|
||||||
params["postgres_uri"] = True
|
params["postgres_uri"] = True
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import pathlib
|
|||||||
import shutil
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
from collections.abc import Sequence
|
from collections.abc import Sequence
|
||||||
from urllib.parse import SplitResult, urlencode, urlsplit, urlunsplit
|
|
||||||
|
|
||||||
import click
|
import click
|
||||||
import click.exceptions
|
import click.exceptions
|
||||||
@@ -141,6 +140,17 @@ OPT_VERBOSE = click.option(
|
|||||||
help="Show more output from the server logs",
|
help="Show more output from the server logs",
|
||||||
)
|
)
|
||||||
OPT_WATCH = click.option("--watch", is_flag=True, help="Restart on file changes")
|
OPT_WATCH = click.option("--watch", is_flag=True, help="Restart on file changes")
|
||||||
|
OPT_DEBUGGER_PORT = click.option(
|
||||||
|
"--debugger-port",
|
||||||
|
type=int,
|
||||||
|
help="Pull the debugger image locally and serve the UI on specified port",
|
||||||
|
)
|
||||||
|
OPT_DEBUGGER_BASE_URL = click.option(
|
||||||
|
"--debugger-base-url",
|
||||||
|
type=str,
|
||||||
|
help="URL used by the debugger to access LangGraph API. Defaults to http://127.0.0.1:[PORT]",
|
||||||
|
)
|
||||||
|
|
||||||
OPT_POSTGRES_URI = click.option(
|
OPT_POSTGRES_URI = click.option(
|
||||||
"--postgres-uri",
|
"--postgres-uri",
|
||||||
help="Postgres URI to use for the database. Defaults to launching a local database",
|
help="Postgres URI to use for the database. Defaults to launching a local database",
|
||||||
@@ -232,94 +242,18 @@ cli.add_command(deploy)
|
|||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
def _validated_http_url(value: str, option_name: str) -> SplitResult:
|
|
||||||
try:
|
|
||||||
parsed = urlsplit(value)
|
|
||||||
hostname = parsed.hostname
|
|
||||||
_ = parsed.port
|
|
||||||
except ValueError as exc:
|
|
||||||
raise click.UsageError(
|
|
||||||
f"{option_name} must be a valid HTTP(S) URL without credentials."
|
|
||||||
) from exc
|
|
||||||
|
|
||||||
if (
|
|
||||||
value != value.strip()
|
|
||||||
or parsed.scheme not in {"http", "https"}
|
|
||||||
or not parsed.netloc
|
|
||||||
or not hostname
|
|
||||||
or parsed.username is not None
|
|
||||||
or parsed.password is not None
|
|
||||||
):
|
|
||||||
raise click.UsageError(
|
|
||||||
f"{option_name} must be a valid HTTP(S) URL without credentials."
|
|
||||||
)
|
|
||||||
return parsed
|
|
||||||
|
|
||||||
|
|
||||||
def _studio_link(
|
|
||||||
*,
|
|
||||||
port: int,
|
|
||||||
studio_url: str | None,
|
|
||||||
api_url: str | None,
|
|
||||||
debugger_base_url: str | None,
|
|
||||||
) -> str:
|
|
||||||
if debugger_base_url is not None:
|
|
||||||
if api_url is not None and api_url != debugger_base_url:
|
|
||||||
raise click.UsageError(
|
|
||||||
"--api-url and --debugger-base-url cannot specify different URLs."
|
|
||||||
)
|
|
||||||
click.echo(
|
|
||||||
"Warning: --debugger-base-url is deprecated; use --api-url instead.",
|
|
||||||
err=True,
|
|
||||||
)
|
|
||||||
api_url = debugger_base_url
|
|
||||||
|
|
||||||
studio_url = "https://smith.langchain.com" if studio_url is None else studio_url
|
|
||||||
api_url = f"http://127.0.0.1:{port}" if api_url is None else api_url
|
|
||||||
studio_parts = _validated_http_url(studio_url, "--studio-url")
|
|
||||||
_validated_http_url(api_url, "--api-url")
|
|
||||||
if studio_parts.query or studio_parts.fragment:
|
|
||||||
raise click.UsageError(
|
|
||||||
"--studio-url must not include a query string or fragment."
|
|
||||||
)
|
|
||||||
|
|
||||||
studio_path = f"{studio_parts.path.rstrip('/')}/studio/"
|
|
||||||
return urlunsplit(
|
|
||||||
studio_parts._replace(
|
|
||||||
path=studio_path,
|
|
||||||
query=urlencode({"baseUrl": api_url}),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@OPT_RECREATE
|
@OPT_RECREATE
|
||||||
@OPT_PULL
|
@OPT_PULL
|
||||||
@OPT_PORT
|
@OPT_PORT
|
||||||
@OPT_DOCKER_COMPOSE
|
@OPT_DOCKER_COMPOSE
|
||||||
@OPT_CONFIG
|
@OPT_CONFIG
|
||||||
@OPT_VERBOSE
|
@OPT_VERBOSE
|
||||||
|
@OPT_DEBUGGER_PORT
|
||||||
|
@OPT_DEBUGGER_BASE_URL
|
||||||
@OPT_WATCH
|
@OPT_WATCH
|
||||||
@OPT_POSTGRES_URI
|
@OPT_POSTGRES_URI
|
||||||
@OPT_API_VERSION
|
@OPT_API_VERSION
|
||||||
@OPT_ENGINE_RUNTIME_MODE
|
@OPT_ENGINE_RUNTIME_MODE
|
||||||
@click.option(
|
|
||||||
"--studio-url",
|
|
||||||
type=str,
|
|
||||||
default=None,
|
|
||||||
help="URL of the LangGraph Studio instance. Defaults to https://smith.langchain.com",
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
"--api-url",
|
|
||||||
type=str,
|
|
||||||
default=None,
|
|
||||||
help="URL that LangGraph Studio uses to access the API. Defaults to http://127.0.0.1:[PORT]",
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
"--debugger-base-url",
|
|
||||||
type=str,
|
|
||||||
default=None,
|
|
||||||
hidden=True,
|
|
||||||
)
|
|
||||||
@click.option(
|
@click.option(
|
||||||
"--image",
|
"--image",
|
||||||
type=str,
|
type=str,
|
||||||
@@ -350,21 +284,14 @@ def up(
|
|||||||
watch: bool,
|
watch: bool,
|
||||||
wait: bool,
|
wait: bool,
|
||||||
verbose: bool,
|
verbose: bool,
|
||||||
|
debugger_port: int | None,
|
||||||
|
debugger_base_url: str | None,
|
||||||
postgres_uri: str | None,
|
postgres_uri: str | None,
|
||||||
api_version: str | None,
|
api_version: str | None,
|
||||||
engine_runtime_mode: str,
|
engine_runtime_mode: str,
|
||||||
studio_url: str | None,
|
|
||||||
api_url: str | None,
|
|
||||||
debugger_base_url: str | None,
|
|
||||||
image: str | None,
|
image: str | None,
|
||||||
base_image: str | None,
|
base_image: str | None,
|
||||||
):
|
):
|
||||||
studio_link = _studio_link(
|
|
||||||
port=port,
|
|
||||||
studio_url=studio_url,
|
|
||||||
api_url=api_url,
|
|
||||||
debugger_base_url=debugger_base_url,
|
|
||||||
)
|
|
||||||
click.secho("Starting LangGraph API server...", fg="green")
|
click.secho("Starting LangGraph API server...", fg="green")
|
||||||
click.secho(
|
click.secho(
|
||||||
"""For local dev, requires env var LANGSMITH_API_KEY with access to LangSmith Deployment.
|
"""For local dev, requires env var LANGSMITH_API_KEY with access to LangSmith Deployment.
|
||||||
@@ -381,6 +308,8 @@ For production use, requires a license key in env var LANGGRAPH_CLOUD_LICENSE_KE
|
|||||||
pull=pull,
|
pull=pull,
|
||||||
watch=watch,
|
watch=watch,
|
||||||
verbose=verbose,
|
verbose=verbose,
|
||||||
|
debugger_port=debugger_port,
|
||||||
|
debugger_base_url=debugger_base_url,
|
||||||
postgres_uri=postgres_uri,
|
postgres_uri=postgres_uri,
|
||||||
api_version=api_version,
|
api_version=api_version,
|
||||||
engine_runtime_mode=engine_runtime_mode,
|
engine_runtime_mode=engine_runtime_mode,
|
||||||
@@ -408,12 +337,20 @@ For production use, requires a license key in env var LANGGRAPH_CLOUD_LICENSE_KE
|
|||||||
if "unpacking to docker.io" in line:
|
if "unpacking to docker.io" in line:
|
||||||
set("Starting...")
|
set("Starting...")
|
||||||
elif "Application startup complete" in line:
|
elif "Application startup complete" in line:
|
||||||
|
debugger_origin = (
|
||||||
|
f"http://localhost:{debugger_port}"
|
||||||
|
if debugger_port
|
||||||
|
else "https://smith.langchain.com"
|
||||||
|
)
|
||||||
|
debugger_base_url_query = (
|
||||||
|
debugger_base_url or f"http://127.0.0.1:{port}"
|
||||||
|
)
|
||||||
set("")
|
set("")
|
||||||
sys.stdout.write(
|
sys.stdout.write(
|
||||||
f"""Ready!
|
f"""Ready!
|
||||||
- API: http://localhost:{port}
|
- API: http://localhost:{port}
|
||||||
- Docs: http://localhost:{port}/docs
|
- Docs: http://localhost:{port}/docs
|
||||||
- LangGraph Studio: {studio_link}
|
- LangGraph Studio: {debugger_origin}/studio/?baseUrl={debugger_base_url_query}
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
@@ -998,6 +935,8 @@ def prepare_args_and_stdin(
|
|||||||
docker_compose: pathlib.Path | None,
|
docker_compose: pathlib.Path | None,
|
||||||
port: int,
|
port: int,
|
||||||
watch: bool,
|
watch: bool,
|
||||||
|
debugger_port: int | None = None,
|
||||||
|
debugger_base_url: str | None = None,
|
||||||
postgres_uri: str | None = None,
|
postgres_uri: str | None = None,
|
||||||
api_version: str | None = None,
|
api_version: str | None = None,
|
||||||
engine_runtime_mode: str = "combined_queue_worker",
|
engine_runtime_mode: str = "combined_queue_worker",
|
||||||
@@ -1011,6 +950,8 @@ def prepare_args_and_stdin(
|
|||||||
stdin = langgraph_cli.docker.compose(
|
stdin = langgraph_cli.docker.compose(
|
||||||
capabilities,
|
capabilities,
|
||||||
port=port,
|
port=port,
|
||||||
|
debugger_port=debugger_port,
|
||||||
|
debugger_base_url=debugger_base_url,
|
||||||
postgres_uri=postgres_uri,
|
postgres_uri=postgres_uri,
|
||||||
image=image,
|
image=image,
|
||||||
base_image=base_image,
|
base_image=base_image,
|
||||||
@@ -1048,6 +989,8 @@ def prepare(
|
|||||||
pull: bool,
|
pull: bool,
|
||||||
watch: bool,
|
watch: bool,
|
||||||
verbose: bool,
|
verbose: bool,
|
||||||
|
debugger_port: int | None = None,
|
||||||
|
debugger_base_url: str | None = None,
|
||||||
postgres_uri: str | None = None,
|
postgres_uri: str | None = None,
|
||||||
api_version: str | None = None,
|
api_version: str | None = None,
|
||||||
engine_runtime_mode: str = "combined_queue_worker",
|
engine_runtime_mode: str = "combined_queue_worker",
|
||||||
@@ -1089,6 +1032,8 @@ def prepare(
|
|||||||
docker_compose=docker_compose,
|
docker_compose=docker_compose,
|
||||||
port=port,
|
port=port,
|
||||||
watch=watch,
|
watch=watch,
|
||||||
|
debugger_port=debugger_port,
|
||||||
|
debugger_base_url=debugger_base_url or f"http://127.0.0.1:{port}",
|
||||||
postgres_uri=postgres_uri,
|
postgres_uri=postgres_uri,
|
||||||
api_version=api_version,
|
api_version=api_version,
|
||||||
engine_runtime_mode=engine_runtime_mode,
|
engine_runtime_mode=engine_runtime_mode,
|
||||||
|
|||||||
@@ -805,6 +805,46 @@ def _find_deployment(
|
|||||||
selector: ByName | ByAgent,
|
selector: ByName | ByAgent,
|
||||||
*,
|
*,
|
||||||
not_found_message: str,
|
not_found_message: str,
|
||||||
|
<<<<<<< HEAD
|
||||||
|
agent: dict[str, str] | None = None,
|
||||||
|
) -> tuple[str | None, bool, int]:
|
||||||
|
"""Resolve an existing deployment by ID or exact name match."""
|
||||||
|
needs_creation = False
|
||||||
|
if deployment_id:
|
||||||
|
_log_deploy_step(step, f"Using deployment {deployment_id}")
|
||||||
|
_call_host_backend_with_optional_tenant(
|
||||||
|
client, lambda c: c.get_deployment(deployment_id)
|
||||||
|
)
|
||||||
|
return deployment_id, needs_creation, step + 1
|
||||||
|
|
||||||
|
if agent is not None:
|
||||||
|
_log_deploy_step(
|
||||||
|
step, f"Looking up agent '{agent['agent_id']}' in {agent['environment']}"
|
||||||
|
)
|
||||||
|
existing = _call_host_backend_with_optional_tenant(
|
||||||
|
client,
|
||||||
|
lambda c: c.list_deployments(
|
||||||
|
agent_id=agent["agent_id"], agent_environment=agent["environment"]
|
||||||
|
),
|
||||||
|
)
|
||||||
|
found_id = next(
|
||||||
|
(
|
||||||
|
dep["id"]
|
||||||
|
for dep in existing.get("resources", [])
|
||||||
|
if not dep.get("is_preview")
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
_log_deploy_step(step, f"Looking up deployment '{name}'")
|
||||||
|
found_id = _call_host_backend_with_optional_tenant(
|
||||||
|
client, lambda c: find_deployment_id_by_name(c, name)
|
||||||
|
)
|
||||||
|
em = _get_emitter()
|
||||||
|
if found_id:
|
||||||
|
deployment_id = str(found_id)
|
||||||
|
em.info(f"Found existing deployment (ID: {deployment_id})")
|
||||||
|
=======
|
||||||
) -> tuple[ExistingDeployment | None, int]:
|
) -> tuple[ExistingDeployment | None, int]:
|
||||||
if isinstance(selector, ByAgent):
|
if isinstance(selector, ByAgent):
|
||||||
_log_deploy_step(
|
_log_deploy_step(
|
||||||
@@ -832,6 +872,7 @@ def _find_deployment(
|
|||||||
),
|
),
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
|
>>>>>>> origin
|
||||||
else:
|
else:
|
||||||
_log_deploy_step(step, f"Looking up deployment '{selector.name}'")
|
_log_deploy_step(step, f"Looking up deployment '{selector.name}'")
|
||||||
found = _call_host_backend_with_optional_tenant(
|
found = _call_host_backend_with_optional_tenant(
|
||||||
@@ -856,12 +897,22 @@ def _create_deployment(
|
|||||||
step: int,
|
step: int,
|
||||||
*,
|
*,
|
||||||
name: str | None,
|
name: str | None,
|
||||||
|
<<<<<<< HEAD
|
||||||
|
deployment_type: str,
|
||||||
|
source: str,
|
||||||
|
config_rel: str | None = None,
|
||||||
|
secrets: list[dict[str, str]] | None = None,
|
||||||
|
agent: dict[str, str] | None = None,
|
||||||
|
) -> tuple[str, int]:
|
||||||
|
"""Create a deployment and return its ID and next step number."""
|
||||||
|
=======
|
||||||
source: str,
|
source: str,
|
||||||
source_config: dict[str, object],
|
source_config: dict[str, object],
|
||||||
source_revision_config: dict[str, object],
|
source_revision_config: dict[str, object],
|
||||||
secrets: list[dict[str, str]],
|
secrets: list[dict[str, str]],
|
||||||
agent: dict[str, str] | None = None,
|
agent: dict[str, str] | None = None,
|
||||||
) -> tuple[CreatedDeployment, int]:
|
) -> tuple[CreatedDeployment, int]:
|
||||||
|
>>>>>>> origin
|
||||||
_log_deploy_step(
|
_log_deploy_step(
|
||||||
step,
|
step,
|
||||||
f"Creating deployment for agent '{agent['agent_id']}' in {agent['environment']}"
|
f"Creating deployment for agent '{agent['agent_id']}' in {agent['environment']}"
|
||||||
@@ -871,9 +922,15 @@ def _create_deployment(
|
|||||||
try:
|
try:
|
||||||
created = client.create_deployment(
|
created = client.create_deployment(
|
||||||
name=name,
|
name=name,
|
||||||
|
<<<<<<< HEAD
|
||||||
|
deployment_type=deployment_type,
|
||||||
|
source=source,
|
||||||
|
config_path=config_rel,
|
||||||
|
=======
|
||||||
source=source,
|
source=source,
|
||||||
source_config=source_config,
|
source_config=source_config,
|
||||||
source_revision_config=source_revision_config,
|
source_revision_config=source_revision_config,
|
||||||
|
>>>>>>> origin
|
||||||
secrets=secrets,
|
secrets=secrets,
|
||||||
agent=agent,
|
agent=agent,
|
||||||
)
|
)
|
||||||
@@ -890,9 +947,36 @@ def _create_deployment(
|
|||||||
"POST /v2/deployments succeeded but response missing a valid 'id'"
|
"POST /v2/deployments succeeded but response missing a valid 'id'"
|
||||||
)
|
)
|
||||||
if agent is not None:
|
if agent is not None:
|
||||||
|
<<<<<<< HEAD
|
||||||
|
_get_emitter().info(f"Deployment name: {created['name']}")
|
||||||
|
_get_emitter().info(f"Deployment ID: {created_id}", deployment_id=created_id)
|
||||||
|
return created_id, step + 1
|
||||||
|
|
||||||
|
|
||||||
|
def _smith_dashboard_base_url(host_url: str | None) -> str:
|
||||||
|
"""Derive the LangSmith dashboard base URL from the API host URL."""
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
|
if not host_url:
|
||||||
|
return "https://smith.langchain.com"
|
||||||
|
parsed = urlparse(host_url)
|
||||||
|
hostname = parsed.hostname or ""
|
||||||
|
if hostname in ("localhost", "127.0.0.1"):
|
||||||
|
return host_url.rstrip("/")
|
||||||
|
|
||||||
|
for api_host_suffix in ("api.host.langchain.com", "api.smith.langchain.com"):
|
||||||
|
if hostname == api_host_suffix:
|
||||||
|
return "https://smith.langchain.com"
|
||||||
|
if hostname.endswith(f".{api_host_suffix}"):
|
||||||
|
prefix = hostname[: -(len(api_host_suffix) + 1)]
|
||||||
|
return f"https://{prefix}.smith.langchain.com"
|
||||||
|
|
||||||
|
return "https://smith.langchain.com"
|
||||||
|
=======
|
||||||
_get_emitter().info(f"Deployment name: {created.get('name')}")
|
_get_emitter().info(f"Deployment name: {created.get('name')}")
|
||||||
_get_emitter().info(f"Deployment ID: {created_id}", deployment_id=created_id)
|
_get_emitter().info(f"Deployment ID: {created_id}", deployment_id=created_id)
|
||||||
return CreatedDeployment(created_id, created), step + 1
|
return CreatedDeployment(created_id, created), step + 1
|
||||||
|
>>>>>>> origin
|
||||||
|
|
||||||
|
|
||||||
def _get_deployment_status_url(
|
def _get_deployment_status_url(
|
||||||
@@ -2226,6 +2310,15 @@ def _deploy_cmd(
|
|||||||
validate_deploy_commands(install_command, build_command)
|
validate_deploy_commands(install_command, build_command)
|
||||||
agent = None
|
agent = None
|
||||||
if agent_id is not None or environment is not None:
|
if agent_id is not None or environment is not None:
|
||||||
|
<<<<<<< HEAD
|
||||||
|
if not agent_id or not agent_id.strip() or not environment:
|
||||||
|
raise click.UsageError(
|
||||||
|
"--agent-id and --environment are required together."
|
||||||
|
)
|
||||||
|
if name is not None or deployment_id is not None:
|
||||||
|
raise click.UsageError(
|
||||||
|
"--agent-id and --environment cannot be combined with --name or --deployment-id."
|
||||||
|
=======
|
||||||
em.note("Note: --agent-id and --agent-environment flags are in private beta")
|
em.note("Note: --agent-id and --agent-environment flags are in private beta")
|
||||||
if not agent_id or not agent_id.strip() or not environment:
|
if not agent_id or not agent_id.strip() or not environment:
|
||||||
raise click.UsageError(
|
raise click.UsageError(
|
||||||
@@ -2234,6 +2327,7 @@ def _deploy_cmd(
|
|||||||
if name is not None or deployment_id is not None:
|
if name is not None or deployment_id is not None:
|
||||||
raise click.UsageError(
|
raise click.UsageError(
|
||||||
"--agent-id and --agent-environment cannot be combined with --name or --deployment-id."
|
"--agent-id and --agent-environment cannot be combined with --name or --deployment-id."
|
||||||
|
>>>>>>> origin
|
||||||
)
|
)
|
||||||
agent = {"agent_id": agent_id, "environment": environment}
|
agent = {"agent_id": agent_id, "environment": environment}
|
||||||
if not config.exists():
|
if not config.exists():
|
||||||
@@ -2281,6 +2375,41 @@ def _deploy_cmd(
|
|||||||
)
|
)
|
||||||
|
|
||||||
client = _create_host_backend_client(host_url, api_key, env_vars=env_vars)
|
client = _create_host_backend_client(host_url, api_key, env_vars=env_vars)
|
||||||
|
<<<<<<< HEAD
|
||||||
|
step = 1
|
||||||
|
|
||||||
|
deployment_id, needs_creation, step = _resolve_deployment(
|
||||||
|
client,
|
||||||
|
step,
|
||||||
|
deployment_id,
|
||||||
|
name,
|
||||||
|
not_found_message=(
|
||||||
|
"No deployment found. Will create."
|
||||||
|
if use_remote_build
|
||||||
|
else "No deployment found. Will create after build."
|
||||||
|
),
|
||||||
|
agent=agent,
|
||||||
|
)
|
||||||
|
|
||||||
|
if needs_creation:
|
||||||
|
deployment_id, step = _create_deployment(
|
||||||
|
client,
|
||||||
|
step,
|
||||||
|
name=name,
|
||||||
|
deployment_type=deployment_type,
|
||||||
|
source="internal_source" if use_remote_build else "internal_docker",
|
||||||
|
secrets=secrets,
|
||||||
|
agent=agent,
|
||||||
|
)
|
||||||
|
|
||||||
|
if not deployment_id:
|
||||||
|
raise click.ClickException("Failed to determine deployment ID")
|
||||||
|
|
||||||
|
# Scan local sources for tracked packages so the new revision carries
|
||||||
|
# the same metadata GitHub-backed deploys produce. Failures must never
|
||||||
|
# block a deploy.
|
||||||
|
=======
|
||||||
|
>>>>>>> origin
|
||||||
try:
|
try:
|
||||||
tracked_packages = find_tracked_packages(config, config_json) or None
|
tracked_packages = find_tracked_packages(config, config_json) or None
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
@@ -2385,11 +2514,14 @@ def deploy_list(
|
|||||||
agent_id: str | None,
|
agent_id: str | None,
|
||||||
environment: str | None,
|
environment: str | None,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
<<<<<<< HEAD
|
||||||
|
=======
|
||||||
if agent_id is not None or environment is not None:
|
if agent_id is not None or environment is not None:
|
||||||
click.secho(
|
click.secho(
|
||||||
"Note: --agent-id and --agent-environment flags are in private beta",
|
"Note: --agent-id and --agent-environment flags are in private beta",
|
||||||
fg="yellow",
|
fg="yellow",
|
||||||
)
|
)
|
||||||
|
>>>>>>> origin
|
||||||
if agent_id is not None and not agent_id.strip():
|
if agent_id is not None and not agent_id.strip():
|
||||||
raise click.UsageError("--agent-id must not be empty.")
|
raise click.UsageError("--agent-id must not be empty.")
|
||||||
filters = {}
|
filters = {}
|
||||||
@@ -2401,6 +2533,15 @@ def deploy_list(
|
|||||||
deployments = _call_host_backend_with_optional_tenant(
|
deployments = _call_host_backend_with_optional_tenant(
|
||||||
client,
|
client,
|
||||||
lambda c: c.list_deployments(name_contains=name_contains, **filters),
|
lambda c: c.list_deployments(name_contains=name_contains, **filters),
|
||||||
|
<<<<<<< HEAD
|
||||||
|
)
|
||||||
|
resources = response.get("resources") if isinstance(response, dict) else None
|
||||||
|
deployments = (
|
||||||
|
[item for item in resources if isinstance(item, dict)]
|
||||||
|
if isinstance(resources, list)
|
||||||
|
else []
|
||||||
|
=======
|
||||||
|
>>>>>>> origin
|
||||||
)
|
)
|
||||||
if not deployments:
|
if not deployments:
|
||||||
click.echo("No deployments found.")
|
click.echo("No deployments found.")
|
||||||
|
|||||||
@@ -142,6 +142,29 @@ def check_capabilities(runner) -> DockerCapabilities:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def debugger_compose(*, port: int | None = None, base_url: str | None = None) -> dict:
|
||||||
|
if port is None:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
config = {
|
||||||
|
"langgraph-debugger": {
|
||||||
|
"image": "langchain/langgraph-debugger",
|
||||||
|
"restart": "on-failure",
|
||||||
|
"depends_on": {
|
||||||
|
"langgraph-postgres": {"condition": "service_healthy"},
|
||||||
|
},
|
||||||
|
"ports": [f'"{port}:3968"'],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if base_url:
|
||||||
|
config["langgraph-debugger"]["environment"] = {
|
||||||
|
"VITE_STUDIO_LOCAL_GRAPH_URL": base_url
|
||||||
|
}
|
||||||
|
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
# Function to convert dictionary to YAML
|
# Function to convert dictionary to YAML
|
||||||
def dict_to_yaml(d: dict, *, indent: int = 0) -> str:
|
def dict_to_yaml(d: dict, *, indent: int = 0) -> str:
|
||||||
"""Convert a dictionary to a YAML string."""
|
"""Convert a dictionary to a YAML string."""
|
||||||
@@ -168,6 +191,8 @@ def compose_as_dict(
|
|||||||
capabilities: DockerCapabilities,
|
capabilities: DockerCapabilities,
|
||||||
*,
|
*,
|
||||||
port: int,
|
port: int,
|
||||||
|
debugger_port: int | None = None,
|
||||||
|
debugger_base_url: str | None = None,
|
||||||
# postgres://user:password@host:port/database?option=value
|
# postgres://user:password@host:port/database?option=value
|
||||||
postgres_uri: str | None = None,
|
postgres_uri: str | None = None,
|
||||||
# If you are running against an already-built image, you can pass it here
|
# If you are running against an already-built image, you can pass it here
|
||||||
@@ -228,6 +253,12 @@ def compose_as_dict(
|
|||||||
else:
|
else:
|
||||||
services["langgraph-postgres"]["healthcheck"]["interval"] = "5s"
|
services["langgraph-postgres"]["healthcheck"]["interval"] = "5s"
|
||||||
|
|
||||||
|
# Add optional debugger service if debugger_port is specified
|
||||||
|
if debugger_port:
|
||||||
|
services["langgraph-debugger"] = debugger_compose(
|
||||||
|
port=debugger_port, base_url=debugger_base_url
|
||||||
|
)["langgraph-debugger"]
|
||||||
|
|
||||||
# Add langgraph-api service
|
# Add langgraph-api service
|
||||||
api_environment = {
|
api_environment = {
|
||||||
"REDIS_URI": "redis://langgraph-redis:6379",
|
"REDIS_URI": "redis://langgraph-redis:6379",
|
||||||
@@ -258,7 +289,7 @@ def compose_as_dict(
|
|||||||
"test": "python /api/healthcheck.py",
|
"test": "python /api/healthcheck.py",
|
||||||
"interval": "60s",
|
"interval": "60s",
|
||||||
"start_interval": "1s",
|
"start_interval": "1s",
|
||||||
"start_period": "60s",
|
"start_period": "10s",
|
||||||
}
|
}
|
||||||
|
|
||||||
# Final compose dictionary with volumes included if needed
|
# Final compose dictionary with volumes included if needed
|
||||||
@@ -274,6 +305,8 @@ def compose(
|
|||||||
capabilities: DockerCapabilities,
|
capabilities: DockerCapabilities,
|
||||||
*,
|
*,
|
||||||
port: int,
|
port: int,
|
||||||
|
debugger_port: int | None = None,
|
||||||
|
debugger_base_url: str | None = None,
|
||||||
# postgres://user:password@host:port/database?option=value
|
# postgres://user:password@host:port/database?option=value
|
||||||
postgres_uri: str | None = None,
|
postgres_uri: str | None = None,
|
||||||
image: str | None = None,
|
image: str | None = None,
|
||||||
@@ -285,6 +318,8 @@ def compose(
|
|||||||
compose_content = compose_as_dict(
|
compose_content = compose_as_dict(
|
||||||
capabilities,
|
capabilities,
|
||||||
port=port,
|
port=port,
|
||||||
|
debugger_port=debugger_port,
|
||||||
|
debugger_base_url=debugger_base_url,
|
||||||
postgres_uri=postgres_uri,
|
postgres_uri=postgres_uri,
|
||||||
image=image,
|
image=image,
|
||||||
base_image=base_image,
|
base_image=base_image,
|
||||||
|
|||||||
@@ -8,11 +8,10 @@ from contextlib import contextmanager
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import click
|
import click
|
||||||
import pytest
|
|
||||||
from click.testing import CliRunner
|
from click.testing import CliRunner
|
||||||
|
|
||||||
import langgraph_cli.deploy as deploy_module
|
import langgraph_cli.deploy as deploy_module
|
||||||
from langgraph_cli.cli import _studio_link, cli, prepare_args_and_stdin
|
from langgraph_cli.cli import cli, prepare_args_and_stdin
|
||||||
from langgraph_cli.config import Config, _get_pip_cleanup_lines, validate_config
|
from langgraph_cli.config import Config, _get_pip_cleanup_lines, validate_config
|
||||||
from langgraph_cli.docker import DEFAULT_POSTGRES_URI, DockerCapabilities, Version
|
from langgraph_cli.docker import DEFAULT_POSTGRES_URI, DockerCapabilities, Version
|
||||||
from langgraph_cli.util import clean_empty_lines
|
from langgraph_cli.util import clean_empty_lines
|
||||||
@@ -57,6 +56,8 @@ def test_prepare_args_and_stdin() -> None:
|
|||||||
Config(dependencies=[".", "../../.."], graphs={"agent": "agent.py:graph"})
|
Config(dependencies=[".", "../../.."], graphs={"agent": "agent.py:graph"})
|
||||||
)
|
)
|
||||||
port = 8000
|
port = 8000
|
||||||
|
debugger_port = 8001
|
||||||
|
debugger_graph_url = f"http://127.0.0.1:{port}"
|
||||||
|
|
||||||
actual_args, actual_stdin = prepare_args_and_stdin(
|
actual_args, actual_stdin = prepare_args_and_stdin(
|
||||||
capabilities=DEFAULT_DOCKER_CAPABILITIES,
|
capabilities=DEFAULT_DOCKER_CAPABILITIES,
|
||||||
@@ -64,6 +65,8 @@ def test_prepare_args_and_stdin() -> None:
|
|||||||
config=config,
|
config=config,
|
||||||
docker_compose=pathlib.Path("custom-docker-compose.yml"),
|
docker_compose=pathlib.Path("custom-docker-compose.yml"),
|
||||||
port=port,
|
port=port,
|
||||||
|
debugger_port=debugger_port,
|
||||||
|
debugger_base_url=debugger_graph_url,
|
||||||
watch=True,
|
watch=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -107,6 +110,16 @@ services:
|
|||||||
retries: 5
|
retries: 5
|
||||||
interval: 60s
|
interval: 60s
|
||||||
start_interval: 1s
|
start_interval: 1s
|
||||||
|
langgraph-debugger:
|
||||||
|
image: langchain/langgraph-debugger
|
||||||
|
restart: on-failure
|
||||||
|
depends_on:
|
||||||
|
langgraph-postgres:
|
||||||
|
condition: service_healthy
|
||||||
|
ports:
|
||||||
|
- "{debugger_port}:3968"
|
||||||
|
environment:
|
||||||
|
VITE_STUDIO_LOCAL_GRAPH_URL: {debugger_graph_url}
|
||||||
langgraph-api:
|
langgraph-api:
|
||||||
ports:
|
ports:
|
||||||
- "8000:8000"
|
- "8000:8000"
|
||||||
@@ -122,7 +135,7 @@ services:
|
|||||||
test: python /api/healthcheck.py
|
test: python /api/healthcheck.py
|
||||||
interval: 60s
|
interval: 60s
|
||||||
start_interval: 1s
|
start_interval: 1s
|
||||||
start_period: 60s
|
start_period: 10s
|
||||||
|
|
||||||
pull_policy: build
|
pull_policy: build
|
||||||
build:
|
build:
|
||||||
@@ -165,6 +178,8 @@ def test_prepare_args_and_stdin_with_image() -> None:
|
|||||||
Config(dependencies=[".", "../../.."], graphs={"agent": "agent.py:graph"})
|
Config(dependencies=[".", "../../.."], graphs={"agent": "agent.py:graph"})
|
||||||
)
|
)
|
||||||
port = 8000
|
port = 8000
|
||||||
|
debugger_port = 8001
|
||||||
|
debugger_graph_url = f"http://127.0.0.1:{port}"
|
||||||
|
|
||||||
actual_args, actual_stdin = prepare_args_and_stdin(
|
actual_args, actual_stdin = prepare_args_and_stdin(
|
||||||
capabilities=DEFAULT_DOCKER_CAPABILITIES,
|
capabilities=DEFAULT_DOCKER_CAPABILITIES,
|
||||||
@@ -172,6 +187,8 @@ def test_prepare_args_and_stdin_with_image() -> None:
|
|||||||
config=config,
|
config=config,
|
||||||
docker_compose=pathlib.Path("custom-docker-compose.yml"),
|
docker_compose=pathlib.Path("custom-docker-compose.yml"),
|
||||||
port=port,
|
port=port,
|
||||||
|
debugger_port=debugger_port,
|
||||||
|
debugger_base_url=debugger_graph_url,
|
||||||
watch=True,
|
watch=True,
|
||||||
image="my-cool-image",
|
image="my-cool-image",
|
||||||
)
|
)
|
||||||
@@ -216,6 +233,16 @@ services:
|
|||||||
retries: 5
|
retries: 5
|
||||||
interval: 60s
|
interval: 60s
|
||||||
start_interval: 1s
|
start_interval: 1s
|
||||||
|
langgraph-debugger:
|
||||||
|
image: langchain/langgraph-debugger
|
||||||
|
restart: on-failure
|
||||||
|
depends_on:
|
||||||
|
langgraph-postgres:
|
||||||
|
condition: service_healthy
|
||||||
|
ports:
|
||||||
|
- "{debugger_port}:3968"
|
||||||
|
environment:
|
||||||
|
VITE_STUDIO_LOCAL_GRAPH_URL: {debugger_graph_url}
|
||||||
langgraph-api:
|
langgraph-api:
|
||||||
ports:
|
ports:
|
||||||
- "8000:8000"
|
- "8000:8000"
|
||||||
@@ -232,7 +259,7 @@ services:
|
|||||||
test: python /api/healthcheck.py
|
test: python /api/healthcheck.py
|
||||||
interval: 60s
|
interval: 60s
|
||||||
start_interval: 1s
|
start_interval: 1s
|
||||||
start_period: 60s
|
start_period: 10s
|
||||||
|
|
||||||
|
|
||||||
develop:
|
develop:
|
||||||
@@ -262,82 +289,6 @@ def test_version_option() -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_up_help_shows_hosted_studio_options() -> None:
|
|
||||||
result = CliRunner().invoke(cli, ["up", "--help"])
|
|
||||||
|
|
||||||
assert result.exit_code == 0, result.output
|
|
||||||
assert "--studio-url" in result.output
|
|
||||||
assert "--api-url" in result.output
|
|
||||||
assert "--debugger-port" not in result.output
|
|
||||||
assert "--debugger-base-url" not in result.output
|
|
||||||
|
|
||||||
|
|
||||||
def test_studio_link_defaults_to_hosted_studio() -> None:
|
|
||||||
assert _studio_link(
|
|
||||||
port=8123,
|
|
||||||
studio_url=None,
|
|
||||||
api_url=None,
|
|
||||||
debugger_base_url=None,
|
|
||||||
) == ("https://smith.langchain.com/studio/?baseUrl=http%3A%2F%2F127.0.0.1%3A8123")
|
|
||||||
|
|
||||||
|
|
||||||
def test_studio_link_supports_self_hosted_and_remote_urls() -> None:
|
|
||||||
assert _studio_link(
|
|
||||||
port=8123,
|
|
||||||
studio_url="https://langsmith.example.com/prefix/",
|
|
||||||
api_url="https://api.example.com/graph?tenant=a®ion=eu",
|
|
||||||
debugger_base_url=None,
|
|
||||||
) == (
|
|
||||||
"https://langsmith.example.com/prefix/studio/"
|
|
||||||
"?baseUrl=https%3A%2F%2Fapi.example.com%2Fgraph%3Ftenant%3Da%26region%3Deu"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_studio_link_supports_deprecated_debugger_base_url(capsys) -> None:
|
|
||||||
assert _studio_link(
|
|
||||||
port=8123,
|
|
||||||
studio_url=None,
|
|
||||||
api_url=None,
|
|
||||||
debugger_base_url="https://api.example.com",
|
|
||||||
).endswith("?baseUrl=https%3A%2F%2Fapi.example.com")
|
|
||||||
assert "--debugger-base-url is deprecated; use --api-url" in capsys.readouterr().err
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
|
||||||
("studio_url", "api_url"),
|
|
||||||
[
|
|
||||||
("javascript:alert(1)", None),
|
|
||||||
("https://user:password@example.com", None),
|
|
||||||
("https://smith.langchain.com?workspace=test", None),
|
|
||||||
(None, "file:///tmp/langgraph.sock"),
|
|
||||||
(None, "https://user:password@example.com"),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
def test_studio_link_rejects_unsafe_urls(
|
|
||||||
studio_url: str | None, api_url: str | None
|
|
||||||
) -> None:
|
|
||||||
with pytest.raises(click.UsageError):
|
|
||||||
_studio_link(
|
|
||||||
port=8123,
|
|
||||||
studio_url=studio_url,
|
|
||||||
api_url=api_url,
|
|
||||||
debugger_base_url=None,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_studio_link_rejects_conflicting_api_url_aliases() -> None:
|
|
||||||
with pytest.raises(
|
|
||||||
click.UsageError,
|
|
||||||
match="cannot specify different URLs",
|
|
||||||
):
|
|
||||||
_studio_link(
|
|
||||||
port=8123,
|
|
||||||
studio_url=None,
|
|
||||||
api_url="https://api.example.com",
|
|
||||||
debugger_base_url="https://other.example.com",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_top_level_help_shows_deploy_subcommands() -> None:
|
def test_top_level_help_shows_deploy_subcommands() -> None:
|
||||||
runner = CliRunner()
|
runner = CliRunner()
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ DEFAULT_DOCKER_CAPABILITIES = DockerCapabilities(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_compose_with_custom_db():
|
def test_compose_with_no_debugger_and_custom_db():
|
||||||
port = 8123
|
port = 8123
|
||||||
custom_postgres_uri = "custom_postgres_uri"
|
custom_postgres_uri = "custom_postgres_uri"
|
||||||
actual_compose_str = compose(
|
actual_compose_str = compose(
|
||||||
@@ -42,7 +42,7 @@ def test_compose_with_custom_db():
|
|||||||
assert clean_empty_lines(actual_compose_str) == expected_compose_str
|
assert clean_empty_lines(actual_compose_str) == expected_compose_str
|
||||||
|
|
||||||
|
|
||||||
def test_compose_with_custom_db_and_healthcheck():
|
def test_compose_with_no_debugger_and_custom_db_with_healthcheck():
|
||||||
port = 8123
|
port = 8123
|
||||||
custom_postgres_uri = "custom_postgres_uri"
|
custom_postgres_uri = "custom_postgres_uri"
|
||||||
actual_compose_str = compose(
|
actual_compose_str = compose(
|
||||||
@@ -71,11 +71,39 @@ def test_compose_with_custom_db_and_healthcheck():
|
|||||||
test: python /api/healthcheck.py
|
test: python /api/healthcheck.py
|
||||||
interval: 60s
|
interval: 60s
|
||||||
start_interval: 1s
|
start_interval: 1s
|
||||||
start_period: 60s"""
|
start_period: 10s"""
|
||||||
assert clean_empty_lines(actual_compose_str) == expected_compose_str
|
assert clean_empty_lines(actual_compose_str) == expected_compose_str
|
||||||
|
|
||||||
|
|
||||||
def test_compose_with_default_db():
|
def test_compose_with_debugger_and_custom_db():
|
||||||
|
port = 8123
|
||||||
|
custom_postgres_uri = "custom_postgres_uri"
|
||||||
|
actual_compose_str = compose(
|
||||||
|
DEFAULT_DOCKER_CAPABILITIES,
|
||||||
|
port=port,
|
||||||
|
postgres_uri=custom_postgres_uri,
|
||||||
|
)
|
||||||
|
expected_compose_str = f"""services:
|
||||||
|
langgraph-redis:
|
||||||
|
image: redis:6
|
||||||
|
healthcheck:
|
||||||
|
test: redis-cli ping
|
||||||
|
interval: 5s
|
||||||
|
timeout: 1s
|
||||||
|
retries: 5
|
||||||
|
langgraph-api:
|
||||||
|
ports:
|
||||||
|
- "{port}:8000"
|
||||||
|
depends_on:
|
||||||
|
langgraph-redis:
|
||||||
|
condition: service_healthy
|
||||||
|
environment:
|
||||||
|
REDIS_URI: redis://langgraph-redis:6379
|
||||||
|
POSTGRES_URI: {custom_postgres_uri}"""
|
||||||
|
assert clean_empty_lines(actual_compose_str) == expected_compose_str
|
||||||
|
|
||||||
|
|
||||||
|
def test_compose_with_debugger_and_default_db():
|
||||||
port = 8123
|
port = 8123
|
||||||
actual_compose_str = compose(DEFAULT_DOCKER_CAPABILITIES, port=port)
|
actual_compose_str = compose(DEFAULT_DOCKER_CAPABILITIES, port=port)
|
||||||
expected_compose_str = f"""volumes:
|
expected_compose_str = f"""volumes:
|
||||||
@@ -274,6 +302,72 @@ def test_compose_with_api_version_and_custom_postgres():
|
|||||||
assert clean_empty_lines(actual_compose_str) == expected_compose_str
|
assert clean_empty_lines(actual_compose_str) == expected_compose_str
|
||||||
|
|
||||||
|
|
||||||
|
def test_compose_with_api_version_and_debugger():
|
||||||
|
"""Test compose function with api_version and debugger port."""
|
||||||
|
port = 8123
|
||||||
|
debugger_port = 8001
|
||||||
|
api_version = "0.2.74"
|
||||||
|
|
||||||
|
actual_compose_str = compose(
|
||||||
|
DEFAULT_DOCKER_CAPABILITIES,
|
||||||
|
port=port,
|
||||||
|
api_version=api_version,
|
||||||
|
debugger_port=debugger_port,
|
||||||
|
)
|
||||||
|
|
||||||
|
expected_compose_str = f"""volumes:
|
||||||
|
langgraph-data:
|
||||||
|
driver: local
|
||||||
|
services:
|
||||||
|
langgraph-redis:
|
||||||
|
image: redis:6
|
||||||
|
healthcheck:
|
||||||
|
test: redis-cli ping
|
||||||
|
interval: 5s
|
||||||
|
timeout: 1s
|
||||||
|
retries: 5
|
||||||
|
langgraph-postgres:
|
||||||
|
image: pgvector/pgvector:pg16
|
||||||
|
ports:
|
||||||
|
- "5433:5432"
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: postgres
|
||||||
|
POSTGRES_USER: postgres
|
||||||
|
POSTGRES_PASSWORD: postgres
|
||||||
|
command:
|
||||||
|
- postgres
|
||||||
|
- -c
|
||||||
|
- shared_preload_libraries=vector
|
||||||
|
volumes:
|
||||||
|
- langgraph-data:/var/lib/postgresql/data
|
||||||
|
healthcheck:
|
||||||
|
test: pg_isready -U postgres
|
||||||
|
start_period: 10s
|
||||||
|
timeout: 1s
|
||||||
|
retries: 5
|
||||||
|
interval: 5s
|
||||||
|
langgraph-debugger:
|
||||||
|
image: langchain/langgraph-debugger
|
||||||
|
restart: on-failure
|
||||||
|
depends_on:
|
||||||
|
langgraph-postgres:
|
||||||
|
condition: service_healthy
|
||||||
|
ports:
|
||||||
|
- "{debugger_port}:3968"
|
||||||
|
langgraph-api:
|
||||||
|
ports:
|
||||||
|
- "{port}:8000"
|
||||||
|
depends_on:
|
||||||
|
langgraph-redis:
|
||||||
|
condition: service_healthy
|
||||||
|
langgraph-postgres:
|
||||||
|
condition: service_healthy
|
||||||
|
environment:
|
||||||
|
REDIS_URI: redis://langgraph-redis:6379
|
||||||
|
POSTGRES_URI: {DEFAULT_POSTGRES_URI}"""
|
||||||
|
assert clean_empty_lines(actual_compose_str) == expected_compose_str
|
||||||
|
|
||||||
|
|
||||||
def test_compose_distributed_mode_with_custom_db():
|
def test_compose_distributed_mode_with_custom_db():
|
||||||
"""Test compose with engine_runtime_mode='distributed' adds N_JOBS_PER_WORKER=0."""
|
"""Test compose with engine_runtime_mode='distributed' adds N_JOBS_PER_WORKER=0."""
|
||||||
port = 8123
|
port = 8123
|
||||||
|
|||||||
Reference in New Issue
Block a user