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

Add unwrap method to recursive convert to plain old python objects #187

Merged
merged 22 commits into from
May 22, 2022
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
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
93 changes: 93 additions & 0 deletions tests/test_items.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,22 @@
from tomlkit.items import Comment
from tomlkit.items import InlineTable
from tomlkit.items import Integer
from tomlkit.items import Float
from tomlkit.items import DateTime
from tomlkit.items import Date
from tomlkit.items import Time
from tomlkit.items import Array
from tomlkit.items import KeyType
from tomlkit.items import SingleKey as Key
from tomlkit.items import String
from tomlkit.items import StringType
from tomlkit.items import Table
from tomlkit.items import Trivia
from tomlkit.items import Item
from tomlkit.items import item
from tomlkit.items import Null
from tomlkit.parser import Parser
from tomlkit.check import is_tomlkit


@pytest.fixture()
Expand Down Expand Up @@ -69,6 +77,91 @@ def dst(self, dt):
return UTC()


def test_item_base_has_no_unwrap():
trivia = Trivia(indent='\t', comment_ws=' ', comment='For unit test')
item = Item(trivia)
try:
item.unwrap()
except NotImplementedError:
pass
else:
raise AssertionError("`items.Item` should not implement `unwrap`")

def assert_is_ppo(v_unwrapped, TomlkitType, unwrappedType):
assert type(v_unwrapped) != TomlkitType
assert type(v_unwrapped) == unwrappedType

def elementary_test(v, TomlkitType, unwrappedType):
v_unwrapped = v.unwrap()
assert type(v) == TomlkitType
assert type(v) != unwrappedType
assert_is_ppo(v_unwrapped, TomlkitType, unwrappedType)

def elementary_fail(v, TomlkitType, unwrappedType):
v_unwrapped = v.unwrap()
assert type(v) == TomlkitType
assert type(v_unwrapped) == TomlkitType
assert type(v_unwrapped) == unwrappedType
assert type(v) != unwrappedType

def test_integer_unwrap():
elementary_test(item(666), Integer, int)
def test_float_unwrap():
elementary_test(item(2.78), Float, float)
def test_false_unwrap():
elementary_test(item(False), Bool, bool)
def test_true_unwrap():
elementary_test(item(True), Bool, bool)
def test_datetime_unwrap():
dt=datetime.utcnow()
elementary_test(item(dt), DateTime, datetime)
def test_string_unwrap():
elementary_test(item("hello"), String, str)
def test_null_unwrap():
n = Null()
elementary_test(n, Null, type(None))
def test_aot_unwrap():
d = item([{"a": "A"}, {"b": "B"}])
assert is_tomlkit(d)
unwrapped = d.unwrap()
assert type(unwrapped) == list
for de in unwrapped:
assert type(de) == dict
for k in de:
v = de[k]
assert type(k) == str
assert type(v) == str

def test_time_unwrap():
t=time(3, 8, 14)
elementary_test(item(t), Time, time)
def test_date_unwrap():
d=date.today()
elementary_test(item(d), Date, date)
def test_array_unwrap():
trivia = Trivia(indent='\t', comment_ws=' ', comment='For unit test')
i=item(666)
f=item(2.78)
b=item(False)
a=Array([i, f, b], trivia)
a_unwrapped=a.unwrap()
assert type(a_unwrapped) == list
assert_is_ppo(a_unwrapped[0], Integer, int)
assert_is_ppo(a_unwrapped[1], Float, float)
assert_is_ppo(a_unwrapped[2], Bool, bool)
def test_abstract_table_unwrap():
table = item({"foo": "bar"})
super_table = item({"table": table, "baz": "borg"})
assert is_tomlkit(super_table["table"])

table_unwrapped = super_table.unwrap()
assert type(table_unwrapped) == dict
sub_table = table_unwrapped["table"]
assert type(sub_table) == dict
for (k, v) in zip(sub_table.keys(), sub_table):
assert type(k) == str
assert type(v) == str

def test_key_comparison():
k = Key("foo")

Expand Down
11 changes: 11 additions & 0 deletions tomlkit/check.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
def is_tomlkit(v):
from .items import Item as _Item
from .container import Container
from .container import OutOfOrderTableProxy
if isinstance(v, _Item):
return True
if isinstance(v, Container):
return True
if isinstance(v, OutOfOrderTableProxy):
return True
return False
39 changes: 26 additions & 13 deletions tomlkit/container.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,23 +46,33 @@ def __init__(self, parsed: bool = False) -> None:
def body(self) -> List[Tuple[Optional[Key], Item]]:
return self._body

@property
def value(self) -> Dict[Any, Any]:
d = {}
for k, v in self._body:
if k is None:
continue
def _unwrap_inner(self, k, v, d):
if k is None:
return

k = k.key
v = v.value

k = k.key
if isinstance(v, Container):
v = v.value

if isinstance(v, Container):
v = v.value
if k in d:
merge_dicts(d[k], v)
else:
d[k] = v

if k in d:
merge_dicts(d[k], v)
else:
d[k] = v
def unwrap(self) -> str:
unwrapped = {}
for k, v in self.items():
self._unwrap_inner(k, v, unwrapped)

return unwrapped

@property
def value(self) -> Dict[Any, Any]:
d = {}
for k, v in self._body:
self._unwrap_inner(k, v, d)

return d

Expand Down Expand Up @@ -796,6 +806,9 @@ def __init__(self, container: Container, indices: Tuple[int]) -> None:
if k is not None:
dict.__setitem__(self, k.key, v)

def unwrap(self) -> str:
return self._internal_container.unwrap()

@property
def value(self):
return self._internal_container.value
Expand Down
65 changes: 65 additions & 0 deletions tomlkit/items.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
from ._utils import escape_string
from .exceptions import InvalidStringError
from .toml_char import TOMLChar
from .check import is_tomlkit


if TYPE_CHECKING: # pragma: no cover
Expand Down Expand Up @@ -492,6 +493,10 @@ def as_string(self) -> str:
"""The TOML representation"""
raise NotImplementedError()

def unwrap(self):
"""Returns as pure python object (ppo)"""
raise NotImplementedError()

# Helpers

def comment(self, comment: str) -> "Item":
Expand Down Expand Up @@ -610,6 +615,9 @@ def __init__(self, _: int, trivia: Trivia, raw: str) -> None:
if re.match(r"^[+\-]\d+$", raw):
self._sign = True

def unwrap(self) -> int:
return int(self)

@property
def discriminant(self) -> int:
return 2
Expand Down Expand Up @@ -678,6 +686,9 @@ def __init__(self, _: float, trivia: Trivia, raw: str) -> None:
if re.match(r"^[+\-].+$", raw):
self._sign = True

def unwrap(self) -> float:
return float(self)

@property
def discriminant(self) -> int:
return 3
Expand Down Expand Up @@ -739,6 +750,9 @@ def __init__(self, t: int, trivia: Trivia) -> None:

self._value = bool(t)

def unwrap(self) -> bool:
return bool(self)

@property
def discriminant(self) -> int:
return 4
Expand Down Expand Up @@ -821,6 +835,10 @@ def __init__(

self._raw = raw or self.isoformat()

def unwrap(self) -> datetime:
(year, month, day, hour, minute, second, microsecond, tzinfo, _, _) = self._getstate()
return datetime(year, month, day, hour, minute, second, microsecond, tzinfo)

@property
def discriminant(self) -> int:
return 5
Expand Down Expand Up @@ -924,6 +942,10 @@ def __init__(

self._raw = raw

def unwrap(self) -> date:
(year, month, day, _, _) = self._getstate()
return date(year, month, day)

@property
def discriminant(self) -> int:
return 6
Expand Down Expand Up @@ -996,6 +1018,10 @@ def __init__(

self._raw = raw

def unwrap(self) -> datetime:
(hour, minute, second, microsecond, tzinfo, _, _) = self._getstate()
return time(hour, minute, second, microsecond, tzinfo)

@property
def discriminant(self) -> int:
return 7
Expand Down Expand Up @@ -1051,6 +1077,15 @@ def __init__(self, value: list, trivia: Trivia, multiline: bool = False) -> None
self._multiline = multiline
self._reindex()

def unwrap(self) -> str:
unwrapped = []
for v in self:
if is_tomlkit(v):
unwrapped.append(v.unwrap())
else:
unwrapped.append(v)
return unwrapped

@property
def discriminant(self) -> int:
return 8
Expand Down Expand Up @@ -1295,6 +1330,21 @@ def __init__(self, value: "container.Container", trivia: Trivia):
if k is not None:
dict.__setitem__(self, k.key, v)

def unwrap(self):
unwrapped = {}
for k in self:
if is_tomlkit(k):
nk = k.unwrap()
else:
nk = k
if is_tomlkit(self[k]):
nv = self[k].unwrap()
else:
nv = self[k]
unwrapped[nk] = nv

return unwrapped

@property
def value(self) -> "container.Container":
return self._value
Expand Down Expand Up @@ -1617,6 +1667,9 @@ def __init__(self, t: StringType, _: str, original: str, trivia: Trivia) -> None
self._t = t
self._original = original

def unwrap(self) -> str:
return self.as_string()

@property
def discriminant(self) -> int:
return 11
Expand Down Expand Up @@ -1675,6 +1728,15 @@ def __init__(
for table in body:
self.append(table)

def unwrap(self) -> str:
unwrapped = []
for t in self._body:
if is_tomlkit(t):
Copy link
Contributor

@frostming frostming May 21, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, a child of an array can't be a container. isinstance(t, Item) is enough for this case. But this also works

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

unwrapped.append(t.unwrap())
else:
unwrapped.append(t)
return unwrapped

@property
def body(self) -> List[Table]:
return self._body
Expand Down Expand Up @@ -1766,6 +1828,9 @@ class Null(Item):
def __init__(self) -> None:
pass

def unwrap(self) -> str:
return None

@property
def discriminant(self) -> int:
return -1
Expand Down