'I'm trying to type annotate around boto3, but module 'botocore.client' has no attribute 'EC2'

I'm writing my own wrapper around boto3 for quick firing functions.

I'm trying to type annotate what boto3.session().client('ec2') returns.

Debugger says it's <class 'botocore.client.EC2'>, but if I write it down like that, python crashes with a runtime error

ec2: botocore.client.EC2
AttributeError: module 'botocore.client' has no attribute 'EC2'

Removing type annotation works for runtime, but it makes linting very limited.

Is there a reasonably fast way or a hack to get typing working with this boto3 case?

The code I'm talking about is following:

class AWS_Client:
    # debugger says it's <class 'botocore.client.EC2'>,
    # but mypy says this class does not exist
    ec2: botocore.client.EC2
    asg: botocore.client.AutoScaling
    region: str
    instance_id: str

    def __init__(self,
                 profile_name: Optional[str] = None,
                 instance_id: str = '',
                 region_name: str = '',
                 **kwargs) -> None:
        global config

        self.instance_id = instance_id or ec2_meta('instance-id').text
        self.region = region_name or ec2_meta(
            'placement/availability-zone').text[:-1]
        boto3.set_stream_logger('botocore', level=logging.DEBUG)
        self.session = call_partial(
            boto3.Session,
            region_name=self.region,
            profile_name=profile_name,
            **kwargs)
        self.ec2 = self.session.client('ec2', region_name=self.region, **kwargs)
        self.asg = self.session.client(
            'autoscaling', region_name=self.region, **kwargs)

    def get_tags(self) -> Dict[str, str]:
        self.tags = self.ec2.describe_tags(Filters=[{
            'Name': 'resource-id',
            'Values': [self.instance_id]
        }])['Tags']
        return self.tags

aws = AWS_Client()
print(aws.get_tags())


Solution 1:[1]

Try mypy-boto3 mypy plugin.

You can install it via

python -m pip install 'boto3-stubs[ec2]'
import boto3
from mypy_boto3_ec2 import EC2Client


def foo(ec2_client: EC2Client) -> None:
    ...


ec2_client = boto3.client("ec2")

foo(ec2_client)  # OK

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Paweł Rubin