Silent data loss: emptying an implicitly-created table (dotted key) makes it vanish from as_string() while unwrap() still contains it
Les mainteneurs répondent en général sous 1 jour
Personne n'a encore pris cette issue.
Évaluation
- Difficulté
- 3/5
- Temps estimé
- 1-2 jours
- Accessibilité débutants
- 76/100
Piste de recherche
Start with the dotted-key table creation in items.py around lines 1948-1949, then trace Container._render_table in container.py lines 672-708 and removal logic in lines 476-514. Reproduce the minimal examples and add regression coverage for remove() and clear() on implicit tables. Done means as_string() output reparses to the same unwrap() structure without affecting explicit tables, whole-table deletion, inline tables, or AoT elements.
Rédigé par le modèle d'indexation à partir du texte de l'issue.
Description
Summary
After removing keys from a table that was created implicitly (via a dotted key, with no [header] of its own), an empty intermediate table remains in the in-memory model (unwrap() / dict view) but is silently dropped from as_string() / dumps() output. The rendered document no longer round-trips: parse(doc.as_string()).unwrap() != doc.unwrap() — data is lost without any warning.
This is a silent data loss: no exception, no error, the output is perfectly valid TOML — it just no longer contains a table the user can still see in the in-memory model.
Minimal reproduction (copy-paste)
import tomlkit
doc = tomlkit.parse("[a]\nb.c = 1\nd = 2\n")
del doc["a"]["b"]["c"] # empty the implicitly-created table `a.b`
s = doc.as_string()
print(repr(s)) # '[a]\nd = 2\n' <- no trace of `a.b`
print(doc.unwrap()) # {'a': {'b': {}, 'd': 2}} <- `a.b` still here
print(tomlkit.parse(s).unwrap())# {'a': {'d': 2}} <- `a.b` lost
Also works with pure top-level dotted keys, remove(), and clear():
doc = tomlkit.parse("a.b.c = 1\na.d = 2\n")
doc["a"]["b"].remove("c")
# as_string() -> 'a.d = 2\n'
# unwrap() -> {'a': {'b': {}, 'd': 2}}
# reparse -> {'a': {'d': 2}}
doc = tomlkit.parse("[a]\nb.c = 1\nd = 2\n")
doc["a"]["b"].clear()
# same mismatch
What is NOT affected (verified)
- Tables with an explicit
[a.b]header: the empty header is preserved ([a.b]\n), round-trips fine. - Deleting the whole table (
del doc["a"]["b"]): consistent. - Inline tables and AoT elements: consistent.
So the bug is specific to implicit tables — those created by dotted keys (b.c = 1, a.b.c = 1) or by out-of-order [a.b] sections where the parent got no header of its own.
Frequency
A random-mutation fuzz (parse -> 1-4 random delete/add/replace ops -> compare unwrap() vs parse(as_string()).unwrap()) hit this in 274 of 3000 runs. It is the dominant round-trip failure mode.
Root cause
When the parser creates an intermediate table from a dotted key, it sets Table._is_super_table = True (items.py:1948-1949). is_super_table() returns that frozen flag first, so the if not self: return False fallback (which would correctly make an empty table render its own header) is never reached after the table has been emptied.
Container._render_table (container.py:672-708) then treats the table as a super table: no [a.b] header is emitted, and with no children left to render, the table renders as the empty string — while Container.remove / _remove_at (container.py:476-514) never invalidate the frozen _is_super_table flag on the emptied table, and the table stays in the parent's _body and dict view (hence unwrap() still contains it).
Expected
Either the emptied implicit table keeps rendering (e.g. as [a.b], matching the explicit-header behavior), or it is removed from the in-memory model together with the rendered output — but unwrap() and parse(as_string()).unwrap() should never disagree.
Related
- #204 "Deleting a table creates invalid output" (closed) — different symptom (duplicate headers when deleting a whole multi-tier table); this one is about the contents of an implicit table being emptied.
Environment
tomlkit master @ 8c959b5 (0.15.1+), Python 3.12.
- Langage dominant
- Python
- Étoiles
- 850
- Forks
- 163
- Merge moyen
- 13 min
- PR mergées (30 j)
- 2
Préparer son environnement
Nous n'avons pas encore vérifié les fichiers d'installation de ce projet. Commencez par son README, et consultez notre guide de la première contribution pour les étapes générales.
Par où commencer
- Lisez l'issue en entier, puis le guide de contribution du projet.
- Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
- Forkez le dépôt et travaillez sur une branche.
- Ouvrez une pull request qui référence le numéro de l'issue.
Autres issues de python-poetry/tomlkit
-
Difficulté 2/5 1-3 heures Accessibilité débutants 72/100
python-poetry/tomlkit#546 · 2 commentaires ·
Les mainteneurs répondent en général sous 1 jour
-
Difficulté 5/5 Plus d'une semaine Accessibilité débutants 35/100
python-poetry/tomlkit#614 · 2 commentaires ·
Les mainteneurs répondent en général sous 1 jour
-
Difficulté 3/5 1-2 jours Accessibilité débutants 68/100
python-poetry/tomlkit#603 ·
Les mainteneurs répondent en général sous 1 jour
-
Difficulté 3/5 1-2 jours Accessibilité débutants 52/100
python-poetry/tomlkit#580 · 1 commentaire ·
Les mainteneurs répondent en général sous 1 jour
-
Difficulté 3/5 1-2 jours Accessibilité débutants 72/100
python-poetry/tomlkit#577 ·
Les mainteneurs répondent en général sous 1 jour
Toutes les issues de python-poetry/tomlkit
Issues similaires
-
Claiming namespace `apoint`Ouvertenamespace operations
Difficulté 1/5 Moins d'une heure Accessibilité débutants 82/100
EclipseFdn/open-vsx.org#13573 ·
Les mainteneurs répondent en général sous 1 jour
-
Difficulté 2/5 1-3 heures Accessibilité débutants 72/100
collective/icalendar#1854 ·
Les mainteneurs répondent en général sous 1 jour
-
Difficulté 2/5 1-3 heures Accessibilité débutants 72/100
rancher/rancher-ai-agent#412 ·
Les mainteneurs répondent en général sous 6 jours
-
Difficulté 2/5 1-3 heures Accessibilité débutants 84/100
TUDelftGeodesy/DePSI#134 ·
-
Difficulté 2/5 1-3 heures Accessibilité débutants 88/100
HenriquesLab/rxiv-maker#335 ·