Skip to content

Commit

Permalink
Merge pull request python-poetry#122 from sdispater/fixes
Browse files Browse the repository at this point in the history
Various fixes
  • Loading branch information
sdispater committed May 19, 2021
2 parents ced451c + f18d53b commit b069211
Show file tree
Hide file tree
Showing 8 changed files with 173 additions and 99 deletions.
18 changes: 18 additions & 0 deletions tests/test_build.py
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
@@ -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
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
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
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
@@ -1,3 +1,5 @@
from __future__ import unicode_literals

from typing import Optional


Expand Down

0 comments on commit b069211

Please sign in to comment.