django-sqlite-ops 0.1.0a1__py3-none-any.whl
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_sqlite_ops/__init__.py +1 -0
- django_sqlite_ops/apps.py +19 -0
- django_sqlite_ops/boot/__init__.py +4 -0
- django_sqlite_ops/boot/__main__.py +6 -0
- django_sqlite_ops/boot/cli.py +1066 -0
- django_sqlite_ops/boot/decide.py +350 -0
- django_sqlite_ops/boot/litestream.py +542 -0
- django_sqlite_ops/checks.py +400 -0
- django_sqlite_ops/compat/__init__.py +1 -0
- django_sqlite_ops/compat/_channels_lite_aio.py +49 -0
- django_sqlite_ops/compat/channels_lite.py +132 -0
- django_sqlite_ops/database.py +118 -0
- django_sqlite_ops/doctor.py +1051 -0
- django_sqlite_ops/health.py +1129 -0
- django_sqlite_ops/management/__init__.py +0 -0
- django_sqlite_ops/management/commands/__init__.py +0 -0
- django_sqlite_ops/management/commands/sqlite_doctor.py +161 -0
- django_sqlite_ops/restore_check.py +709 -0
- django_sqlite_ops/wal.py +287 -0
- django_sqlite_ops-0.1.0a1.dist-info/METADATA +987 -0
- django_sqlite_ops-0.1.0a1.dist-info/RECORD +23 -0
- django_sqlite_ops-0.1.0a1.dist-info/WHEEL +4 -0
- django_sqlite_ops-0.1.0a1.dist-info/licenses/LICENSE +21 -0
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"""``manage.py sqlite_doctor`` — 실제 연결로 SQLite 운영 상태를 진단한다 (DESIGN §6-2).
|
|
2
|
+
|
|
3
|
+
로직은 ``django_sqlite_ops.doctor``(일반 진단)와 ``django_sqlite_ops.restore_check``
|
|
4
|
+
(``--restore-test``)에 있다. 이 명령은 옵션을 받아 출력하고 종료 코드를 정한다.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import math
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
from django.core.management.base import BaseCommand
|
|
11
|
+
|
|
12
|
+
from ...boot.litestream import DEFAULT_RESTORE_TIMEOUT
|
|
13
|
+
from ...doctor import diagnose, exit_code, format_text, to_json
|
|
14
|
+
from ...restore_check import DEFAULT_INTEGRITY_TIMEOUT, INTEGRITY_MODES, restore_test
|
|
15
|
+
|
|
16
|
+
HELP = (
|
|
17
|
+
"Diagnose SQLite databases with a real connection: actual PRAGMA values after "
|
|
18
|
+
"init_command, file and WAL sizes, filesystem type, Litestream config paths and the "
|
|
19
|
+
"channel layer. Connecting runs OPTIONS['init_command'], which can change the database "
|
|
20
|
+
"(PRAGMA journal_mode=WAL persists in the file), and connecting to a WAL database can "
|
|
21
|
+
"create -wal/-shm files. A database whose file does not exist is reported and NOT "
|
|
22
|
+
"connected (connecting would create an empty file); neither is a read-only alias of a "
|
|
23
|
+
"WAL database (diagnose it through the writer's alias or an immutable=1 URI). "
|
|
24
|
+
"With --restore-test it does only one thing instead: restore each write database from its "
|
|
25
|
+
"Litestream replica into a new directory under --restore-dir, check the TXID and run "
|
|
26
|
+
"PRAGMA integrity_check on the copy (without --database, only the write file databases; other "
|
|
27
|
+
"sqlite3 aliases are reported as skipped, and having none to test is an error). That mode "
|
|
28
|
+
"opens no Django connection. It downloads the "
|
|
29
|
+
"whole snapshot (time and S3 egress grow with the database). It refuses a --restore-dir that "
|
|
30
|
+
"overlaps a database directory or a litestream meta directory it knows of for any SQLite "
|
|
31
|
+
"backend alias or any database in the --litestream-config, when TMPDIR is inside "
|
|
32
|
+
"--restore-dir, or when an alias's backend cannot be loaded "
|
|
33
|
+
"(SQLITE_OPS['HEALTH'] meta_path or the default .<db>-litestream); a YAML meta-path outside "
|
|
34
|
+
"the database directory that is not in SQLITE_OPS['HEALTH'] is not protected. "
|
|
35
|
+
"Exit status: 0 no problems, 1 warnings or undeterminable items, 2 errors "
|
|
36
|
+
"(including invalid --restore-test arguments)."
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
# --restore-test 에만 의미가 있는 옵션(dest → 옵션 이름).
|
|
40
|
+
_RESTORE_ONLY = {
|
|
41
|
+
"restore_dir": "--restore-dir",
|
|
42
|
+
"integrity": "--integrity",
|
|
43
|
+
"restore_timeout": "--restore-timeout",
|
|
44
|
+
"integrity_timeout": "--integrity-timeout",
|
|
45
|
+
"keep": "--keep",
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _usage_error(message: str) -> None:
|
|
50
|
+
"""이 모드의 인자 오류는 exit 2 다(D-20). ``CommandError`` 의 기본 코드 1 은 쓰지 않는다."""
|
|
51
|
+
print(f"sqlite_doctor: error: {message}", file=sys.stderr)
|
|
52
|
+
raise SystemExit(2)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _seconds(option: str, value: object) -> float:
|
|
56
|
+
try:
|
|
57
|
+
seconds = float(value)
|
|
58
|
+
except (TypeError, ValueError):
|
|
59
|
+
_usage_error(f"{option} must be a number of seconds, got {value!r}")
|
|
60
|
+
if not math.isfinite(seconds) or seconds <= 0:
|
|
61
|
+
_usage_error(f"{option} must be a positive number of seconds, got {value!r}")
|
|
62
|
+
return seconds
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class Command(BaseCommand):
|
|
66
|
+
help = HELP
|
|
67
|
+
requires_system_checks: list[str] = []
|
|
68
|
+
|
|
69
|
+
def add_arguments(self, parser):
|
|
70
|
+
parser.add_argument(
|
|
71
|
+
"--database",
|
|
72
|
+
action="append",
|
|
73
|
+
dest="databases",
|
|
74
|
+
metavar="ALIAS",
|
|
75
|
+
help="Database alias to diagnose; repeatable. Default: every sqlite3 alias.",
|
|
76
|
+
)
|
|
77
|
+
parser.add_argument(
|
|
78
|
+
"--litestream-config",
|
|
79
|
+
metavar="PATH",
|
|
80
|
+
help="Litestream config file to match against DATABASES. Skipped when omitted "
|
|
81
|
+
"(required with --restore-test).",
|
|
82
|
+
)
|
|
83
|
+
parser.add_argument(
|
|
84
|
+
"--litestream",
|
|
85
|
+
metavar="BIN",
|
|
86
|
+
default="litestream",
|
|
87
|
+
help="Litestream binary (default: litestream on PATH).",
|
|
88
|
+
)
|
|
89
|
+
parser.add_argument("--json", action="store_true", help="Print JSON (schema version 1).")
|
|
90
|
+
group = parser.add_argument_group("restore test (--restore-test)")
|
|
91
|
+
group.add_argument(
|
|
92
|
+
"--restore-test",
|
|
93
|
+
action="store_true",
|
|
94
|
+
help="Only restore each write database from its replica and verify the copy.",
|
|
95
|
+
)
|
|
96
|
+
group.add_argument(
|
|
97
|
+
"--restore-dir",
|
|
98
|
+
metavar="DIR",
|
|
99
|
+
help="Existing real directory (not a symlink) for the restored copies; required. "
|
|
100
|
+
"Not /tmp on a tmpfs server, not the database directory, not around TMPDIR.",
|
|
101
|
+
)
|
|
102
|
+
group.add_argument(
|
|
103
|
+
"--integrity",
|
|
104
|
+
choices=INTEGRITY_MODES,
|
|
105
|
+
help="PRAGMA integrity_check (full, default) or quick_check (quick).",
|
|
106
|
+
)
|
|
107
|
+
group.add_argument(
|
|
108
|
+
"--restore-timeout",
|
|
109
|
+
metavar="SEC",
|
|
110
|
+
help=f"Time limit for each restore (default {DEFAULT_RESTORE_TIMEOUT:g}).",
|
|
111
|
+
)
|
|
112
|
+
group.add_argument(
|
|
113
|
+
"--integrity-timeout",
|
|
114
|
+
metavar="SEC",
|
|
115
|
+
help="Separate time limit for each integrity check "
|
|
116
|
+
f"(default {DEFAULT_INTEGRITY_TIMEOUT:g}).",
|
|
117
|
+
)
|
|
118
|
+
group.add_argument(
|
|
119
|
+
"--keep",
|
|
120
|
+
action="store_true",
|
|
121
|
+
help="Keep the restored copies even when every check passes.",
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
def handle(self, *args, **options):
|
|
125
|
+
if options["restore_test"]:
|
|
126
|
+
items = self._restore_test(options)
|
|
127
|
+
else:
|
|
128
|
+
given = [n for dest, n in _RESTORE_ONLY.items() if options[dest] not in (None, False)]
|
|
129
|
+
if given:
|
|
130
|
+
_usage_error(f"{', '.join(given)} require(s) --restore-test")
|
|
131
|
+
items = diagnose(
|
|
132
|
+
options["databases"],
|
|
133
|
+
litestream_config=options["litestream_config"],
|
|
134
|
+
litestream_binary=options["litestream"],
|
|
135
|
+
)
|
|
136
|
+
self.stdout.write(to_json(items) if options["json"] else format_text(items))
|
|
137
|
+
code = exit_code(items)
|
|
138
|
+
if code:
|
|
139
|
+
sys.exit(code)
|
|
140
|
+
|
|
141
|
+
def _restore_test(self, options):
|
|
142
|
+
if not options["restore_dir"]:
|
|
143
|
+
_usage_error("--restore-test requires --restore-dir DIR (D-20)")
|
|
144
|
+
if not options["litestream_config"]:
|
|
145
|
+
_usage_error("--restore-test requires --litestream-config PATH")
|
|
146
|
+
restore_timeout = DEFAULT_RESTORE_TIMEOUT
|
|
147
|
+
if options["restore_timeout"] is not None:
|
|
148
|
+
restore_timeout = _seconds("--restore-timeout", options["restore_timeout"])
|
|
149
|
+
integrity_timeout = DEFAULT_INTEGRITY_TIMEOUT
|
|
150
|
+
if options["integrity_timeout"] is not None:
|
|
151
|
+
integrity_timeout = _seconds("--integrity-timeout", options["integrity_timeout"])
|
|
152
|
+
return restore_test(
|
|
153
|
+
options["databases"],
|
|
154
|
+
restore_dir=options["restore_dir"],
|
|
155
|
+
litestream_config=options["litestream_config"],
|
|
156
|
+
litestream_binary=options["litestream"],
|
|
157
|
+
integrity=options["integrity"] or "full",
|
|
158
|
+
restore_timeout=restore_timeout,
|
|
159
|
+
integrity_timeout=integrity_timeout,
|
|
160
|
+
keep=options["keep"],
|
|
161
|
+
)
|