django-dbs 0.1.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.
Files changed (45) hide show
  1. django_dbs-0.1.0/LICENSE +21 -0
  2. django_dbs-0.1.0/PKG-INFO +194 -0
  3. django_dbs-0.1.0/README.md +166 -0
  4. django_dbs-0.1.0/dbs/__init__.py +34 -0
  5. django_dbs-0.1.0/dbs/_cli.py +31 -0
  6. django_dbs-0.1.0/dbs/apps.py +17 -0
  7. django_dbs-0.1.0/dbs/container/__init__.py +29 -0
  8. django_dbs-0.1.0/dbs/container/blocks.py +202 -0
  9. django_dbs-0.1.0/dbs/container/format.py +155 -0
  10. django_dbs-0.1.0/dbs/contrib/__init__.py +10 -0
  11. django_dbs-0.1.0/dbs/contrib/admin.py +109 -0
  12. django_dbs-0.1.0/dbs/contrib/urls.py +12 -0
  13. django_dbs-0.1.0/dbs/crypto/__init__.py +25 -0
  14. django_dbs-0.1.0/dbs/crypto/envelope.py +131 -0
  15. django_dbs-0.1.0/dbs/crypto/kdf.py +62 -0
  16. django_dbs-0.1.0/dbs/engine/__init__.py +12 -0
  17. django_dbs-0.1.0/dbs/engine/backup.py +129 -0
  18. django_dbs-0.1.0/dbs/engine/payload.py +57 -0
  19. django_dbs-0.1.0/dbs/engine/restore.py +91 -0
  20. django_dbs-0.1.0/dbs/engine/validate.py +66 -0
  21. django_dbs-0.1.0/dbs/exceptions.py +42 -0
  22. django_dbs-0.1.0/dbs/files.py +150 -0
  23. django_dbs-0.1.0/dbs/introspect.py +135 -0
  24. django_dbs-0.1.0/dbs/management/__init__.py +0 -0
  25. django_dbs-0.1.0/dbs/management/commands/__init__.py +0 -0
  26. django_dbs-0.1.0/dbs/management/commands/dbs_backup.py +60 -0
  27. django_dbs-0.1.0/dbs/management/commands/dbs_restore.py +49 -0
  28. django_dbs-0.1.0/dbs/management/commands/dbs_validate.py +39 -0
  29. django_dbs-0.1.0/dbs/registry.py +94 -0
  30. django_dbs-0.1.0/dbs/serialize.py +52 -0
  31. django_dbs-0.1.0/dbs/transports/__init__.py +5 -0
  32. django_dbs-0.1.0/dbs/transports/ssh.py +145 -0
  33. django_dbs-0.1.0/django_dbs.egg-info/PKG-INFO +194 -0
  34. django_dbs-0.1.0/django_dbs.egg-info/SOURCES.txt +43 -0
  35. django_dbs-0.1.0/django_dbs.egg-info/dependency_links.txt +1 -0
  36. django_dbs-0.1.0/django_dbs.egg-info/requires.txt +12 -0
  37. django_dbs-0.1.0/django_dbs.egg-info/top_level.txt +1 -0
  38. django_dbs-0.1.0/pyproject.toml +41 -0
  39. django_dbs-0.1.0/setup.cfg +4 -0
  40. django_dbs-0.1.0/tests/test_admin.py +59 -0
  41. django_dbs-0.1.0/tests/test_commands.py +43 -0
  42. django_dbs-0.1.0/tests/test_container.py +92 -0
  43. django_dbs-0.1.0/tests/test_crypto.py +48 -0
  44. django_dbs-0.1.0/tests/test_engine.py +131 -0
  45. django_dbs-0.1.0/tests/test_ssh.py +38 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 bn7ya
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,194 @@
1
+ Metadata-Version: 2.4
2
+ Name: django-dbs
3
+ Version: 0.1.0
4
+ Summary: Redundant, self-healing, encrypted single-file backup for Django projects.
5
+ Author: bn7ya
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/bn7ya/dbs
8
+ Keywords: django,backup,restore,encryption,reed-solomon,redundancy
9
+ Classifier: Framework :: Django
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Topic :: System :: Archiving :: Backup
13
+ Classifier: Topic :: Security :: Cryptography
14
+ Requires-Python: >=3.9
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Requires-Dist: Django>=4.2
18
+ Requires-Dist: cryptography>=41
19
+ Requires-Dist: argon2-cffi>=21.3
20
+ Requires-Dist: reedsolo>=1.7
21
+ Provides-Extra: ssh
22
+ Requires-Dist: paramiko>=3; extra == "ssh"
23
+ Provides-Extra: dev
24
+ Requires-Dist: pytest>=7; extra == "dev"
25
+ Requires-Dist: pytest-django>=4; extra == "dev"
26
+ Requires-Dist: paramiko>=3; extra == "dev"
27
+ Dynamic: license-file
28
+
29
+ # DBS — Django Backup Solution
30
+
31
+ A backup library you drop into a Django project's source. It reads your models,
32
+ relations and files and writes **one encrypted file** that is *redundant* and
33
+ *self-healing*: every backup stores **two copies** of the data plus
34
+ **Reed-Solomon parity**, so silent corruption — the kind a non-ECC RAM stick
35
+ produces — is detected and repaired on restore instead of quietly poisoning
36
+ your data.
37
+
38
+ ```
39
+ io ──► security (Argon2id + AES-256-GCM) ──► integrity (2 copies · per-block
40
+ hashes · Reed-Solomon) ──► data (models · relations · files)
41
+ ```
42
+
43
+ ## Why not django-dbbackup?
44
+
45
+ `django-dbbackup` is great for "dump the DB + media and ship it somewhere".
46
+ DBS targets a different need:
47
+
48
+ | | django-dbbackup | **DBS** |
49
+ |---|---|---|
50
+ | Two self-healing copies in one file | ✗ | ✅ |
51
+ | Reed-Solomon FEC for bit-rot | ✗ | ✅ |
52
+ | Per-field mapping (value / file / file-path) | ✗ | ✅ |
53
+ | Passphrase-derived key, **never stored** (Argon2id) | ✗ (GPG keys) | ✅ |
54
+ | Per-file + per-block integrity hashes | partial | ✅ |
55
+ | CLI · admin-UI download · SFTP | CLI · remote storages | ✅ |
56
+
57
+ ## Install
58
+
59
+ ```bash
60
+ pip install django-dbs # core
61
+ pip install "django-dbs[ssh]" # + SFTP transport (paramiko)
62
+ ```
63
+
64
+ Add the app:
65
+
66
+ ```python
67
+ INSTALLED_APPS = [..., "dbs"]
68
+ ```
69
+
70
+ ## Quick start
71
+
72
+ ### 1. (Optional) declare what to back up
73
+
74
+ By default DBS auto-discovers every model, treats `FileField`/`ImageField` as
75
+ files, and preserves relations. Register a model only to override the defaults —
76
+ in a `dbs.py` module inside your app (auto-discovered like `admin.py`):
77
+
78
+ ```python
79
+ # myapp/dbs.py
80
+ from dbs import backup_registry, FieldType, ModelBackup
81
+ from .models import Invoice
82
+
83
+ @backup_registry.register(Invoice)
84
+ class InvoiceBackup(ModelBackup):
85
+ overrides = {
86
+ "scanned_pdf_path": FieldType.FILE_PATH, # CharField holding a path -> embed the file
87
+ "render_cache": FieldType.EXCLUDE, # don't back this column up
88
+ }
89
+ file_roots = ["/srv/myapp/uploads"] # extra non-model file trees
90
+ ```
91
+
92
+ `FieldType` values: `VALUE` (default), `FILE` (embed a FileField's bytes),
93
+ `FILE_PATH` (a string column whose path's file is embedded), `EXCLUDE`.
94
+
95
+ ### 2. Back up / restore / validate from the CLI
96
+
97
+ ```bash
98
+ python manage.py dbs_backup backup.dbs # prompts for a passphrase
99
+ python manage.py dbs_validate backup.dbs # structural check, no passphrase
100
+ python manage.py dbs_validate backup.dbs -p secret # + verify decryption
101
+ python manage.py dbs_restore backup.dbs # restore rows + files
102
+ ```
103
+
104
+ The passphrase comes from `--passphrase`, then `$DBS_PASSPHRASE`, then a prompt.
105
+
106
+ ### 3. Download / upload from the admin UI
107
+
108
+ ```python
109
+ # urls.py
110
+ urlpatterns += [path("dbs/", include("dbs.contrib.urls"))]
111
+ ```
112
+
113
+ Superusers can then visit `/dbs/backup/` to download an encrypted backup and
114
+ `/dbs/restore/` to upload one. The passphrase is entered in the form and never
115
+ stored server-side.
116
+
117
+ ### 4. Ship a backup to another server (SFTP)
118
+
119
+ ```python
120
+ # settings.py — profiles reference a key by path; no secrets embedded
121
+ DBS_SSH_TARGETS = {
122
+ "offsite": {
123
+ "host": "backups.example.com", "username": "deploy",
124
+ "key_filename": "/home/deploy/.ssh/id_ed25519",
125
+ "remote_dir": "/var/backups/myproject",
126
+ "known_hosts": "/home/deploy/.ssh/known_hosts",
127
+ }
128
+ }
129
+ ```
130
+
131
+ ```python
132
+ from dbs import create_backup
133
+ from dbs.transports import SSHTarget, push_backup
134
+
135
+ data = create_backup("my passphrase")
136
+ push_backup(data, "backup.dbs", SSHTarget.from_settings("offsite"))
137
+ ```
138
+
139
+ ### Python API
140
+
141
+ ```python
142
+ from dbs import create_backup, restore_backup, validate_backup
143
+
144
+ blob = create_backup("passphrase", output="backup.dbs")
145
+ report = validate_backup(blob, "passphrase") # report.ok / report.summary()
146
+ result = restore_backup(blob, "passphrase") # result.healed is True if it repaired corruption
147
+ ```
148
+
149
+ ## How it heals
150
+
151
+ On write, the encrypted stream is split into blocks; each block gets a BLAKE2b
152
+ hash and a layer of Reed-Solomon parity, and the whole stream is stored **twice**
153
+ (plus the header and manifest are stored twice). On read, each block is taken
154
+ from whichever copy verifies; sparse bit-flips are corrected in place by
155
+ Reed-Solomon even when *both* copies are hit. Every recovered block is checked
156
+ against its stored hash, so a mis-correction can never slip through — and a
157
+ freshly written backup is re-read and verified end-to-end (**verify-after-write**)
158
+ before the command reports success.
159
+
160
+ What it can recover from: whole-block loss in one copy, and sparse byte errors
161
+ (up to the parity budget, ~8 bytes per 255-byte codeword by default) in both
162
+ copies. What it cannot: a block destroyed *beyond* the parity budget in **both**
163
+ copies — DBS then refuses to restore and reports exactly which blocks failed,
164
+ rather than producing silently wrong data.
165
+
166
+ ## Security model
167
+
168
+ * **Argon2id** derives a key from your passphrase (memory-hard ⇒ brute-force
169
+ resistant). Raise `KDFParams` cost for more resistance.
170
+ * **Envelope encryption**: a random data key encrypts the payload with
171
+ **AES-256-GCM**; that data key is wrapped by the passphrase-derived key. The
172
+ file stores only the salt, Argon2 parameters and the wrapped key — **never the
173
+ passphrase and never the raw data key**.
174
+ * A wrong passphrase fails the GCM tag check and is reported as such; it can
175
+ never yield partial/garbage data.
176
+
177
+ ## Settings reference
178
+
179
+ | Setting | Purpose |
180
+ |---|---|
181
+ | `DBS_EXCLUDE_MODELS` | `["app.Model", ...]` to skip (defaults skip contenttypes, permissions, admin log, sessions). |
182
+ | `DBS_FILE_ROOTS` | Extra directories embedded in every backup. |
183
+ | `DBS_SSH_TARGETS` | Named SFTP connection profiles. |
184
+
185
+ ## Development
186
+
187
+ ```bash
188
+ pip install -e ".[dev]"
189
+ pytest
190
+ ```
191
+
192
+ ## License
193
+
194
+ MIT
@@ -0,0 +1,166 @@
1
+ # DBS — Django Backup Solution
2
+
3
+ A backup library you drop into a Django project's source. It reads your models,
4
+ relations and files and writes **one encrypted file** that is *redundant* and
5
+ *self-healing*: every backup stores **two copies** of the data plus
6
+ **Reed-Solomon parity**, so silent corruption — the kind a non-ECC RAM stick
7
+ produces — is detected and repaired on restore instead of quietly poisoning
8
+ your data.
9
+
10
+ ```
11
+ io ──► security (Argon2id + AES-256-GCM) ──► integrity (2 copies · per-block
12
+ hashes · Reed-Solomon) ──► data (models · relations · files)
13
+ ```
14
+
15
+ ## Why not django-dbbackup?
16
+
17
+ `django-dbbackup` is great for "dump the DB + media and ship it somewhere".
18
+ DBS targets a different need:
19
+
20
+ | | django-dbbackup | **DBS** |
21
+ |---|---|---|
22
+ | Two self-healing copies in one file | ✗ | ✅ |
23
+ | Reed-Solomon FEC for bit-rot | ✗ | ✅ |
24
+ | Per-field mapping (value / file / file-path) | ✗ | ✅ |
25
+ | Passphrase-derived key, **never stored** (Argon2id) | ✗ (GPG keys) | ✅ |
26
+ | Per-file + per-block integrity hashes | partial | ✅ |
27
+ | CLI · admin-UI download · SFTP | CLI · remote storages | ✅ |
28
+
29
+ ## Install
30
+
31
+ ```bash
32
+ pip install django-dbs # core
33
+ pip install "django-dbs[ssh]" # + SFTP transport (paramiko)
34
+ ```
35
+
36
+ Add the app:
37
+
38
+ ```python
39
+ INSTALLED_APPS = [..., "dbs"]
40
+ ```
41
+
42
+ ## Quick start
43
+
44
+ ### 1. (Optional) declare what to back up
45
+
46
+ By default DBS auto-discovers every model, treats `FileField`/`ImageField` as
47
+ files, and preserves relations. Register a model only to override the defaults —
48
+ in a `dbs.py` module inside your app (auto-discovered like `admin.py`):
49
+
50
+ ```python
51
+ # myapp/dbs.py
52
+ from dbs import backup_registry, FieldType, ModelBackup
53
+ from .models import Invoice
54
+
55
+ @backup_registry.register(Invoice)
56
+ class InvoiceBackup(ModelBackup):
57
+ overrides = {
58
+ "scanned_pdf_path": FieldType.FILE_PATH, # CharField holding a path -> embed the file
59
+ "render_cache": FieldType.EXCLUDE, # don't back this column up
60
+ }
61
+ file_roots = ["/srv/myapp/uploads"] # extra non-model file trees
62
+ ```
63
+
64
+ `FieldType` values: `VALUE` (default), `FILE` (embed a FileField's bytes),
65
+ `FILE_PATH` (a string column whose path's file is embedded), `EXCLUDE`.
66
+
67
+ ### 2. Back up / restore / validate from the CLI
68
+
69
+ ```bash
70
+ python manage.py dbs_backup backup.dbs # prompts for a passphrase
71
+ python manage.py dbs_validate backup.dbs # structural check, no passphrase
72
+ python manage.py dbs_validate backup.dbs -p secret # + verify decryption
73
+ python manage.py dbs_restore backup.dbs # restore rows + files
74
+ ```
75
+
76
+ The passphrase comes from `--passphrase`, then `$DBS_PASSPHRASE`, then a prompt.
77
+
78
+ ### 3. Download / upload from the admin UI
79
+
80
+ ```python
81
+ # urls.py
82
+ urlpatterns += [path("dbs/", include("dbs.contrib.urls"))]
83
+ ```
84
+
85
+ Superusers can then visit `/dbs/backup/` to download an encrypted backup and
86
+ `/dbs/restore/` to upload one. The passphrase is entered in the form and never
87
+ stored server-side.
88
+
89
+ ### 4. Ship a backup to another server (SFTP)
90
+
91
+ ```python
92
+ # settings.py — profiles reference a key by path; no secrets embedded
93
+ DBS_SSH_TARGETS = {
94
+ "offsite": {
95
+ "host": "backups.example.com", "username": "deploy",
96
+ "key_filename": "/home/deploy/.ssh/id_ed25519",
97
+ "remote_dir": "/var/backups/myproject",
98
+ "known_hosts": "/home/deploy/.ssh/known_hosts",
99
+ }
100
+ }
101
+ ```
102
+
103
+ ```python
104
+ from dbs import create_backup
105
+ from dbs.transports import SSHTarget, push_backup
106
+
107
+ data = create_backup("my passphrase")
108
+ push_backup(data, "backup.dbs", SSHTarget.from_settings("offsite"))
109
+ ```
110
+
111
+ ### Python API
112
+
113
+ ```python
114
+ from dbs import create_backup, restore_backup, validate_backup
115
+
116
+ blob = create_backup("passphrase", output="backup.dbs")
117
+ report = validate_backup(blob, "passphrase") # report.ok / report.summary()
118
+ result = restore_backup(blob, "passphrase") # result.healed is True if it repaired corruption
119
+ ```
120
+
121
+ ## How it heals
122
+
123
+ On write, the encrypted stream is split into blocks; each block gets a BLAKE2b
124
+ hash and a layer of Reed-Solomon parity, and the whole stream is stored **twice**
125
+ (plus the header and manifest are stored twice). On read, each block is taken
126
+ from whichever copy verifies; sparse bit-flips are corrected in place by
127
+ Reed-Solomon even when *both* copies are hit. Every recovered block is checked
128
+ against its stored hash, so a mis-correction can never slip through — and a
129
+ freshly written backup is re-read and verified end-to-end (**verify-after-write**)
130
+ before the command reports success.
131
+
132
+ What it can recover from: whole-block loss in one copy, and sparse byte errors
133
+ (up to the parity budget, ~8 bytes per 255-byte codeword by default) in both
134
+ copies. What it cannot: a block destroyed *beyond* the parity budget in **both**
135
+ copies — DBS then refuses to restore and reports exactly which blocks failed,
136
+ rather than producing silently wrong data.
137
+
138
+ ## Security model
139
+
140
+ * **Argon2id** derives a key from your passphrase (memory-hard ⇒ brute-force
141
+ resistant). Raise `KDFParams` cost for more resistance.
142
+ * **Envelope encryption**: a random data key encrypts the payload with
143
+ **AES-256-GCM**; that data key is wrapped by the passphrase-derived key. The
144
+ file stores only the salt, Argon2 parameters and the wrapped key — **never the
145
+ passphrase and never the raw data key**.
146
+ * A wrong passphrase fails the GCM tag check and is reported as such; it can
147
+ never yield partial/garbage data.
148
+
149
+ ## Settings reference
150
+
151
+ | Setting | Purpose |
152
+ |---|---|
153
+ | `DBS_EXCLUDE_MODELS` | `["app.Model", ...]` to skip (defaults skip contenttypes, permissions, admin log, sessions). |
154
+ | `DBS_FILE_ROOTS` | Extra directories embedded in every backup. |
155
+ | `DBS_SSH_TARGETS` | Named SFTP connection profiles. |
156
+
157
+ ## Development
158
+
159
+ ```bash
160
+ pip install -e ".[dev]"
161
+ pytest
162
+ ```
163
+
164
+ ## License
165
+
166
+ MIT
@@ -0,0 +1,34 @@
1
+ """DBS -- Django Backup Solution.
2
+
3
+ A redundant, self-healing, encrypted, single-file backup for Django projects.
4
+
5
+ Public API::
6
+
7
+ from dbs import backup_registry, FieldType, ModelBackup
8
+ from dbs import create_backup, restore_backup, validate_backup
9
+ """
10
+
11
+ from .registry import BackupRegistry, FieldType, ModelBackup, backup_registry
12
+
13
+ __version__ = "0.1.0"
14
+
15
+ __all__ = [
16
+ "backup_registry",
17
+ "BackupRegistry",
18
+ "FieldType",
19
+ "ModelBackup",
20
+ "create_backup",
21
+ "restore_backup",
22
+ "validate_backup",
23
+ "__version__",
24
+ ]
25
+
26
+
27
+ def __getattr__(name):
28
+ # Lazily expose the engine entry points so importing ``dbs`` never forces the
29
+ # Django app registry to be ready (the registry/FieldType imports above don't).
30
+ if name in {"create_backup", "restore_backup", "validate_backup"}:
31
+ from . import engine
32
+
33
+ return getattr(engine, name)
34
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
@@ -0,0 +1,31 @@
1
+ """Shared helpers for the management commands (passphrase resolution, etc.)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import getpass
6
+ import os
7
+
8
+ from django.core.management.base import CommandError
9
+
10
+ ENV_VAR = "DBS_PASSPHRASE"
11
+
12
+
13
+ def resolve_passphrase(option_value: str | None, *, confirm: bool = False) -> str:
14
+ """Resolve a passphrase from the CLI option, the environment, or a prompt.
15
+
16
+ Order: explicit ``--passphrase`` > ``$DBS_PASSPHRASE`` > interactive prompt.
17
+ When ``confirm`` is set (backups), an interactive prompt asks twice.
18
+ """
19
+ if option_value:
20
+ return option_value
21
+ env_value = os.environ.get(ENV_VAR)
22
+ if env_value:
23
+ return env_value
24
+ passphrase = getpass.getpass("DBS passphrase: ")
25
+ if not passphrase:
26
+ raise CommandError("A passphrase is required.")
27
+ if confirm:
28
+ again = getpass.getpass("Confirm passphrase: ")
29
+ if again != passphrase:
30
+ raise CommandError("Passphrases did not match.")
31
+ return passphrase
@@ -0,0 +1,17 @@
1
+ """Django app configuration for DBS."""
2
+
3
+ from django.apps import AppConfig
4
+
5
+
6
+ class DbsConfig(AppConfig):
7
+ name = "dbs"
8
+ verbose_name = "Django Backup Solution"
9
+ default_auto_field = "django.db.models.BigAutoField"
10
+
11
+ def ready(self):
12
+ # Auto-discover ``dbs.py`` modules in installed apps, mirroring how the
13
+ # admin discovers ``admin.py``. This lets projects register models via
14
+ # ``@backup_registry.register(...)`` without an explicit import.
15
+ from django.utils.module_loading import autodiscover_modules
16
+
17
+ autodiscover_modules("dbs")
@@ -0,0 +1,29 @@
1
+ """The container layer: block framing, redundancy, Reed-Solomon and file format.
2
+
3
+ This is the part that makes the backup *self-healing*. The encrypted payload is
4
+ split into fixed-size blocks; every block carries its own hash and a layer of
5
+ Reed-Solomon parity, and the whole encrypted stream is written twice. On read,
6
+ each block is recovered from whichever copy verifies, with Reed-Solomon
7
+ correcting the kind of sparse bit-flips a non-ECC RAM stick produces.
8
+ """
9
+
10
+ from .blocks import (
11
+ DEFAULT_BLOCK_SIZE,
12
+ DEFAULT_NSYM,
13
+ BlockPlan,
14
+ RepairReport,
15
+ encode_copy,
16
+ repair_copies,
17
+ )
18
+ from .format import read_container, write_container
19
+
20
+ __all__ = [
21
+ "DEFAULT_BLOCK_SIZE",
22
+ "DEFAULT_NSYM",
23
+ "BlockPlan",
24
+ "RepairReport",
25
+ "encode_copy",
26
+ "repair_copies",
27
+ "read_container",
28
+ "write_container",
29
+ ]