Skip to content

Commit ada9368

Browse files
authored
feat: add PUT /lambda-endpoint route to update lambda client endpoint (#16)
1 parent 5cafcf4 commit ada9368

5 files changed

Lines changed: 186 additions & 0 deletions

File tree

src/aws_durable_execution_sdk_python_testing/invoker.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@ def invoke(
6868
input: DurableExecutionInvocationInput,
6969
) -> DurableExecutionInvocationOutput: ... # pragma: no cover
7070

71+
def update_endpoint(
72+
self, endpoint_url: str, region_name: str
73+
) -> None: ... # pragma: no cover
74+
7175

7276
class InProcessInvoker(Invoker):
7377
def __init__(self, handler: Callable, service_client: InMemoryServiceClient):
@@ -102,6 +106,9 @@ def invoke(
102106
response_dict = self.handler(input_with_client, context)
103107
return DurableExecutionInvocationOutput.from_dict(response_dict)
104108

109+
def update_endpoint(self, endpoint_url: str, region_name: str) -> None:
110+
"""No-op for in-process invoker."""
111+
105112

106113
class LambdaInvoker(Invoker):
107114
def __init__(self, lambda_client: Any) -> None:
@@ -116,6 +123,12 @@ def create(endpoint_url: str, region_name: str) -> LambdaInvoker:
116123
)
117124
)
118125

126+
def update_endpoint(self, endpoint_url: str, region_name: str) -> None:
127+
"""Update the Lambda client endpoint."""
128+
self.lambda_client = boto3.client(
129+
"lambdainternal", endpoint_url=endpoint_url, region_name=region_name
130+
)
131+
119132
def create_invocation_input(
120133
self, execution: Execution
121134
) -> DurableExecutionInvocationInput:

src/aws_durable_execution_sdk_python_testing/web/handlers.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -769,3 +769,40 @@ def handle(self, parsed_route: Route, request: HTTPRequest) -> HTTPResponse: #
769769
"""
770770
# TODO: Implement metrics collection logic
771771
return self._success_response({"metrics": {}})
772+
773+
774+
class UpdateLambdaEndpointHandler(EndpointHandler):
775+
"""Handler for PUT /lambda-endpoint."""
776+
777+
def handle(self, parsed_route: Route, request: HTTPRequest) -> HTTPResponse: # noqa: ARG002
778+
"""Handle update Lambda endpoint request.
779+
780+
Args:
781+
parsed_route: The strongly-typed route object
782+
request: The HTTP request data
783+
784+
Returns:
785+
HTTPResponse: The HTTP response to send to the client
786+
"""
787+
try:
788+
body = self._parse_json_body(request)
789+
endpoint_url = body.get("EndpointUrl")
790+
region_name = body.get("RegionName", "us-east-1")
791+
792+
if not endpoint_url:
793+
return HTTPResponse.create_json(
794+
400, {"error": "EndpointUrl is required"}
795+
)
796+
797+
# Update the invoker's Lambda endpoint
798+
invoker = self.executor._invoker # noqa: SLF001
799+
logger.info("Updating lambda endpoint to %s", endpoint_url)
800+
invoker.update_endpoint(endpoint_url, region_name)
801+
return self._success_response(
802+
{"message": "Lambda endpoint updated successfully"}
803+
)
804+
805+
except (AttributeError, TypeError) as e:
806+
return HTTPResponse.create_json(
807+
500, {"error": f"Failed to update Lambda endpoint: {e!s}"}
808+
)

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: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2048,6 +2048,104 @@ 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_default_region():
2116+
"""Test UpdateLambdaEndpointHandler uses default region when not specified."""
2117+
from aws_durable_execution_sdk_python_testing.invoker import LambdaInvoker
2118+
from aws_durable_execution_sdk_python_testing.web.handlers import (
2119+
UpdateLambdaEndpointHandler,
2120+
)
2121+
from aws_durable_execution_sdk_python_testing.web.routes import (
2122+
UpdateLambdaEndpointRoute,
2123+
)
2124+
2125+
executor = Mock()
2126+
lambda_invoker = Mock(spec=LambdaInvoker)
2127+
executor._invoker = lambda_invoker # noqa: SLF001
2128+
handler = UpdateLambdaEndpointHandler(executor)
2129+
2130+
base_route = Route.from_string("/lambda-endpoint")
2131+
update_route = UpdateLambdaEndpointRoute.from_route(base_route)
2132+
2133+
request = HTTPRequest(
2134+
method="PUT",
2135+
path=update_route,
2136+
headers={"Content-Type": "application/json"},
2137+
query_params={},
2138+
body={"EndpointUrl": "http://localhost:8080"},
2139+
)
2140+
2141+
response = handler.handle(update_route, request)
2142+
2143+
assert response.status_code == 200
2144+
lambda_invoker.update_endpoint.assert_called_once_with(
2145+
"http://localhost:8080", "us-east-1"
2146+
)
2147+
2148+
20512149
def test_send_durable_execution_callback_failure_handler_empty_body():
20522150
"""Test SendDurableExecutionCallbackFailureHandler with empty body."""
20532151
executor = Mock()

0 commit comments

Comments
 (0)