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

Various fixes #122

Merged
merged 5 commits into from
May 19, 2021
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
18 changes: 18 additions & 0 deletions tests/test_build.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ def test_build_example(example):

doc.add(nl())
doc.add(comment("Products"))
doc.add(nl())

products = aot()
doc["products"] = products
Expand Down Expand Up @@ -128,3 +129,20 @@ def test_append_table_after_multiple_indices():
"""
doc = parse(content)
doc.append("foobar", {"name": "John"})


def test_top_level_keys_are_put_at_the_root_of_the_document():
doc = document()
doc.add(comment("Comment"))
doc["foo"] = {"name": "test"}
doc["bar"] = 1

expected = """\
# Comment
bar = 1

[foo]
name = "test"
"""

assert doc.as_string()
17 changes: 17 additions & 0 deletions tests/test_parser.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import pytest

from tomlkit.exceptions import EmptyTableNameError
from tomlkit.exceptions import InternalParserError
from tomlkit.items import StringType
from tomlkit.parser import Parser
Expand All @@ -13,3 +14,19 @@ def test_parser_should_raise_an_internal_error_if_parsing_wrong_type_of_string()

assert e.value.line == 1
assert e.value.col == 0


def test_parser_should_raise_an_error_for_empty_tables():
content = """
[one]

[]
"""

parser = Parser(content)

with pytest.raises(EmptyTableNameError) as e:
parser.parse()

assert e.value.line == 4
assert e.value.col == 1
51 changes: 51 additions & 0 deletions tests/test_toml_document.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import tomlkit

from tomlkit import parse
from tomlkit._compat import PY36
from tomlkit._utils import _utc
from tomlkit.exceptions import NonExistentKey

Expand Down Expand Up @@ -618,3 +619,53 @@ def test_string_output_order_is_preserved_for_out_of_order_tables():
"""

assert expected == doc.as_string()


def test_updating_nested_value_keeps_correct_indent():
content = """
[Key1]
[key1.Key2]
Value1 = 10
Value2 = 30
"""

doc = parse(content)
doc["key1"]["Key2"]["Value1"] = 20

expected = """
[Key1]
[key1.Key2]
Value1 = 20
Value2 = 30
"""

assert doc.as_string() == expected


@pytest.mark.skipif(not PY36, reason="Dict order is not deterministic on Python < 3.6")
def test_repr():
content = """
namespace.key1 = "value1"
namespace.key2 = "value2"

[tool.poetry.foo]
option = "test"

[tool.poetry.bar]
option = "test"
inline = {"foo" = "bar", "bar" = "baz"}
"""

doc = parse(content)

assert (
repr(doc)
== "{'namespace': {'key1': 'value1', 'key2': 'value2'}, 'tool': {'poetry': {'foo': {'option': 'test'}, 'bar': {'option': 'test', 'inline': {'foo': 'bar', 'bar': 'baz'}}}}}"
)

assert (
repr(doc["tool"])
== "{'poetry': {'foo': {'option': 'test'}, 'bar': {'option': 'test', 'inline': {'foo': 'bar', 'bar': 'baz'}}}}"
)

assert repr(doc["namespace"]) == "{'key1': 'value1', 'key2': 'value2'}"
5 changes: 5 additions & 0 deletions tomlkit/_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,11 @@ def _name_from_offset(delta):
else:
from collections import OrderedDict

try:
from collections.abc import MutableMapping
except ImportError:
from collections import MutableMapping


def decode(string, encodings=None):
if not PY2 and not isinstance(string, bytes):
Expand Down
78 changes: 32 additions & 46 deletions tomlkit/container.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from typing import Tuple
from typing import Union

from ._compat import MutableMapping
from ._compat import decode
from ._utils import merge_dicts
from .exceptions import KeyAlreadyPresent
Expand All @@ -29,7 +30,7 @@
_NOT_SET = object()


class Container(dict):
class Container(MutableMapping, dict):
"""
A container for items within a TOMLDocument.
"""
Expand Down Expand Up @@ -111,8 +112,6 @@ def append(self, key, item): # type: (Union[Key, str, None], Item) -> Container
if isinstance(item, AoT) and self._body and not self._parsed:
if item and "\n" not in item[0].trivia.indent:
item[0].trivia.indent = "\n" + item[0].trivia.indent
else:
self.append(None, Whitespace("\n"))

if key is not None and key in self:
current_idx = self._map[key]
Expand Down Expand Up @@ -210,7 +209,7 @@ def append(self, key, item): # type: (Union[Key, str, None], Item) -> Container

if key_after is not None:
if isinstance(key_after, int):
if key_after + 1 < len(self._body) - 1:
if key_after + 1 < len(self._body):
return self._insert_at(key_after + 1, key, item)
else:
previous_item = self._body[-1][1]
Expand Down Expand Up @@ -247,7 +246,7 @@ def append(self, key, item): # type: (Union[Key, str, None], Item) -> Container
self._table_keys.append(key)

if key is not None:
super(Container, self).__setitem__(key.key, item.value)
dict.__setitem__(self, key.key, item.value)

return self

Expand All @@ -265,7 +264,7 @@ def remove(self, key): # type: (Union[Key, str]) -> Container
else:
self._body[idx] = (None, Null())

super(Container, self).__delitem__(key.key)
dict.__delitem__(self, key.key)

return self

Expand Down Expand Up @@ -312,7 +311,7 @@ def _insert_after(
self._body.insert(idx + 1, (other_key, item))

if key is not None:
super(Container, self).__setitem__(other_key.key, item.value)
dict.__setitem__(self, other_key.key, item.value)

return self

Expand Down Expand Up @@ -354,7 +353,7 @@ def _insert_at(
self._body.insert(idx, (key, item))

if key is not None:
super(Container, self).__setitem__(key.key, item.value)
dict.__setitem__(self, key.key, item.value)

return self

Expand Down Expand Up @@ -513,33 +512,6 @@ def _render_simple_item(self, key, item, prefix=None):

# Dictionary methods

def keys(self): # type: () -> Generator[str]
return super(Container, self).keys()

def values(self): # type: () -> Generator[Item]
for k in self.keys():
yield self[k]

def items(self): # type: () -> Generator[Item]
for k, v in self.value.items():
if k is None:
continue

yield k, v

def update(self, other): # type: (Dict) -> None
for k, v in other.items():
self[k] = v

def get(self, key, default=None): # type: (Any, Optional[Any]) -> Any
if not isinstance(key, Key):
key = Key(key)

if key not in self:
return default

return self[key]

def pop(self, key, default=_NOT_SET):
try:
value = self[key]
Expand All @@ -556,8 +528,7 @@ def pop(self, key, default=_NOT_SET):
def setdefault(
self, key, default=None
): # type: (Union[Key, str], Any) -> Union[Item, Container]
if key not in self:
self[key] = default
super(Container, self).setdefault(key, default=default)

return self[key]

Expand All @@ -567,6 +538,12 @@ def __contains__(self, key): # type: (Union[Key, str]) -> bool

return key in self._map

def __setitem__(self, key, value): # type: (Union[Key, str], Any) -> None
if key is not None and key in self:
self._replace(key, key, value)
else:
self.append(key, value)

def __getitem__(self, key): # type: (Union[Key, str]) -> Union[Item, Container]
if not isinstance(key, Key):
key = Key(key)
Expand Down Expand Up @@ -596,6 +573,12 @@ def __setitem__(self, key, value): # type: (Union[Key, str], Any) -> None
def __delitem__(self, key): # type: (Union[Key, str]) -> None
self.remove(key)

def __len__(self): # type: () -> int
return dict.__len__(self)

def __iter__(self): # type: () -> Iterator[str]
return iter(dict.keys(self))

def _replace(
self, key, new_key, value
): # type: (Union[Key, str], Union[Key, str], Item) -> None
Expand Down Expand Up @@ -627,7 +610,7 @@ def _replace_at(

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

if isinstance(self._map[new_key], tuple):
self._map[new_key] = self._map[new_key][0]
Expand All @@ -647,13 +630,13 @@ def _replace_at(

self._body[idx] = (new_key, value)

super(Container, self).__setitem__(new_key.key, value.value)
dict.__setitem__(self, new_key.key, value.value)

def __str__(self): # type: () -> str
return str(self.value)

def __repr__(self): # type: () -> str
return super(Container, self).__repr__()
return repr(self.value)

def __eq__(self, other): # type: (Dict) -> bool
if not isinstance(other, dict):
Expand Down Expand Up @@ -684,16 +667,16 @@ def copy(self): # type: () -> Container

def __copy__(self): # type: () -> Container
c = self.__class__(self._parsed)
for k, v in super(Container, self).copy().items():
super(Container, c).__setitem__(k, v)
for k, v in dict.items(self):
dict.__setitem__(c, k, v)

c._body += self.body
c._map.update(self._map)

return c


class OutOfOrderTableProxy(dict):
class OutOfOrderTableProxy(MutableMapping, dict):
def __init__(self, container, indices): # type: (Container, Tuple) -> None
self._container = container
self._internal_container = Container(self._container.parsing)
Expand All @@ -711,12 +694,12 @@ def __init__(self, container, indices): # type: (Container, Tuple) -> None
self._internal_container.append(k, v)
self._tables_map[k] = table_idx
if k is not None:
super(OutOfOrderTableProxy, self).__setitem__(k.key, v)
dict.__setitem__(self, k.key, v)
else:
self._internal_container.append(key, item)
self._map[key] = i
if key is not None:
super(OutOfOrderTableProxy, self).__setitem__(key.key, item)
dict.__setitem__(self, key.key, item)

@property
def value(self):
Expand All @@ -742,7 +725,7 @@ def __setitem__(self, key, item): # type: (Union[Key, str], Any) -> None
self._container[key] = item

if key is not None:
super(OutOfOrderTableProxy, self).__setitem__(key, item)
dict.__setitem__(self, key, item)

def __delitem__(self, key): # type: (Union[Key, str]) -> None
if key in self._map:
Expand Down Expand Up @@ -784,6 +767,9 @@ def setdefault(
def __contains__(self, key):
return key in self._internal_container

def __iter__(self): # type: () -> Iterator[str]
return iter(self._internal_container)

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

Expand Down
2 changes: 2 additions & 0 deletions tomlkit/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import unicode_literals

from typing import Optional


Expand Down
Loading