Skip to content

Commit dea696e

Browse files
mploskiMichal Ploskiheitorlessa
authored
feat(mypy): complete mypy support for the entire codebase (#943)
Co-authored-by: Michal Ploski <mploski@amazon.com> Co-authored-by: Heitor Lessa <heitor.lessa@hotmail.com> Co-authored-by: heitorlessa <lessa@amazon.co.uk>
1 parent ed7a978 commit dea696e

File tree

30 files changed

+145
-83
lines changed

30 files changed

+145
-83
lines changed

.github/workflows/python_build.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ jobs:
3030
run: make dev
3131
- name: Formatting and Linting
3232
run: make lint
33+
- name: Static type checking
34+
run: make mypy
3335
- name: Test with pytest
3436
run: make test
3537
- name: Security baseline

CONTRIBUTING.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,14 @@ You might find useful to run both the documentation website and the API referenc
4949

5050
Category | Convention
5151
------------------------------------------------- | ---------------------------------------------------------------------------------
52-
**Docstring** | We use a slight variation of numpy convention with markdown to help generate more readable API references.
53-
**Style guide** | We use black as well as flake8 extensions to enforce beyond good practices [PEP8](https://pep8.org/). We strive to make use of type annotation as much as possible, but don't overdo in creating custom types.
52+
**Docstring** | We use a slight variation of Numpy convention with markdown to help generate more readable API references.
53+
**Style guide** | We use black as well as flake8 extensions to enforce beyond good practices [PEP8](https://pep8.org/). We use type annotations and enforce static type checking at CI (mypy).
5454
**Core utilities** | Core utilities use a Class, always accept `service` as a constructor parameter, can work in isolation, and are also available in other languages implementation.
5555
**Utilities** | Utilities are not as strict as core and focus on solving a developer experience problem while following the project [Tenets](https://awslabs.github.io/aws-lambda-powertools-python/#tenets).
5656
**Exceptions** | Specific exceptions live within utilities themselves and use `Error` suffix e.g. `MetricUnitError`.
57-
**Git commits** | We follow [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/). These are not enforced as we squash and merge PRs, but PR titles are enforced during CI.
58-
**Documentation** | API reference docs are generated from docstrings which should have Examples section to allow developers to have what they need within their own IDE. Documentation website covers the wider usage, tips, and strive to be concise.
57+
**Git commits** | We follow [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/). We do not enforce conventional commits on contributors to lower the entry bar. Instead, we enforce a conventional PR title so our label automation and changelog are generated correctly.
58+
**API documentation** | API reference docs are generated from docstrings which should have Examples section to allow developers to have what they need within their own IDE. Documentation website covers the wider usage, tips, and strive to be concise.
59+
**Documentation** | We treat it like a product. We sub-divide content aimed at getting started (80% of customers) vs advanced usage (20%). We also ensure customers know how to unit test their code when using our features.
5960

6061
## Finding contributions to work on
6162

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ coverage-html:
2929
pre-commit:
3030
pre-commit run --show-diff-on-failure
3131

32-
pr: lint pre-commit test security-baseline complexity-baseline
32+
pr: lint mypy pre-commit test security-baseline complexity-baseline
3333

3434
build: pr
3535
poetry build

aws_lambda_powertools/event_handler/api_gateway.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from enum import Enum
1111
from functools import partial
1212
from http import HTTPStatus
13-
from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Type, Union
13+
from typing import Any, Callable, Dict, List, Match, Optional, Pattern, Set, Tuple, Type, Union
1414

1515
from aws_lambda_powertools.event_handler import content_types
1616
from aws_lambda_powertools.event_handler.exceptions import NotFoundError, ServiceError
@@ -167,7 +167,7 @@ class Route:
167167
"""Internally used Route Configuration"""
168168

169169
def __init__(
170-
self, method: str, rule: Any, func: Callable, cors: bool, compress: bool, cache_control: Optional[str]
170+
self, method: str, rule: Pattern, func: Callable, cors: bool, compress: bool, cache_control: Optional[str]
171171
):
172172
self.method = method.upper()
173173
self.rule = rule
@@ -555,7 +555,7 @@ def _resolve(self) -> ResponseBuilder:
555555
for route in self._routes:
556556
if method != route.method:
557557
continue
558-
match_results: Optional[re.Match] = route.rule.match(path)
558+
match_results: Optional[Match] = route.rule.match(path)
559559
if match_results:
560560
logger.debug("Found a registered route. Calling function")
561561
return self._call_route(route, match_results.groupdict()) # pass fn args

aws_lambda_powertools/metrics/metrics.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def lambda_handler():
5353
----------
5454
service : str, optional
5555
service name to be used as metric dimension, by default "service_undefined"
56-
namespace : str
56+
namespace : str, optional
5757
Namespace for metrics
5858
5959
Raises
@@ -209,5 +209,6 @@ def __add_cold_start_metric(self, context: Any) -> None:
209209
logger.debug("Adding cold start metric and function_name dimension")
210210
with single_metric(name="ColdStart", unit=MetricUnit.Count, value=1, namespace=self.namespace) as metric:
211211
metric.add_dimension(name="function_name", value=context.function_name)
212-
metric.add_dimension(name="service", value=self.service)
212+
if self.service:
213+
metric.add_dimension(name="service", value=str(self.service))
213214
is_cold_start = False

aws_lambda_powertools/shared/functions.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Any, Optional, Union
1+
from typing import Optional, Union
22

33

44
def strtobool(value: str) -> bool:
@@ -38,21 +38,23 @@ def resolve_truthy_env_var_choice(env: str, choice: Optional[bool] = None) -> bo
3838
return choice if choice is not None else strtobool(env)
3939

4040

41-
def resolve_env_var_choice(env: Any, choice: Optional[Any] = None) -> Union[bool, Any]:
41+
def resolve_env_var_choice(
42+
env: Optional[str] = None, choice: Optional[Union[str, float]] = None
43+
) -> Optional[Union[str, float]]:
4244
"""Pick explicit choice over env, if available, otherwise return env value received
4345
4446
NOTE: Environment variable should be resolved by the caller.
4547
4648
Parameters
4749
----------
48-
env : Any
50+
env : str, Optional
4951
environment variable actual value
50-
choice : bool
52+
choice : str|float, optional
5153
explicit choice
5254
5355
Returns
5456
-------
55-
choice : str
57+
choice : str, Optional
5658
resolved choice as either bool or environment value
5759
"""
5860
return choice if choice is not None else env

aws_lambda_powertools/utilities/batch/exceptions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from types import TracebackType
66
from typing import List, Optional, Tuple, Type
77

8-
ExceptionInfo = Tuple[Type[BaseException], BaseException, TracebackType]
8+
ExceptionInfo = Tuple[Optional[Type[BaseException]], Optional[BaseException], Optional[TracebackType]]
99

1010

1111
class BaseBatchProcessingError(Exception):

aws_lambda_powertools/utilities/batch/sqs.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@
55
"""
66
import logging
77
import sys
8-
from typing import Callable, Dict, List, Optional, Tuple
8+
from typing import Callable, Dict, List, Optional, Tuple, cast
99

1010
import boto3
1111
from botocore.config import Config
1212

13+
from aws_lambda_powertools.utilities.data_classes.sqs_event import SQSRecord
14+
1315
from ...middleware_factory import lambda_handler_decorator
1416
from .base import BasePartialProcessor
1517
from .exceptions import SQSBatchProcessingError
@@ -84,11 +86,17 @@ def _get_queue_url(self) -> Optional[str]:
8486
*_, account_id, queue_name = self.records[0]["eventSourceARN"].split(":")
8587
return f"{self.client._endpoint.host}/{account_id}/{queue_name}"
8688

87-
def _get_entries_to_clean(self) -> List:
89+
def _get_entries_to_clean(self) -> List[Dict[str, str]]:
8890
"""
8991
Format messages to use in batch deletion
9092
"""
91-
return [{"Id": msg["messageId"], "ReceiptHandle": msg["receiptHandle"]} for msg in self.success_messages]
93+
entries = []
94+
# success_messages has generic type of union of SQS, Dynamodb and Kinesis Streams records or Pydantic models.
95+
# Here we get SQS Record only
96+
messages = cast(List[SQSRecord], self.success_messages)
97+
for msg in messages:
98+
entries.append({"Id": msg["messageId"], "ReceiptHandle": msg["receiptHandle"]})
99+
return entries
92100

93101
def _process_record(self, record) -> Tuple:
94102
"""

aws_lambda_powertools/utilities/idempotency/idempotency.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ def process_order(customer_id: str, order: dict, **kwargs):
112112
return {"StatusCode": 200}
113113
"""
114114

115-
if function is None:
115+
if not function:
116116
return cast(
117117
AnyCallableT,
118118
functools.partial(
@@ -132,7 +132,7 @@ def decorate(*args, **kwargs):
132132

133133
payload = kwargs.get(data_keyword_argument)
134134

135-
if payload is None:
135+
if not payload:
136136
raise RuntimeError(
137137
f"Unable to extract '{data_keyword_argument}' from keyword arguments."
138138
f" Ensure this exists in your function's signature as well as the caller used it as a keyword argument"

aws_lambda_powertools/utilities/idempotency/persistence/base.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,16 +92,16 @@ def status(self) -> str:
9292
else:
9393
raise IdempotencyInvalidStatusError(self._status)
9494

95-
def response_json_as_dict(self) -> dict:
95+
def response_json_as_dict(self) -> Optional[dict]:
9696
"""
9797
Get response data deserialized to python dict
9898
9999
Returns
100100
-------
101-
dict
101+
Optional[dict]
102102
previous response data deserialized
103103
"""
104-
return json.loads(self.response_data)
104+
return json.loads(self.response_data) if self.response_data else None
105105

106106

107107
class BasePersistenceLayer(ABC):
@@ -121,7 +121,6 @@ def __init__(self):
121121
self.raise_on_no_idempotency_key = False
122122
self.expires_after_seconds: int = 60 * 60 # 1 hour default
123123
self.use_local_cache = False
124-
self._cache: Optional[LRUDict] = None
125124
self.hash_function = None
126125

127126
def configure(self, config: IdempotencyConfig, function_name: Optional[str] = None) -> None:

0 commit comments

Comments
 (0)