Common Patterns

This guide shows what infrahouse-core simplifies compared to raw boto3, and documents deprecated constructor parameters.

From boto3 to infrahouse-core

infrahouse-core is a convenience layer on top of boto3, not a replacement. boto3 is still the engine underneath – infrahouse-core simplifies common operations by providing sensible defaults, role assumption, and property-based access to resource attributes.

You don’t need to stop using boto3. Use infrahouse-core where it saves you boilerplate, and drop down to boto3 directly when you need full control.

EC2 instance metadata

Before (boto3):

import boto3

ec2 = boto3.client("ec2")
response = ec2.describe_instances(InstanceIds=["i-abc123"])
instance = response["Reservations"][0]["Instances"][0]
state = instance["State"]["Name"]
private_ip = instance.get("PrivateIpAddress")
tags = {t["Key"]: t["Value"] for t in instance.get("Tags", [])}

After (infrahouse-core):

from infrahouse_core.aws.ec2_instance import EC2Instance

instance = EC2Instance("i-abc123", region="us-east-1")
state = instance.state
private_ip = instance.private_ip
tags = instance.tags

Running commands via SSM

Before (boto3):

import boto3
from time import sleep

ssm = boto3.client("ssm")
response = ssm.send_command(
    InstanceIds=["i-abc123"],
    DocumentName="AWS-RunShellScript",
    Parameters={"commands": ["hostname"]},
)
command_id = response["Command"]["CommandId"]
# ... poll for completion, handle retries, parse output ...

After (infrahouse-core):

instance = EC2Instance("i-abc123", region="us-east-1")
exit_code, stdout, stderr = instance.execute_command("hostname")

Secrets Manager

Before (boto3):

import boto3

client = boto3.client("secretsmanager")
response = client.get_secret_value(SecretId="my-secret")
value = response["SecretString"]

After (infrahouse-core):

from infrahouse_core.aws.secretsmanager import Secret

secret = Secret("my-secret", region="us-east-1")
value = secret.value

The Secret class also provides ensure_present() and ensure_absent() for idempotent create/delete operations.

DynamoDB distributed locking

Before (boto3) – manual conditional writes, retry loops, TTL management.

After (infrahouse-core):

from infrahouse_core.aws.dynamodb import DynamoDBTable

table = DynamoDBTable("my-locks", region="us-east-1")
with table.lock("deploy-service", timeout=60, ttl=600):
    run_deployment()

Cross-account access

All resource classes accept role_arn for cross-account operations:

secret = Secret("my-secret", role_arn="arn:aws:iam::123456789012:role/Reader")
instance = EC2Instance("i-abc123", role_arn="arn:aws:iam::123456789012:role/Admin")

Breaking Changes

GitHubActions: runners / find_runners_by_label are now iterators

Changed in: v1.0.0

GitHubActions.runners and GitHubActions.find_runners_by_label() now return Iterator[GitHubActionsRunner] instead of List[GitHubActionsRunner]. Pages are fetched from the GitHub API lazily as the iterator advances, keeping memory usage bounded to a single API page. This fixes OOM crashes in 128 MB AWS Lambda functions running against large organizations.

Code that iterated over the results keeps working unchanged:

for runner in gha.runners:
    ...

for runner in gha.find_runners_by_label("instance_id:i-abc123"):
    ...

Code that relied on list semantics must wrap with list():

Before:

all_runners = gha.runners
count = len(all_runners)
first = all_runners[0]

matches = gha.find_runners_by_label("alpha")
if matches == []:
    ...

After:

all_runners = list(gha.runners)
count = len(all_runners)
first = all_runners[0]

matches = list(gha.find_runners_by_label("alpha"))
if not matches:
    ...

Deprecated Parameters

EC2Instance: ec2_client and ssm_client

Deprecated in: v0.20+

The ec2_client and ssm_client constructor parameters are deprecated. Use role_arn instead – the class creates its own clients internally.

Before (deprecated):

import boto3
from infrahouse_core.aws.ec2_instance import EC2Instance

ec2_client = boto3.client("ec2", region_name="us-east-1")
ssm_client = boto3.client("ssm", region_name="us-east-1")
instance = EC2Instance("i-abc123", ec2_client=ec2_client, ssm_client=ssm_client)

After:

from infrahouse_core.aws.ec2_instance import EC2Instance

instance = EC2Instance("i-abc123", region="us-east-1")

# For cross-account access:
instance = EC2Instance("i-abc123", role_arn="arn:aws:iam::123456789012:role/MyRole")