1- """Main module."""
2-
3- from pathlib import Path
1+ """AWS module."""
2+ from json import loads
43
54import boto3
5+ from botocore .exceptions import ClientError
66
77from ._cache import cached_fetch
8-
9-
10- # Optional: import TOML only if available
11- try :
12- import tomllib # Python 3.11+
13- except ImportError :
14- try :
15- import tomli as tomllib # Python 3.10
16- except ImportError :
17- tomllib = None
18-
19- try :
20- import tomli_w
21- except ImportError :
22- tomli_w = None
23-
8+ from ._constants import DEFAULT_CACHE_TTL , DEFAULT_AWS_REGION
249
2510# Module-level caches
26- _secret_cache = {}
27- _param_cache = {}
2811_boto_clients = {}
2912
30- # TOML cache file (optional)
31- _CACHE_FILE = Path .home () / '.secrets_cache.toml'
3213
33-
34- def get_boto_client (service : str , region : str = 'us-east-1' ):
14+ def get_boto_client (service : str , region : str ):
3515 """Cache boto3 clients per service/region."""
3616 key = service , region
3717 _client = _boto_clients .get (key )
@@ -40,49 +20,65 @@ def get_boto_client(service: str, region: str = 'us-east-1'):
4020 return _client
4121
4222
43- def _read_toml_cache ():
44- if not tomllib or not _CACHE_FILE .exists ():
45- return {}
46- with _CACHE_FILE .open ('rb' ) as f :
47- return tomllib .load (f )
48-
49-
50- def _write_toml_cache (data ):
51- if not tomli_w or not _CACHE_FILE .parent .exists ():
52- return
53- with _CACHE_FILE .open ('wb' ) as f :
54- tomli_w .dump (data , f )
23+ def get_secret (name : str ,
24+ region : str = DEFAULT_AWS_REGION ,
25+ ttl : int = DEFAULT_CACHE_TTL ,
26+ force_refresh : bool = False ,
27+ raw : bool = False ) -> str | bytes | dict :
28+ """Get secret from AWS Secrets Manager with optional caching."""
29+ value = cached_fetch ('secretsmanager' , name , region , _fetch_secret , ttl , force_refresh )
5530
31+ if raw :
32+ return value
5633
57- def get_secret (name : str , region : str = 'us-east-1' , ttl : int = 7 * 24 * 3600 ):
58- """Get secret from AWS Secrets Manager with optional caching."""
59- return cached_fetch (_secret_cache , name , region , _fetch_secret , ttl )
34+ # Try to parse JSON (most Secrets Manager use case)
35+ try :
36+ return loads (value )
37+ except (ValueError , TypeError ):
38+ return value
6039
6140
62- def get_param (name : str , region : str = 'us-east-1' , ttl : int = 7 * 24 * 3600 ):
41+ def get_param (name : str ,
42+ region : str = DEFAULT_AWS_REGION ,
43+ ttl : int = DEFAULT_CACHE_TTL ,
44+ force_refresh : bool = False ) -> str :
6345 """Get parameter from AWS SSM Parameter Store with optional caching."""
64- return cached_fetch (_param_cache , name , region , _fetch_param , ttl )
46+ return cached_fetch ('ssm' , name , region , _fetch_param , ttl , force_refresh )
6547
6648
67- def _fetch_secret (name : str , region : str = 'us-east-1' ) :
49+ def _fetch_secret (name : str , region : str ) -> str | bytes | None :
6850 client = get_boto_client ('secretsmanager' , region )
69- resp = client .get_secret_value (SecretId = name )
70- value = resp ['SecretString' ]
7151
72- # Update local TOML cache if available
73- data = _read_toml_cache ()
74- data [name ] = value
75- _write_toml_cache (data )
76- return value
77-
78-
79- def _fetch_param (name : str , region : str = 'us-east-1' ):
52+ try :
53+ resp = client .get_secret_value (
54+ SecretId = name
55+ )
56+ except ClientError as e :
57+ # For a list of exceptions thrown, see
58+ # https://docs.aws.amazon.com/secretsmanager/latest/apireference/API_GetSecretValue.html
59+ err_code = e .response ['Error' ]['Code' ]
60+ if err_code == 'ResourceNotFoundException' :
61+ print (f'The requested secret { name } was not found' )
62+ elif err_code == 'InvalidRequestException' :
63+ print ('The request was invalid due to:' , e )
64+ elif err_code == 'InvalidParameterException' :
65+ print ('The request had invalid params:' , e )
66+ elif err_code == 'DecryptionFailure' :
67+ print ('The requested secret can\' t be decrypted using the provided KMS key:' , e )
68+ elif err_code == 'InternalServiceError' :
69+ print ('An error occurred on service side:' , e )
70+ else :
71+ # Secrets Manager decrypts the secret value using the associated KMS CMK
72+ # Depending on whether the secret was a string or binary, only one of these fields will be populated
73+ if (secret := resp .get ('SecretString' )) is not None :
74+ return secret
75+ return resp ['SecretBinary' ]
76+
77+
78+ def _fetch_param (name : str , region : str ):
8079 client = get_boto_client ('ssm' , region )
80+
8181 resp = client .get_parameter (Name = name , WithDecryption = True )
82- value = resp ['Parameter' ]['Value' ]
8382
84- # Update local TOML cache if available
85- data = _read_toml_cache ()
86- data [name ] = value
87- _write_toml_cache (data )
88- return value
83+ param = resp ['Parameter' ]['Value' ]
84+ return param
0 commit comments