Skip to content

Commit 045a688

Browse files
Fix Compression of DjangoConnector Backups (#667)
Fix Compression of DjangoConnector Backups --------- Co-authored-by: Archmonger <16909269+Archmonger@users.noreply.github.com>
1 parent 3af92e6 commit 045a688

5 files changed

Lines changed: 52 additions & 22 deletions

File tree

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ jobs:
6565
fail-fast: false
6666
matrix:
6767
os: [ubuntu-latest, windows-latest]
68-
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
68+
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
6969
steps:
7070
- uses: actions/checkout@v4
7171
- uses: actions/setup-python@v5

CHANGELOG.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@ Don't forget to remove deprecated code on each major release!
1414

1515
## [Unreleased]
1616

17-
- Nothing (yet)!
17+
### Fixed
18+
19+
- Fix compression when using `DjangoConnector`.
20+
- Fix an issue with paramiko `os.stat` sizes were larger on the target compared to the source.
1821

1922
## [5.2.0] - 2026-02-10
2023

dbbackup/db/django.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
any Django-supported database backend.
77
"""
88

9+
import codecs
910
import contextlib
1011
import os
1112
import tempfile
@@ -35,8 +36,11 @@ def _create_dump(self):
3536
Returns a file-like object containing the serialized database data
3637
in JSON format.
3738
"""
38-
# Create a SpooledTemporaryFile in text mode for direct use with dumpdata
39-
dump_file = SpooledTemporaryFile(mode="w+t", encoding="utf-8")
39+
40+
binary_dump_file = SpooledTemporaryFile(mode="w+b")
41+
42+
# Wrap Binary SpooledTemporaryFile in text mode for direct use with dumpdata
43+
dump_file = codecs.getwriter("utf-8")(binary_dump_file)
4044

4145
# Prepare arguments for dumpdata command
4246
dump_args = []
@@ -108,8 +112,8 @@ def _create_dump(self):
108112
call_command("dumpdata", *dump_args, **dump_kwargs)
109113

110114
# Reset file position to beginning for reading
111-
dump_file.seek(0)
112-
return dump_file
115+
binary_dump_file.seek(0)
116+
return binary_dump_file
113117

114118
def _restore_dump(self, dump):
115119
"""

pyproject.toml

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,32 +18,32 @@ keywords = [
1818
]
1919
license = "BSD-3-Clause"
2020
authors = [{ name = "Mark Bakhit", email = "archiethemonger@gmail.com" }]
21-
requires-python = ">=3.9"
21+
requires-python = ">=3.10"
2222
classifiers = [
2323
"Development Status :: 5 - Production/Stable",
2424
"Environment :: Web Environment",
2525
"Environment :: Console",
26-
"Framework :: Django :: 4.2",
2726
"Framework :: Django :: 5.0",
2827
"Framework :: Django :: 5.1",
2928
"Framework :: Django :: 5.2",
29+
"Framework :: Django :: 6.0",
3030
"Intended Audience :: Developers",
3131
"Intended Audience :: System Administrators",
3232
"License :: OSI Approved :: BSD License",
3333
"Natural Language :: English",
3434
"Operating System :: OS Independent",
3535
"Programming Language :: Python",
36-
"Programming Language :: Python :: 3.9",
3736
"Programming Language :: Python :: 3.10",
3837
"Programming Language :: Python :: 3.11",
3938
"Programming Language :: Python :: 3.12",
4039
"Programming Language :: Python :: 3.13",
40+
"Programming Language :: Python :: 3.14",
4141
"Topic :: Database",
4242
"Topic :: System :: Archiving",
4343
"Topic :: System :: Archiving :: Backup",
4444
"Topic :: System :: Archiving :: Compression",
4545
]
46-
dependencies = ["django>=4.2"]
46+
dependencies = ["django>=5.0"]
4747
dynamic = ["version"]
4848
urls.Changelog = "https://archmonger.github.io/django-dbbackup/latest/changelog/"
4949
urls.Documentation = "https://archmonger.github.io/django-dbbackup"
@@ -80,11 +80,6 @@ matrix-name-format = "{variable}-{value}"
8080
[tool.hatch.envs.hatch-test.env-vars]
8181
DJANGO_SETTINGS_MODULE = "tests.settings"
8282

83-
# Django 4.2
84-
[[tool.hatch.envs.hatch-test.matrix]]
85-
python = ["3.9", "3.10", "3.11", "3.12"]
86-
django = ["4.2"]
87-
8883
# Django 5.0
8984
[[tool.hatch.envs.hatch-test.matrix]]
9085
python = ["3.10", "3.11", "3.12"]
@@ -100,11 +95,13 @@ django = ["5.1"]
10095
python = ["3.10", "3.11", "3.12", "3.13"]
10196
django = ["5.2"]
10297

98+
# Django 6.0
99+
[[tool.hatch.envs.hatch-test.matrix]]
100+
python = ["3.12", "3.13", "3.14"]
101+
django = ["6.0"]
102+
103103
[tool.hatch.envs.hatch-test.overrides]
104104
matrix.django.dependencies = [
105-
{ if = [
106-
"4.2",
107-
], value = "django>=4.2,<4.3" },
108105
{ if = [
109106
"5.0",
110107
], value = "django>=5.0,<5.1" },
@@ -114,6 +111,9 @@ matrix.django.dependencies = [
114111
{ if = [
115112
"5.2",
116113
], value = "django>=5.2,<5.3" },
114+
{ if = [
115+
"6.0",
116+
], value = "django>=6.0,<6.1" },
117117
]
118118

119119
# >>> Documentation Scripts <<<
@@ -191,7 +191,6 @@ postgres = ["python scripts/postgres_live_test.py {args}"]
191191

192192
[tool.ruff]
193193
line-length = 120
194-
target-version = "py39"
195194
extend-exclude = [".eggs/*", ".tox/*", ".venv/*", "build/*", "*/migrations/*"]
196195
format.preview = true
197196
lint.extend-ignore = [

tests/test_connectors/test_django.py

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,9 @@ def mock_dumpdata(*args, **kwargs):
4747
# Verify dump content
4848
assert isinstance(dump, SpooledTemporaryFile)
4949
dump.seek(0)
50-
content = dump.read() # Already a string in text mode
51-
assert '"model": "auth.user"' in content
52-
assert '"username": "test"' in content
50+
content = dump.read()
51+
assert b'"model": "auth.user"' in content
52+
assert b'"username": "test"' in content
5353

5454
@patch("dbbackup.db.django.call_command")
5555
def test_create_dump_with_exclude_app_model_format(self, mock_call_command):
@@ -243,3 +243,27 @@ def mock_dumpdata(*args, **kwargs):
243243

244244
# Verify loaddata was called
245245
assert mock_call_command.call_count == 1
246+
247+
@patch("dbbackup.db.django.call_command")
248+
def test_dump_is_binary(self, mock_call_command):
249+
"""Test that the created dump is a binary file and can be compressed."""
250+
# Mock the dumpdata command to write JSON to stdout
251+
def mock_dumpdata(*args, **kwargs):
252+
if "stdout" in kwargs:
253+
kwargs["stdout"].write('[{"model": "auth.user", "pk": 1, "fields": {"username": "test"}}]')
254+
255+
mock_call_command.side_effect = mock_dumpdata
256+
257+
# Create the dump
258+
dump_file = self.connector.create_dump()
259+
dump_file.seek(0)
260+
261+
# Try to compress it (this will fail if it's not a binary file)
262+
import gzip
263+
compressed_file = SpooledTemporaryFile()
264+
with gzip.GzipFile(fileobj=compressed_file, mode="wb") as gz_file:
265+
gz_file.write(dump_file.read())
266+
267+
# Check that we have compressed data
268+
compressed_file.seek(0)
269+
assert len(compressed_file.read()) > 0

0 commit comments

Comments
 (0)