Skip to content

Commit 0c77e3a

Browse files
committed
Merge branch 'main' of github.com:latchbio/latch into ayush/fix-lps
2 parents 99c4f75 + be01441 commit 0c77e3a

File tree

8 files changed

+107
-34
lines changed

8 files changed

+107
-34
lines changed

CHANGELOG.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,25 @@ Types of changes
1616

1717
# Latch SDK Changelog
1818

19+
## 2.58.1 - 2024-04-03
20+
21+
### Fixed
22+
23+
* `latch register --workflow-module` treating `.` separated python paths as file paths
24+
25+
## 2.58.0 - 2024-03-26
26+
27+
### Fixed
28+
29+
* `latch launch` error messages being suppressed
30+
* `g6e-12xlarge` GPU instance type incorrect GPU annotation (1 -> 4 L40s GPUs)
31+
32+
## 2.57.3 - 2024-03-19
33+
34+
### Fixed
35+
36+
* Use Nextflow base image when generating Dockerfile for a Nextflow workflow.
37+
1938
## 2.57.2 - 2024-03-07
2039

2140
### Changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ include = ["src/**/*.py", "src/latch_cli/services/init/*"]
1212

1313
[project]
1414
name = "latch"
15-
version = "2.57.2"
15+
version = "2.58.1"
1616
description = "The Latch SDK"
1717
authors = [{ name = "Kenny Workman", email = "[email protected]" }]
1818
maintainers = [

src/latch/resources/tasks.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -696,9 +696,9 @@ def _get_l40s_pod(instance_type: str, cpu: int, memory_gib: int, gpus: int) -> P
696696

697697
g6e_12xlarge_task = functools.partial(
698698
task,
699-
task_config=_get_l40s_pod("g6e-12xlarge", cpu=48, memory_gib=384, gpus=1)
699+
task_config=_get_l40s_pod("g6e-12xlarge", cpu=48, memory_gib=384, gpus=4)
700700
)
701-
"""48 vCPUs, 384 GiB RAM, 1 L40s GPU"""
701+
"""48 vCPUs, 384 GiB RAM, 4 L40s GPUs"""
702702

703703
g6e_16xlarge_task = functools.partial(
704704
task,

src/latch_cli/centromere/ctx.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,15 @@ def __init__(
9595
self.disable_auto_version = disable_auto_version
9696
self.wf_module = wf_module if wf_module is not None else "wf"
9797

98+
if self.wf_module.startswith("."):
99+
click.secho(
100+
dedent(f"""\
101+
Workflow module `{self.wf_module}` must be absolute (i.e. must not start with `.`)
102+
"""),
103+
fg="red",
104+
)
105+
raise click.exceptions.Exit(1)
106+
98107
try:
99108
self.token = retrieve_or_login()
100109
self.account_id = current_workspace()
@@ -173,17 +182,20 @@ def __init__(
173182
self.container_map: Dict[str, _Container] = {}
174183

175184
if self.workflow_type == WorkflowType.latchbiosdk:
185+
# fixme(ayush): this sucks
186+
module_path = pkg_root / Path(self.wf_module.replace(".", "/"))
187+
176188
try:
177-
flyte_objects = get_flyte_objects(self.pkg_root / self.wf_module)
189+
flyte_objects = get_flyte_objects(module_path)
178190
except ModuleNotFoundError as e:
179191
click.secho(
180192
dedent(
181193
f"""
182-
Unable to locate workflow module `{self.wf_module}`. Check that:
194+
Unable to locate workflow module `{self.wf_module}` in `{self.pkg_root.resolve()}`. Check that:
183195
184-
1. Package `{self.wf_module}` exists in {pkg_root.resolve()}.
185-
2. Package `{self.wf_module}` is an importable Python path (e.g. `workflows.my_workflow`).
186-
3. Any directories in `{self.wf_module}` contain an `__init__.py` file."""
196+
1. {module_path} exists.
197+
2. Package `{self.wf_module}` is an absolute importable Python path (e.g. `workflows.my_workflow`).
198+
3. All directories in `{module_path}` contain an `__init__.py` file."""
187199
),
188200
fg="red",
189201
)

src/latch_cli/docker_utils/__init__.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@
99
import yaml
1010

1111
from latch_cli.utils import WorkflowType
12-
from latch_cli.workflow_config import LatchWorkflowConfig, get_or_create_workflow_config
12+
from latch_cli.workflow_config import (
13+
BaseImageOptions,
14+
LatchWorkflowConfig,
15+
get_or_create_workflow_config,
16+
)
1317

1418

1519
class DockerCmdBlockOrder(str, Enum):
@@ -424,8 +428,12 @@ def generate_dockerignore(
424428
def get_default_dockerfile(pkg_root: Path, *, wf_type: WorkflowType):
425429
default_dockerfile = pkg_root / "Dockerfile"
426430

427-
# shitty: assumption that config is already there so base image will be ignored
428-
config = get_or_create_workflow_config(pkg_root=pkg_root)
431+
config = get_or_create_workflow_config(
432+
pkg_root=pkg_root,
433+
base_image_type=BaseImageOptions.nextflow
434+
if wf_type == WorkflowType.nextflow
435+
else BaseImageOptions.default,
436+
)
429437

430438
if not default_dockerfile.exists():
431439
default_dockerfile = pkg_root / ".latch" / "Dockerfile"

src/latch_cli/main.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import os
44
import sys
5+
import traceback
56
from pathlib import Path
67
from textwrap import dedent
78
from typing import Callable, List, Optional, Tuple, TypeVar, Union
@@ -647,12 +648,14 @@ def register(
647648
def launch(params_file: Path, version: Union[str, None] = None):
648649
"""Launch a workflow using a python parameter map."""
649650

650-
crash_handler.message = f"Unable to launch workflow"
651-
crash_handler.pkg_root = str(Path.cwd())
652-
653651
from latch_cli.services.launch import launch
654652

655-
wf_name = launch(params_file, version)
653+
try:
654+
wf_name = launch(params_file, version)
655+
except Exception as e:
656+
traceback.print_exc()
657+
raise click.exceptions.Exit(1) from e
658+
656659
if version is None:
657660
version = "latest"
658661

src/latch_cli/services/launch.py

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -192,10 +192,8 @@ def _get_workflow_interface(
192192
def _launch_workflow(token: str, wf_id: str, params: dict) -> bool:
193193
"""Launch the workflow of given id with parameter map.
194194
195-
Return True if success
195+
Return True if success, raises appropriate exceptions on failure.
196196
"""
197-
198-
# TODO (kenny) - pull out to consolidated requests class
199197
# Server sometimes stalls on requests with python user-agent
200198
headers = {
201199
"Authorization": f"Bearer {token}",
@@ -213,17 +211,57 @@ def _launch_workflow(token: str, wf_id: str, params: dict) -> bool:
213211
url = config.api.execution.create
214212

215213
response = requests.post(url, headers=headers, json=_interface_request)
214+
response_data = response.json()
215+
216+
def extract_error_message(data: dict) -> str:
217+
if "error" in data:
218+
error = data["error"]
219+
source = error.get("source", "unknown")
220+
221+
error_data = error.get("data", {})
222+
message = (
223+
error_data.get("stderr") or
224+
error_data.get("message") or
225+
str(error_data)
226+
)
227+
228+
if isinstance(message, str):
229+
error_lines = [line for line in message.split("\n") if "Error:" in line]
230+
if error_lines:
231+
message = error_lines[-1].replace("Error:", "").strip()
232+
233+
return f"({source}): {message}"
234+
return str(data)
235+
236+
if response.status_code != 200:
237+
print("\nRaw server response:")
238+
print(response_data)
216239

217240
if response.status_code == 403:
218241
raise PermissionError(
219242
"You need access to the latch sdk beta ~ join the waitlist @"
220243
" https://latch.bio/sdk"
221244
)
222-
elif response.status_code == 401:
245+
if response.status_code == 401:
223246
raise ValueError(
224247
"your token has expired - please run latch login to refresh your token and"
225248
" try again."
226249
)
227-
wf_interface_resp = response.json()
228-
229-
return wf_interface_resp.get("success") is True
250+
if response.status_code == 429:
251+
error_msg = extract_error_message(response_data)
252+
print(f"\nFormatted error message: {error_msg}")
253+
raise RuntimeError(f"Rate limit reached - {error_msg}")
254+
if response.status_code == 400:
255+
error_msg = extract_error_message(response_data)
256+
print(f"\nFormatted error message: {error_msg}")
257+
raise ValueError(f"Workflow launch failed - {error_msg}")
258+
if response.status_code != 200:
259+
error_msg = extract_error_message(response_data)
260+
print(f"\nFormatted error message: {error_msg}")
261+
raise RuntimeError(f"Server error (HTTP {response.status_code}) - {error_msg}")
262+
if "error" in response_data or response_data.get("status") != "Successfully launched workflow":
263+
error_msg = extract_error_message(response_data)
264+
print(f"\nFormatted error message: {error_msg}")
265+
raise RuntimeError(f"Workflow launch failed - {error_msg}")
266+
267+
return True

src/latch_cli/services/register/utils.py

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -110,9 +110,7 @@ def build_image(
110110
def upload_image(ctx: _CentromereCtx, image_name: str) -> List[str]:
111111
assert ctx.dkr_client is not None
112112
return ctx.dkr_client.push(
113-
repository=f"{ctx.dkr_repo}/{image_name}",
114-
stream=True,
115-
decode=True,
113+
repository=f"{ctx.dkr_repo}/{image_name}", stream=True, decode=True
116114
)
117115

118116

@@ -123,6 +121,8 @@ def serialize_pkg_in_container(
123121
wf_name_override: Optional[str] = None,
124122
) -> Tuple[List[str], str]:
125123
assert ctx.dkr_client is not None
124+
assert ctx.dkr_repo is not None
125+
assert ctx.version is not None
126126

127127
_env = {"LATCH_DKR_REPO": ctx.dkr_repo, "LATCH_VERSION": ctx.version}
128128
if wf_name_override is not None:
@@ -146,12 +146,7 @@ def serialize_pkg_in_container(
146146
volumes=[serialize_dir],
147147
environment=_env,
148148
host_config=ctx.dkr_client.create_host_config(
149-
binds={
150-
serialize_dir: {
151-
"bind": "/tmp/output",
152-
"mode": "rw",
153-
},
154-
}
149+
binds={serialize_dir: {"bind": "/tmp/output", "mode": "rw"}} # noqa: S108
155150
),
156151
)
157152
container_id = typing.cast(str, container.get("Id"))
@@ -191,9 +186,7 @@ def register_serialized_pkg(
191186
serialize_files[fh.name] = fh
192187

193188
response = requests.post(
194-
latch_register_url,
195-
headers=headers,
196-
files=serialize_files,
189+
latch_register_url, headers=headers, files=serialize_files
197190
)
198191
response.raise_for_status()
199192
return response.json()

0 commit comments

Comments
 (0)