Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix behavior for out of order tables #68

Merged
merged 1 commit into from
Jan 29, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions tests/test_toml_document.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@

from datetime import datetime

import pytest
import tomlkit
from tomlkit import parse
from tomlkit._utils import _utc
from tomlkit.exceptions import NonExistentKey


def test_document_is_a_dict(example):
Expand Down Expand Up @@ -473,3 +475,63 @@ def test_declare_sub_table_with_intermediate_table():
doc = parse(content)
assert {"tommy": 87, "mary": 66, "bob": {"score": 91}} == doc["students"]
assert {"tommy": 87, "mary": 66, "bob": {"score": 91}} == doc.get("students")


def test_values_can_still_be_set_for_out_of_order_tables():
content = """
[a.a]
key = "value"

[a.b]

[a.a.c]
"""

doc = parse(content)
doc["a"]["a"]["key"] = "new_value"

assert "new_value" == doc["a"]["a"]["key"]

expected = """
[a.a]
key = "new_value"

[a.b]

[a.a.c]
"""

assert expected == doc.as_string()

doc["a"]["a"]["bar"] = "baz"

expected = """
[a.a]
key = "new_value"
bar = "baz"

[a.b]

[a.a.c]
"""

assert expected == doc.as_string()

del doc["a"]["a"]["key"]

expected = """
[a.a]
bar = "baz"

[a.b]

[a.a.c]
"""

assert expected == doc.as_string()

with pytest.raises(NonExistentKey):
doc["a"]["a"]["key"]

with pytest.raises(NonExistentKey):
del doc["a"]["a"]["key"]
92 changes: 79 additions & 13 deletions tomlkit/container.py
Original file line number Diff line number Diff line change
Expand Up @@ -524,21 +524,12 @@ def __getitem__(self, key): # type: (Union[Key, str]) -> Union[Item, Container]
raise NonExistentKey(key)

if isinstance(idx, tuple):
container = Container(True)

for i in idx:
item = self._body[i][1]

if isinstance(item, Table):
for k, v in item.value.body:
container.append(k, v)
else:
container.append(key, item)

return container
# The item we are getting is an out of order table
# so we need a proxy to retrieve the proper objects
# from the parent container
return OutOfOrderTableProxy(self, idx)

item = self._body[idx][1]

if item.is_boolean():
return item.value

Expand Down Expand Up @@ -571,6 +562,9 @@ def _replace(
def _replace_at(
self, idx, new_key, value
): # type: (Union[int, Tuple[int]], Union[Key, str], Item) -> None
if not isinstance(new_key, Key):
new_key = Key(new_key)

if isinstance(idx, tuple):
for i in idx[1:]:
self._body[i] = (None, Null())
Expand All @@ -580,6 +574,8 @@ def _replace_at(
k, v = self._body[idx]

self._map[new_key] = self._map.pop(k)
if new_key != k:
super(Container, self).__delitem__(k)

if isinstance(self._map[new_key], tuple):
self._map[new_key] = self._map[new_key][0]
Expand Down Expand Up @@ -640,3 +636,73 @@ def __copy__(self): # type: () -> Container
c._map.update(self._map)

return c


class OutOfOrderTableProxy(object):
def __init__(self, container, indices): # type: (Container, Tuple) -> None
self._container = container
self._internal_container = Container(self._container.parsing)
self._tables = []
self._tables_map = {}
self._map = {}

for i in indices:
key, item = self._container._body[i]

if isinstance(item, Table):
self._tables.append(item)
table_idx = len(self._tables) - 1
for k, v in item.value.body:
self._internal_container.append(k, v)
self._tables_map[k] = table_idx
else:
self._internal_container.append(key, item)
self._map[key] = i

def __getitem__(self, key): # type: (Union[Key, str]) -> Any
if key not in self._internal_container:
raise NonExistentKey(key)

return self._internal_container[key]

def __setitem__(self, key, item): # type: (Union[Key, str], Any) -> None
if key in self._map:
idx = self._map[key]
self._container._replace_at(idx, key, item)
elif key in self._tables_map:
table = self._tables[self._tables_map[key]]
table[key] = item
elif self._tables:
table = self._tables[0]
table[key] = item
else:
self._container[key] = item

def __delitem__(self, key): # type: (Union[Key, str]) -> None
if key in self._map:
idx = self._map[key]
del self._container[key]
del self._map[key]
elif key in self._tables_map:
table = self._tables[self._tables_map[key]]
del table[key]
del self._tables_map[key]
else:
raise NonExistentKey(key)

del self._internal_container[key]

def __str__(self):
return str(self._internal_container)

def __repr__(self):
return repr(self._internal_container)

def __eq__(self, other): # type: (Dict) -> bool
if not isinstance(other, dict):
return NotImplemented

return self._internal_container == other

def __getattr__(self, attribute):
return getattr(self._internal_container, attribute)