π Quick Start GuideΒΆ
Transform Your boto3 Experience with Type-Safe DataclassesΒΆ
Welcome to boto3-dataclass! This library transforms boring boto3 dictionaries into beautiful, type-safe dataclasses with full autocomplete support. Say goodbye to response['Key']['SubKey'] and hello to response.Key.SubKey with full IDE support!
π‘ What Youβll GetΒΆ
β Full autocomplete in your IDE
β Type safety with mypy
β Easy dot notation instead of dictionary access
β Zero performance overhead with lazy loading
β 100% compatible with existing boto3 code
π¦ InstallationΒΆ
Install the service you need (e.g., for IAM, S3):
pip install boto3-dataclass[iam,s3]
Or match with your boto3 version
pip install "boto3-dataclass[iam,s3]>=1.40.0,<1.41.0"
Or install everything at once:
pip install boto3-dataclass[all] # Installs all AWS services
β‘ 30-Second DemoΒΆ
Letβs see the magic in action with AWS IAM:
Before (Standard boto3)ΒΆ
import boto3
# β No type hints, no autocomplete
client = boto3.client("iam")
response = client.get_role(RoleName="my-role")
# β Dictionary access - error-prone and no autocomplete
role_name = response["Role"]["RoleName"]
request_id = response["ResponseMetadata"]["RequestId"]
After (With boto3-dataclass)ΒΆ
from boto3_dataclass_iam import iam_caster
import boto3
# β
Get your response as usual
client = boto3.client("iam")
response = client.get_role(RoleName="my-role")
# β
Transform it to a type-safe dataclass
typed_response = iam_caster.get_role(response)
# β
Enjoy autocomplete and type safety!
role_name = typed_response.Role.RoleName
request_id = typed_response.ResponseMetadata.RequestId
π οΈ Complete Working ExampleΒΆ
Hereβs a complete example showing common patterns:
[15]:
aws_profile = "bmt_app_dev_us_east_1"
aws_region = "us-east-1"
[17]:
# Step 1: Import the caster for your service
from boto3_dataclass_iam import iam_caster
import boto3
# Step 2: Create your boto3 client as usual
boto_ses = boto3.Session(profile_name=aws_profile, region_name=aws_region)
client = boto_ses.client("iam")
# Step 3: Make your API calls as normal
response = client.get_role(RoleName="lambda-power-user-role")
# Step 4: Convert to dataclass for better experience
role_info = iam_caster.get_role(response)
# Step 5: Enjoy type-safe access with autocomplete!
print(f"Role Name: {role_info.Role.RoleName}")
print(f"Created: {role_info.Role.CreateDate}")
print(f"Request ID: {role_info.ResponseMetadata.RequestId}")
# =============================================================================
# AVAILABLE ATTRIBUTES WITH AUTOCOMPLETION
# =============================================================================
# Top-level response attributes:
# response.ResponseMetadata - AWS response metadata
# response.Role - The IAM role details
# response.boto3_raw_data - Original raw response (for debugging)
# Role-specific attributes (all with IDE autocompletion):
# response.Role.Path - Role path
# response.Role.RoleName - Name of the role
# response.Role.RoleId - Unique role identifier
# response.Role.Arn - Amazon Resource Name
# response.Role.CreateDate - When the role was created
# response.Role.AssumeRolePolicyDocument - Trust policy document
# response.Role.Description - Role description (if set)
# response.Role.MaxSessionDuration - Maximum session duration
# response.Role.PermissionsBoundary - Permissions boundary (if set)
# response.Role.Tags - Role tags
# response.Role.RoleLastUsed - Last usage information
# =============================================================================
# BENEFITS
# =============================================================================
# β IDE autocompletion and IntelliSense
# β Type safety and error prevention
# β Better code readability
# β Reduced runtime errors from typos
# β Access to original raw data when needed
Role Name: lambda-power-user-role
Created: 2023-05-11 04:07:16+00:00
Request ID: 95dd9d22-e4ad-471b-8682-02bf8c465fff
Pro Tip: Using with boto-session-managerΒΆ
For even better type hints on your boto3 clients, combine with boto-session-manager:
[19]:
from boto_session_manager import BotoSesManager
# Get fully typed boto3 clients
bsm = BotoSesManager(profile_name=aws_profile, region_name=aws_region)
iam_client = bsm.iam_client # β
This has full type hints!
# Make calls and convert responses
response = iam_client.get_role(RoleName="lambda-power-user-role")
role_info = iam_caster.get_role(response) # β
Full autocomplete here too!
# Enjoy type-safe access with autocomplete!
print(f"Role Name: {role_info.Role.RoleName}")
print(f"Created: {role_info.Role.CreateDate}")
print(f"Request ID: {role_info.ResponseMetadata.RequestId}")
Role Name: lambda-power-user-role
Created: 2023-05-11 04:07:16+00:00
Request ID: ab7910de-e09f-46bb-bb32-4252ce4f5afb
π― How It Works - The Complete PictureΒΆ
Understanding the boto3-dataclass ecosystem is key to using it effectively. Hereβs how all the pieces fit together:
π§© The Three-Layer ArchitectureΒΆ
Layer 1: Standard boto3
Each AWS Service has a service name (the string you use to create clients)
For example, IAM uses:
iam_client = boto3.client('iam')
Layer 2: mypy-boto3 Type Stubs
Each AWS Service has a Python stub-only package like
mypy_boto3_{service_name}For example: mypy-boto3-iam provides TypedDict-style type hints
Limitation: These stubs give you type checking but NO autocomplete for attribute access
Layer 3: boto3-dataclass (This Library!)
Each AWS Service gets a dataclass package:
boto3_dataclass_{service_name}For example: boto3-dataclass-iam
Benefit: Converts response dictionaries to user-friendly dataclasses with full autocomplete and type hints
π The Conversion PatternΒΆ
The magic happens with a simple, predictable pattern:
When you have:
A service client:
{service_name}_clientfrom boto3A response dictionary from:
client.method_name(...)
You get:
A corresponding caster:
{service_name}_casterfromboto3_dataclass_{service_name}A conversion method:
caster.method_name(response)that transforms dictionaries to dataclasses
π Quick ReferenceΒΆ
# The Universal Pattern for ANY AWS Service:
# 1. Create boto3 client
client = boto3.client("{service_name}")
# 2. Import corresponding caster
from boto3_dataclass_{service_name} import {service_name}_caster
# 3. Make API calls as normal
response = client.method_name(...)
# 4. Convert to dataclass
typed_response = {service_name}_caster.method_name(response)
# 5. Enjoy autocomplete and type safety!
π― Why This Design WorksΒΆ
Predictable: Same pattern across all 200+ AWS services
Optional: Use dataclass conversion only when you need it
Compatible: Works with existing boto3 code without changes
Performance: Zero overhead until you convert responses
ποΈ Available ServicesΒΆ
We support all AWS services that have mypy-boto3 stubs!
π Behind the Scenes (Optional Reading)ΒΆ
Curious how the magic works? Hereβs the technical overview:
The ArchitectureΒΆ
Our system automatically generates dataclasses from mypy-boto3 type stubs:
Parser: Reads
mypy_boto3_{service_name}/type_defs.pyifilesGenerator: Creates dataclass definitions in
boto3_dataclass_{service_name}/type_defs.pyCaster: Provides conversion methods in
boto3_dataclass_{service_name}/caster.py
Smart Design ChoicesΒΆ
Lazy Loading: Attributes are loaded only when accessed (zero overhead)
Raw Data Access: Original dict always available via
.boto3_raw_dataType Safety: Full mypy compatibility with proper type annotations
Zero Magic: No monkey patching or runtime modifications
Example Generated CodeΒΆ
1. Dataclass Definitions (``type_defs.py``) Hereβs what gets generated for an IAM Role:
[20]:
# content of boto3_dataclass_iam/type_defs.py
# -*- coding: utf-8 -*-
import typing as T
import dataclasses
from functools import cached_property
if T.TYPE_CHECKING: # pragma: no cover
from mypy_boto3_iam import type_defs
def field(name: str):
def getter(self):
return self.boto3_raw_data[name]
return cached_property(getter)
@dataclasses.dataclass(frozen=True)
class ResponseMetadata:
boto3_raw_data: "type_defs.ResponseMetadataTypeDef" = dataclasses.field()
RequestId = field("RequestId")
HTTPStatusCode = field("HTTPStatusCode")
HTTPHeaders = field("HTTPHeaders")
RetryAttempts = field("RetryAttempts")
HostId = field("HostId")
@classmethod
def make_one(cls, boto3_raw_data: T.Optional["type_defs.ResponseMetadataTypeDef"]):
if boto3_raw_data is None:
return None
return cls(boto3_raw_data=boto3_raw_data)
@classmethod
def make_many(
cls,
boto3_raw_data_list: T.Optional[
T.Iterable["type_defs.ResponseMetadataTypeDef"]
],
):
if boto3_raw_data_list is None:
return None
return [
cls(boto3_raw_data=boto3_raw_data) for boto3_raw_data in boto3_raw_data_list
]
# More dataclass model ...
@dataclasses.dataclass(frozen=True)
class Role:
boto3_raw_data: "type_defs.RoleTypeDef" = dataclasses.field()
Path = field("Path")
RoleName = field("RoleName")
RoleId = field("RoleId")
Arn = field("Arn")
CreateDate = field("CreateDate")
AssumeRolePolicyDocument = field("AssumeRolePolicyDocument")
Description = field("Description")
MaxSessionDuration = field("MaxSessionDuration")
@cached_property
def PermissionsBoundary(self): # pragma: no cover
return AttachedPermissionsBoundary.make_one(
self.boto3_raw_data["PermissionsBoundary"]
)
@cached_property
def Tags(self): # pragma: no cover
return Tag.make_many(self.boto3_raw_data["Tags"])
@cached_property
def RoleLastUsed(self): # pragma: no cover
return RoleLastUsed.make_one(self.boto3_raw_data["RoleLastUsed"])
@classmethod
def make_one(cls, boto3_raw_data: T.Optional["type_defs.RoleTypeDef"]):
if boto3_raw_data is None:
return None
return cls(boto3_raw_data=boto3_raw_data)
@classmethod
def make_many(
cls, boto3_raw_data_list: T.Optional[T.Iterable["type_defs.RoleTypeDef"]]
):
if boto3_raw_data_list is None:
return None
return [
cls(boto3_raw_data=boto3_raw_data) for boto3_raw_data in boto3_raw_data_list
]
@dataclasses.dataclass(frozen=True)
class GetRoleResponse:
boto3_raw_data: "type_defs.GetRoleResponseTypeDef" = dataclasses.field()
@cached_property
def Role(self): # pragma: no cover
return Role.make_one(self.boto3_raw_data["Role"])
@cached_property
def ResponseMetadata(self): # pragma: no cover
return ResponseMetadata.make_one(self.boto3_raw_data["ResponseMetadata"])
@classmethod
def make_one(cls, boto3_raw_data: T.Optional["type_defs.GetRoleResponseTypeDef"]):
if boto3_raw_data is None:
return None
return cls(boto3_raw_data=boto3_raw_data)
@classmethod
def make_many(
cls,
boto3_raw_data_list: T.Optional[T.Iterable["type_defs.GetRoleResponseTypeDef"]],
):
if boto3_raw_data_list is None:
return None
return [
cls(boto3_raw_data=boto3_raw_data) for boto3_raw_data in boto3_raw_data_list
]
2. Caster Module (``caster.py``)
Then we have a boto3_dataclass_{service_name}/caster.py module for each service like this:
[14]:
# -*- coding: utf-8 -*-
import typing as T
from . import type_defs as dc_td
if T.TYPE_CHECKING: # pragma: no cover
from mypy_boto3_iam import type_defs as bs_td
class IAMCaster:
def get_role(
self,
res: "bs_td.GetRoleResponseTypeDef",
) -> "dc_td.GetRoleResponse":
return dc_td.GetRoleResponse.make_one(res)
# More client method ...
iam_caster = IAMCaster()
The Caster Magic:
The caster class mimics the exact same method names as the boto3 client
When you call
client.get_role(...), you usecaster.get_role(response)No memorization needed - if the client has the method, so does the caster!
Each method converts the raw response dictionary to a type-safe dataclass
π Next StepsΒΆ
Ready to upgrade your boto3 experience?
Install the service package you need:
pip install boto3-dataclass[{service}]Import the caster:
from boto3_dataclass_{service} import {service}_casterConvert your responses:
typed_response = caster.method_name(response)Enjoy the autocomplete and type safety!
Questions or issues? Check out our GitHub repository or documentation.
Happy coding! πβ¨
[ ]: