weightsdb 0.2.0__tar.gz → 0.2.1__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.
- {weightsdb-0.2.0 → weightsdb-0.2.1}/CHANGELOG.md +18 -6
- {weightsdb-0.2.0 → weightsdb-0.2.1}/PKG-INFO +1 -1
- {weightsdb-0.2.0 → weightsdb-0.2.1}/docs/packages/weightsdb/development-plan.md +2 -2
- {weightsdb-0.2.0 → weightsdb-0.2.1}/docs/packages/weightsdb/spec.md +4 -4
- weightsdb-0.2.1/src/weightsdb/__about__.py +1 -0
- {weightsdb-0.2.0 → weightsdb-0.2.1}/src/weightsdb/engine.py +7 -1
- {weightsdb-0.2.0 → weightsdb-0.2.1}/tests/integration/test_migrations.py +20 -0
- {weightsdb-0.2.0 → weightsdb-0.2.1}/tests/unit/test_engine.py +16 -0
- weightsdb-0.2.0/src/weightsdb/__about__.py +0 -1
- {weightsdb-0.2.0 → weightsdb-0.2.1}/.editorconfig +0 -0
- {weightsdb-0.2.0 → weightsdb-0.2.1}/.github/workflows/ci.yml +0 -0
- {weightsdb-0.2.0 → weightsdb-0.2.1}/.github/workflows/release.yml +0 -0
- {weightsdb-0.2.0 → weightsdb-0.2.1}/.gitignore +0 -0
- {weightsdb-0.2.0 → weightsdb-0.2.1}/.importlinter +0 -0
- {weightsdb-0.2.0 → weightsdb-0.2.1}/.pre-commit-config.yaml +0 -0
- {weightsdb-0.2.0 → weightsdb-0.2.1}/CONTRIBUTING.md +0 -0
- {weightsdb-0.2.0 → weightsdb-0.2.1}/LICENSE +0 -0
- {weightsdb-0.2.0 → weightsdb-0.2.1}/README.md +0 -0
- {weightsdb-0.2.0 → weightsdb-0.2.1}/SECURITY.md +0 -0
- {weightsdb-0.2.0 → weightsdb-0.2.1}/docs/README.md +0 -0
- {weightsdb-0.2.0 → weightsdb-0.2.1}/docs/adoption-checklist.md +0 -0
- {weightsdb-0.2.0 → weightsdb-0.2.1}/docs/quickstart.md +0 -0
- {weightsdb-0.2.0 → weightsdb-0.2.1}/pyproject.toml +0 -0
- {weightsdb-0.2.0 → weightsdb-0.2.1}/requirements/README.md +0 -0
- {weightsdb-0.2.0 → weightsdb-0.2.1}/requirements/ci.lock +0 -0
- {weightsdb-0.2.0 → weightsdb-0.2.1}/requirements/release.in +0 -0
- {weightsdb-0.2.0 → weightsdb-0.2.1}/requirements/release.lock +0 -0
- {weightsdb-0.2.0 → weightsdb-0.2.1}/src/weightsdb/__init__.py +0 -0
- {weightsdb-0.2.0 → weightsdb-0.2.1}/src/weightsdb/backup.py +0 -0
- {weightsdb-0.2.0 → weightsdb-0.2.1}/src/weightsdb/errors.py +0 -0
- {weightsdb-0.2.0 → weightsdb-0.2.1}/src/weightsdb/health.py +0 -0
- {weightsdb-0.2.0 → weightsdb-0.2.1}/src/weightsdb/migrations.py +0 -0
- {weightsdb-0.2.0 → weightsdb-0.2.1}/src/weightsdb/py.typed +0 -0
- {weightsdb-0.2.0 → weightsdb-0.2.1}/src/weightsdb/redaction.py +0 -0
- {weightsdb-0.2.0 → weightsdb-0.2.1}/src/weightsdb/session.py +0 -0
- {weightsdb-0.2.0 → weightsdb-0.2.1}/src/weightsdb/testing.py +0 -0
- {weightsdb-0.2.0 → weightsdb-0.2.1}/src/weightsdb/types.py +0 -0
- {weightsdb-0.2.0 → weightsdb-0.2.1}/tests/conftest.py +0 -0
- {weightsdb-0.2.0 → weightsdb-0.2.1}/tests/contract/test_public_api.py +0 -0
- {weightsdb-0.2.0 → weightsdb-0.2.1}/tests/integration/_migration_fixture/__init__.py +0 -0
- {weightsdb-0.2.0 → weightsdb-0.2.1}/tests/integration/_migration_fixture/env.py +0 -0
- {weightsdb-0.2.0 → weightsdb-0.2.1}/tests/integration/_migration_fixture/models.py +0 -0
- {weightsdb-0.2.0 → weightsdb-0.2.1}/tests/integration/_migration_fixture/versions/0001_create_widgets.py +0 -0
- {weightsdb-0.2.0 → weightsdb-0.2.1}/tests/integration/_migration_fixture/versions/0002_add_note.py +0 -0
- {weightsdb-0.2.0 → weightsdb-0.2.1}/tests/integration/test_backup_restore.py +0 -0
- {weightsdb-0.2.0 → weightsdb-0.2.1}/tests/integration/test_two_schemas.py +0 -0
- {weightsdb-0.2.0 → weightsdb-0.2.1}/tests/performance/test_db_overhead.py +0 -0
- {weightsdb-0.2.0 → weightsdb-0.2.1}/tests/unit/test_health.py +0 -0
- {weightsdb-0.2.0 → weightsdb-0.2.1}/tests/unit/test_redaction.py +0 -0
- {weightsdb-0.2.0 → weightsdb-0.2.1}/tests/unit/test_session.py +0 -0
- {weightsdb-0.2.0 → weightsdb-0.2.1}/tests/unit/test_testing.py +0 -0
- {weightsdb-0.2.0 → weightsdb-0.2.1}/tests/unit/test_types.py +0 -0
|
@@ -7,12 +7,16 @@ packaging and release standards §3.
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
10
|
+
## [0.2.1] — 2026-08-31
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **SQLite connections now set `PRAGMA secure_delete=ON`** alongside the existing per-connection
|
|
14
|
+
pragmas. `secure_delete` defaults to whatever the host's SQLite build chose, so whether a
|
|
15
|
+
deleted row's content was actually overwritten on disk varied by machine — and a consumer's
|
|
16
|
+
retention scrub is a promise about the disk, not about the schema (the M4 handoff's
|
|
17
|
+
"WeightsDB 0.2.1" item, raised by LoadCoach's retention verification). The pragma is applied
|
|
18
|
+
by the same `connect` listener as the rest, so a pool reconnect keeps it, and the test observes
|
|
19
|
+
it on a live connection rather than trusting the connect string.
|
|
16
20
|
|
|
17
21
|
## [0.2.0] — 2026-08-29
|
|
18
22
|
|
|
@@ -34,6 +38,14 @@ packaging and release standards §3.
|
|
|
34
38
|
- SQLite lock contention beyond `busy_timeout` now raises the typed `StorageBusy` instead of a raw
|
|
35
39
|
`sqlalchemy.exc.OperationalError` — a gap in the code this package was extracted from, invisible
|
|
36
40
|
with one consumer and no test for it.
|
|
41
|
+
- `database_health()` no longer conflates a database *ahead of head* with a *pending migration*:
|
|
42
|
+
any `current != head` read as "pending migration", which is actively misleading when the
|
|
43
|
+
database was written by a newer build. Phase 3's own test list names the two as distinct cases.
|
|
44
|
+
- **Coverage fell below the 95 % floor on Python 3.12 (94.89 %).** The uncovered lines were real
|
|
45
|
+
gaps, not measurement noise: `upsert`'s documented refusal of a third dialect, the `StorageBusy`
|
|
46
|
+
translation on a *read-only* `BEGIN`, `temporary_postgres`'s `WEIGHTSDB_REQUIRE_POSTGRES=1`
|
|
47
|
+
failure path, longest-prefix mount selection against an unsorted mount table, and two backups
|
|
48
|
+
sharing an mtime. Each now has a test; the floor is met on 3.12, 3.13 and 3.14 with margin.
|
|
37
49
|
- **`psycopg[binary]` is now part of the `dev` extra.** Seventeen tests assert dialect-specific
|
|
38
50
|
behaviour by building a `postgresql://` engine — no server involved — which SQLAlchemy cannot do
|
|
39
51
|
without an importable DBAPI. Every environment that installed `[dev]` alone failed them with
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: weightsdb
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: Shared SQLAlchemy + Alembic plumbing: engines, sessions, pragmas, migrations, backup and health. No application table, no shared schema.
|
|
5
5
|
Project-URL: Homepage, https://github.com/JPKell/WeightsDB
|
|
6
6
|
Project-URL: Documentation, https://github.com/JPKell/WeightsDB/tree/main/docs
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# WeightsDB — Development Plan
|
|
2
2
|
|
|
3
3
|
**Sequence position:** extracted during **LoadCoach Phase 1**, from FreeWeight's in-application
|
|
4
|
-
storage layer. Not built speculatively (ADR-0011).
|
|
4
|
+
storage layer. Not built speculatively ([ADR-0011](../../adr/0011-shared-package-boundaries.md)).
|
|
5
5
|
**Target:** `weightsdb 0.2.0` by the end of Phase 3; FreeWeight adopts it in FreeWeight Phase 12.
|
|
6
6
|
|
|
7
7
|
**Precondition for starting:** FreeWeight is shipping with a working `freeweight.infrastructure.db`
|
|
@@ -26,7 +26,7 @@ identical (not yet swapped).
|
|
|
26
26
|
* `upsert(session, model, values, *, index_elements)` — the suite's one sanctioned
|
|
27
27
|
`INSERT … ON CONFLICT DO UPDATE`, because select-then-insert is a race under both dialects and a
|
|
28
28
|
hand-written clause is how a dialect-specific variant gets in
|
|
29
|
-
(ADR-0006).
|
|
29
|
+
([ADR-0006](../../adr/0006-sqlite-and-postgresql-roles.md)).
|
|
30
30
|
* Errors: `DatabaseUnavailable`, `StorageBusy`, `SchemaAhead`, `MigrationRequired`,
|
|
31
31
|
`MigrationFailed`.
|
|
32
32
|
* Credential redaction helper used by every error and log path.
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
**Type:** Python package · **Import/distribution name:** `weightsdb` · **Layer:** 3 (capability package)
|
|
4
4
|
**Status:** Specified, not implemented. **Extraction timing:** LoadCoach Phase 1, from FreeWeight's
|
|
5
|
-
`freeweight.infrastructure.db` (ADR-0011).
|
|
6
|
-
**Decision records:** ADR-0005, ADR-0006.
|
|
5
|
+
`freeweight.infrastructure.db` ([ADR-0011](../../adr/0011-shared-package-boundaries.md)).
|
|
6
|
+
**Decision records:** [ADR-0005](../../adr/0005-database-strategy.md), [ADR-0006](../../adr/0006-sqlite-and-postgresql-roles.md).
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
@@ -40,7 +40,7 @@ mechanics are shared.
|
|
|
40
40
|
| Responsibility | Detail |
|
|
41
41
|
|---|---|
|
|
42
42
|
| Engine | `create_engine_for(url, …)` applying dialect-correct settings and pooling |
|
|
43
|
-
| SQLite settings | `foreign_keys=ON`, `journal_mode=WAL`, `busy_timeout`, `synchronous=NORMAL`, applied per connection |
|
|
43
|
+
| SQLite settings | `foreign_keys=ON`, `journal_mode=WAL`, `busy_timeout`, `synchronous=NORMAL`, `secure_delete=ON`, applied per connection |
|
|
44
44
|
| PostgreSQL settings | `statement_timeout`, `lock_timeout`, application name, schema selection |
|
|
45
45
|
| Sessions | `session_factory`, `session_scope()` context manager with commit/rollback semantics |
|
|
46
46
|
| Transactions | `transaction(session)` including `BEGIN IMMEDIATE` on SQLite |
|
|
@@ -262,4 +262,4 @@ Coverage floor: **95 %**.
|
|
|
262
262
|
* Query-plan assertion helpers for tests (`assert_uses_index`), currently duplicated in applications.
|
|
263
263
|
* Retention/pruning helpers for high-volume tables (samples, telemetry, events).
|
|
264
264
|
* Optional DuckDB analytics attachment for FreeWeight, if the aggregate budgets are ever missed
|
|
265
|
-
(ADR-0006 revisit trigger).
|
|
265
|
+
([ADR-0006](../../adr/0006-sqlite-and-postgresql-roles.md) revisit trigger).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.2.1"
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"""weightsdb.engine — dialect-correct engine construction.
|
|
2
2
|
|
|
3
3
|
Database standards §2: SQLite gets ``foreign_keys=ON``, ``journal_mode=WAL``, ``busy_timeout``,
|
|
4
|
-
``synchronous=NORMAL``, applied per connection so a pool reconnect never
|
|
4
|
+
``synchronous=NORMAL``, ``secure_delete=ON``, applied per connection so a pool reconnect never
|
|
5
|
+
silently loses them;
|
|
5
6
|
PostgreSQL gets ``statement_timeout``, ``lock_timeout`` and ``application_name``. Only these two
|
|
6
7
|
dialects are supported (§2) — a third requires an ADR, not a code change here.
|
|
7
8
|
|
|
@@ -168,6 +169,11 @@ def _configure_sqlite(engine: Engine, *, busy_timeout_ms: int) -> None:
|
|
|
168
169
|
cursor.execute("PRAGMA journal_mode=WAL")
|
|
169
170
|
cursor.execute(f"PRAGMA busy_timeout={int(busy_timeout_ms)}")
|
|
170
171
|
cursor.execute("PRAGMA synchronous=NORMAL")
|
|
172
|
+
# secure_delete defaults to whatever the host's SQLite build chose, so whether a
|
|
173
|
+
# deleted row's content is actually overwritten on disk would otherwise vary by
|
|
174
|
+
# machine — and a consumer's retention scrub is a promise about the disk, not about
|
|
175
|
+
# the schema. Set explicitly so the scrub means the same thing everywhere.
|
|
176
|
+
cursor.execute("PRAGMA secure_delete=ON")
|
|
171
177
|
finally:
|
|
172
178
|
cursor.close()
|
|
173
179
|
|
|
@@ -32,6 +32,26 @@ def test_fresh_database_migrates_to_head_sqlite() -> None:
|
|
|
32
32
|
assert columns == {"id", "name", "note"}
|
|
33
33
|
|
|
34
34
|
|
|
35
|
+
def test_the_default_version_table_name_is_a_contract() -> None:
|
|
36
|
+
"""The revision lives in ``alembic_version`` unless a consumer says otherwise.
|
|
37
|
+
|
|
38
|
+
Both applications' shipped databases record their revision under the default name
|
|
39
|
+
(FreeWeight P12's named failure mode): a changed default would make every existing consumer
|
|
40
|
+
database look unmigrated — so the default is pinned here, in WeightsDB itself, not only in
|
|
41
|
+
its consumers' suites.
|
|
42
|
+
"""
|
|
43
|
+
with temporary_sqlite() as engine:
|
|
44
|
+
MigrationRunner(engine, script_location=_SCRIPT_LOCATION).upgrade(backup=False)
|
|
45
|
+
with engine.connect() as connection:
|
|
46
|
+
tables = {
|
|
47
|
+
row[0]
|
|
48
|
+
for row in connection.execute(
|
|
49
|
+
text("SELECT name FROM sqlite_master WHERE type='table'")
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
assert "alembic_version" in tables
|
|
53
|
+
|
|
54
|
+
|
|
35
55
|
def test_fresh_database_migrates_to_head_postgres() -> None:
|
|
36
56
|
with temporary_postgres() as engine:
|
|
37
57
|
runner = MigrationRunner(engine, script_location=_SCRIPT_LOCATION)
|
|
@@ -30,6 +30,19 @@ def test_sqlite_pragmas_applied_on_fresh_connection() -> None:
|
|
|
30
30
|
assert pragmas["journal_mode"] == "wal"
|
|
31
31
|
|
|
32
32
|
|
|
33
|
+
def test_sqlite_secure_delete_is_on_for_every_connection() -> None:
|
|
34
|
+
"""A retention scrub must mean the same thing on every machine.
|
|
35
|
+
|
|
36
|
+
``secure_delete`` defaults to whatever the host's SQLite build chose, so whether deleted
|
|
37
|
+
content is actually overwritten on disk would otherwise vary by machine (the M4 handoff's
|
|
38
|
+
WeightsDB 0.2.1 item). Observed on a live connection, not trusted from the connect string.
|
|
39
|
+
"""
|
|
40
|
+
with temporary_sqlite() as engine:
|
|
41
|
+
with engine.connect() as connection:
|
|
42
|
+
secure_delete = connection.execute(text("PRAGMA secure_delete")).scalar_one()
|
|
43
|
+
assert secure_delete == 1
|
|
44
|
+
|
|
45
|
+
|
|
33
46
|
def test_sqlite_pragmas_applied_after_forced_reconnect() -> None:
|
|
34
47
|
"""A pool recycle must not silently drop the pragmas (spec §7, §11.1)."""
|
|
35
48
|
with temporary_sqlite() as engine:
|
|
@@ -44,8 +57,10 @@ def test_sqlite_pragmas_applied_after_forced_reconnect() -> None:
|
|
|
44
57
|
connection.rollback()
|
|
45
58
|
foreign_keys = connection.execute(text("PRAGMA foreign_keys")).scalar_one()
|
|
46
59
|
journal_mode = connection.execute(text("PRAGMA journal_mode")).scalar_one()
|
|
60
|
+
secure_delete = connection.execute(text("PRAGMA secure_delete")).scalar_one()
|
|
47
61
|
assert foreign_keys == 1
|
|
48
62
|
assert journal_mode == "wal"
|
|
63
|
+
assert secure_delete == 1
|
|
49
64
|
|
|
50
65
|
|
|
51
66
|
def test_sqlite_busy_timeout_raises_storage_busy() -> None:
|
|
@@ -202,6 +217,7 @@ def test_sqlite_connect_listener_hands_transaction_control_to_sqlalchemy() -> No
|
|
|
202
217
|
"PRAGMA journal_mode=WAL",
|
|
203
218
|
"PRAGMA busy_timeout=5000",
|
|
204
219
|
"PRAGMA synchronous=NORMAL",
|
|
220
|
+
"PRAGMA secure_delete=ON",
|
|
205
221
|
]
|
|
206
222
|
|
|
207
223
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.2.0"
|
|
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
|
|
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
|
{weightsdb-0.2.0 → weightsdb-0.2.1}/tests/integration/_migration_fixture/versions/0002_add_note.py
RENAMED
|
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
|