Skip to content
Open
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
12 changes: 9 additions & 3 deletions Doc/library/tomllib.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,25 @@
.. module:: tomllib
:synopsis: Parse TOML files.

.. versionadded:: 3.11

.. moduleauthor:: Taneli Hukkinen
.. sectionauthor:: Taneli Hukkinen

**Source code:** :source:`Lib/tomllib`

--------------

This module provides an interface for parsing TOML 1.0.0 (Tom's Obvious Minimal
This module provides an interface for parsing TOML 1.1.0 (Tom's Obvious Minimal
Language, `https://toml.io <https://toml.io/en/>`_). This module does not
support writing TOML.

.. versionadded:: 3.11
The module was added with support for TOML 1.0.0.

.. versionchanged:: next
Added TOML 1.1.0 support.
See the :ref:`What's New <whatsnew315-tomllib-1-1-0>` for details.


.. seealso::

The :pypi:`Tomli-W package <tomli-w>`
Expand Down
50 changes: 50 additions & 0 deletions Doc/whatsnew/3.15.rst
Original file line number Diff line number Diff line change
Expand Up @@ -798,6 +798,56 @@ tkinter
with outdated names.
(Contributed by Serhiy Storchaka in :gh:`143754`)


.. _whatsnew315-tomllib-1-1-0:

tomllib
-------

* The :mod:`tomllib` module now supports TOML 1.1.0.
This is a backwards compatible update, meaning that all valid TOML 1.0.0
documents are parsed the same way.

The changes, according to the `official TOML changelog`_, are:

- Allow newlines and trailing commas in inline tables.

Previously an inline table had to be on a single line and couldn't end
with a trailing comma. This is now relaxed so that the following is valid:

.. syntax highlighting needs TOML 1.1.0 support in Pygments,
see https://github.com/pygments/pygments/issues/3026

.. code-block:: text

tbl = {
key = "a string",
moar-tbl = {
key = 1,
},
}

- Add ``\xHH`` notation to basic strings for codepoints under 255,
and the ``\e`` escape for the escape character:

.. code-block:: text

null = "null byte: \x00; letter a: \x61"
csi = "\e["

- Seconds in datetime and time values are now optional.
The following are now valid:

.. code-block:: text

dt = 2010-02-03 14:15
t = 14:15

(Contributed by Taneli Hukkinen in :gh:`142956`.)

.. _official TOML changelog: https://github.com/toml-lang/toml/blob/main/CHANGELOG.md


types
------

Expand Down
33 changes: 8 additions & 25 deletions Lib/test/test_tomllib/burntsushi.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,8 @@
import datetime
from typing import Any

# Aliases for converting TOML compliance format [1] to BurntSushi format [2]
# [1] https://github.com/toml-lang/compliance/blob/db7c3211fda30ff9ddb10292f4aeda7e2e10abc4/docs/json-encoding.md # noqa: E501
# [2] https://github.com/BurntSushi/toml-test/blob/4634fdf3a6ecd6aaea5f4cdcd98b2733c2694993/README.md # noqa: E501
_aliases = {
"boolean": "bool",
"offset datetime": "datetime",
"local datetime": "datetime-local",
"local date": "date-local",
"local time": "time-local",
}


def convert(obj): # noqa: C901

def convert(obj):
if isinstance(obj, str):
return {"type": "string", "value": obj}
elif isinstance(obj, bool):
Expand Down Expand Up @@ -53,31 +42,25 @@ def convert(obj): # noqa: C901
def normalize(obj: Any) -> Any:
"""Normalize test objects.

This normalizes primitive values (e.g. floats), and also converts from
TOML compliance format [1] to BurntSushi format [2].

[1] https://github.com/toml-lang/compliance/blob/db7c3211fda30ff9ddb10292f4aeda7e2e10abc4/docs/json-encoding.md # noqa: E501
[2] https://github.com/BurntSushi/toml-test/blob/4634fdf3a6ecd6aaea5f4cdcd98b2733c2694993/README.md # noqa: E501
"""
This normalizes primitive values (e.g. floats)."""
if isinstance(obj, list):
return [normalize(item) for item in obj]
if isinstance(obj, dict):
if "type" in obj and "value" in obj:
type_ = obj["type"]
norm_type = _aliases.get(type_, type_)
value = obj["value"]
if norm_type == "float":
if type_ == "float":
norm_value = _normalize_float_str(value)
elif norm_type in {"datetime", "datetime-local"}:
elif type_ in {"datetime", "datetime-local"}:
norm_value = _normalize_datetime_str(value)
elif norm_type == "time-local":
elif type_ == "time-local":
norm_value = _normalize_localtime_str(value)
else:
norm_value = value

if norm_type == "array":
if type_ == "array":
return [normalize(item) for item in value]
return {"type": norm_type, "value": norm_value}
return {"type": type_, "value": norm_value}
return {k: normalize(v) for k, v in obj.items()}
raise AssertionError("Burntsushi fixtures should be dicts/lists only")

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"local-dt": {"type":"datetime-local","value":"1988-10-27t01:01:01"},
"local-dt-no-seconds": {"type":"datetime-local","value":"2025-04-18t20:05:00"},
"zulu-dt": {"type":"datetime","value":"1988-10-27t01:01:01z"}
}
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
local-dt=1988-10-27t01:01:01
local-dt-no-seconds=2025-04-18T20:05
zulu-dt=1988-10-27t01:01:01z
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
{"t":
{"type":"time-local","value":"00:00:00.999999"}}
{"type":"time-local","value":"00:00:00.999999"},
"t2":
{"type":"time-local","value":"00:00:00"}}
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
t=00:00:00.99999999999999
t=00:00:00.99999999999999
t2=00:00
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"multiline": {
"a": {
"type": "integer",
"value": "1"
},
"b": {
"type": "integer",
"value": "2"
},
"c": [
{
"type": "integer",
"value": "1"
},
{
"type": "integer",
"value": "2"
},
{
"type": "integer",
"value": "3"
}
],
"d": {
"type": "integer",
"value": "3"
},
"e": {
"type": "integer",
"value": "4"
},
"f": {}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
multiline = {
"a" = 1, "b" = 2,
c = [
1,
2,
3,
],# comment
d = 3,
e = 4, f = {
# comment
},
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"escape": {"type":"string","value":"\u001B"},
"tab": {"type":"string","value":"\t"},
"upper-j": {"type":"string","value":"J"},
"upper-j-2": {"type":"string","value":"J"}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
escape = "\e"
tab = "\x09"
upper-j = "\x4a"
upper-j-2 = "\x4A"
19 changes: 1 addition & 18 deletions Lib/test/test_tomllib/test_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,6 @@

from . import burntsushi, tomllib


class MissingFile:
def __init__(self, path: Path):
self.path = path


DATA_DIR = Path(__file__).parent / "data"

VALID_FILES = tuple((DATA_DIR / "valid").glob("**/*.toml"))
Expand All @@ -22,10 +16,7 @@ def __init__(self, path: Path):
_expected_files = []
for p in VALID_FILES:
json_path = p.with_suffix(".json")
try:
text = json.loads(json_path.read_bytes().decode())
except FileNotFoundError:
text = MissingFile(json_path)
text = json.loads(json_path.read_bytes().decode())
_expected_files.append(text)
VALID_FILES_EXPECTED = tuple(_expected_files)

Expand All @@ -49,14 +40,6 @@ def test_invalid(self):
def test_valid(self):
for valid, expected in zip(VALID_FILES, VALID_FILES_EXPECTED):
with self.subTest(msg=valid.stem):
if isinstance(expected, MissingFile):
# For a poor man's xfail, assert that this is one of the
# test cases where expected data is known to be missing.
assert valid.stem in {
"qa-array-inline-nested-1000",
"qa-table-inline-nested-1000",
}
continue
toml_str = valid.read_bytes().decode()
actual = tomllib.loads(toml_str)
actual = burntsushi.convert(actual)
Expand Down
Loading
Loading