Skip to content

Commit 9d24e15

Browse files
committed
fix: respect user-provided imageName in Live* classes (BYOI)
Model validators were unconditionally overwriting imageName, and the mixin property was ignoring the stored value. Now validators only set defaults when imageName is not provided, and the no-op property is removed. Tests updated for new image naming scheme.
1 parent eaf9bc2 commit 9d24e15

5 files changed

Lines changed: 33 additions & 68 deletions

File tree

Lines changed: 15 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
# Ship serverless code as you write it. No builds, no deploys -- just run.
2-
from typing import ClassVar
3-
1+
# Ship serverless code as you write it. No builds, no deploys — just run.
42
from pydantic import model_validator
53

64
from .constants import (
7-
DEFAULT_PYTHON_VERSION,
5+
GPU_BASE_IMAGE_PYTHON_VERSION,
86
get_image_name,
7+
local_python_version,
98
)
109
from .injection import build_injection_cmd
1110
from .load_balancer_sls_resource import (
@@ -26,23 +25,6 @@ class LiveServerlessMixin:
2625
runtime, not by the Docker image.
2726
"""
2827

29-
_image_type: ClassVar[str] = (
30-
"" # override in subclasses: 'gpu', 'cpu', 'lb', 'lb-cpu'
31-
)
32-
33-
@property
34-
def _live_image(self) -> str:
35-
python_version = getattr(self, "python_version", None) or DEFAULT_PYTHON_VERSION
36-
return get_image_name(self._image_type, python_version)
37-
38-
@property
39-
def imageName(self):
40-
return self._live_image
41-
42-
@imageName.setter
43-
def imageName(self, value):
44-
pass
45-
4628
def _create_new_template(self) -> PodTemplate:
4729
"""Create template with dockerArgs for process injection."""
4830
template = super()._create_new_template() # type: ignore[misc]
@@ -59,54 +41,50 @@ def _configure_existing_template(self) -> None:
5941
class LiveServerless(LiveServerlessMixin, ServerlessEndpoint):
6042
"""GPU-only live serverless endpoint."""
6143

62-
_image_type: ClassVar[str] = "gpu"
63-
6444
@model_validator(mode="before")
6545
@classmethod
6646
def set_live_serverless_template(cls, data: dict):
6747
"""Set default GPU image for Live Serverless."""
68-
python_version = data.get("python_version") or DEFAULT_PYTHON_VERSION
69-
data["imageName"] = get_image_name("gpu", python_version)
48+
if "imageName" not in data:
49+
python_version = data.get("python_version") or GPU_BASE_IMAGE_PYTHON_VERSION
50+
data["imageName"] = get_image_name("gpu", python_version)
7051
return data
7152

7253

7354
class CpuLiveServerless(LiveServerlessMixin, CpuServerlessEndpoint):
7455
"""CPU-only live serverless endpoint with automatic disk sizing."""
7556

76-
_image_type: ClassVar[str] = "cpu"
77-
7857
@model_validator(mode="before")
7958
@classmethod
8059
def set_live_serverless_template(cls, data: dict):
8160
"""Set default CPU image for Live Serverless."""
82-
python_version = data.get("python_version") or DEFAULT_PYTHON_VERSION
83-
data["imageName"] = get_image_name("cpu", python_version)
61+
if "imageName" not in data:
62+
python_version = data.get("python_version") or local_python_version()
63+
data["imageName"] = get_image_name("cpu", python_version)
8464
return data
8565

8666

8767
class LiveLoadBalancer(LiveServerlessMixin, LoadBalancerSlsResource):
8868
"""Live load-balanced endpoint."""
8969

90-
_image_type: ClassVar[str] = "lb"
91-
9270
@model_validator(mode="before")
9371
@classmethod
9472
def set_live_lb_template(cls, data: dict):
9573
"""Set default image for Live Load-Balanced endpoint."""
96-
python_version = data.get("python_version") or DEFAULT_PYTHON_VERSION
97-
data["imageName"] = get_image_name("lb", python_version)
74+
if "imageName" not in data:
75+
python_version = data.get("python_version") or GPU_BASE_IMAGE_PYTHON_VERSION
76+
data["imageName"] = get_image_name("lb", python_version)
9877
return data
9978

10079

10180
class CpuLiveLoadBalancer(LiveServerlessMixin, CpuLoadBalancerSlsResource):
10281
"""CPU-only live load-balanced endpoint."""
10382

104-
_image_type: ClassVar[str] = "lb-cpu"
105-
10683
@model_validator(mode="before")
10784
@classmethod
10885
def set_live_cpu_lb_template(cls, data: dict):
10986
"""Set default CPU image for Live Load-Balanced endpoint."""
110-
python_version = data.get("python_version") or DEFAULT_PYTHON_VERSION
111-
data["imageName"] = get_image_name("lb-cpu", python_version)
87+
if "imageName" not in data:
88+
python_version = data.get("python_version") or local_python_version()
89+
data["imageName"] = get_image_name("lb-cpu", python_version)
11290
return data

tests/integration/test_cpu_disk_sizing.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ def test_live_serverless_cpu_integration(self):
129129
# 2. CPU utilities calculate minimum disk size
130130
# 3. Template creation with auto-sizing
131131
# 4. Validation passes
132-
assert live_serverless.imageName == "python:3.11-slim"
132+
assert "runpod/flash-cpu:" in live_serverless.imageName
133133
assert live_serverless.instanceIds == [
134134
CpuInstanceType.CPU5C_1_2,
135135
CpuInstanceType.CPU5C_2_4,
@@ -254,8 +254,8 @@ def test_live_serverless_image_defaults(self):
254254

255255
# Verify different base images are used
256256
assert gpu_live.imageName != cpu_live.imageName
257-
assert "pytorch" in gpu_live.imageName
258-
assert "python" in cpu_live.imageName
257+
assert "runpod/flash:" in gpu_live.imageName
258+
assert "runpod/flash-cpu:" in cpu_live.imageName
259259

260260
# Verify images can be overridden (BYOI)
261261
custom_gpu = LiveServerless(

tests/integration/test_lb_remote_execution.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -114,13 +114,13 @@ async def echo(message: str):
114114
# Verify resource is correctly configured
115115
# Note: name may have "-fb" appended by flash boot validator
116116
assert "test-live-api" in lb.name
117-
assert "pytorch" in lb.imageName # GPU base image
117+
assert "runpod/flash-lb:" in lb.imageName # GPU LB base image
118118
assert echo.__remote_config__["method"] == "POST"
119119

120120
def test_live_load_balancer_default_image(self):
121-
"""Test that LiveLoadBalancer uses GPU base image by default."""
121+
"""Test that LiveLoadBalancer uses GPU LB base image by default."""
122122
lb = LiveLoadBalancer(name="test-api")
123-
assert "pytorch" in lb.imageName
123+
assert "runpod/flash-lb:" in lb.imageName
124124

125125
def test_live_load_balancer_allows_custom_image(self):
126126
"""Test that LiveLoadBalancer allows user to set custom image (BYOI)."""

tests/unit/resources/test_live_load_balancer.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,7 @@ def test_live_load_balancer_default_image_tag(self):
5050
def test_live_load_balancer_user_can_override_image(self):
5151
"""Test user can set custom imageName (BYOI)."""
5252
lb = LiveLoadBalancer(name="test-lb", imageName="custom/image:v1")
53-
# imageName property returns _live_image, setter is no-op
54-
assert lb.imageName is not None
53+
assert lb.imageName == "custom/image:v1"
5554

5655
def test_live_load_balancer_template_creation(self):
5756
"""Test LiveLoadBalancer creates proper template from imageName."""
@@ -210,8 +209,7 @@ def test_cpu_live_load_balancer_default_image_tag(self):
210209
def test_cpu_live_load_balancer_user_can_override_image(self):
211210
"""Test CpuLiveLoadBalancer allows user image override."""
212211
lb = CpuLiveLoadBalancer(name="test-lb", imageName="python:3.11-slim")
213-
# imageName property returns _live_image, setter is no-op
214-
assert lb.imageName is not None
212+
assert lb.imageName == "python:3.11-slim"
215213

216214
def test_cpu_live_load_balancer_defaults(self):
217215
"""Test CpuLiveLoadBalancer defaults to CPU3G_2_8."""

tests/unit/resources/test_live_serverless.py

Lines changed: 10 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,7 @@ def test_live_serverless_user_can_override_image(self):
4444
live_serverless = LiveServerless(
4545
name="test", imageName="nvidia/cuda:12.8.0-runtime-ubuntu22.04"
4646
)
47-
# imageName setter is a no-op, so value is always the computed _live_image
48-
# The model_validator sets data["imageName"] but the property overrides reads
49-
assert live_serverless.imageName is not None
47+
assert live_serverless.imageName == "nvidia/cuda:12.8.0-runtime-ubuntu22.04"
5048

5149
def test_live_serverless_with_custom_template(self):
5250
"""Test LiveServerless with custom template."""
@@ -102,8 +100,7 @@ def test_cpu_live_serverless_multiple_instances(self):
102100
def test_cpu_live_serverless_user_can_override_image(self):
103101
"""Test CpuLiveServerless allows user to set custom image."""
104102
live_serverless = CpuLiveServerless(name="test", imageName="python:3.11-slim")
105-
# imageName property returns _live_image, setter is no-op
106-
assert live_serverless.imageName is not None
103+
assert live_serverless.imageName == "python:3.11-slim"
107104

108105
def test_cpu_live_serverless_validation_failure(self):
109106
"""Test CpuLiveServerless validation fails with excessive disk size."""
@@ -200,23 +197,15 @@ def test_cpu_live_load_balancer_defaults(self):
200197
assert lb.template is not None
201198
assert lb.template.dockerArgs
202199

203-
def test_image_name_setter_ignored_gpu(self):
204-
"""Test LiveServerless imageName setter is ignored."""
205-
live_serverless = LiveServerless(name="test")
206-
original_image = live_serverless.imageName
207-
208-
live_serverless.imageName = "should-be-ignored"
209-
210-
assert live_serverless.imageName == original_image
211-
212-
def test_image_name_setter_ignored_cpu(self):
213-
"""Test CpuLiveServerless imageName setter is ignored."""
214-
live_serverless = CpuLiveServerless(name="test")
215-
original_image = live_serverless.imageName
216-
217-
live_serverless.imageName = "should-be-ignored"
200+
def test_live_serverless_byoi_gpu(self):
201+
"""Test LiveServerless respects user-provided imageName."""
202+
live_serverless = LiveServerless(name="test", imageName="custom/gpu:v1")
203+
assert live_serverless.imageName == "custom/gpu:v1"
218204

219-
assert live_serverless.imageName == original_image
205+
def test_live_serverless_byoi_cpu(self):
206+
"""Test CpuLiveServerless respects user-provided imageName."""
207+
live_serverless = CpuLiveServerless(name="test", imageName="custom/cpu:v1")
208+
assert live_serverless.imageName == "custom/cpu:v1"
220209

221210

222211
class TestLiveServerlessPythonVersion:

0 commit comments

Comments
 (0)