from __future__ import annotations _C=True _B=False _A='\n' from collections.abc import Generator,Mapping from datetime import date,datetime,time from decimal import Decimal import string from types import MappingProxyType from typing import Any,BinaryIO,NamedTuple ASCII_CTRL=frozenset(chr(A)for A in range(32))|frozenset(chr(127)) ILLEGAL_BASIC_STR_CHARS=frozenset('"\\')|ASCII_CTRL-frozenset('\t') BARE_KEY_CHARS=frozenset(string.ascii_letters+string.digits+'-_') ARRAY_TYPES=list,tuple ARRAY_INDENT=' '*4 MAX_LINE_LENGTH=100 COMPACT_ESCAPES=MappingProxyType({'\x08':'\\b',_A:'\\n','\x0c':'\\f','\r':'\\r','"':'\\"','\\':'\\\\'}) def dump(__obj,__fp,*,multiline_strings=_B): A=Context(multiline_strings,{}) for B in gen_table_chunks(__obj,A,name=''):__fp.write(B.encode()) def dumps(__obj,*,multiline_strings=_B):A=Context(multiline_strings,{});return''.join(gen_table_chunks(__obj,A,name='')) class Context(NamedTuple):allow_multiline:bool;inline_table_cache:dict[int,str] def gen_table_chunks(table,ctx,*,name,inside_aot=_B): H=inside_aot;G=ctx;C=name;D=_B;E=[];F=[] for(B,A)in table.items(): if isinstance(A,dict):F.append((B,A,_B)) elif is_aot(A)and not all(is_suitable_inline_table(A,G)for A in A):F.extend((B,A,_C)for A in A) else:E.append((B,A)) if H or C and(E or not F):D=_C;yield f"[[{C}]]\n"if H else f"[{C}]\n" if E: D=_C for(B,A)in E:yield f"{format_key_part(B)} = {format_literal(A,G)}\n" for(B,A,J)in F: if D:yield _A else:D=_C I=format_key_part(B);K=f"{C}.{I}"if C else I;yield from gen_table_chunks(A,G,name=K,inside_aot=J) def format_literal(obj,ctx,*,nest_level=0): B=ctx;A=obj if isinstance(A,bool):return'true'if A else'false' if isinstance(A,(int,float,date,datetime)):return str(A) if isinstance(A,Decimal):return format_decimal(A) if isinstance(A,time): if A.tzinfo:raise ValueError('TOML does not support offset times') return str(A) if isinstance(A,str):return format_string(A,allow_multiline=B.allow_multiline) if isinstance(A,ARRAY_TYPES):return format_inline_array(A,B,nest_level) if isinstance(A,dict):return format_inline_table(A,B) raise TypeError(f"Object of type {type(A)} is not TOML serializable") def format_decimal(obj): C='-inf';B='inf';A=obj if A.is_nan():return'nan' if A==Decimal(B):return B if A==Decimal(C):return C return str(A) def format_inline_table(obj,ctx): B=obj;A=ctx;C=id(B) if C in A.inline_table_cache:return A.inline_table_cache[C] if not B:D='{}' else:D='{ '+', '.join(f"{format_key_part(B)} = {format_literal(C,A)}"for(B,C)in B.items())+' }' A.inline_table_cache[C]=D;return D def format_inline_array(obj,ctx,nest_level): A=nest_level if not obj:return'[]' B=ARRAY_INDENT*(1+A);C=ARRAY_INDENT*A;return'[\n'+',\n'.join(B+format_literal(C,ctx,nest_level=A+1)for C in obj)+f",\n{C}]" def format_key_part(part): A=part if A and BARE_KEY_CHARS.issuperset(A):return A return format_string(A,allow_multiline=_B) def format_string(s,*,allow_multiline): D=allow_multiline and _A in s if D:A='"""\n';s=s.replace('\r\n',_A) else:A='"' B=E=0 while _C: try:C=s[B] except IndexError: A+=s[E:B] if D:return A+'"""' return A+'"' if C in ILLEGAL_BASIC_STR_CHARS: A+=s[E:B] if C in COMPACT_ESCAPES: if D and C==_A:A+=_A else:A+=COMPACT_ESCAPES[C] else:A+='\\u'+hex(ord(C))[2:].rjust(4,'0') E=B+1 B+=1 def is_aot(obj):A=obj;return bool(isinstance(A,ARRAY_TYPES)and A and all(isinstance(A,dict)for A in A)) def is_suitable_inline_table(obj,ctx):A=f"{ARRAY_INDENT}{format_inline_table(obj,ctx)},";return len(A)<=MAX_LINE_LENGTH and _A not in A