AWS Secrets Manager adapter for production secrets.
Implements SecretsProtocol using AWS Secrets Manager with in-memory caching.
File: aws_adapter.py → class AWSAdapter (PEP 8 naming)
Classes
AWSAdapter
Bases: BaseSecretsAdapter
Production secrets from AWS Secrets Manager.
Features
- In-memory caching (reduce API calls, cost savings)
- Hierarchical naming: /dashtam/{env}/{category}/{name}
- Automatic retry with exponential backoff (boto3 default)
Caching Strategy
- Cache secrets in memory to reduce AWS API calls
- Cost: $0.05 per 10,000 API calls
- Without cache: ~100,000 calls/month = $0.50
- With cache: ~1,000 calls/month (startup only) = $0.005
- Savings: 99% reduction in API costs
Source code in src/infrastructure/secrets/aws_adapter.py
| class AWSAdapter(BaseSecretsAdapter):
"""Production secrets from AWS Secrets Manager.
Features:
- In-memory caching (reduce API calls, cost savings)
- Hierarchical naming: /dashtam/{env}/{category}/{name}
- Automatic retry with exponential backoff (boto3 default)
Caching Strategy:
- Cache secrets in memory to reduce AWS API calls
- Cost: $0.05 per 10,000 API calls
- Without cache: ~100,000 calls/month = $0.50
- With cache: ~1,000 calls/month (startup only) = $0.005
- Savings: 99% reduction in API costs
"""
def __init__(self, environment: str, region: str = "us-east-1") -> None:
"""Initialize AWS Secrets Manager client.
Args:
environment: 'staging' or 'production'.
region: AWS region for secrets (default: us-east-1).
Raises:
ImportError: If boto3 not installed.
"""
try:
import boto3
except ImportError as e:
raise ImportError(
"boto3 required for AWS Secrets Manager. Install with: uv add boto3"
) from e
self.client = boto3.client("secretsmanager", region_name=region)
self.environment = environment
self._cache: dict[str, str] = {}
def get_secret(self, secret_path: str) -> Result[str, SecretsError]:
"""Get secret from AWS Secrets Manager.
Args:
secret_path: Path like 'database/url' or 'schwab/api_key'.
Returns:
Success(secret_value) if found in AWS.
Failure(SecretsError) if not found or access denied.
Example:
>>> adapter = AWSAdapter(environment="production")
>>> result = adapter.get_secret("database/url")
>>> # Fetches from: /dashtam/production/database/url
>>> # Success("postgresql://...")
"""
secret_id = f"/dashtam/{self.environment}/{secret_path}"
# Check cache first
if secret_id in self._cache:
return Success(value=self._cache[secret_id])
try:
response = self.client.get_secret_value(SecretId=secret_id)
secret_value = response["SecretString"]
# Cache for future calls
self._cache[secret_id] = secret_value
return Success(value=secret_value)
except self.client.exceptions.ResourceNotFoundException:
return Failure(
error=SecretsError(
code=ErrorCode.SECRET_NOT_FOUND,
message=f"Secret not found in AWS: {secret_id}",
)
)
except Exception as e:
return Failure(
error=SecretsError(
code=ErrorCode.SECRET_ACCESS_DENIED,
message=f"Failed to access AWS secret: {secret_id}",
details={"error": str(e)},
)
)
# get_secret_json() inherited from BaseSecretsAdapter
def refresh_cache(self) -> None:
"""Clear cache to force reload on next access.
Call this after rotating secrets in AWS console or Terraform.
Next get_secret() call will fetch fresh value from AWS.
Example:
>>> adapter = AWSAdapter(environment="production")
>>> adapter.refresh_cache() # Clear cache
>>> result = adapter.get_secret("database/password")
>>> # Fetches fresh from AWS
"""
self._cache.clear()
|
Functions
__init__
__init__(
environment: str, region: str = "us-east-1"
) -> None
Parameters:
| Name |
Type |
Description |
Default |
environment
|
str
|
'staging' or 'production'.
|
required
|
region
|
str
|
AWS region for secrets (default: us-east-1).
|
'us-east-1'
|
Raises:
| Type |
Description |
ImportError
|
|
Source code in src/infrastructure/secrets/aws_adapter.py
| def __init__(self, environment: str, region: str = "us-east-1") -> None:
"""Initialize AWS Secrets Manager client.
Args:
environment: 'staging' or 'production'.
region: AWS region for secrets (default: us-east-1).
Raises:
ImportError: If boto3 not installed.
"""
try:
import boto3
except ImportError as e:
raise ImportError(
"boto3 required for AWS Secrets Manager. Install with: uv add boto3"
) from e
self.client = boto3.client("secretsmanager", region_name=region)
self.environment = environment
self._cache: dict[str, str] = {}
|
get_secret
get_secret(secret_path: str) -> Result[str, SecretsError]
Get secret from AWS Secrets Manager.
Parameters:
| Name |
Type |
Description |
Default |
secret_path
|
str
|
Path like 'database/url' or 'schwab/api_key'.
|
required
|
Returns:
| Type |
Description |
Result[str, SecretsError]
|
Success(secret_value) if found in AWS.
|
Result[str, SecretsError]
|
Failure(SecretsError) if not found or access denied.
|
Example
adapter = AWSAdapter(environment="production")
result = adapter.get_secret("database/url")
Fetches from: /dashtam/production/database/url
Success("postgresql://...")
Source code in src/infrastructure/secrets/aws_adapter.py
| def get_secret(self, secret_path: str) -> Result[str, SecretsError]:
"""Get secret from AWS Secrets Manager.
Args:
secret_path: Path like 'database/url' or 'schwab/api_key'.
Returns:
Success(secret_value) if found in AWS.
Failure(SecretsError) if not found or access denied.
Example:
>>> adapter = AWSAdapter(environment="production")
>>> result = adapter.get_secret("database/url")
>>> # Fetches from: /dashtam/production/database/url
>>> # Success("postgresql://...")
"""
secret_id = f"/dashtam/{self.environment}/{secret_path}"
# Check cache first
if secret_id in self._cache:
return Success(value=self._cache[secret_id])
try:
response = self.client.get_secret_value(SecretId=secret_id)
secret_value = response["SecretString"]
# Cache for future calls
self._cache[secret_id] = secret_value
return Success(value=secret_value)
except self.client.exceptions.ResourceNotFoundException:
return Failure(
error=SecretsError(
code=ErrorCode.SECRET_NOT_FOUND,
message=f"Secret not found in AWS: {secret_id}",
)
)
except Exception as e:
return Failure(
error=SecretsError(
code=ErrorCode.SECRET_ACCESS_DENIED,
message=f"Failed to access AWS secret: {secret_id}",
details={"error": str(e)},
)
)
|
refresh_cache
Clear cache to force reload on next access.
Call this after rotating secrets in AWS console or Terraform.
Next get_secret() call will fetch fresh value from AWS.
Example
adapter = AWSAdapter(environment="production")
adapter.refresh_cache() # Clear cache
result = adapter.get_secret("database/password")
Fetches fresh from AWS
Source code in src/infrastructure/secrets/aws_adapter.py
| def refresh_cache(self) -> None:
"""Clear cache to force reload on next access.
Call this after rotating secrets in AWS console or Terraform.
Next get_secret() call will fetch fresh value from AWS.
Example:
>>> adapter = AWSAdapter(environment="production")
>>> adapter.refresh_cache() # Clear cache
>>> result = adapter.get_secret("database/password")
>>> # Fetches fresh from AWS
"""
self._cache.clear()
|