Skip to content

Commit 17431d6

Browse files
committed
feat: add PUT /lambda-endpoint route to update lambda client endpoint
1 parent 09f2eb4 commit 17431d6

5 files changed

Lines changed: 213 additions & 0 deletions

File tree

src/aws_durable_execution_sdk_python_testing/invoker.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,12 @@ def create(endpoint_url: str, region_name: str) -> LambdaInvoker:
116116
)
117117
)
118118

119+
def update_endpoint(self, endpoint_url: str, region_name: str) -> None:
120+
"""Update the Lambda client endpoint."""
121+
self.lambda_client = boto3.client(
122+
"lambdainternal", endpoint_url=endpoint_url, region_name=region_name
123+
)
124+
119125
def create_invocation_input(
120126
self, execution: Execution
121127
) -> DurableExecutionInvocationInput:

src/aws_durable_execution_sdk_python_testing/web/handlers.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
InvalidParameterValueException,
1515
ServiceException,
1616
)
17+
from aws_durable_execution_sdk_python_testing.invoker import LambdaInvoker
1718
from aws_durable_execution_sdk_python_testing.model import (
1819
CheckpointDurableExecutionRequest,
1920
CheckpointDurableExecutionResponse,
@@ -769,3 +770,43 @@ def handle(self, parsed_route: Route, request: HTTPRequest) -> HTTPResponse: #
769770
"""
770771
# TODO: Implement metrics collection logic
771772
return self._success_response({"metrics": {}})
773+
774+
775+
class UpdateLambdaEndpointHandler(EndpointHandler):
776+
"""Handler for PUT /lambda-endpoint."""
777+
778+
def handle(self, parsed_route: Route, request: HTTPRequest) -> HTTPResponse: # noqa: ARG002
779+
"""Handle update Lambda endpoint request.
780+
781+
Args:
782+
parsed_route: The strongly-typed route object
783+
request: The HTTP request data
784+
785+
Returns:
786+
HTTPResponse: The HTTP response to send to the client
787+
"""
788+
try:
789+
body = self._parse_json_body(request)
790+
endpoint_url = body.get("EndpointUrl")
791+
region_name = body.get("RegionName", "us-east-1")
792+
793+
if not endpoint_url:
794+
return HTTPResponse.create_json(
795+
400, {"error": "EndpointUrl is required"}
796+
)
797+
798+
# Update the invoker's Lambda endpoint
799+
invoker = self.executor._invoker # noqa: SLF001
800+
if isinstance(invoker, LambdaInvoker):
801+
logger.info("Updating lambda endpoint to %s", endpoint_url)
802+
invoker.update_endpoint(endpoint_url, region_name)
803+
return self._success_response(
804+
{"message": "Lambda endpoint updated successfully"}
805+
)
806+
807+
return HTTPResponse.create_json(400, {"error": "Not using LambdaInvoker"})
808+
809+
except (AttributeError, TypeError) as e:
810+
return HTTPResponse.create_json(
811+
500, {"error": f"Failed to update Lambda endpoint: {e!s}"}
812+
)

src/aws_durable_execution_sdk_python_testing/web/routes.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -561,6 +561,36 @@ def from_route(cls, route: Route) -> HealthRoute:
561561
return cls(raw_path=route.raw_path, segments=route.segments)
562562

563563

564+
@dataclass(frozen=True)
565+
class UpdateLambdaEndpointRoute(Route):
566+
"""Route: PUT /lambda-endpoint"""
567+
568+
@classmethod
569+
def is_match(cls, route: Route, method: str) -> bool:
570+
"""Check if the route and HTTP method match this route type.
571+
572+
Args:
573+
route: Route to check
574+
method: HTTP method to check
575+
576+
Returns:
577+
True if the route and method match
578+
"""
579+
return route.raw_path == "/lambda-endpoint" and method == "PUT"
580+
581+
@classmethod
582+
def from_route(cls, route: Route) -> UpdateLambdaEndpointRoute:
583+
"""Create UpdateLambdaEndpointRoute from base route.
584+
585+
Args:
586+
route: Base route to convert
587+
588+
Returns:
589+
UpdateLambdaEndpointRoute instance
590+
"""
591+
return cls(raw_path=route.raw_path, segments=route.segments)
592+
593+
564594
@dataclass(frozen=True)
565595
class MetricsRoute(Route):
566596
"""Route: GET /metrics"""
@@ -607,6 +637,7 @@ def from_route(cls, route: Route) -> MetricsRoute:
607637
CallbackFailureRoute,
608638
CallbackHeartbeatRoute,
609639
HealthRoute,
640+
UpdateLambdaEndpointRoute,
610641
MetricsRoute,
611642
]
612643

src/aws_durable_execution_sdk_python_testing/web/server.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
SendDurableExecutionCallbackSuccessHandler,
3636
StartExecutionHandler,
3737
StopDurableExecutionHandler,
38+
UpdateLambdaEndpointHandler,
3839
)
3940
from aws_durable_execution_sdk_python_testing.web.models import (
4041
HTTPRequest,
@@ -56,6 +57,7 @@
5657
Router,
5758
StartExecutionRoute,
5859
StopDurableExecutionRoute,
60+
UpdateLambdaEndpointRoute,
5961
)
6062

6163

@@ -91,6 +93,10 @@ def do_POST(self) -> None: # noqa: N802
9193
"""Handle POST requests."""
9294
self._handle_request("POST")
9395

96+
def do_PUT(self) -> None: # noqa: N802
97+
"""Handle PUT requests."""
98+
self._handle_request("PUT")
99+
94100
def _handle_request(self, method: str) -> None:
95101
"""Handle HTTP request with strongly-typed routing."""
96102
try:
@@ -212,6 +218,7 @@ def _create_endpoint_handlers(self) -> dict[type[Route], EndpointHandler]:
212218
self.executor
213219
),
214220
HealthRoute: HealthHandler(self.executor),
221+
UpdateLambdaEndpointRoute: UpdateLambdaEndpointHandler(self.executor),
215222
MetricsRoute: MetricsHandler(self.executor),
216223
}
217224

tests/web/handlers_test.py

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2048,6 +2048,134 @@ def test_send_durable_execution_callback_failure_handler():
20482048
assert call_args[1]["error"].message == "Test error"
20492049

20502050

2051+
def test_update_lambda_endpoint_handler_success():
2052+
"""Test UpdateLambdaEndpointHandler with valid request."""
2053+
from aws_durable_execution_sdk_python_testing.invoker import LambdaInvoker
2054+
from aws_durable_execution_sdk_python_testing.web.handlers import (
2055+
UpdateLambdaEndpointHandler,
2056+
)
2057+
from aws_durable_execution_sdk_python_testing.web.routes import (
2058+
UpdateLambdaEndpointRoute,
2059+
)
2060+
2061+
executor = Mock()
2062+
lambda_invoker = Mock(spec=LambdaInvoker)
2063+
executor._invoker = lambda_invoker # noqa: SLF001
2064+
handler = UpdateLambdaEndpointHandler(executor)
2065+
2066+
base_route = Route.from_string("/lambda-endpoint")
2067+
update_route = UpdateLambdaEndpointRoute.from_route(base_route)
2068+
2069+
request = HTTPRequest(
2070+
method="PUT",
2071+
path=update_route,
2072+
headers={"Content-Type": "application/json"},
2073+
query_params={},
2074+
body={"EndpointUrl": "http://localhost:8080", "RegionName": "us-west-2"},
2075+
)
2076+
2077+
response = handler.handle(update_route, request)
2078+
2079+
assert response.status_code == 200
2080+
assert response.body == {"message": "Lambda endpoint updated successfully"}
2081+
lambda_invoker.update_endpoint.assert_called_once_with(
2082+
"http://localhost:8080", "us-west-2"
2083+
)
2084+
2085+
2086+
def test_update_lambda_endpoint_handler_missing_endpoint_url():
2087+
"""Test UpdateLambdaEndpointHandler with missing EndpointUrl."""
2088+
from aws_durable_execution_sdk_python_testing.web.handlers import (
2089+
UpdateLambdaEndpointHandler,
2090+
)
2091+
from aws_durable_execution_sdk_python_testing.web.routes import (
2092+
UpdateLambdaEndpointRoute,
2093+
)
2094+
2095+
executor = Mock()
2096+
handler = UpdateLambdaEndpointHandler(executor)
2097+
2098+
base_route = Route.from_string("/lambda-endpoint")
2099+
update_route = UpdateLambdaEndpointRoute.from_route(base_route)
2100+
2101+
request = HTTPRequest(
2102+
method="PUT",
2103+
path=update_route,
2104+
headers={"Content-Type": "application/json"},
2105+
query_params={},
2106+
body={"RegionName": "us-west-2"},
2107+
)
2108+
2109+
response = handler.handle(update_route, request)
2110+
2111+
assert response.status_code == 400
2112+
assert response.body == {"error": "EndpointUrl is required"}
2113+
2114+
2115+
def test_update_lambda_endpoint_handler_not_lambda_invoker():
2116+
"""Test UpdateLambdaEndpointHandler when not using LambdaInvoker."""
2117+
from aws_durable_execution_sdk_python_testing.web.handlers import (
2118+
UpdateLambdaEndpointHandler,
2119+
)
2120+
from aws_durable_execution_sdk_python_testing.web.routes import (
2121+
UpdateLambdaEndpointRoute,
2122+
)
2123+
2124+
executor = Mock()
2125+
executor._invoker = Mock() # Not a LambdaInvoker # noqa: SLF001
2126+
handler = UpdateLambdaEndpointHandler(executor)
2127+
2128+
base_route = Route.from_string("/lambda-endpoint")
2129+
update_route = UpdateLambdaEndpointRoute.from_route(base_route)
2130+
2131+
request = HTTPRequest(
2132+
method="PUT",
2133+
path=update_route,
2134+
headers={"Content-Type": "application/json"},
2135+
query_params={},
2136+
body={"EndpointUrl": "http://localhost:8080"},
2137+
)
2138+
2139+
response = handler.handle(update_route, request)
2140+
2141+
assert response.status_code == 400
2142+
assert response.body == {"error": "Not using LambdaInvoker"}
2143+
2144+
2145+
def test_update_lambda_endpoint_handler_default_region():
2146+
"""Test UpdateLambdaEndpointHandler uses default region when not specified."""
2147+
from aws_durable_execution_sdk_python_testing.invoker import LambdaInvoker
2148+
from aws_durable_execution_sdk_python_testing.web.handlers import (
2149+
UpdateLambdaEndpointHandler,
2150+
)
2151+
from aws_durable_execution_sdk_python_testing.web.routes import (
2152+
UpdateLambdaEndpointRoute,
2153+
)
2154+
2155+
executor = Mock()
2156+
lambda_invoker = Mock(spec=LambdaInvoker)
2157+
executor._invoker = lambda_invoker # noqa: SLF001
2158+
handler = UpdateLambdaEndpointHandler(executor)
2159+
2160+
base_route = Route.from_string("/lambda-endpoint")
2161+
update_route = UpdateLambdaEndpointRoute.from_route(base_route)
2162+
2163+
request = HTTPRequest(
2164+
method="PUT",
2165+
path=update_route,
2166+
headers={"Content-Type": "application/json"},
2167+
query_params={},
2168+
body={"EndpointUrl": "http://localhost:8080"},
2169+
)
2170+
2171+
response = handler.handle(update_route, request)
2172+
2173+
assert response.status_code == 200
2174+
lambda_invoker.update_endpoint.assert_called_once_with(
2175+
"http://localhost:8080", "us-east-1"
2176+
)
2177+
2178+
20512179
def test_send_durable_execution_callback_failure_handler_empty_body():
20522180
"""Test SendDurableExecutionCallbackFailureHandler with empty body."""
20532181
executor = Mock()

0 commit comments

Comments
 (0)