使用mypy和boto3存根时出现类型错误

0 投票
1 回答
59 浏览
提问于 2025-04-13 01:52

我在AWS上使用带类型的Python。(使用mypy和boto3的类型定义)

我对类型系统还不太熟悉,所以需要你们的建议和解释。

我有一个这样的函数:

def select_time_range() -> dict[str, datetime]:

    try:
        current_datetime = datetime.now()
        datetime_one_day_before = current_datetime - timedelta(days=1)
        datetime_24_hours_ago = current_datetime - timedelta(hours=24)

        report_timerange_choice = {
            'previous_day': {
                'from': datetime(datetime_one_day_before.year, datetime_one_day_before.month, datetime_one_day_before.day,
                                 0, 0),
                'to': datetime(current_datetime.year, current_datetime.month, current_datetime.day, 0, 0)
            },
            'last_24_hours': {

                    'from': datetime(datetime_24_hours_ago.year, datetime_24_hours_ago.month, datetime_24_hours_ago.day, datetime_24_hours_ago.hour, datetime_24_hours_ago.minute),
                    'to': datetime(current_datetime.year, current_datetime.month, current_datetime.day, current_datetime.hour, current_datetime.minute),
                }
            }

    except Exception as e:
        LOGGER.error(e)
        raise e

    return report_timerange_choice[TIMEFRAME_PICKER]

这个函数返回的字典大概是这样的:

{'from': datetime.datetime(2024, 3, 21, 0, 0) 'to':

 datetime.datetime(2024, 3, 22, 0, 0)}

然后我有另一个函数,我把这个字典作为参数传给它。问题是,describe_events()这个函数期待接收一个类型为DateTimeRangeTypeDeftimeframe,但它却收到了dict[str, datetime]。我该如何“改变类型”或者我需要做什么才能正确写呢?

def get_account_events(timeframe: dict[str, datetime]) -> DescribeEventsResponseTypeDef:
    try:
        #DAILY
        daily_events = health_client.describe_events(

            filter={
                'startTimes': [timeframe]
                
            }
        )

    except Exception as e:
        LOGGER.error(e)
        raise e

    return daily_events

当我把第一个函数中的类型从

dict[str, datetime]

改成

DateTimeRangeTypeDef

时,它却提示我说类型是dict[str, datetime]

1 个回答

1

为了更清楚,我假设我们在讨论的是 health_client = boto3.client('health') 这段代码。

在代码的简化版本中,它是这样定义的:

TimestampTypeDef = Union[datetime, str]
...
DateTimeRangeTypeDef = TypedDict(
    "DateTimeRangeTypeDef",
    {
        "from": NotRequired[TimestampTypeDef],
        "to": NotRequired[TimestampTypeDef],
    },
)

查看这些简化版本的最简单方法是使用 pip install 安装相应的简化包(比如 pip install 'boto3-stubs[health]'),然后查看它的文件夹内容(例如 grep -rn DateTimeRangeTypeDef ./venv/lib/python3.XX/site-packages/mypy_boto3_health/)。接着,找到定义这个内容的文件,看看里面的内容。这个方法适用于任何服务,只需将上面文本中的 health 替换为你用来创建客户端的服务名称即可。

所以,直接用 DateTimeRangeTypeDef 替代 dict[str, datetime]。你可能需要给外层的字典加上注释。下面的代码在类型检查时没有问题(我稍微修改了一些几乎肯定是错误的地方,比如在处理程序里面的 raise e,但要注意,这里使用的 except Exception 是有点奇怪的,而且 LOGGER.error 默认不会打印错误追踪信息,你可能想用 LOGGER.exception 或者 LOGGER.error(..., exc_info=True) 来替代)。

from __future__ import annotations

import logging
from datetime import datetime, timedelta
from typing import TYPE_CHECKING, Final, Literal, TypeAlias

import boto3

if TYPE_CHECKING:
    from mypy_boto3_health.type_defs import (
        DateTimeRangeTypeDef,
        DescribeEventsResponseTypeDef,
    )

health_client: Final = boto3.client("health")
LOGGER: Final = logging.getLogger(__name__)
TimeFramePickerT: TypeAlias = Literal["previous_day", "last_24_hours"]
TIMEFRAME_PICKER: Final[TimeFramePickerT] = "last_24_hours"


def select_time_range() -> DateTimeRangeTypeDef:
    try:
        current_datetime = datetime.now()
        datetime_one_day_before = current_datetime - timedelta(days=1)
        datetime_24_hours_ago = current_datetime - timedelta(hours=24)

        report_timerange_choice: dict[TimeFramePickerT, DateTimeRangeTypeDef] = {
            "previous_day": {
                "from": datetime(
                    datetime_one_day_before.year,
                    datetime_one_day_before.month,
                    datetime_one_day_before.day,
                    0,
                    0,
                ),
                "to": datetime(
                    current_datetime.year,
                    current_datetime.month,
                    current_datetime.day,
                    0,
                    0,
                ),
            },
            "last_24_hours": {
                "from": datetime(
                    datetime_24_hours_ago.year,
                    datetime_24_hours_ago.month,
                    datetime_24_hours_ago.day,
                    datetime_24_hours_ago.hour,
                    datetime_24_hours_ago.minute,
                ),
                "to": datetime(
                    current_datetime.year,
                    current_datetime.month,
                    current_datetime.day,
                    current_datetime.hour,
                    current_datetime.minute,
                ),
            },
        }
    except Exception as e:
        LOGGER.exception(e)
        raise

    return report_timerange_choice[TIMEFRAME_PICKER]


def get_account_events(
    timeframe: DateTimeRangeTypeDef,
) -> DescribeEventsResponseTypeDef:
    try:
        daily_events = health_client.describe_events(
            filter={
                "startTimes": [timeframe],
            }
        )
    except Exception as e:
        LOGGER.error(e)
        raise

    return daily_events

撰写回答