| import copy |
| import json |
| import pickle |
| |
| from datetime import datetime |
| from textwrap import dedent |
| |
| import pytest |
| |
| import tomlkit |
| |
| from tomlkit import parse |
| from tomlkit import ws |
| from tomlkit._utils import _utc |
| from tomlkit.api import document |
| from tomlkit.exceptions import NonExistentKey |
| |
| from .util import assert_is_ppo |
| |
| |
| def test_document_is_a_dict(example): |
| content = example("example") |
| |
| doc = parse(content) |
| |
| assert isinstance(doc, dict) |
| assert "owner" in doc |
| |
| # owner |
| owner = doc["owner"] |
| assert doc.get("owner") == owner |
| assert isinstance(owner, dict) |
| assert "name" in owner |
| assert owner["name"] == "Tom Preston-Werner" |
| assert owner["organization"] == "GitHub" |
| assert owner["bio"] == "GitHub Cofounder & CEO\nLikes tater tots and beer." |
| assert owner["dob"] == datetime(1979, 5, 27, 7, 32, tzinfo=_utc) |
| |
| # database |
| database = doc["database"] |
| assert isinstance(database, dict) |
| assert database["server"] == "192.168.1.1" |
| assert database["ports"] == [8001, 8001, 8002] |
| assert database["connection_max"] == 5000 |
| assert database["enabled"] is True |
| |
| # servers |
| servers = doc["servers"] |
| assert isinstance(servers, dict) |
| |
| alpha = servers["alpha"] |
| assert servers.get("alpha") == alpha |
| assert isinstance(alpha, dict) |
| assert alpha["ip"] == "10.0.0.1" |
| assert alpha["dc"] == "eqdc10" |
| |
| beta = servers["beta"] |
| assert isinstance(beta, dict) |
| assert beta["ip"] == "10.0.0.2" |
| assert beta["dc"] == "eqdc10" |
| assert beta["country"] == "中国" |
| |
| # clients |
| clients = doc["clients"] |
| assert isinstance(clients, dict) |
| |
| data = clients["data"] |
| assert isinstance(data, list) |
| assert data[0] == ["gamma", "delta"] |
| assert data[1] == [1, 2] |
| |
| assert clients["hosts"] == ["alpha", "omega"] |
| |
| # Products |
| products = doc["products"] |
| assert isinstance(products, list) |
| |
| hammer = products[0] |
| assert hammer == {"name": "Hammer", "sku": 738594937} |
| |
| nail = products[1] |
| assert nail["name"] == "Nail" |
| assert nail["sku"] == 284758393 |
| assert nail["color"] == "gray" |
| |
| nail["color"] = "black" |
| assert nail["color"] == "black" |
| assert doc["products"][1]["color"] == "black" |
| assert nail.get("color") == "black" |
| |
| content = """foo = "bar" |
| """ |
| |
| doc = parse(content) |
| doc.update({"bar": "baz"}) |
| |
| assert ( |
| doc.as_string() |
| == """foo = "bar" |
| bar = "baz" |
| """ |
| ) |
| |
| doc.update({"bar": "boom"}) |
| |
| assert ( |
| doc.as_string() |
| == """foo = "bar" |
| bar = "boom" |
| """ |
| ) |
| |
| assert doc.setdefault("bar", "waldo") == "boom" |
| |
| assert ( |
| doc.as_string() |
| == """foo = "bar" |
| bar = "boom" |
| """ |
| ) |
| |
| assert doc.setdefault("thud", "waldo") == "waldo" |
| |
| assert ( |
| doc.as_string() |
| == """foo = "bar" |
| bar = "boom" |
| thud = "waldo" |
| """ |
| ) |
| |
| |
| def test_toml_document_without_super_tables(): |
| content = """[tool.poetry] |
| name = "foo" |
| """ |
| |
| doc = parse(content) |
| assert "tool" in doc |
| assert "poetry" in doc["tool"] |
| |
| assert doc["tool"]["poetry"]["name"] == "foo" |
| |
| doc["tool"]["poetry"]["name"] = "bar" |
| |
| assert ( |
| doc.as_string() |
| == """[tool.poetry] |
| name = "bar" |
| """ |
| ) |
| |
| d = {} |
| d.update(doc) |
| |
| assert "tool" in d |
| |
| |
| def test_toml_document_unwrap(): |
| content = """[tool.poetry] |
| name = "foo" |
| """ |
| |
| doc = parse(content) |
| unwrapped = doc.unwrap() |
| assert_is_ppo(unwrapped, dict) |
| assert_is_ppo(list(unwrapped.keys())[0], str) |
| assert_is_ppo(unwrapped["tool"], dict) |
| assert_is_ppo(list(unwrapped["tool"].keys())[0], str) |
| assert_is_ppo(unwrapped["tool"]["poetry"]["name"], str) |
| |
| |
| def test_toml_document_with_dotted_keys(example): |
| content = example("0.5.0") |
| |
| doc = parse(content) |
| |
| assert "physical" in doc |
| assert "color" in doc["physical"] |
| assert "shape" in doc["physical"] |
| assert doc["physical"]["color"] == "orange" |
| assert doc["physical"]["shape"] == "round" |
| |
| assert "site" in doc |
| assert "google.com" in doc["site"] |
| assert doc["site"]["google.com"] |
| |
| assert doc["a"]["b"]["c"] == 1 |
| assert doc["a"]["b"]["d"] == 2 |
| |
| |
| def test_toml_document_super_table_with_different_sub_sections(example): |
| content = example("pyproject") |
| |
| doc = parse(content) |
| tool = doc["tool"] |
| |
| assert "poetry" in tool |
| assert "black" in tool |
| |
| |
| def test_adding_an_element_to_existing_table_with_ws_remove_ws(): |
| content = """[foo] |
| |
| [foo.bar] |
| |
| """ |
| |
| doc = parse(content) |
| doc["foo"]["int"] = 34 |
| |
| expected = """[foo] |
| int = 34 |
| |
| [foo.bar] |
| |
| """ |
| |
| assert expected == doc.as_string() |
| |
| |
| def test_document_with_aot_after_sub_tables(): |
| content = """[foo.bar] |
| name = "Bar" |
| |
| [foo.bar.baz] |
| name = "Baz" |
| |
| [[foo.bar.tests]] |
| name = "Test 1" |
| """ |
| |
| doc = parse(content) |
| assert doc["foo"]["bar"]["tests"][0]["name"] == "Test 1" |
| |
| |
| def test_document_with_new_sub_table_after_other_table(): |
| content = """[foo] |
| name = "Bar" |
| |
| [bar] |
| name = "Baz" |
| |
| [foo.baz] |
| name = "Test 1" |
| """ |
| |
| doc = parse(content) |
| assert doc["foo"]["name"] == "Bar" |
| assert doc["bar"]["name"] == "Baz" |
| assert doc["foo"]["baz"]["name"] == "Test 1" |
| |
| assert doc.as_string() == content |
| |
| |
| def test_document_with_new_sub_table_after_other_table_delete(): |
| content = """[foo] |
| name = "Bar" |
| |
| [bar] |
| name = "Baz" |
| |
| [foo.baz] |
| name = "Test 1" |
| """ |
| |
| doc = parse(content) |
| |
| del doc["foo"] |
| |
| assert ( |
| doc.as_string() |
| == """[bar] |
| name = "Baz" |
| |
| """ |
| ) |
| |
| |
| def test_document_with_new_sub_table_after_other_table_replace(): |
| content = """[foo] |
| name = "Bar" |
| |
| [bar] |
| name = "Baz" |
| |
| [foo.baz] |
| name = "Test 1" |
| """ |
| |
| doc = parse(content) |
| |
| doc["foo"] = {"a": "b"} |
| |
| assert ( |
| doc.as_string() |
| == """[foo] |
| a = "b" |
| |
| [bar] |
| name = "Baz" |
| |
| """ |
| ) |
| |
| |
| def test_inserting_after_element_with_no_new_line_adds_a_new_line(): |
| doc = parse("foo = 10") |
| doc["bar"] = 11 |
| |
| expected = """foo = 10 |
| bar = 11 |
| """ |
| |
| assert expected == doc.as_string() |
| |
| doc = parse("# Comment") |
| doc["bar"] = 11 |
| |
| expected = """# Comment |
| bar = 11 |
| """ |
| |
| assert expected == doc.as_string() |
| |
| |
| def test_inserting_after_deletion(): |
| doc = parse("foo = 10\n") |
| del doc["foo"] |
| |
| doc["bar"] = 11 |
| |
| expected = """bar = 11 |
| """ |
| |
| assert expected == doc.as_string() |
| |
| |
| def test_toml_document_with_dotted_keys_inside_table(example): |
| content = example("0.5.0") |
| |
| doc = parse(content) |
| t = doc["table"] |
| |
| assert "a" in t |
| |
| assert t["a"]["b"]["c"] == 1 |
| assert t["a"]["b"]["d"] == 2 |
| assert t["a"]["c"] == 3 |
| |
| |
| def test_toml_document_with_super_aot_after_super_table(example): |
| content = example("pyproject") |
| |
| doc = parse(content) |
| aot = doc["tool"]["foo"] |
| |
| assert isinstance(aot, list) |
| |
| first = aot[0] |
| assert first["name"] == "first" |
| |
| second = aot[1] |
| assert second["name"] == "second" |
| |
| |
| def test_toml_document_has_always_a_new_line_after_table_header(): |
| content = """[section.sub]""" |
| |
| doc = parse(content) |
| assert doc.as_string() == """[section.sub]""" |
| |
| doc["section"]["sub"]["foo"] = "bar" |
| assert ( |
| doc.as_string() |
| == """[section.sub] |
| foo = "bar" |
| """ |
| ) |
| |
| del doc["section"]["sub"]["foo"] |
| |
| assert doc.as_string() == """[section.sub]""" |
| |
| |
| def test_toml_document_is_pickable(example): |
| content = example("example") |
| |
| doc = parse(content) |
| assert pickle.loads(pickle.dumps(doc)).as_string() == content |
| |
| |
| def test_toml_document_set_super_table_element(): |
| content = """[site.user] |
| name = "John" |
| """ |
| |
| doc = parse(content) |
| doc["site"]["user"] = "Tom" |
| |
| assert ( |
| doc.as_string() |
| == """[site] |
| user = "Tom" |
| """ |
| ) |
| |
| |
| def test_toml_document_can_be_copied(): |
| content = "[foo]\nbar=1" |
| |
| doc = parse(content) |
| doc = copy.copy(doc) |
| |
| assert ( |
| doc.as_string() |
| == """[foo] |
| bar=1""" |
| ) |
| |
| assert doc == {"foo": {"bar": 1}} |
| assert doc["foo"]["bar"] == 1 |
| assert json.loads(json.dumps(doc)) == {"foo": {"bar": 1}} |
| |
| doc = parse(content) |
| doc = doc.copy() |
| |
| assert ( |
| doc.as_string() |
| == """[foo] |
| bar=1""" |
| ) |
| |
| assert doc == {"foo": {"bar": 1}} |
| assert doc["foo"]["bar"] == 1 |
| assert json.loads(json.dumps(doc)) == {"foo": {"bar": 1}} |
| |
| |
| def test_getting_inline_table_is_still_an_inline_table(): |
| content = """\ |
| [tool.poetry] |
| name = "foo" |
| |
| [tool.poetry.dependencies] |
| |
| [tool.poetry.dev-dependencies] |
| """ |
| |
| doc = parse(content) |
| poetry_section = doc["tool"]["poetry"] |
| dependencies = poetry_section["dependencies"] |
| dependencies["foo"] = tomlkit.inline_table() |
| dependencies["foo"]["version"] = "^2.0" |
| dependencies["foo"]["source"] = "local" |
| dependencies["bar"] = tomlkit.inline_table() |
| dependencies["bar"]["version"] = "^3.0" |
| dependencies["bar"]["source"] = "remote" |
| dev_dependencies = poetry_section["dev-dependencies"] |
| dev_dependencies["baz"] = tomlkit.inline_table() |
| dev_dependencies["baz"]["version"] = "^4.0" |
| dev_dependencies["baz"]["source"] = "other" |
| |
| assert ( |
| """\ |
| [tool.poetry] |
| name = "foo" |
| |
| [tool.poetry.dependencies] |
| foo = {version = "^2.0", source = "local"} |
| bar = {version = "^3.0", source = "remote"} |
| |
| [tool.poetry.dev-dependencies] |
| baz = {version = "^4.0", source = "other"} |
| """ |
| == doc.as_string() |
| ) |
| |
| |
| def test_declare_sub_table_with_intermediate_table(): |
| content = """ |
| [students] |
| tommy = 87 |
| mary = 66 |
| |
| [subjects] |
| maths = "maths" |
| english = "english" |
| |
| [students.bob] |
| score = 91 |
| """ |
| |
| 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"] |
| |
| |
| def test_out_of_order_table_can_add_multiple_tables(): |
| content = """\ |
| [a.a.b] |
| x = 1 |
| [foo] |
| bar = 1 |
| [a.a.c] |
| y = 1 |
| [a.a.d] |
| z = 1 |
| """ |
| doc = parse(content) |
| assert doc.as_string() == content |
| assert doc["a"]["a"] == {"b": {"x": 1}, "c": {"y": 1}, "d": {"z": 1}} |
| |
| |
| def test_out_of_order_tables_are_still_dicts(): |
| content = """ |
| [a.a] |
| key = "value" |
| |
| [a.b] |
| |
| [a.a.c] |
| """ |
| |
| doc = parse(content) |
| assert isinstance(doc["a"], dict) |
| assert isinstance(doc["a"]["a"], dict) |
| |
| table = doc["a"]["a"] |
| assert "key" in table |
| assert "c" in table |
| assert "value" == table.get("key") |
| assert {} == table.get("c") |
| assert table.get("d") is None |
| assert "foo" == table.get("d", "foo") |
| |
| assert "bar" == table.setdefault("d", "bar") |
| assert "bar" == table["d"] |
| |
| assert "value" == table.pop("key") |
| assert "key" not in table |
| |
| assert "baz" == table.pop("missing", default="baz") |
| |
| with pytest.raises(KeyError): |
| table.pop("missing") |
| |
| |
| def test_string_output_order_is_preserved_for_out_of_order_tables(): |
| content = """ |
| [tool.poetry] |
| name = "foo" |
| |
| [tool.poetry.dependencies] |
| python = "^3.6" |
| bar = "^1.0" |
| |
| |
| [build-system] |
| requires = ["poetry-core"] |
| backend = "poetry.core.masonry.api" |
| |
| |
| [tool.other] |
| a = "b" |
| """ |
| |
| doc = parse(content) |
| constraint = tomlkit.inline_table() |
| constraint["version"] = "^1.0" |
| doc["tool"]["poetry"]["dependencies"]["bar"] = constraint |
| |
| assert "^1.0" == doc["tool"]["poetry"]["dependencies"]["bar"]["version"] |
| |
| expected = """ |
| [tool.poetry] |
| name = "foo" |
| |
| [tool.poetry.dependencies] |
| python = "^3.6" |
| bar = {version = "^1.0"} |
| |
| |
| [build-system] |
| requires = ["poetry-core"] |
| backend = "poetry.core.masonry.api" |
| |
| |
| [tool.other] |
| a = "b" |
| """ |
| |
| assert expected == doc.as_string() |
| |
| |
| def test_remove_from_out_of_order_table(): |
| content = """[a] |
| x = 1 |
| |
| [c] |
| z = 3 |
| |
| [a.b] |
| y = 2 |
| """ |
| document = parse(content) |
| del document["a"]["b"] |
| assert ( |
| document.as_string() |
| == """[a] |
| x = 1 |
| |
| [c] |
| z = 3 |
| |
| """ |
| ) |
| assert json.dumps(document) == '{"a": {"x": 1}, "c": {"z": 3}}' |
| |
| |
| 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 |
| |
| |
| 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'}" |
| |
| |
| def test_deepcopy(): |
| content = """ |
| [tool] |
| name = "foo" |
| [tool.project.section] |
| option = "test" |
| """ |
| doc = parse(content) |
| copied = copy.deepcopy(doc) |
| assert copied == doc |
| assert copied.as_string() == content |
| |
| |
| def test_move_table(): |
| content = """a = 1 |
| [x] |
| a = 1 |
| |
| [y] |
| b = 1 |
| """ |
| doc = parse(content) |
| doc["a"] = doc.pop("x") |
| doc["z"] = doc.pop("y") |
| assert ( |
| doc.as_string() |
| == """[a] |
| a = 1 |
| |
| [z] |
| b = 1 |
| """ |
| ) |
| |
| |
| def test_replace_with_table(): |
| content = """a = 1 |
| b = 2 |
| c = 3 |
| """ |
| doc = parse(content) |
| doc["b"] = {"foo": "bar"} |
| assert ( |
| doc.as_string() |
| == """a = 1 |
| c = 3 |
| |
| [b] |
| foo = "bar" |
| """ |
| ) |
| |
| |
| def test_replace_table_with_value(): |
| content = """[foo] |
| a = 1 |
| |
| [bar] |
| b = 2 |
| """ |
| doc = parse(content) |
| doc["bar"] = 42 |
| assert ( |
| doc.as_string() |
| == """bar = 42 |
| [foo] |
| a = 1 |
| |
| """ |
| ) |
| |
| |
| def test_replace_preserve_sep(): |
| content = """a = 1 |
| |
| [foo] |
| b = "what" |
| """ |
| doc = parse(content) |
| doc["a"] = 2 |
| doc["foo"]["b"] = "how" |
| assert ( |
| doc.as_string() |
| == """a = 2 |
| |
| [foo] |
| b = "how" |
| """ |
| ) |
| |
| |
| def test_replace_with_table_of_nested(): |
| example = """\ |
| [a] |
| x = 1 |
| |
| [a.b] |
| y = 2 |
| """ |
| doc = parse(dedent(example)) |
| doc["c"] = doc.pop("a") |
| expected = """\ |
| [c] |
| x = 1 |
| |
| [c.b] |
| y = 2 |
| """ |
| assert doc.as_string().strip() == dedent(expected).strip() |
| |
| |
| def test_replace_with_aot_of_nested(): |
| example = """\ |
| [a] |
| x = 1 |
| |
| [[a.b]] |
| y = 2 |
| |
| [[a.b]] |
| |
| [a.b.c] |
| z = 2 |
| |
| [[a.b.c.d]] |
| w = 2 |
| """ |
| doc = parse(dedent(example)) |
| doc["f"] = doc.pop("a") |
| expected = """\ |
| [f] |
| x = 1 |
| |
| [[f.b]] |
| y = 2 |
| |
| [[f.b]] |
| |
| [f.b.c] |
| z = 2 |
| |
| [[f.b.c.d]] |
| w = 2 |
| """ |
| assert doc.as_string().strip() == dedent(expected).strip() |
| |
| |
| def test_replace_with_comment(): |
| content = 'a = "1"' |
| doc = parse(content) |
| a = tomlkit.item(int(doc["a"])) |
| a.comment("`a` should be an int") |
| doc["a"] = a |
| expected = "a = 1 # `a` should be an int" |
| assert doc.as_string() == expected |
| |
| content = 'a = "1, 2, 3"' |
| doc = parse(content) |
| a = tomlkit.array() |
| a.comment("`a` should be an array") |
| for x in doc["a"].split(","): |
| a.append(int(x.strip())) |
| doc["a"] = a |
| expected = "a = [1, 2, 3] # `a` should be an array" |
| assert doc.as_string() == expected |
| |
| doc = parse(content) |
| a = tomlkit.inline_table() |
| a.comment("`a` should be an inline-table") |
| for x in doc["a"].split(","): |
| i = int(x.strip()) |
| a.append(chr(ord("a") + i - 1), i) |
| doc["a"] = a |
| expected = "a = {a = 1, b = 2, c = 3} # `a` should be an inline-table" |
| assert doc.as_string() == expected |
| |
| |
| def test_no_spurious_whitespaces(): |
| content = """\ |
| [x] |
| a = 1 |
| |
| [y] |
| b = 2 |
| """ |
| doc = parse(dedent(content)) |
| doc["z"] = doc.pop("y") |
| expected = """\ |
| [x] |
| a = 1 |
| |
| [z] |
| b = 2 |
| """ |
| assert doc.as_string() == dedent(expected) |
| doc["w"] = {"c": 3} |
| expected = """\ |
| [x] |
| a = 1 |
| |
| [z] |
| b = 2 |
| |
| [w] |
| c = 3 |
| """ |
| assert doc.as_string() == dedent(expected) |
| |
| doc = parse(dedent(content)) |
| del doc["x"] |
| doc["z"] = {"c": 3} |
| expected = """\ |
| [y] |
| b = 2 |
| |
| [z] |
| c = 3 |
| """ |
| assert doc.as_string() == dedent(expected) |
| |
| |
| def test_pop_add_whitespace_and_insert_table_work_togheter(): |
| content = """\ |
| a = 1 |
| b = 2 |
| c = 3 |
| d = 4 |
| """ |
| doc = parse(dedent(content)) |
| doc.pop("a") |
| doc.pop("b") |
| doc.add(ws("\n")) |
| doc["e"] = {"foo": "bar"} |
| expected = """\ |
| c = 3 |
| d = 4 |
| |
| [e] |
| foo = "bar" |
| """ |
| text = doc.as_string() |
| out = parse(text) |
| assert out["d"] == 4 |
| assert "d" not in out["e"] |
| assert text == dedent(expected) |
| |
| |
| def test_add_newline_before_super_table(): |
| doc = document() |
| doc["a"] = 1 |
| doc["b"] = {"c": {}} |
| doc["d"] = {"e": {}} |
| expected = """\ |
| a = 1 |
| |
| [b.c] |
| |
| [d.e] |
| """ |
| assert doc.as_string() == dedent(expected) |