Skip to content

Commit c2107d7

Browse files
fix: InjectSecretString clobbering self arg in class methods (#24) (#58)
1 parent c7e5fb1 commit c2107d7

File tree

4 files changed

+89
-31
lines changed

4 files changed

+89
-31
lines changed

.github/workflows/python-package.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ jobs:
3737
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
3838
- name: Lint with PyLint
3939
run: pylint --rcfile=.pylintrc src/aws_secretsmanager_caching
40+
- name: Check formatting with Ruff
41+
uses: astral-sh/ruff-action@v3
4042
- name: Test with pytest
4143
run: |
4244
pytest test/unit/

README.md

Lines changed: 57 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -11,47 +11,56 @@ The AWS Secrets Manager Python caching client enables in-process caching of secr
1111

1212
To use this client you must have:
1313

14-
* Python 3.8 or newer. Use of Python versions 3.7 or older are not supported.
15-
* An Amazon Web Services (AWS) account to access secrets stored in AWS Secrets Manager.
16-
* **To create an AWS account**, go to [Sign In or Create an AWS Account](https://portal.aws.amazon.com/gp/aws/developer/registration/index.html) and then choose **I am a new user.** Follow the instructions to create an AWS account.
14+
- Python 3.8 or newer. Use of Python versions 3.7 or older are not supported.
15+
- An Amazon Web Services (AWS) account to access secrets stored in AWS Secrets Manager.
1716

18-
* **To create a secret in AWS Secrets Manager**, go to [Creating Secrets](https://docs.aws.amazon.com/secretsmanager/latest/userguide/manage_create-basic-secret.html) and follow the instructions on that page.
17+
- **To create an AWS account**, go to [Sign In or Create an AWS Account](https://portal.aws.amazon.com/gp/aws/developer/registration/index.html) and then choose **I am a new user.** Follow the instructions to create an AWS account.
1918

20-
* This library makes use of botocore, the low-level core functionality of the boto3 SDK. For more information on boto3 and botocore, please review the [AWS SDK for Python](https://aws.amazon.com/sdk-for-python/) and [Botocore](https://botocore.amazonaws.com/v1/documentation/api/latest/index.html) documentation.
19+
- **To create a secret in AWS Secrets Manager**, go to [Creating Secrets](https://docs.aws.amazon.com/secretsmanager/latest/userguide/manage_create-basic-secret.html) and follow the instructions on that page.
20+
21+
- This library makes use of botocore, the low-level core functionality of the boto3 SDK. For more information on boto3 and botocore, please review the [AWS SDK for Python](https://aws.amazon.com/sdk-for-python/) and [Botocore](https://botocore.amazonaws.com/v1/documentation/api/latest/index.html) documentation.
2122

2223
### Dependencies
24+
2325
This library requires the following standard dependencies:
24-
* botocore
25-
* setuptools_scm
26-
* setuptools
26+
27+
- botocore
28+
- setuptools_scm
29+
- setuptools
2730

2831
For development and testing purposes, this library requires the following additional dependencies:
29-
* pytest
30-
* pytest-cov
31-
* pytest-sugar
32-
* codecov
33-
* pylint
34-
* sphinx
35-
* flake8
36-
* tox
32+
33+
- pytest
34+
- pytest-cov
35+
- pytest-sugar
36+
- codecov
37+
- pylint
38+
- sphinx
39+
- flake8
40+
- tox
3741

3842
Please review the `requirements.txt` and `dev-requirements.txt` file for specific version requirements.
3943

4044
### Installation
45+
4146
Installing the latest release via **pip**:
47+
4248
```bash
4349
$ pip install aws-secretsmanager-caching
4450
```
4551

4652
Installing the latest development release:
53+
4754
```bash
4855
$ git clone https://github.com/aws/aws-secretsmanager-caching-python.git
4956
$ cd aws-secretsmanager-caching-python
5057
$ python setup.py install
5158
```
5259

5360
### Development
61+
5462
#### Getting Started
63+
5564
Assuming that you have Python and virtualenv installed, set up your environment and install the required dependencies like this instead of the `pip install aws_secretsmanager_caching` defined above:
5665

5766
```bash
@@ -64,25 +73,35 @@ $ pip install -r requirements.txt -r dev-requirements.txt
6473
$ pip install -e .
6574
```
6675

76+
**NOTE:** Please use [Ruff](https://docs.astral.sh/ruff/formatter/) for formatting.
77+
6778
#### Running Tests
79+
6880
You can run tests in all supported Python versions using tox. By default, it will run all of the unit and integration tests, but you can also specify your own arguments to past to `pytest`.
81+
6982
```bash
7083
$ tox # runs integ/unit tests, flake8 tests and pylint tests
7184
$ tox -- test/unit/test_decorators.py # runs specific test file
7285
$ tox -e py37 -- test/integ/ # runs specific test directory
7386
```
7487

7588
#### Documentation
89+
7690
You can locally-generate the Sphinx-based documentation via:
91+
7792
```bash
7893
$ tox -e docs
7994
```
95+
8096
Which will subsequently be viewable at `file://${CLONE_DIR}/.tox/docs_out/index.html`
8197

8298
### Usage
99+
83100
Using the client consists of the following steps:
84-
1. Instantiate the client while optionally passing in a `SecretCacheConfig()` object to the `config` parameter. You can also pass in an existing `botocore.client.BaseClient` client to the client parameter.
101+
102+
1. Instantiate the client while optionally passing in a `SecretCacheConfig()` object to the `config` parameter. You can also pass in an existing `botocore.client.BaseClient` client to the client parameter.
85103
2. Request the secret from the client instance.
104+
86105
```python
87106
import botocore
88107
import botocore.session
@@ -96,19 +115,24 @@ secret = cache.get_secret_string('mysecret')
96115
```
97116

98117
#### Cache Configuration
118+
99119
You can configure the cache config object with the following parameters:
100-
* `max_cache_size` - The maximum number of secrets to cache. The default value is `1024`.
101-
* `exception_retry_delay_base` - The number of seconds to wait after an exception is encountered and before retrying the request. The default value is `1`.
102-
* `exception_retry_growth_factor` - The growth factor to use for calculating the wait time between retries of failed requests. The default value is `2`.
103-
* `exception_retry_delay_max` - The maximum amount of time in seconds to wait between failed requests. The default value is `3600`.
104-
* `default_version_stage` - The default version stage to request. The default value is `'AWSCURRENT'`
105-
* `secret_refresh_interval` - The number of seconds to wait between refreshing cached secret information. The default value is `3600.0`.
106-
* `secret_cache_hook` - An implementation of the SecretCacheHook abstract class. The default value is `None`.
120+
121+
- `max_cache_size` - The maximum number of secrets to cache. The default value is `1024`.
122+
- `exception_retry_delay_base` - The number of seconds to wait after an exception is encountered and before retrying the request. The default value is `1`.
123+
- `exception_retry_growth_factor` - The growth factor to use for calculating the wait time between retries of failed requests. The default value is `2`.
124+
- `exception_retry_delay_max` - The maximum amount of time in seconds to wait between failed requests. The default value is `3600`.
125+
- `default_version_stage` - The default version stage to request. The default value is `'AWSCURRENT'`
126+
- `secret_refresh_interval` - The number of seconds to wait between refreshing cached secret information. The default value is `3600.0`.
127+
- `secret_cache_hook` - An implementation of the SecretCacheHook abstract class. The default value is `None`.
107128

108129
#### Decorators
130+
109131
The library also includes several decorator functions to wrap existing function calls with SecretString-based secrets:
110-
* `@InjectedKeywordedSecretString` - This decorator expects the secret id and cache as the first and second arguments, with subsequent arguments mapping a parameter key from the function that is being wrapped to a key in the secret. The secret being retrieved from the cache must contain a SecretString and that string must be JSON-based.
111-
* `@InjectSecretString` - This decorator also expects the secret id and cache as the first and second arguments. However, this decorator simply returns the result of the cache lookup directly to the first argument of the wrapped function. The secret does not need to be JSON-based but it must contain a SecretString.
132+
133+
- `@InjectedKeywordedSecretString` - This decorator expects the secret id and cache as the first and second arguments, with subsequent arguments mapping a parameter key from the function that is being wrapped to a key in the secret. The secret being retrieved from the cache must contain a SecretString and that string must be JSON-based.
134+
- `@InjectSecretString` - This decorator also expects the secret id and cache as the first and second arguments. However, this decorator simply returns the result of the cache lookup directly to the first argument of the wrapped function. The secret does not need to be JSON-based but it must contain a SecretString.
135+
112136
```python
113137
from aws_secretsmanager_caching import SecretCache
114138
from aws_secretsmanager_caching import InjectKeywordedSecretString, InjectSecretString
@@ -127,10 +151,13 @@ def function_to_be_decorated(arg1, arg2, arg3):
127151
```
128152

129153
## Getting Help
154+
130155
Please use these community resources for getting help:
131-
* Ask a question on [Stack Overflow](https://stackoverflow.com/) and tag it with [aws-secrets-manager](https://stackoverflow.com/questions/tagged/aws-secrets-manager).
132-
* Open a support ticket with [AWS Support](https://console.aws.amazon.com/support/home#/)
133-
* If it turns out that you may have found a bug, or have a feature request, please [open an issue](https://github.com/aws/aws-secretsmanager-caching-python/issues/new).
156+
157+
- Ask a question on [Stack Overflow](https://stackoverflow.com/) and tag it with [aws-secrets-manager](https://stackoverflow.com/questions/tagged/aws-secrets-manager).
158+
- Open a support ticket with [AWS Support](https://console.aws.amazon.com/support/home#/)
159+
- If it turns out that you may have found a bug, or have a feature request, please [open an issue](https://github.com/aws/aws-secretsmanager-caching-python/issues/new).
160+
134161
## License
135162

136-
This library is licensed under the Apache 2.0 License.
163+
This library is licensed under the Apache 2.0 License.

src/aws_secretsmanager_caching/decorators.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"""Decorators for use with caching library"""
1414

1515
import json
16+
from functools import wraps
1617

1718

1819
class InjectSecretString:
@@ -41,13 +42,21 @@ def __call__(self, func):
4142
:return The function with the injected argument.
4243
"""
4344

45+
# Using functools.wraps preserves the metadata of the wrapped function
46+
@wraps(func)
4447
def _wrapped_func(*args, **kwargs):
4548
"""
4649
Internal function to execute wrapped function
4750
"""
4851
secret = self.cache.get_secret_string(secret_id=self.secret_id)
4952

50-
return func(secret, *args, **kwargs)
53+
# Prevent clobbering self arg in class methods
54+
if args and hasattr(args[0].__class__, func.__name__):
55+
new_args = (args[0], secret) + args[1:]
56+
else:
57+
new_args = (secret,) + args
58+
59+
return func(*new_args, **kwargs)
5160

5261
return _wrapped_func
5362

@@ -83,6 +92,7 @@ def __call__(self, func):
8392
:return The original function with injected keyword arguments
8493
"""
8594

95+
@wraps(func)
8696
def _wrapped_func(*args, **kwargs):
8797
"""
8898
Internal function to execute wrapped function

test/unit/test_decorators.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,3 +191,22 @@ def function_to_be_decorated(arg1, arg2, arg3):
191191
self.assertEqual(arg3, 'bar')
192192

193193
function_to_be_decorated(arg2='foo', arg3='bar')
194+
195+
def test_string_with_class_method(self):
196+
secret = 'not json'
197+
response = {}
198+
versions = {
199+
'01234567890123456789012345678901': ['AWSCURRENT']
200+
}
201+
version_response = {'SecretString': secret}
202+
cache = SecretCache(client=self.get_client(response, versions, version_response))
203+
204+
class TestClass(unittest.TestCase):
205+
@InjectSecretString('test', cache)
206+
def class_method(self, arg1, arg2, arg3):
207+
self.assertEqual(arg1, secret)
208+
self.assertEqual(arg2, 'foo')
209+
self.assertEqual(arg3, 'bar')
210+
211+
t = TestClass()
212+
t.class_method(arg2="foo", arg3="bar")

0 commit comments

Comments
 (0)