Hacktoberfest 2026: le issue che i maintainer hanno segnato per ottobre, aperte e adatte ai principianti. Sfoglia le issue Hacktoberfest

Silent data loss: emptying an implicitly-created table (dotted key) makes it vanish from as_string() while unwrap() still contains it

Aperta
#615 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

I maintainer di solito rispondono entro 1 giorno

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
3/5
Tempo stimato
1-2 giorni
Idoneità per principianti
76/100
Tipo di issue
Bug
Chiarezza
Specificata chiaramente
Stato di attività
Attiva
Stack tecnologico
python
Ambito
backend

Direzione di ricerca

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.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

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.

Lingua principale
Python
Stelle
850
Fork
163
Merge medio
13m
PR unite (30g)
2

Preparare l'ambiente

Non abbiamo ancora controllato i file di configurazione di questo progetto. Parti dal suo README e consulta la nostra guida al primo contributo per i passaggi generali.

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di python-poetry/tomlkit

Tutte le issue di python-poetry/tomlkit

Issue simili

Altre issue su Python

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.