From db1e544aaa5453a3214ae7ad8289d8ae572a3b46 Mon Sep 17 00:00:00 2001 From: Chris Ramshaw Date: Thu, 3 Dec 2020 14:28:17 +0000 Subject: [PATCH 1/2] bump django to 3.1.x --- Pipfile.lock | 55 +++++++++++++++++++++++------------ README.md | 9 ++++-- mysql_rds/backend/client.py | 4 +-- mysql_rds/backend/creation.py | 10 +++---- tests/test_client.py | 7 +++-- 5 files changed, 54 insertions(+), 31 deletions(-) diff --git a/Pipfile.lock b/Pipfile.lock index b19609e..f6baa12 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -16,52 +16,71 @@ ] }, "default": { + "asgiref": { + "hashes": [ + "sha256:5ee950735509d04eb673bd7f7120f8fa1c9e2df495394992c73234d526907e17", + "sha256:7162a3cb30ab0609f1a4c95938fd73e8604f63bdba516a7f7d64b83ff09478f0" + ], + "markers": "python_version >= '3.5'", + "version": "==3.3.1" + }, "django": { "hashes": [ - "sha256:7c3543e4fb070d14e10926189a7fcf42ba919263b7473dceaefce34d54e8a119", - "sha256:a2814bffd1f007805b19194eb0b9a331933b82bd5da1c3ba3d7b7ba16e06dc4b" + "sha256:5c866205f15e7a7123f1eec6ab939d22d5bde1416635cab259684af66d8e48a2", + "sha256:edb10b5c45e7e9c0fb1dc00b76ec7449aca258a39ffd613dbd078c51d19c9f03" ], "index": "pypi", - "version": "==2.2" + "version": "==3.1.4" }, "mysqlclient": { "hashes": [ - "sha256:425e733b05e359a714d6007c0fc44582be66b63e5a3df0a50949274ae16f4bc6", - "sha256:62e4770b6a797b9416bcf70488365b7d6b9c9066878108499c559293bb464380", - "sha256:f257d250f2675d0ef99bd318906f3cfc05cef4a2f385ea695ff32a3f04b9f9a7" + "sha256:3f39855a4ad22805361e782cc4d1010ac74796225fa2d1c03cc16673ccdc983a", + "sha256:a6b5648f648b16335e3b1aaec93dc3fcc81a9a661180e306936437cc522c810b", + "sha256:edd42ccaa444b00702d5374b2f5f7585c9d0ce201917f15339f1c3cf91c1b1ed", + "sha256:fb2f75aea14722390d2d8ddf384ad99da708c707a96656210a7be8af20a2c5e5" ], "index": "pypi", - "version": "==1.4.2.post1" + "version": "==2.0.1" }, "pytz": { "hashes": [ - "sha256:303879e36b721603cc54604edcac9d20401bdbe31e1e4fdee5b9f98d5d31dfda", - "sha256:d747dd3d23d77ef44c6a3526e274af6efeb0a6f1afd5a69ba4d5be4098c8e141" + "sha256:3e6b7dd2d1e0a59084bcee14a17af60c5c562cdc16d828e8eba2e683d3a7e268", + "sha256:5c55e189b682d420be27c6995ba6edce0c0a77dd67bfbe2ae6607134d5851ffd" ], - "version": "==2019.1" + "version": "==2020.4" }, "sqlparse": { "hashes": [ - "sha256:40afe6b8d4b1117e7dff5504d7a8ce07d9a1b15aeeade8a2d10f130a834f8177", - "sha256:7c3dca29c022744e95b547e867cee89f4fce4373f3549ccd8797d8eb52cdb873" + "sha256:017cde379adbd6a1f15a61873f43e8274179378e95ef3fede90b5aa64d304ed0", + "sha256:0f91fd2e829c44362cbcfab3e9ae12e22badaa8a29ad5ff599f9ec109f0454e8" ], - "version": "==0.3.0" + "markers": "python_version >= '3.5'", + "version": "==0.4.1" } }, "develop": { "autopep8": { "hashes": [ - "sha256:4d8eec30cc81bc5617dbf1218201d770dc35629363547f17577c61683ccfb3ee" + "sha256:d21d3901cb0da6ebd1e83fc9b0dfbde8b46afc2ede4fe32fbda0c7c6118ca094" ], "index": "pypi", - "version": "==1.4.4" + "version": "==1.5.4" }, "pycodestyle": { "hashes": [ - "sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56", - "sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c" + "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367", + "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==2.6.0" + }, + "toml": { + "hashes": [ + "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", + "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" ], - "version": "==2.5.0" + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==0.10.2" } } } diff --git a/README.md b/README.md index f8b08fc..e2e88ae 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ DATABASES = { } } ``` + And you've discovered that after ~15 minutes you make a request and receive `Access Denied for user@instance` because the password has expired. This package allows you to generate the password at connection time by passing a callable instead: @@ -62,20 +63,21 @@ def generate_pw(): pip install django-mysql-rds ``` -or +or ``` git clone git@github.com:cramshaw/django-mysql-rds.git ``` ## Why? + When I searched for a way to connect to an AWS RDS MySQL instance using SSL inside Django, I was unable to find anything that could handle the fact that the db auth token generated by AWS would expire every 15 minutes. The problem is that when anything in the settings module changes, Django needs to reload. This isn't practical in a long running web app. I needed a way for the password to be generated at the time of connection. ## How? -On close inspection of the `django.db.backends.mysql` code, it became clear that the `DatabaseWrapper.get_connection_params` method takes the settings dict, and transforms it into the kwargs that are passed to `mysql.connect`. I have subclassed this and extended to recognise if the password passed in is a callable, and if so, to call it and pass on the returned value. This leads to +On close inspection of the `django.db.backends.mysql` code, it became clear that the `DatabaseWrapper.get_connection_params` method takes the settings dict, and transforms it into the kwargs that are passed to `mysql.connect`. I have subclassed this and extended to recognise if the password passed in is a callable, and if so, to call it and pass on the returned value. This leads to Django receiving a fresh password every time a connection is created. A very similar thing happens in the `DatabaseClient.settings_to_cmd_args` which is used for things like dumping and loading data. This has also been subclassed and changed to ensure the password generation method actually runs before attempting to create a run a shell. @@ -98,8 +100,9 @@ python -m unittest tests/test* Bump version in setup.py then: + ``` rm -rf dist/ python3 setup.py sdist bdist_wheel python3 -m twine upload dist/* -``` \ No newline at end of file +``` diff --git a/mysql_rds/backend/client.py b/mysql_rds/backend/client.py index 349d28d..9842d84 100644 --- a/mysql_rds/backend/client.py +++ b/mysql_rds/backend/client.py @@ -4,8 +4,8 @@ class DatabaseClient(MySQLClient): @classmethod - def settings_to_cmd_args(cls, settings_dict): + def settings_to_cmd_args(cls, settings_dict, parameters): if callable(settings_dict['PASSWORD']): settings_dict['PASSWORD'] = settings_dict['PASSWORD']() - return super().settings_to_cmd_args(settings_dict) + return super().settings_to_cmd_args(settings_dict, parameters) diff --git a/mysql_rds/backend/creation.py b/mysql_rds/backend/creation.py index 2332a8d..1c8c449 100644 --- a/mysql_rds/backend/creation.py +++ b/mysql_rds/backend/creation.py @@ -3,17 +3,17 @@ from django.db.backends.mysql.creation import DatabaseCreation as MySQLCreation from .client import DatabaseClient -# https://github.com/django/django/blob/stable/2.2.x/django/db/backends/mysql/creation.py +# https://github.com/django/django/blob/stable/3.1.x/django/db/backends/mysql/creation.py class DatabaseCreation(MySQLCreation): def _clone_db(self, source_database_name, target_database_name): dump_args = DatabaseClient.settings_to_cmd_args( - self.connection.settings_dict)[1:] - dump_args[-1] = source_database_name - dump_cmd = ['mysqldump', '--routines', '--events'] + dump_args + self.connection.settings_dict, [])[1:] + dump_cmd = ['mysqldump', *dump_args[:-1], + '--routines', '--events', source_database_name] load_cmd = DatabaseClient.settings_to_cmd_args( - self.connection.settings_dict) + self.connection.settings_dict, []) load_cmd[-1] = target_database_name with subprocess.Popen(dump_cmd, stdout=subprocess.PIPE) as dump_proc: diff --git a/tests/test_client.py b/tests/test_client.py index a302fed..68efdbb 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -35,13 +35,14 @@ class DatabaseClientTest(TestCase): def test_get_callable_cmd_args(self): conn_settings = SETTINGS_DICT conn_settings['PASSWORD'] = generate_pw - rds_args = DatabaseClient.settings_to_cmd_args(conn_settings) + rds_args = DatabaseClient.settings_to_cmd_args(conn_settings, []) self.assertEqual(rds_args[2], f'--password={CALLABLE_PASSWORD}') def test_get_cmd_args_strings(self): conn_settings = SETTINGS_DICT conn_settings['PASSWORD'] = STRING_PASSWORD - rds_args = DatabaseClient.settings_to_cmd_args(conn_settings) - mysql_args = MySQLDatabaseClient.settings_to_cmd_args(conn_settings) + rds_args = DatabaseClient.settings_to_cmd_args(conn_settings, []) + mysql_args = MySQLDatabaseClient.settings_to_cmd_args( + conn_settings, []) self.assertEqual(rds_args, mysql_args) self.assertEqual(rds_args[2], f'--password={STRING_PASSWORD}') From 80c2b66d995e53ece66f1a6cec15405686ddeac7 Mon Sep 17 00:00:00 2001 From: Chris Ramshaw Date: Thu, 3 Dec 2020 14:39:07 +0000 Subject: [PATCH 2/2] bump version --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 2a4c559..31a88f3 100644 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ setup( name='django-mysql-rds', - version='0.3.1', + version='0.4.0', packages=find_packages(exclude=['tests']), include_package_data=True, license='Mozilla Public License 2.0 (MPL 2.0)',