neues skript sql_dump_diff.py zum Vergleich von 2 sql-dumps.

- leere, frisch initialisierte Datenbanken der Versionen verglichen
- datasetprogramme erkennen jetzt auch suche nach standardtabellen wie uom
This commit is contained in:
2026-08-22 17:45:46 +02:00
parent c0dd24b6c5
commit a55774eff2
19 changed files with 78583 additions and 269 deletions
+60
View File
@@ -25,6 +25,27 @@ Pro Modell wird zusätzlich `persisted` erkannt (erbt die Klasse `ModelSQL`
o. ä., oder nur `ModelView` - dann ist es ein reines Wizard-/
Transient-Formular ohne eigene Tabelle).
Verfolgt zusätzlich ganz normale **Python-Vererbung innerhalb derselben
Datei**: erbt eine Klasse mit eigenem `__name__` von einer anderen lokalen
Klasse - z. B. `class BOMOutput(BOMInput): __name__ = 'production.bom.output'`
in `production/bom.py`, die selbst keine einzige `fields.X(...)`-Zeile
deklariert, sondern alle Felder von `BOMInput` übernimmt - werden die
geerbten Felder mit aufgenommen (eigene Felder überschreiben geerbte bei
Namensgleichheit). Ohne das sähe ein rein über Vererbung wiederverwendetes
Modell wie eines OHNE Felder aus, und Änderungen daran (z. B. eine
Feldumbenennung im Elternteil) blieben komplett unsichtbar - genau das hat
`sql_dump_diff.py` (Schritt 4) einmal live aufgedeckt.
Die Basisklassen-Auflösung ist bewusst auf "gleiche Datei" beschränkt
(nicht "gleiches Modul"): derselbe lokale Klassenname wie `Cron` taucht
innerhalb eines Moduls oft mehrfach für völlig unterschiedliche Klassen
auf (Tryton-Konvention: eine PoolMeta-Erweiterung heißt oft genauso wie
das Original, z. B. `currency/currency.py` definiert `class Cron(ModelSQL,
ModelView): __name__ = 'currency.cron'`, während `currency/ir.py`
unabhängig davon eine andere Klasse `class Cron(metaclass=PoolMeta):` zur
Erweiterung von `ir.cron` definiert) - ein modulweites Registry würde sich
da gegenseitig überschreiben.
```bash
python3 extract_model_tree.py --repo /mnt/tryton \
--modules company country currency party product ... \
@@ -81,11 +102,50 @@ python3 model_coverage.py --model-tree reports/model_tree_5.0.json \
--report reports/coverage_5.0.md
```
### 4. `sql_dump_diff.py` – gegen den echten DB-Zustand validieren
Alles bisherige ist reine Code-Analyse. Zum Cross-Check: zwei mit
`pg_dump` (Klartext-Format) erzeugte Dumps frisch initialisierter (leerer)
Datenbanken beider Versionen strukturell vergleichen - Tabellen
entfernt/neu, Spalten hinzugefügt/entfernt/Typ geändert, Zeilenzahl
(z. B. mehr XML-Fixture-Daten in der neueren Version). Zeilen werden
bereits vollständig geparst (für eine spätere inhaltliche
Vergleichs-Erweiterung, sobald es Snapshots mit echten Testdaten gibt),
aktuell nutzt der Report davon aber nur die Zeilenzahl.
Mit `--model-diff reports/model_diff.json` wird pro betroffener Tabelle
zusätzlich geprüft, ob die rein statische Code-Analyse (Schritt 2)
dieselbe Änderung schon vorhergesagt hatte (Modellname -> Tabellenname:
Punkte durch Unterstriche ersetzen, wie `extract_updates.py`/
`compare_snapshots.py` es auch tun) - Übereinstimmung bzw. Abweichung wird
markiert.
```bash
python3 sql_dump_diff.py --dump-a data/leer5.sql --label-a 5.0 \
--dump-b data/leer8.sql --label-b 8.0 \
--model-diff reports/model_diff.json \
--output reports/dump_diff_5.0_8.0.json --report reports/dump_diff_5.0_8.0.md
```
**Stand:** 78 von 92 vergleichbaren Fällen stimmen überein (~85%). Die
verbleibenden Abweichungen gehen meist in eine erwartbare Richtung: die
Code-Analyse findet MEHR Veränderung, als im leeren DB-Schema sichtbar
ist (z. B. ein entferntes Function-Feld ohne eigene Spalte, oder
Selection-Wertänderungen ohne Typänderung) - kein Fehler, sondern zwei
Werkzeuge, die unterschiedliche Ebenen messen (Python-Feld-Definition vs.
physisches SQL-Schema). Der `production.bom.output`-Fund oben (Schritt 1)
kam genau aus einer vorherigen Abweichung dieser Art zustande.
## Bekannte Grenzen
- Rein statische Analyse: Felder, die nicht als einfaches
`name = fields.X(...)` auf Klassenebene stehen (z. B. per Schleife/Helper
dynamisch erzeugt), werden nicht erkannt.
- Python-Vererbung wird nur INNERHALB derselben Datei aufgelöst (s. o.).
Erbt eine Klasse von einer Basisklasse aus einer anderen Datei desselben
oder eines anderen Moduls (Import statt lokaler Definition), werden
deren Felder nicht gefunden - unauffällig, aber ein bekannter blinder
Fleck.
- `persisted`-Erkennung ist eine Textsuche über die Basisklassen-Ausdrücke
(`ModelSQL`/`Workflow`/`DeactivableMixin`) - deckt den Normalfall ab, ist
aber kein echtes Type-Checking.
+31 -4
View File
@@ -42,10 +42,27 @@ erkennt:
→ wird zu einem YAML-Record mit `key`, `model`, `fields`. Referenzen auf
zuvor im selben File angelegte Records werden zu `{"$ref": "..."}`,
`Decimal(...)`/`date(...)` zu `{"$decimal": ...}`/`{"$date": ...}`. Alles
nicht automatisch Auflösbare (Wizards, `.click(...)`, Helper-Funktionsaufrufe)
wird als `{"$manual": "<Ausdruck>"}` markiert oder ganz übersprungen -
bewusst sichtbar unvollständig statt still falsch.
`Decimal(...)`/`date(...)` zu `{"$decimal": ...}`/`{"$date": ...}`.
Ein weiteres, sehr häufiges Muster wird ebenfalls erkannt: Zugriff auf
bereits vorhandene Stammdaten (Länder, Währungen, Einheiten,
Standard-Lagerorte, ... - i. d. R. per XML-Fixture beim Modul-Aktivieren
angelegt, nicht Teil des eigentlichen Testdatensatzes):
```
>>> ProductUom = Model.get('product.uom')
>>> unit, = ProductUom.find([('name', '=', 'Unit')])
```
→ wird zu `{"$search": {"model": "product.uom", "domain": [["name", "=", "Unit"]]}}`
- aufgelöst wird das erst live beim Anlegen durch `create_dataset.py`
(Schritt 3), nicht schon hier, da diese Records ja nicht selbst Teil der
YAML sind. Nur literale Domains werden erkannt (keine Variablen darin);
sonst bleibt es wie bisher `$manual`.
Alles sonst nicht automatisch Auflösbare (Wizards, `.click(...)`,
Helper-Funktionsaufrufe) wird als `{"$manual": "<Ausdruck>"}` markiert oder
ganz übersprungen - bewusst sichtbar unvollständig statt still falsch.
Record-Keys werden mit dem Dateinamen präfixiert, damit beim
Zusammenführen mehrerer Module keine Kollisionen entstehen.
@@ -83,6 +100,16 @@ bleibt trotzdem ein Fehler - das ist ja gerade das gesuchte Signal für eine
echte Schema-Änderung. Weitere Umbenennungen bei Bedarf in `FIELD_RENAMES`
ergänzen, sobald ein Lauf sie aufdeckt.
**`$search`-Auflösung:** Werte der Form `{"$search": {"model": ...,
"domain": ...}}` (von `rst_to_dataset.py` erzeugt, siehe Schritt 1) werden
live per `Model.get(model).find(domain)` gegen den Zielserver aufgelöst -
für Stammdaten, die nicht selbst angelegt werden, sondern schon per
XML-Fixture da sind. Ergebnis wird pro Lauf gecacht (dieselbe Suche, z. B.
nach der Einheit "Unit", kommt in vielen Records vor). Kein Treffer ->
Fehler mit Klartext statt eines neu angelegten Records - das ist selbst
ein Signal, dass sich die Stammdaten zwischen den Versionen geändert
haben.
### 4. `compare_snapshots.py` – zwei Versionen gegenüberstellen
Vergleicht zwei Snapshots (gleiche YAML, zwei Versionen/Datenbanken) und