Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/tomlkit/api.py: 50%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

135 statements  

1from __future__ import annotations 

2 

3import contextlib 

4import datetime as _datetime 

5import re 

6 

7from collections.abc import Iterable 

8from collections.abc import Mapping 

9from typing import IO 

10from typing import TYPE_CHECKING 

11from typing import Any 

12from typing import TypeVar 

13 

14from tomlkit._utils import parse_rfc3339 

15from tomlkit.container import Container 

16from tomlkit.exceptions import UnexpectedCharError 

17from tomlkit.items import CUSTOM_ENCODERS 

18from tomlkit.items import AoT 

19from tomlkit.items import Array 

20from tomlkit.items import Bool 

21from tomlkit.items import Comment 

22from tomlkit.items import Date 

23from tomlkit.items import DateTime 

24from tomlkit.items import DottedKey 

25from tomlkit.items import Float 

26from tomlkit.items import InlineTable 

27from tomlkit.items import Integer 

28from tomlkit.items import Item as _Item 

29from tomlkit.items import Key 

30from tomlkit.items import SingleKey 

31from tomlkit.items import String 

32from tomlkit.items import StringType as _StringType 

33from tomlkit.items import Table 

34from tomlkit.items import Time 

35from tomlkit.items import Trivia 

36from tomlkit.items import Whitespace 

37from tomlkit.items import item as item 

38from tomlkit.parser import Parser 

39from tomlkit.toml_document import TOMLDocument as TOMLDocument 

40 

41 

42if TYPE_CHECKING: 

43 from tomlkit.items import Encoder 

44 

45 E = TypeVar("E", bound=Encoder) 

46 

47 

48def loads(string: str | bytes) -> TOMLDocument: 

49 """ 

50 Parses a string into a TOMLDocument. 

51 

52 Alias for parse(). 

53 """ 

54 return parse(string) 

55 

56 

57def dumps(data: Mapping[str, Any], sort_keys: bool = False) -> str: 

58 """ 

59 Dumps a TOMLDocument into a string. 

60 """ 

61 if isinstance(data, (Table, InlineTable, Container)): 

62 if not sort_keys: 

63 return data.as_string() 

64 

65 return item(data, _sort_keys=True).as_string() 

66 

67 if isinstance(data, Mapping): 

68 return item(dict(data), _sort_keys=sort_keys).as_string() 

69 

70 try: 

71 # mapping-like wrappers (e.g. dotty_dict's Dotty) delegate 

72 # ``as_string`` to the document they wrap; rendering it directly 

73 # preserves the original layout, which re-encoding through a plain 

74 # dict would lose 

75 return data.as_string() # type: ignore[attr-defined] 

76 except AttributeError as ex: 

77 msg = f"Expecting Mapping or TOML Table or Container, {type(data)} given" 

78 raise TypeError(msg) from ex 

79 

80 

81def load(fp: IO[str] | IO[bytes]) -> TOMLDocument: 

82 """ 

83 Load toml document from a file-like object. 

84 """ 

85 return parse(fp.read()) 

86 

87 

88def dump(data: Mapping[str, Any], fp: IO[str], *, sort_keys: bool = False) -> None: 

89 """ 

90 Dump a TOMLDocument into a writable file stream. 

91 

92 :param data: a dict-like object to dump 

93 :param sort_keys: if true, sort the keys in alphabetic order 

94 

95 :Example: 

96 

97 >>> with open("output.toml", "w") as fp: 

98 ... tomlkit.dump(data, fp) 

99 """ 

100 fp.write(dumps(data, sort_keys=sort_keys)) 

101 

102 

103def parse(string: str | bytes) -> TOMLDocument: 

104 """ 

105 Parses a string or bytes into a TOMLDocument. 

106 """ 

107 return Parser(string).parse() 

108 

109 

110def document() -> TOMLDocument: 

111 """ 

112 Returns a new TOMLDocument instance. 

113 """ 

114 return TOMLDocument() 

115 

116 

117# Items 

118_INTEGER_RE = re.compile( 

119 r"(?:" 

120 r"0x[0-9a-fA-F](?:_?[0-9a-fA-F])*" # hex: lowercase prefix only, no sign 

121 r"|0o[0-7](?:_?[0-7])*" # octal: lowercase prefix only, no sign 

122 r"|0b[01](?:_?[01])*" # binary: lowercase prefix only, no sign 

123 r"|[+-]?(?:0|[1-9](?:_?[0-9])*)" # decimal: optional sign, no leading zeros 

124 r")" 

125) 

126"""Valid TOML v1.0.0 integer literals (https://toml.io/en/v1.0.0#integer). 

127 

128Underscores may only appear between digits; hex/octal/binary use a 

129lowercase prefix and take no sign, mirroring ``Parser._parse_number``. 

130""" 

131 

132 

133def integer(raw: str | int) -> Integer: 

134 """Create an integer item from a number or string. 

135 

136 When ``raw`` is a string it must be a valid TOML v1.0.0 integer 

137 literal; the original text is then preserved verbatim so grouping 

138 underscores (e.g. ``"1_000"``) survive ``dumps()``. Anything else 

139 raises ``ValueError``. 

140 """ 

141 if isinstance(raw, str): 

142 if not _INTEGER_RE.fullmatch(raw): 

143 raise ValueError(f"Invalid TOML integer: {raw!r}") 

144 return Integer(int(raw, 0), Trivia(), raw) 

145 return item(int(raw)) 

146 

147 

148def float_(raw: str | float) -> Float: 

149 """Create an float item from a number or string.""" 

150 return item(float(raw)) 

151 

152 

153def boolean(raw: str | bool) -> Bool: 

154 """Turn `true` or `false` into a boolean item.""" 

155 return item(raw == "true" if isinstance(raw, str) else raw) 

156 

157 

158def string( 

159 raw: str, 

160 *, 

161 literal: bool = False, 

162 multiline: bool = False, 

163 escape: bool = True, 

164) -> String: 

165 """Create a string item. 

166 

167 By default, this function will create *single line basic* strings, but 

168 boolean flags (e.g. ``literal=True`` and/or ``multiline=True``) 

169 can be used for personalization. 

170 

171 For more information, please check the spec: `<https://toml.io/en/v1.0.0#string>`__. 

172 

173 Dumping produces a TOML string that faithfully represents the given string. 

174 This includes character escaping for basic strings. 

175 This behaviour can be disabled by setting ``escape=False``. 

176 In that case, the given string is used verbatim for the content of the TOML string. 

177 Please note that this makes you responsible for ensuring the given string does not 

178 contain any forbidden character or sequence. 

179 """ 

180 type_ = _StringType.select(literal, multiline) 

181 return String.from_raw(raw, type_, escape) 

182 

183 

184def date(raw: str) -> Date: 

185 """Create a TOML date.""" 

186 value = parse_rfc3339(raw) 

187 if isinstance(value, _datetime.datetime) or not isinstance(value, _datetime.date): 

188 raise ValueError("date() only accepts date strings.") 

189 

190 return item(value) 

191 

192 

193def time(raw: str) -> Time: 

194 """Create a TOML time.""" 

195 value = parse_rfc3339(raw) 

196 if not isinstance(value, _datetime.time): 

197 raise ValueError("time() only accepts time strings.") 

198 

199 return item(value) 

200 

201 

202def datetime(raw: str) -> DateTime: 

203 """Create a TOML datetime.""" 

204 value = parse_rfc3339(raw) 

205 if not isinstance(value, _datetime.datetime): 

206 raise ValueError("datetime() only accepts datetime strings.") 

207 

208 return item(value) 

209 

210 

211def array(raw: str = "[]") -> Array: 

212 """Create an array item for its string representation. 

213 

214 :Example: 

215 

216 >>> array("[1, 2, 3]") # Create from a string 

217 [1, 2, 3] 

218 >>> a = array() 

219 >>> a.extend([1, 2, 3]) # Create from a list 

220 >>> a 

221 [1, 2, 3] 

222 """ 

223 v = value(raw) 

224 if not isinstance(v, Array): 

225 raise ValueError(f"Expected an array, got {type(v)}") 

226 return v 

227 

228 

229def table(is_super_table: bool | None = None) -> Table: 

230 """Create an empty table. 

231 

232 :param is_super_table: if true, the table is a super table 

233 

234 :Example: 

235 

236 >>> doc = document() 

237 >>> foo = table(True) 

238 >>> bar = table() 

239 >>> bar.update({'x': 1}) 

240 >>> foo.append('bar', bar) 

241 >>> doc.append('foo', foo) 

242 >>> print(doc.as_string()) 

243 [foo.bar] 

244 x = 1 

245 """ 

246 return Table(Container(), Trivia(), False, is_super_table) 

247 

248 

249def inline_table() -> InlineTable: 

250 """Create an inline table. 

251 

252 :Example: 

253 

254 >>> table = inline_table() 

255 >>> table.update({'x': 1, 'y': 2}) 

256 >>> print(table.as_string()) 

257 {x = 1, y = 2} 

258 """ 

259 return InlineTable(Container(), Trivia(), new=True) 

260 

261 

262def aot() -> AoT: 

263 """Create an array of table. 

264 

265 :Example: 

266 

267 >>> doc = document() 

268 >>> aot = aot() 

269 >>> aot.append(item({'x': 1})) 

270 >>> doc.append('foo', aot) 

271 >>> print(doc.as_string()) 

272 [[foo]] 

273 x = 1 

274 """ 

275 return AoT([]) 

276 

277 

278def key(k: str | Iterable[str]) -> Key: 

279 """Create a key from a string. When a list of string is given, 

280 it will create a dotted key. 

281 

282 :Example: 

283 

284 >>> doc = document() 

285 >>> doc.append(key('foo'), 1) 

286 >>> doc.append(key(['bar', 'baz']), 2) 

287 >>> print(doc.as_string()) 

288 foo = 1 

289 bar.baz = 2 

290 """ 

291 if isinstance(k, str): 

292 return SingleKey(k) 

293 keys = [SingleKey(_k) for _k in k] 

294 if len(keys) == 1: 

295 return keys[0] 

296 return DottedKey(keys) 

297 

298 

299def value(raw: str) -> _Item: 

300 """Parse a simple value from a string. 

301 

302 :Example: 

303 

304 >>> value("1") 

305 1 

306 >>> value("true") 

307 True 

308 >>> value("[1, 2, 3]") 

309 [1, 2, 3] 

310 """ 

311 parser = Parser(raw) 

312 v = parser._parse_value() 

313 if not parser.end(): 

314 raise parser.parse_error(UnexpectedCharError, char=parser._current) 

315 return v 

316 

317 

318def key_value(src: str) -> tuple[Key, _Item]: 

319 """Parse a key-value pair from a string. 

320 

321 Anything other than whitespace or a comment after the pair is an error, 

322 as it is when the same text is given to :func:`parse`. 

323 

324 :Example: 

325 

326 >>> key_value("foo = 1") 

327 (Key('foo'), 1) 

328 """ 

329 parser = Parser(src) 

330 k, v = parser._parse_key_value(parse_comment=True) 

331 if not parser.end(): 

332 raise parser.parse_error(UnexpectedCharError, char=parser._current) 

333 return k, v 

334 

335 

336def ws(src: str) -> Whitespace: 

337 """Create a whitespace from a string.""" 

338 return Whitespace(src, fixed=True) 

339 

340 

341def nl() -> Whitespace: 

342 """Create a newline item.""" 

343 return ws("\n") 

344 

345 

346def comment(string: str) -> Comment: 

347 """Create a comment item. 

348 

349 A multiline string produces one ``#``-prefixed line per line so that the 

350 result is still valid TOML. 

351 """ 

352 lines = string.split("\n") 

353 rendered = "\n".join(f"# {line}" if line else "#" for line in lines) 

354 return Comment(Trivia(comment_ws=" ", comment=rendered)) 

355 

356 

357def register_encoder(encoder: E) -> E: 

358 """Add a custom encoder, which should be a function that will be called 

359 if the value can't otherwise be converted. 

360 

361 The encoder should return a TOMLKit item or raise a ``ConvertError``. 

362 

363 Example:: 

364 

365 @register_encoder 

366 def encode_custom_dict(obj, _parent=None, _sort_keys=False): 

367 if isinstance(obj, CustomDict): 

368 tbl = table() 

369 for key, value in obj.items(): 

370 # Pass along parameters when encoding nested values 

371 tbl[key] = item(value, _parent=tbl, _sort_keys=_sort_keys) 

372 return tbl 

373 raise ConvertError("Not a CustomDict") 

374 """ 

375 CUSTOM_ENCODERS.append(encoder) 

376 return encoder 

377 

378 

379def unregister_encoder(encoder: Encoder) -> None: 

380 """Unregister a custom encoder.""" 

381 with contextlib.suppress(ValueError): 

382 CUSTOM_ENCODERS.remove(encoder)