django-dbbackup 5.3.1__tar.gz → 5.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {django_dbbackup-5.3.1 → django_dbbackup-5.4.0}/PKG-INFO +1 -1
- {django_dbbackup-5.3.1 → django_dbbackup-5.4.0}/dbbackup/__init__.py +1 -1
- {django_dbbackup-5.3.1 → django_dbbackup-5.4.0}/dbbackup/db/base.py +22 -2
- {django_dbbackup-5.3.1 → django_dbbackup-5.4.0}/dbbackup/db/postgresql.py +47 -3
- {django_dbbackup-5.3.1 → django_dbbackup-5.4.0}/dbbackup/management/commands/dbrestore.py +8 -8
- {django_dbbackup-5.3.1 → django_dbbackup-5.4.0}/dbbackup/settings.py +1 -1
- {django_dbbackup-5.3.1 → django_dbbackup-5.4.0}/.gitignore +0 -0
- {django_dbbackup-5.3.1 → django_dbbackup-5.4.0}/LICENSE.md +0 -0
- {django_dbbackup-5.3.1 → django_dbbackup-5.4.0}/README.md +0 -0
- {django_dbbackup-5.3.1 → django_dbbackup-5.4.0}/dbbackup/apps.py +0 -0
- {django_dbbackup-5.3.1 → django_dbbackup-5.4.0}/dbbackup/checks.py +0 -0
- {django_dbbackup-5.3.1 → django_dbbackup-5.4.0}/dbbackup/db/__init__.py +0 -0
- {django_dbbackup-5.3.1 → django_dbbackup-5.4.0}/dbbackup/db/django.py +0 -0
- {django_dbbackup-5.3.1 → django_dbbackup-5.4.0}/dbbackup/db/exceptions.py +0 -0
- {django_dbbackup-5.3.1 → django_dbbackup-5.4.0}/dbbackup/db/mongodb.py +0 -0
- {django_dbbackup-5.3.1 → django_dbbackup-5.4.0}/dbbackup/db/mysql.py +0 -0
- {django_dbbackup-5.3.1 → django_dbbackup-5.4.0}/dbbackup/db/sqlite.py +0 -0
- {django_dbbackup-5.3.1 → django_dbbackup-5.4.0}/dbbackup/log.py +0 -0
- {django_dbbackup-5.3.1 → django_dbbackup-5.4.0}/dbbackup/management/__init__.py +0 -0
- {django_dbbackup-5.3.1 → django_dbbackup-5.4.0}/dbbackup/management/commands/__init__.py +0 -0
- {django_dbbackup-5.3.1 → django_dbbackup-5.4.0}/dbbackup/management/commands/_base.py +0 -0
- {django_dbbackup-5.3.1 → django_dbbackup-5.4.0}/dbbackup/management/commands/dbbackup.py +0 -0
- {django_dbbackup-5.3.1 → django_dbbackup-5.4.0}/dbbackup/management/commands/listbackups.py +0 -0
- {django_dbbackup-5.3.1 → django_dbbackup-5.4.0}/dbbackup/management/commands/mediabackup.py +0 -0
- {django_dbbackup-5.3.1 → django_dbbackup-5.4.0}/dbbackup/management/commands/mediarestore.py +0 -0
- {django_dbbackup-5.3.1 → django_dbbackup-5.4.0}/dbbackup/signals.py +0 -0
- {django_dbbackup-5.3.1 → django_dbbackup-5.4.0}/dbbackup/storage.py +0 -0
- {django_dbbackup-5.3.1 → django_dbbackup-5.4.0}/dbbackup/utils.py +0 -0
- {django_dbbackup-5.3.1 → django_dbbackup-5.4.0}/pyproject.toml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: django-dbbackup
|
|
3
|
-
Version: 5.
|
|
3
|
+
Version: 5.4.0
|
|
4
4
|
Summary: Management commands to help backup and restore a project database and media.
|
|
5
5
|
Project-URL: Changelog, https://archmonger.github.io/django-dbbackup/latest/changelog/
|
|
6
6
|
Project-URL: Documentation, https://archmonger.github.io/django-dbbackup
|
|
@@ -40,9 +40,16 @@ if settings.CUSTOM_CONNECTOR_MAPPING:
|
|
|
40
40
|
CONNECTOR_MAPPING.update(settings.CUSTOM_CONNECTOR_MAPPING)
|
|
41
41
|
|
|
42
42
|
|
|
43
|
-
def get_connector(database_name=None):
|
|
43
|
+
def get_connector(database_name: str | None = None, *, connector_path: str | None = None):
|
|
44
44
|
"""
|
|
45
45
|
Get a connector from its database key in settings.
|
|
46
|
+
|
|
47
|
+
:param database_name: Database alias from ``DATABASES``, defaults to Django's default alias.
|
|
48
|
+
:param connector_path: Dotted path to the connector class to instantiate, for when the
|
|
49
|
+
connector is already known (e.g. from a backup's metadata file). When omitted it is
|
|
50
|
+
resolved from ``DBBACKUP_CONNECTORS[database_name]["CONNECTOR"]``, falling back to the
|
|
51
|
+
engine mapping. Either way the connector is always instantiated with the
|
|
52
|
+
``DBBACKUP_CONNECTORS[database_name]`` options applied.
|
|
46
53
|
"""
|
|
47
54
|
from django.db import DEFAULT_DB_ALIAS, connections
|
|
48
55
|
|
|
@@ -53,7 +60,8 @@ def get_connector(database_name=None):
|
|
|
53
60
|
connector_settings = settings.CONNECTORS.get(database_name, {})
|
|
54
61
|
|
|
55
62
|
# Use Django connector as fallback for unmapped engines
|
|
56
|
-
|
|
63
|
+
if not connector_path:
|
|
64
|
+
connector_path = connector_settings.get("CONNECTOR", CONNECTOR_MAPPING.get(engine, DEFAULT_CONNECTOR))
|
|
57
65
|
|
|
58
66
|
connector_module_path = ".".join(connector_path.split(".")[:-1])
|
|
59
67
|
module = import_module(connector_module_path)
|
|
@@ -71,6 +79,12 @@ class BaseDBConnector:
|
|
|
71
79
|
extension = "dump"
|
|
72
80
|
exclude: ClassVar[list[Any]] = []
|
|
73
81
|
|
|
82
|
+
#: Attributes which either hold a mutable class default or are bound to the very
|
|
83
|
+
#: list/dict objects the user put in ``DBBACKUP_CONNECTORS``. They are copied per
|
|
84
|
+
#: instance so mutating one connector can neither leak into another connector nor
|
|
85
|
+
#: into the user's settings.
|
|
86
|
+
MUTABLE_ATTRIBUTES: ClassVar[tuple[str, ...]] = ("exclude", "schemas", "env", "dump_env", "restore_env")
|
|
87
|
+
|
|
74
88
|
def __init__(self, database_name=None, **kwargs):
|
|
75
89
|
from django.db import DEFAULT_DB_ALIAS, connections
|
|
76
90
|
|
|
@@ -78,6 +92,12 @@ class BaseDBConnector:
|
|
|
78
92
|
self.connection = connections[self.database_name]
|
|
79
93
|
for attr, value in kwargs.items():
|
|
80
94
|
setattr(self, attr.lower(), value)
|
|
95
|
+
for attr in self.MUTABLE_ATTRIBUTES:
|
|
96
|
+
value = getattr(self, attr, None)
|
|
97
|
+
if isinstance(value, list):
|
|
98
|
+
setattr(self, attr, list(value))
|
|
99
|
+
elif isinstance(value, dict):
|
|
100
|
+
setattr(self, attr, dict(value))
|
|
81
101
|
|
|
82
102
|
@property
|
|
83
103
|
def settings(self):
|
|
@@ -9,6 +9,20 @@ from dbbackup.db.base import BaseCommandDBConnector
|
|
|
9
9
|
|
|
10
10
|
logger = logging.getLogger("dbbackup.command")
|
|
11
11
|
|
|
12
|
+
# Connection options understood by the PostgreSQL command line tools, mapped to
|
|
13
|
+
# the ``libpq`` environment variable carrying each one. It mirrors
|
|
14
|
+
# ``settings_to_cmd_args_env`` in ``django/db/backends/postgresql/client.py``,
|
|
15
|
+
# so that ``pg_dump``, ``psql`` and ``pg_restore`` connect with the same TLS,
|
|
16
|
+
# client certificate, password file and service settings as the ORM does.
|
|
17
|
+
PG_OPTIONS_ENV_VARS: dict[str, str] = {
|
|
18
|
+
"sslmode": "PGSSLMODE",
|
|
19
|
+
"sslrootcert": "PGSSLROOTCERT",
|
|
20
|
+
"sslcert": "PGSSLCERT",
|
|
21
|
+
"sslkey": "PGSSLKEY",
|
|
22
|
+
"passfile": "PGPASSFILE",
|
|
23
|
+
"service": "PGSERVICE",
|
|
24
|
+
}
|
|
25
|
+
|
|
12
26
|
|
|
13
27
|
def _host_requires_uri_quoting(host: str) -> bool:
|
|
14
28
|
# Match *nix paths (e.g. /run/postgres) and Windows paths (e.g. C:\path\to\socket)
|
|
@@ -17,6 +31,26 @@ def _host_requires_uri_quoting(host: str) -> bool:
|
|
|
17
31
|
)
|
|
18
32
|
|
|
19
33
|
|
|
34
|
+
def parse_postgres_options_env(connector: PgDumpBinaryConnector | PgDumpConnector) -> dict[str, str]:
|
|
35
|
+
"""
|
|
36
|
+
Translate the ``OPTIONS`` setting of a Postgres connection into the ``libpq``
|
|
37
|
+
environment variables understood by the command line tools.
|
|
38
|
+
|
|
39
|
+
The options are passed through the environment rather than appended to the
|
|
40
|
+
connection URI because ``libpq`` expands ``%XX`` triplets only: a path
|
|
41
|
+
encoded with ``quote_plus`` semantics would reach the tools with its spaces
|
|
42
|
+
turned into ``+`` and would not be found.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
connector: Database connector instance with settings
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
dict: Environment variables to apply to the command
|
|
49
|
+
"""
|
|
50
|
+
options = connector.settings.get("OPTIONS") or {}
|
|
51
|
+
return {env_var: str(options[option]) for option, env_var in PG_OPTIONS_ENV_VARS.items() if options.get(option)}
|
|
52
|
+
|
|
53
|
+
|
|
20
54
|
def parse_postgres_settings(connector: PgDumpBinaryConnector | PgDumpConnector) -> tuple[str, dict[Any, Any]]:
|
|
21
55
|
"""
|
|
22
56
|
Parse the common Postgres connectors settings and
|
|
@@ -41,7 +75,7 @@ def parse_postgres_settings(connector: PgDumpBinaryConnector | PgDumpConnector)
|
|
|
41
75
|
host = f"@{host}"
|
|
42
76
|
port = f":{connector.settings.get('PORT')}" if connector.settings.get("PORT") else ""
|
|
43
77
|
cmd_part = f"--dbname=postgresql://{user}{host}{port}/{cmd_part}"
|
|
44
|
-
env =
|
|
78
|
+
env = parse_postgres_options_env(connector)
|
|
45
79
|
if password is None:
|
|
46
80
|
cmd_part += " --no-password"
|
|
47
81
|
elif password not in ("", -1):
|
|
@@ -123,7 +157,17 @@ class PgDumpGisConnector(PgDumpConnector):
|
|
|
123
157
|
if self.settings.get("PORT"):
|
|
124
158
|
cmd += f" --port={shlex.quote(str(self.settings['PORT']))}"
|
|
125
159
|
|
|
126
|
-
|
|
160
|
+
# This is a separate psql invocation: it needs the same TLS settings as
|
|
161
|
+
# the restore it precedes, otherwise it cannot authenticate with a
|
|
162
|
+
# client certificate and the PostGIS activation fails.
|
|
163
|
+
env = {**self.restore_env, **parse_postgres_options_env(self)}
|
|
164
|
+
# "--no-password" only forbids the interactive prompt, libpq still
|
|
165
|
+
# reads PGPASSWORD, so the credentials of ADMIN_USER go there.
|
|
166
|
+
admin_password = self.settings.get("ADMIN_PASSWORD")
|
|
167
|
+
if admin_password:
|
|
168
|
+
env["PGPASSWORD"] = str(admin_password)
|
|
169
|
+
|
|
170
|
+
return self.run_command(cmd, env=env)
|
|
127
171
|
|
|
128
172
|
def _restore_dump(self, dump):
|
|
129
173
|
if self.settings.get("ADMIN_USER"):
|
|
@@ -202,6 +246,6 @@ class PgDumpBinaryConnector(PgDumpConnector):
|
|
|
202
246
|
cmd.extend(self.restore_suffix if isinstance(self.restore_suffix, list) else [self.restore_suffix])
|
|
203
247
|
|
|
204
248
|
cmd_str = " ".join(cmd)
|
|
205
|
-
stdout, _ = self.run_command(cmd_str, stdin=dump, env={**self.
|
|
249
|
+
stdout, _ = self.run_command(cmd_str, stdin=dump, env={**self.restore_env, **pg_env})
|
|
206
250
|
|
|
207
251
|
return stdout
|
|
@@ -8,7 +8,6 @@ import io
|
|
|
8
8
|
import json
|
|
9
9
|
import os
|
|
10
10
|
import sys
|
|
11
|
-
from importlib import import_module
|
|
12
11
|
|
|
13
12
|
from django.conf import settings
|
|
14
13
|
from django.core.management.base import CommandError
|
|
@@ -227,11 +226,7 @@ class Command(BaseDbBackupCommand):
|
|
|
227
226
|
if metadata and "connector" in metadata:
|
|
228
227
|
connector_path = metadata["connector"]
|
|
229
228
|
try:
|
|
230
|
-
|
|
231
|
-
class_name = connector_path.split(".")[-1]
|
|
232
|
-
module = import_module(module_name)
|
|
233
|
-
connector_class = getattr(module, class_name)
|
|
234
|
-
self.connector = connector_class(self.database_name)
|
|
229
|
+
self.connector = get_connector(self.database_name, connector_path=connector_path)
|
|
235
230
|
self.logger.info("Using connector from metadata: '%s'", connector_path)
|
|
236
231
|
except (ImportError, AttributeError):
|
|
237
232
|
self.logger.warning(
|
|
@@ -250,8 +245,13 @@ class Command(BaseDbBackupCommand):
|
|
|
250
245
|
|
|
251
246
|
if self.schemas:
|
|
252
247
|
self.connector.schemas = self.schemas
|
|
253
|
-
|
|
254
|
-
|
|
248
|
+
# Only override connector attributes when the flag was actually passed, otherwise
|
|
249
|
+
# options configured through DBBACKUP_CONNECTORS (e.g. DROP or PG_OPTIONS) are
|
|
250
|
+
# silently discarded in favour of the option defaults.
|
|
251
|
+
if self.no_drop:
|
|
252
|
+
self.connector.drop = False
|
|
253
|
+
if self.pg_options:
|
|
254
|
+
self.connector.pg_options = self.pg_options
|
|
255
255
|
self.connector.restore_dump(input_file)
|
|
256
256
|
|
|
257
257
|
# Send post_restore signal
|
|
@@ -29,7 +29,7 @@ FILENAME_TEMPLATE = getattr(
|
|
|
29
29
|
)
|
|
30
30
|
MEDIA_FILENAME_TEMPLATE = getattr(settings, "DBBACKUP_MEDIA_FILENAME_TEMPLATE", "{servername}-{datetime}.{extension}")
|
|
31
31
|
GPG_ALWAYS_TRUST = getattr(settings, "DBBACKUP_GPG_ALWAYS_TRUST", False)
|
|
32
|
-
GPG_RECIPIENT =
|
|
32
|
+
GPG_RECIPIENT = getattr(settings, "DBBACKUP_GPG_RECIPIENT", None)
|
|
33
33
|
STORAGES_DBBACKUP_ALIAS = "dbbackup"
|
|
34
34
|
DJANGO_STORAGES = getattr(settings, "STORAGES", {})
|
|
35
35
|
storage: dict = DJANGO_STORAGES.get(STORAGES_DBBACKUP_ALIAS, {})
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_dbbackup-5.3.1 → django_dbbackup-5.4.0}/dbbackup/management/commands/mediarestore.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|