Skip to content

Commit cddff70

Browse files
Implement SQLite backups with the .backup command (#592)
* Implement SqliteBackupConnector --------- Co-authored-by: Mark Bakhit <archiethemonger@gmail.com>
1 parent 515a099 commit cddff70

File tree

4 files changed

+58
-2
lines changed

4 files changed

+58
-2
lines changed

dbbackup/db/sqlite.py

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1+
import sqlite3
12
import warnings
23
from io import BytesIO
34
from shutil import copyfileobj
4-
from tempfile import SpooledTemporaryFile
5+
from tempfile import SpooledTemporaryFile, NamedTemporaryFile
56

67
from django.db import IntegrityError, OperationalError
78

@@ -108,3 +109,38 @@ def restore_dump(self, dump):
108109
path = self.connection.settings_dict["NAME"]
109110
with open(path, "wb") as db_file:
110111
copyfileobj(dump, db_file)
112+
113+
114+
class SqliteBackupConnector(BaseDBConnector):
115+
"""
116+
Create a dump using the SQLite backup command,
117+
which is safe to execute when the database is
118+
in use (unlike simply copying the database file).
119+
Restore by copying the backup file over the
120+
database file.
121+
"""
122+
extension = 'sqlite3'
123+
124+
def _write_dump(self, fileobj):
125+
pass
126+
127+
def create_dump(self):
128+
if not self.connection.is_usable():
129+
self.connection.connect()
130+
# Important: ensure the connection to the DB
131+
# has been established.
132+
self.connection.ensure_connection()
133+
src_db_connection = self.connection.connection
134+
135+
bkp_db_file = NamedTemporaryFile()
136+
bkp_path = bkp_db_file.name
137+
with sqlite3.connect(bkp_path) as bkp_db_connection:
138+
src_db_connection.backup(bkp_db_connection)
139+
140+
bkp_db_file.seek(0)
141+
return bkp_db_file
142+
143+
def restore_dump(self, dump):
144+
path = self.connection.settings_dict["NAME"]
145+
with open(path, "wb") as db_file:
146+
copyfileobj(dump, db_file)

dbbackup/tests/test_connectors/test_sqlite.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from django.db import connection
55
from django.test import TestCase
66

7-
from dbbackup.db.sqlite import SqliteConnector, SqliteCPConnector
7+
from dbbackup.db.sqlite import SqliteConnector, SqliteCPConnector, SqliteBackupConnector
88
from dbbackup.tests.testapp.models import CharModel, TextModel
99

1010

@@ -65,3 +65,17 @@ def test_restore_dump(self):
6565
connector = SqliteCPConnector()
6666
dump = connector.create_dump()
6767
connector.restore_dump(dump)
68+
69+
70+
class SqliteBackupConnectorTest(TestCase):
71+
def test_create_dump(self):
72+
connector = SqliteBackupConnector()
73+
dump = connector.create_dump()
74+
dump_content = dump.read()
75+
self.assertTrue(dump_content)
76+
self.assertTrue(dump_content.startswith(b"SQLite format 3"))
77+
78+
def test_restore_dump(self):
79+
connector = SqliteBackupConnector()
80+
dump = connector.create_dump()
81+
connector.restore_dump(dump)

docs/changelog.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Unreleased
99
* Drop support for end-of-life Python 3.7 and 3.8.
1010
* Drop support for end-of-life Django 3.2.
1111
* Drop support for ``DBBACKUP_STORAGE`` AND ``DBBACKUP_STORAGE_OPTIONS`` settings, use Django's ``STORAGES['dbbackup']`` setting instead.
12+
* Implement new ``SqliteBackupConnector`` to backup SQLite3 databases using the ``.backup`` command (safe to execute on DBs with active connections).
1213

1314
4.3.0 (2025-05-09)
1415
----------

docs/databases.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,11 @@ simple raw copy of your database file, like a snapshot.
128128

129129
In-memory database aren't dumpable with it.
130130

131+
SqliteBackupConnector
132+
~~~~~~~~~~~~~~~~~
133+
134+
The :class:`dbbackup.db.sqlite.SqliteBackupConnector` makes a copy of the SQLite database file using the ``.backup`` command, which is safe to execute while the database has ongoing/active connections. Additionally, it supports dumping in-memory databases by construction.
135+
131136
MySQL
132137
-----
133138

0 commit comments

Comments
 (0)