|
1 | 1 | from contextlib import contextmanager |
| 2 | +from copy import deepcopy |
| 3 | +from typing import Union |
2 | 4 | from zlib import crc32 |
3 | 5 |
|
| 6 | +import psycopg |
| 7 | +from django.conf import settings |
4 | 8 | from django.db import DEFAULT_DB_ALIAS, connection, connections, transaction |
| 9 | +from django.db.backends.postgresql.base import DatabaseWrapper as PsycopgDatabaseWrapper |
5 | 10 | from django.db.migrations.executor import MigrationExecutor |
6 | 11 |
|
7 | 12 |
|
@@ -149,3 +154,62 @@ def advisory_lock(*args, lock_session_timeout_milliseconds=0, **kwargs): |
149 | 154 | yield True |
150 | 155 | else: |
151 | 156 | raise RuntimeError(f'Advisory lock not implemented for database type {connection.vendor}') |
| 157 | + |
| 158 | + |
| 159 | +# Django settings.DATABASES['alias'] dictionary type |
| 160 | +dj_db_dict = dict[str, Union[str, int]] |
| 161 | + |
| 162 | + |
| 163 | +def psycopg_connection_from_django(**kwargs) -> psycopg.Connection: |
| 164 | + "Compatibility with dispatcher connection factory, just returns the Django connection" |
| 165 | + return connection.connection |
| 166 | + |
| 167 | + |
| 168 | +def psycopg_kwargs_from_settings_dict(settings_dict: dj_db_dict) -> dict: |
| 169 | + """Return psycopg connection creation kwargs given Django db settings info |
| 170 | +
|
| 171 | + :param dict setting_dict: DATABASES in Django settings |
| 172 | + :return: kwargs that can be passed to psycopg.connect, or connection classes""" |
| 173 | + psycopg_params = PsycopgDatabaseWrapper(settings_dict).get_connection_params().copy() |
| 174 | + psycopg_params.pop('cursor_factory', None) |
| 175 | + psycopg_params.pop('context', None) |
| 176 | + return psycopg_params |
| 177 | + |
| 178 | + |
| 179 | +def psycopg_conn_string_from_settings_dict(settings_dict: dj_db_dict) -> str: |
| 180 | + conn_params = psycopg_kwargs_from_settings_dict(settings_dict) |
| 181 | + return psycopg.conninfo.make_conninfo(**conn_params) |
| 182 | + |
| 183 | + |
| 184 | +def combine_settings_dict(settings_dict1: dj_db_dict, settings_dict2: dj_db_dict, **extra_options) -> dj_db_dict: |
| 185 | + """Given two Django settings dictionaries, combine them and return a new settings_dict""" |
| 186 | + settings_dict = deepcopy(settings_dict1) |
| 187 | + settings_dict['OPTIONS'] = deepcopy(settings_dict.get('OPTIONS', {})) |
| 188 | + |
| 189 | + # These extra options are used by AWX to set application_name |
| 190 | + settings_dict['OPTIONS'].update(extra_options) |
| 191 | + |
| 192 | + # Apply overrides specifically for the listener connection |
| 193 | + for k, v in settings_dict2.items(): |
| 194 | + if k != 'OPTIONS': |
| 195 | + settings_dict[k] = v |
| 196 | + |
| 197 | + for k, v in settings_dict2.get('OPTIONS', {}).items(): |
| 198 | + settings_dict['OPTIONS'][k] = v |
| 199 | + |
| 200 | + return settings_dict |
| 201 | + |
| 202 | + |
| 203 | +def get_pg_notify_params(alias: str = DEFAULT_DB_ALIAS, **extra_options) -> dict: |
| 204 | + pg_notify_overrides = {} |
| 205 | + if hasattr(settings, 'PG_NOTIFY_DATABASES'): |
| 206 | + pg_notify_overrides = settings.PG_NOTIFY_DATABASES.get(alias, {}) |
| 207 | + elif hasattr(settings, 'LISTENER_DATABASES'): |
| 208 | + pg_notify_overrides = settings.LISTENER_DATABASES.get(alias, {}) |
| 209 | + |
| 210 | + settings_dict = combine_settings_dict(settings.DATABASES[alias], pg_notify_overrides, **extra_options) |
| 211 | + |
| 212 | + # Reuse the Django postgres DB backend to create params for the psycopg library |
| 213 | + psycopg_params = psycopg_kwargs_from_settings_dict(settings_dict) |
| 214 | + |
| 215 | + return psycopg_params |
0 commit comments