$npx -y skills add aws/agent-toolkit-for-aws --skill aws-sdk-python-usageAWS SDK for Python (boto3/botocore) development patterns. You MUST use this skill when writing Python code that uses AWS services via boto3 or botocore. This includes creating service clients or resources, configuring sessions and credentials, handling errors with ClientError, us
| 1 | > Do not use emojis in any code, comments, or output when this skill is active. |
| 2 | |
| 3 | # AWS SDK for Python (boto3) |
| 4 | |
| 5 | boto3 is the high-level Python SDK for AWS. It wraps botocore (the low-level |
| 6 | SDK) and provides two distinct interfaces: **clients** (low-level, 1:1 API |
| 7 | mapping) and **resources** (high-level, object-oriented). Understanding which to |
| 8 | use and when is essential. |
| 9 | |
| 10 | ## Client vs Resource |
| 11 | |
| 12 | **Clients** map directly to AWS service APIs. Every service has a client. |
| 13 | Responses are plain dicts. |
| 14 | |
| 15 | **Resources** provide an object-oriented interface with attributes and actions. |
| 16 | Only some services have resources (S3, DynamoDB, EC2, IAM, SQS, SNS, |
| 17 | CloudFormation, CloudWatch, Glacier). Resources auto-marshal types (especially |
| 18 | useful for DynamoDB). |
| 19 | |
| 20 | ```python |
| 21 | import boto3 |
| 22 | |
| 23 | # Client - low-level, all services |
| 24 | s3_client = boto3.client("s3") |
| 25 | response = s3_client.list_buckets() |
| 26 | buckets = response["Buckets"] # plain dicts |
| 27 | |
| 28 | # Resource - high-level, select services |
| 29 | s3_resource = boto3.resource("s3") |
| 30 | for bucket in s3_resource.buckets.all(): |
| 31 | print(bucket.name) # attribute access, not dict keys |
| 32 | ``` |
| 33 | |
| 34 | Use clients when you need full API coverage or the service has no resource |
| 35 | interface. Use resources when they exist and simplify your code (especially |
| 36 | DynamoDB and S3). |
| 37 | |
| 38 | ## Session and Client Creation |
| 39 | |
| 40 | ```python |
| 41 | import boto3 |
| 42 | |
| 43 | # Default session implicitly created |
| 44 | client = boto3.client("s3") |
| 45 | resource = boto3.resource("dynamodb") |
| 46 | |
| 47 | # Explicit session use when you need to customize how |
| 48 | # clients are created, use an explicit profile, etc. |
| 49 | session = boto3.Session( |
| 50 | profile_name="my-profile", |
| 51 | region_name="us-west-2", |
| 52 | ) |
| 53 | client = session.client("s3") |
| 54 | ``` |
| 55 | |
| 56 | Do not create clients inside loops - reuse a single client instance. Clients |
| 57 | are thread safe and can be shared across threads once they're instantiated. |
| 58 | |
| 59 | ## Making API Calls |
| 60 | |
| 61 | ```python |
| 62 | # Client - pass parameters as keyword arguments, get dicts back |
| 63 | response = client.get_object(Bucket="my-bucket", Key="my-key") |
| 64 | data = response["Body"].read() |
| 65 | |
| 66 | # Resource - use object methods and attributes |
| 67 | obj = s3_resource.Object("my-bucket", "my-key") |
| 68 | response = obj.get() |
| 69 | data = response["Body"].read() |
| 70 | ``` |
| 71 | |
| 72 | Parameter names match the exact casing of the AWS API, |
| 73 | which is typically PascalCase, not snake\_case. |
| 74 | |
| 75 | ## Error Handling |
| 76 | |
| 77 | Only catch exceptions when you have something actionable to do - return a |
| 78 | fallback value, retry, take a different code path. Catching an exception just to |
| 79 | print it and swallow it is wrong: it hides the real error and prevents callers |
| 80 | from reacting. Let exceptions propagate by default. |
| 81 | |
| 82 | When you do catch, prefer typed exceptions on the client over generic |
| 83 | `ClientError` with string code matching through the `client.exceptions` |
| 84 | attribute: |
| 85 | |
| 86 | ```python |
| 87 | lambda_client = boto3.client("lambda") |
| 88 | |
| 89 | def get_function_config(name: str) -> dict | None: |
| 90 | """Return function configuration, or None if it doesn't exist.""" |
| 91 | try: |
| 92 | return lambda_client.get_function_configuration(FunctionName=name) |
| 93 | except lambda_client.exceptions.ResourceNotFoundException: |
| 94 | return None # actionable: convert missing function to None |
| 95 | # Everything else propagates - caller or main() handles it |
| 96 | ``` |
| 97 | |
| 98 | Use generic `ClientError` only as a catch-all in a top-level error handler, not |
| 99 | in business logic functions. It lives in botocore, not boto3: |
| 100 | |
| 101 | ```python |
| 102 | from botocore.exceptions import ClientError |
| 103 | |
| 104 | def main() -> int: |
| 105 | try: |
| 106 | result = do_the_work() |
| 107 | print(result) |
| 108 | return 0 |
| 109 | except ClientError as e: |
| 110 | print(f"Error: {e}", file=sys.stderr) |
| 111 | return 1 |
| 112 | ``` |
| 113 | |
| 114 | For the full error hierarchy and botocore exceptions, see `references/error-handling.md`. |
| 115 | |
| 116 | ## Script Structure |
| 117 | |
| 118 | When asked to write a script that uses `boto3` or `botocore`, keep `if __name__ |
| 119 | == "__main__"` to a single function call. Argument parsing, error presentation, |
| 120 | and exit codes belong in `main()`, not scattered across business logic |
| 121 | functions: |
| 122 | |
| 123 | ```python |
| 124 | def main() -> int: |
| 125 | parser = argparse.ArgumentParser() |
| 126 | parser.add_argument("bucket") |
| 127 | args = parser.parse_args() |
| 128 | |
| 129 | try: |
| 130 | do_the_work(args.bucket) |
| 131 | return 0 |
| 132 | except ClientError as e: |
| 133 | print(f"Error: {e}", file=sys.stderr) |
| 134 | return 1 |
| 135 | |
| 136 | if __name__ == "__main__": |
| 137 | sys.exit(main()) |
| 138 | ``` |
| 139 | |
| 140 | Never call `sys.exit()` from a business logic function -- it makes the function |
| 141 | untestable and unusable as a library. Raise an exception or return an e |