|
1 [![Build Status](https://github.com/hukkin/tomli/workflows/Tests/badge.svg?branch=master)](https://github.com/hukkin/tomli/actions?query=workflow%3ATests+branch%3Amaster+event%3Apush) |
|
2 [![codecov.io](https://codecov.io/gh/hukkin/tomli/branch/master/graph/badge.svg)](https://codecov.io/gh/hukkin/tomli) |
|
3 [![PyPI version](https://img.shields.io/pypi/v/tomli)](https://pypi.org/project/tomli) |
|
4 |
|
5 # Tomli |
|
6 |
|
7 > A lil' TOML parser |
|
8 |
|
9 **Table of Contents** *generated with [mdformat-toc](https://github.com/hukkin/mdformat-toc)* |
|
10 |
|
11 <!-- mdformat-toc start --slug=github --maxlevel=6 --minlevel=2 --> |
|
12 |
|
13 - [Intro](#intro) |
|
14 - [Installation](#installation) |
|
15 - [Usage](#usage) |
|
16 - [Parse a TOML string](#parse-a-toml-string) |
|
17 - [Parse a TOML file](#parse-a-toml-file) |
|
18 - [Handle invalid TOML](#handle-invalid-toml) |
|
19 - [Construct `decimal.Decimal`s from TOML floats](#construct-decimaldecimals-from-toml-floats) |
|
20 - [FAQ](#faq) |
|
21 - [Why this parser?](#why-this-parser) |
|
22 - [Is comment preserving round-trip parsing supported?](#is-comment-preserving-round-trip-parsing-supported) |
|
23 - [Is there a `dumps`, `write` or `encode` function?](#is-there-a-dumps-write-or-encode-function) |
|
24 - [How do TOML types map into Python types?](#how-do-toml-types-map-into-python-types) |
|
25 - [Performance](#performance) |
|
26 |
|
27 <!-- mdformat-toc end --> |
|
28 |
|
29 ## Intro<a name="intro"></a> |
|
30 |
|
31 Tomli is a Python library for parsing [TOML](https://toml.io). |
|
32 Tomli is fully compatible with [TOML v1.0.0](https://toml.io/en/v1.0.0). |
|
33 |
|
34 ## Installation<a name="installation"></a> |
|
35 |
|
36 ```bash |
|
37 pip install tomli |
|
38 ``` |
|
39 |
|
40 ## Usage<a name="usage"></a> |
|
41 |
|
42 ### Parse a TOML string<a name="parse-a-toml-string"></a> |
|
43 |
|
44 ```python |
|
45 import tomli |
|
46 |
|
47 toml_str = """ |
|
48 gretzky = 99 |
|
49 |
|
50 [kurri] |
|
51 jari = 17 |
|
52 """ |
|
53 |
|
54 toml_dict = tomli.loads(toml_str) |
|
55 assert toml_dict == {"gretzky": 99, "kurri": {"jari": 17}} |
|
56 ``` |
|
57 |
|
58 ### Parse a TOML file<a name="parse-a-toml-file"></a> |
|
59 |
|
60 ```python |
|
61 import tomli |
|
62 |
|
63 with open("path_to_file/conf.toml", "rb") as f: |
|
64 toml_dict = tomli.load(f) |
|
65 ``` |
|
66 |
|
67 The file must be opened in binary mode (with the `"rb"` flag). |
|
68 Binary mode will enforce decoding the file as UTF-8 with universal newlines disabled, |
|
69 both of which are required to correctly parse TOML. |
|
70 Support for text file objects is deprecated for removal in the next major release. |
|
71 |
|
72 ### Handle invalid TOML<a name="handle-invalid-toml"></a> |
|
73 |
|
74 ```python |
|
75 import tomli |
|
76 |
|
77 try: |
|
78 toml_dict = tomli.loads("]] this is invalid TOML [[") |
|
79 except tomli.TOMLDecodeError: |
|
80 print("Yep, definitely not valid.") |
|
81 ``` |
|
82 |
|
83 Note that while the `TOMLDecodeError` type is public API, error messages of raised instances of it are not. |
|
84 Error messages should not be assumed to stay constant across Tomli versions. |
|
85 |
|
86 ### Construct `decimal.Decimal`s from TOML floats<a name="construct-decimaldecimals-from-toml-floats"></a> |
|
87 |
|
88 ```python |
|
89 from decimal import Decimal |
|
90 import tomli |
|
91 |
|
92 toml_dict = tomli.loads("precision-matters = 0.982492", parse_float=Decimal) |
|
93 assert toml_dict["precision-matters"] == Decimal("0.982492") |
|
94 ``` |
|
95 |
|
96 Note that `decimal.Decimal` can be replaced with another callable that converts a TOML float from string to a Python type. |
|
97 The `decimal.Decimal` is, however, a practical choice for use cases where float inaccuracies can not be tolerated. |
|
98 |
|
99 Illegal types include `dict`, `list`, and anything that has the `append` attribute. |
|
100 Parsing floats into an illegal type results in undefined behavior. |
|
101 |
|
102 ## FAQ<a name="faq"></a> |
|
103 |
|
104 ### Why this parser?<a name="why-this-parser"></a> |
|
105 |
|
106 - it's lil' |
|
107 - pure Python with zero dependencies |
|
108 - the fastest pure Python parser [\*](#performance): |
|
109 15x as fast as [tomlkit](https://pypi.org/project/tomlkit/), |
|
110 2.4x as fast as [toml](https://pypi.org/project/toml/) |
|
111 - outputs [basic data types](#how-do-toml-types-map-into-python-types) only |
|
112 - 100% spec compliant: passes all tests in |
|
113 [a test set](https://github.com/toml-lang/compliance/pull/8) |
|
114 soon to be merged to the official |
|
115 [compliance tests for TOML](https://github.com/toml-lang/compliance) |
|
116 repository |
|
117 - thoroughly tested: 100% branch coverage |
|
118 |
|
119 ### Is comment preserving round-trip parsing supported?<a name="is-comment-preserving-round-trip-parsing-supported"></a> |
|
120 |
|
121 No. |
|
122 |
|
123 The `tomli.loads` function returns a plain `dict` that is populated with builtin types and types from the standard library only. |
|
124 Preserving comments requires a custom type to be returned so will not be supported, |
|
125 at least not by the `tomli.loads` and `tomli.load` functions. |
|
126 |
|
127 Look into [TOML Kit](https://github.com/sdispater/tomlkit) if preservation of style is what you need. |
|
128 |
|
129 ### Is there a `dumps`, `write` or `encode` function?<a name="is-there-a-dumps-write-or-encode-function"></a> |
|
130 |
|
131 [Tomli-W](https://github.com/hukkin/tomli-w) is the write-only counterpart of Tomli, providing `dump` and `dumps` functions. |
|
132 |
|
133 The core library does not include write capability, as most TOML use cases are read-only, and Tomli intends to be minimal. |
|
134 |
|
135 ### How do TOML types map into Python types?<a name="how-do-toml-types-map-into-python-types"></a> |
|
136 |
|
137 | TOML type | Python type | Details | |
|
138 | ---------------- | ------------------- | ------------------------------------------------------------ | |
|
139 | Document Root | `dict` | | |
|
140 | Key | `str` | | |
|
141 | String | `str` | | |
|
142 | Integer | `int` | | |
|
143 | Float | `float` | | |
|
144 | Boolean | `bool` | | |
|
145 | Offset Date-Time | `datetime.datetime` | `tzinfo` attribute set to an instance of `datetime.timezone` | |
|
146 | Local Date-Time | `datetime.datetime` | `tzinfo` attribute set to `None` | |
|
147 | Local Date | `datetime.date` | | |
|
148 | Local Time | `datetime.time` | | |
|
149 | Array | `list` | | |
|
150 | Table | `dict` | | |
|
151 | Inline Table | `dict` | | |
|
152 |
|
153 ## Performance<a name="performance"></a> |
|
154 |
|
155 The `benchmark/` folder in this repository contains a performance benchmark for comparing the various Python TOML parsers. |
|
156 The benchmark can be run with `tox -e benchmark-pypi`. |
|
157 Running the benchmark on my personal computer output the following: |
|
158 |
|
159 ```console |
|
160 foo@bar:~/dev/tomli$ tox -e benchmark-pypi |
|
161 benchmark-pypi installed: attrs==19.3.0,click==7.1.2,pytomlpp==1.0.2,qtoml==0.3.0,rtoml==0.7.0,toml==0.10.2,tomli==1.1.0,tomlkit==0.7.2 |
|
162 benchmark-pypi run-test-pre: PYTHONHASHSEED='2658546909' |
|
163 benchmark-pypi run-test: commands[0] | python -c 'import datetime; print(datetime.date.today())' |
|
164 2021-07-23 |
|
165 benchmark-pypi run-test: commands[1] | python --version |
|
166 Python 3.8.10 |
|
167 benchmark-pypi run-test: commands[2] | python benchmark/run.py |
|
168 Parsing data.toml 5000 times: |
|
169 ------------------------------------------------------ |
|
170 parser | exec time | performance (more is better) |
|
171 -----------+------------+----------------------------- |
|
172 rtoml | 0.901 s | baseline (100%) |
|
173 pytomlpp | 1.08 s | 83.15% |
|
174 tomli | 3.89 s | 23.15% |
|
175 toml | 9.36 s | 9.63% |
|
176 qtoml | 11.5 s | 7.82% |
|
177 tomlkit | 56.8 s | 1.59% |
|
178 ``` |
|
179 |
|
180 The parsers are ordered from fastest to slowest, using the fastest parser as baseline. |
|
181 Tomli performed the best out of all pure Python TOML parsers, |
|
182 losing only to pytomlpp (wraps C++) and rtoml (wraps Rust). |