Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ dependencies = ["coverage[toml]", "pytest", "pytest-cov"]
cov = "pytest --cov-report=term-missing --cov-config=pyproject.toml --cov=src/aws_durable_execution_sdk_python --cov-fail-under=98"

[tool.hatch.envs.types]
extra-dependencies = ["mypy>=1.0.0", "pytest"]
extra-dependencies = ["mypy>=1.0.0", "pytest", "boto3-stubs[lambda]"]
[tool.hatch.envs.types.scripts]
check = "mypy --install-types --non-interactive {args:src/aws_durable_execution_sdk_python tests}"

Expand Down
4 changes: 2 additions & 2 deletions src/aws_durable_execution_sdk_python/execution.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
if TYPE_CHECKING:
from collections.abc import Callable, MutableMapping

import boto3 # type: ignore
from mypy_boto3_lambda import LambdaClient as Boto3LambdaClient

from aws_durable_execution_sdk_python.types import LambdaContext

Expand Down Expand Up @@ -237,7 +237,7 @@ def create_succeeded(cls, result: str) -> DurableExecutionInvocationOutput:
def durable_execution(
func: Callable[[Any, DurableContext], Any] | None = None,
*,
boto3_client: boto3.client | None = None,
boto3_client: Boto3LambdaClient | None = None,
) -> Callable[[Any, LambdaContext], Any]:
# Decorator called with parameters
if func is None:
Expand Down
52 changes: 30 additions & 22 deletions src/aws_durable_execution_sdk_python/lambda_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
import copy
import datetime
import logging
from collections.abc import MutableMapping
from dataclasses import dataclass, field
from enum import Enum
from typing import TYPE_CHECKING, Any, Protocol, TypeAlias
from typing import TYPE_CHECKING, Any, Protocol, TypeAlias, cast

import boto3 # type: ignore
from botocore.config import Config # type: ignore
import boto3
from botocore.config import Config

from aws_durable_execution_sdk_python.exceptions import (
CallableRuntimeError,
Expand All @@ -17,7 +18,11 @@
)

if TYPE_CHECKING:
from collections.abc import MutableMapping
from mypy_boto3_lambda import LambdaClient as Boto3LambdaClient
from mypy_boto3_lambda.type_defs import (
CheckpointDurableExecutionResponseTypeDef,
GetDurableExecutionStateResponseTypeDef,
)

from aws_durable_execution_sdk_python.identifier import OperationIdentifier

Expand Down Expand Up @@ -1031,9 +1036,9 @@ def get_execution_state(
class LambdaClient(DurableServiceClient):
"""Persist durable operations to the Lambda Durable Function APIs."""

_cached_boto_client: Any = None
_cached_boto_client: Boto3LambdaClient | None = None

def __init__(self, client: Any) -> None:
def __init__(self, client: Boto3LambdaClient) -> None:
self.client = client

@classmethod
Expand Down Expand Up @@ -1066,19 +1071,20 @@ def checkpoint(
client_token: str | None,
) -> CheckpointOutput:
try:
params = {
"DurableExecutionArn": durable_execution_arn,
"CheckpointToken": checkpoint_token,
"Updates": [o.to_dict() for o in updates],
}
optional_params: dict[str, str] = {}
if client_token is not None:
params["ClientToken"] = client_token

result: MutableMapping[str, Any] = self.client.checkpoint_durable_execution(
**params
optional_params["ClientToken"] = client_token

result: CheckpointDurableExecutionResponseTypeDef = (
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd be very tempted to keep this MutableMapping[str, Any] instead of CheckpointDurableExecutionResponseTypeDef - we're immediately serializing the result into a custom dataclass from a dict, and this way we avoid the extra cast..

What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since cast() has virtually zero runtime cost - it just returns the value unchanged, purely a type checker hint - I'd keep the specific TypeDefs.

I see some trends about Python ecosystem is moving towards more stricter typing, and mypy isn't the only option anymore. We now have ty from Astral (the ruff folks) which is gaining traction. Using precise types now means we're better positioned as the tooling improves.

Personally I'd keep it, but happy to revert.

self.client.checkpoint_durable_execution(
DurableExecutionArn=durable_execution_arn,
CheckpointToken=checkpoint_token,
Updates=cast(Any, [o.to_dict() for o in updates]),
**optional_params, # type: ignore[arg-type]
)
)

return CheckpointOutput.from_dict(result)
return CheckpointOutput.from_dict(cast(MutableMapping[str, Any], result))
except Exception as e:
checkpoint_error = CheckpointError.from_exception(e)
logger.exception(
Expand All @@ -1094,13 +1100,15 @@ def get_execution_state(
max_items: int = 1000,
) -> StateOutput:
try:
result: MutableMapping[str, Any] = self.client.get_durable_execution_state(
DurableExecutionArn=durable_execution_arn,
CheckpointToken=checkpoint_token,
Marker=next_marker,
MaxItems=max_items,
result: GetDurableExecutionStateResponseTypeDef = (
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

similar here on MutableMapping

self.client.get_durable_execution_state(
DurableExecutionArn=durable_execution_arn,
CheckpointToken=checkpoint_token,
Marker=next_marker,
MaxItems=max_items,
)
)
return StateOutput.from_dict(result)
return StateOutput.from_dict(cast(MutableMapping[str, Any], result))
except Exception as e:
error = GetExecutionStateError.from_exception(e)
logger.exception(
Expand Down