You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
bazarr/libs/dynaconf/vendor/box/box.py

326 lines
14 KiB

#!/usr/bin/env python
_W='box_settings'
_V='default_box_attr'
_U='Box is frozen'
_T='modify_tuples_box'
_S='box_safe_prefix'
_R='default_box_none_transform'
_Q='__created'
_P='box_dots'
_O='box_duplicates'
_N='ignore'
_M='strict'
_L='box_recast'
_K='box_intact_types'
_J='default_box'
_I='utf-8'
_H='_box_config'
_G=True
_F='camel_killer_box'
_E='conversion_box'
_D='frozen_box'
_C='__safe_keys'
_B=False
_A=None
import copy,re,string,warnings
from collections.abc import Iterable,Mapping,Callable
from keyword import kwlist
from pathlib import Path
from typing import Any,Union,Tuple,List,Dict
from dynaconf.vendor import box
from.converters import _to_json,_from_json,_from_toml,_to_toml,_from_yaml,_to_yaml,BOX_PARAMETERS
from.exceptions import BoxError,BoxKeyError,BoxTypeError,BoxValueError,BoxWarning
__all__=['Box']
_first_cap_re=re.compile('(.)([A-Z][a-z]+)')
_all_cap_re=re.compile('([a-z0-9])([A-Z])')
_list_pos_re=re.compile('\\[(\\d+)\\]')
NO_DEFAULT=object()
def _camel_killer(attr):B='\\1_\\2';A=attr;A=str(A);C=_first_cap_re.sub(B,A);D=_all_cap_re.sub(B,C);return re.sub(' *_+','_',D.lower())
def _recursive_tuples(iterable,box_class,recreate_tuples=_B,**E):
D=recreate_tuples;C=box_class;B=[]
for A in iterable:
if isinstance(A,dict):B.append(C(A,**E))
elif isinstance(A,list)or D and isinstance(A,tuple):B.append(_recursive_tuples(A,C,D,**E))
else:B.append(A)
return tuple(B)
def _parse_box_dots(item):
A=item
for(B,C)in enumerate(A):
if C=='[':return A[:B],A[B:]
elif C=='.':return A[:B],A[B+1:]
raise BoxError('Could not split box dots properly')
def _get_box_config():return{_Q:_B,_C:{}}
class Box(dict):
_protected_keys=['to_dict','to_json','to_yaml','from_yaml','from_json','from_toml','to_toml','merge_update']+[A for A in dir({})if not A.startswith('_')]
def __new__(A,*D,box_settings=_A,default_box=_B,default_box_attr=NO_DEFAULT,default_box_none_transform=_G,frozen_box=_B,camel_killer_box=_B,conversion_box=_G,modify_tuples_box=_B,box_safe_prefix='x',box_duplicates=_N,box_intact_types=(),box_recast=_A,box_dots=_B,**E):C=default_box_attr;B=super(Box,A).__new__(A,*D,**E);B._box_config=_get_box_config();B._box_config.update({_J:default_box,_V:A.__class__ if C is NO_DEFAULT else C,_R:default_box_none_transform,_E:conversion_box,_S:box_safe_prefix,_D:frozen_box,_F:camel_killer_box,_T:modify_tuples_box,_O:box_duplicates,_K:tuple(box_intact_types),_L:box_recast,_P:box_dots,_W:box_settings or{}});return B
def __init__(A,*B,box_settings=_A,default_box=_B,default_box_attr=NO_DEFAULT,default_box_none_transform=_G,frozen_box=_B,camel_killer_box=_B,conversion_box=_G,modify_tuples_box=_B,box_safe_prefix='x',box_duplicates=_N,box_intact_types=(),box_recast=_A,box_dots=_B,**F):
E=default_box_attr;super().__init__();A._box_config=_get_box_config();A._box_config.update({_J:default_box,_V:A.__class__ if E is NO_DEFAULT else E,_R:default_box_none_transform,_E:conversion_box,_S:box_safe_prefix,_D:frozen_box,_F:camel_killer_box,_T:modify_tuples_box,_O:box_duplicates,_K:tuple(box_intact_types),_L:box_recast,_P:box_dots,_W:box_settings or{}})
if not A._box_config[_E]and A._box_config[_O]!=_N:raise BoxError('box_duplicates are only for conversion_boxes')
if len(B)==1:
if isinstance(B[0],str):raise BoxValueError('Cannot extrapolate Box from string')
if isinstance(B[0],Mapping):
for(D,C)in B[0].items():
if C is B[0]:C=A
if C is _A and A._box_config[_J]and A._box_config[_R]:continue
A.__setitem__(D,C)
elif isinstance(B[0],Iterable):
for(D,C)in B[0]:A.__setitem__(D,C)
else:raise BoxValueError('First argument must be mapping or iterable')
elif B:raise BoxTypeError(f"Box expected at most 1 argument, got {len(B)}")
for(D,C)in F.items():
if B and isinstance(B[0],Mapping)and C is B[0]:C=A
A.__setitem__(D,C)
A._box_config[_Q]=_G
def __add__(C,other):
A=other;B=C.copy()
if not isinstance(A,dict):raise BoxTypeError(f"Box can only merge two boxes or a box and a dictionary.")
B.merge_update(A);return B
def __hash__(A):
if A._box_config[_D]:
B=54321
for C in A.items():B^=hash(C)
return B
raise BoxTypeError('unhashable type: "Box"')
def __dir__(B):
D=string.ascii_letters+string.digits+'_';C=set(super().__dir__())
for A in B.keys():
A=str(A)
if' 'not in A and A[0]not in string.digits and A not in kwlist:
for E in A:
if E not in D:break
else:C.add(A)
for A in B.keys():
if A not in C:
if B._box_config[_E]:
A=B._safe_attr(A)
if A:C.add(A)
return list(C)
def get(B,key,default=NO_DEFAULT):
C=key;A=default
if C not in B:
if A is NO_DEFAULT:
if B._box_config[_J]and B._box_config[_R]:return B.__get_default(C)
else:return
if isinstance(A,dict)and not isinstance(A,Box):return Box(A,box_settings=B._box_config.get(_W))
if isinstance(A,list)and not isinstance(A,box.BoxList):return box.BoxList(A)
return A
return B[C]
def copy(A):return Box(super().copy(),**A.__box_config())
def __copy__(A):return Box(super().copy(),**A.__box_config())
def __deepcopy__(A,memodict=_A):
B=memodict;E=A._box_config[_D];D=A.__box_config();D[_D]=_B;C=A.__class__(**D);B=B or{};B[id(A)]=C
for(F,G)in A.items():C[copy.deepcopy(F,B)]=copy.deepcopy(G,B)
C._box_config[_D]=E;return C
def __setstate__(A,state):B=state;A._box_config=B[_H];A.__dict__.update(B)
def keys(A):return super().keys()
def values(A):return[A[B]for B in A.keys()]
def items(A):return[(B,A[B])for B in A.keys()]
def _safe_items(A):return[(B,A._safe_get(B))for B in A.keys()]
def __get_default(B,item):
A=B._box_config[_V]
if A in(B.__class__,dict):C=B.__class__(**B.__box_config())
elif isinstance(A,dict):C=B.__class__(**B.__box_config(),**A)
elif isinstance(A,list):C=box.BoxList(**B.__box_config())
elif isinstance(A,Callable):C=A()
elif hasattr(A,'copy'):C=A.copy()
else:C=A
B.__convert_and_store(item,C);return C
def __box_config(C):
A={}
for(B,D)in C._box_config.copy().items():
if not B.startswith('__'):A[B]=D
return A
def __recast(A,item,value):
C=value;B=item
if A._box_config[_L]and B in A._box_config[_L]:
try:return A._box_config[_L][B](C)
except ValueError:raise BoxValueError(f"Cannot convert {C} to {A._box_config[_L][B]}")from _A
return C
def __convert_and_store(B,item,value):
C=item;A=value
if B._box_config[_E]:D=B._safe_attr(C);B._box_config[_C][D]=C
if isinstance(A,(int,float,str,bytes,bytearray,bool,complex,set,frozenset)):return super().__setitem__(C,A)
if B._box_config[_K]and isinstance(A,B._box_config[_K]):return super().__setitem__(C,A)
if isinstance(A,dict)and not isinstance(A,Box):A=B.__class__(A,**B.__box_config())
elif isinstance(A,list)and not isinstance(A,box.BoxList):
if B._box_config[_D]:A=_recursive_tuples(A,B.__class__,recreate_tuples=B._box_config[_T],**B.__box_config())
else:A=box.BoxList(A,box_class=B.__class__,**B.__box_config())
elif B._box_config[_T]and isinstance(A,tuple):A=_recursive_tuples(A,B.__class__,recreate_tuples=_G,**B.__box_config())
super().__setitem__(C,A)
def __getitem__(B,item,_ignore_default=_B):
A=item
try:return super().__getitem__(A)
except KeyError as E:
if A==_H:raise BoxKeyError('_box_config should only exist as an attribute and is never defaulted')from _A
if B._box_config[_P]and isinstance(A,str)and('.'in A or'['in A):
C,F=_parse_box_dots(A)
if C in B.keys():
if hasattr(B[C],'__getitem__'):return B[C][F]
if B._box_config[_F]and isinstance(A,str):
D=_camel_killer(A)
if D in B.keys():return super().__getitem__(D)
if B._box_config[_J]and not _ignore_default:return B.__get_default(A)
raise BoxKeyError(str(E))from _A
def __getattr__(A,item):
B=item
try:
try:C=A.__getitem__(B,_ignore_default=_G)
except KeyError:C=object.__getattribute__(A,B)
except AttributeError as E:
if B=='__getstate__':raise BoxKeyError(B)from _A
if B==_H:raise BoxError('_box_config key must exist')from _A
if A._box_config[_E]:
D=A._safe_attr(B)
if D in A._box_config[_C]:return A.__getitem__(A._box_config[_C][D])
if A._box_config[_J]:return A.__get_default(B)
raise BoxKeyError(str(E))from _A
return C
def __setitem__(A,key,value):
C=value;B=key
if B!=_H and A._box_config[_Q]and A._box_config[_D]:raise BoxError(_U)
if A._box_config[_P]and isinstance(B,str)and'.'in B:
D,E=_parse_box_dots(B)
if D in A.keys():
if hasattr(A[D],'__setitem__'):return A[D].__setitem__(E,C)
C=A.__recast(B,C)
if B not in A.keys()and A._box_config[_F]:
if A._box_config[_F]and isinstance(B,str):B=_camel_killer(B)
if A._box_config[_E]and A._box_config[_O]!=_N:A._conversion_checks(B)
A.__convert_and_store(B,C)
def __setattr__(A,key,value):
C=value;B=key
if B!=_H and A._box_config[_D]and A._box_config[_Q]:raise BoxError(_U)
if B in A._protected_keys:raise BoxKeyError(f'Key name "{B}" is protected')
if B==_H:return object.__setattr__(A,B,C)
C=A.__recast(B,C);D=A._safe_attr(B)
if D in A._box_config[_C]:B=A._box_config[_C][D]
A.__setitem__(B,C)
def __delitem__(A,key):
B=key
if A._box_config[_D]:raise BoxError(_U)
if B not in A.keys()and A._box_config[_P]and isinstance(B,str)and'.'in B:
C,E=B.split('.',1)
if C in A.keys()and isinstance(A[C],dict):return A[C].__delitem__(E)
if B not in A.keys()and A._box_config[_F]:
if A._box_config[_F]and isinstance(B,str):
for D in A:
if _camel_killer(B)==D:B=D;break
super().__delitem__(B)
def __delattr__(A,item):
B=item
if A._box_config[_D]:raise BoxError(_U)
if B==_H:raise BoxError('"_box_config" is protected')
if B in A._protected_keys:raise BoxKeyError(f'Key name "{B}" is protected')
try:A.__delitem__(B)
except KeyError as D:
if A._box_config[_E]:
C=A._safe_attr(B)
if C in A._box_config[_C]:A.__delitem__(A._box_config[_C][C]);del A._box_config[_C][C];return
raise BoxKeyError(D)
def pop(B,key,*C):
A=key
if C:
if len(C)!=1:raise BoxError('pop() takes only one optional argument "default"')
try:D=B[A]
except KeyError:return C[0]
else:del B[A];return D
try:D=B[A]
except KeyError:raise BoxKeyError('{0}'.format(A))from _A
else:del B[A];return D
def clear(A):super().clear();A._box_config[_C].clear()
def popitem(A):
try:B=next(A.__iter__())
except StopIteration:raise BoxKeyError('Empty box')from _A
return B,A.pop(B)
def __repr__(A):return f"<Box: {A.to_dict()}>"
def __str__(A):return str(A.to_dict())
def __iter__(A):
for B in A.keys():yield B
def __reversed__(A):
for B in reversed(list(A.keys())):yield B
def to_dict(D):
A=dict(D)
for(C,B)in A.items():
if B is D:A[C]=A
elif isinstance(B,Box):A[C]=B.to_dict()
elif isinstance(B,box.BoxList):A[C]=B.to_list()
return A
def update(C,__m=_A,**D):
B=__m
if B:
if hasattr(B,'keys'):
for A in B:C.__convert_and_store(A,B[A])
else:
for(A,E)in B:C.__convert_and_store(A,E)
for A in D:C.__convert_and_store(A,D[A])
def merge_update(A,__m=_A,**E):
C=__m
def D(k,v):
B=A._box_config[_K]and isinstance(v,A._box_config[_K])
if isinstance(v,dict)and not B:
v=A.__class__(v,**A.__box_config())
if k in A and isinstance(A[k],dict):
if isinstance(A[k],Box):A[k].merge_update(v)
else:A[k].update(v)
return
if isinstance(v,list)and not B:v=box.BoxList(v,**A.__box_config())
A.__setitem__(k,v)
if C:
if hasattr(C,'keys'):
for B in C:D(B,C[B])
else:
for(B,F)in C:D(B,F)
for B in E:D(B,E[B])
def setdefault(B,item,default=_A):
C=item;A=default
if C in B:return B[C]
if isinstance(A,dict):A=B.__class__(A,**B.__box_config())
if isinstance(A,list):A=box.BoxList(A,box_class=B.__class__,**B.__box_config())
B[C]=A;return A
def _safe_attr(C,attr):
B=attr;G=string.ascii_letters+string.digits+'_'
if isinstance(B,tuple):B='_'.join([str(A)for A in B])
B=B.decode(_I,_N)if isinstance(B,bytes)else str(B)
if C.__box_config()[_F]:B=_camel_killer(B)
A=[];D=0
for(E,F)in enumerate(B):
if F in G:D=E;A.append(F)
elif not A:continue
elif D==E-1:A.append('_')
A=''.join(A)[:D+1]
try:int(A[0])
except(ValueError,IndexError):pass
else:A=f"{C.__box_config()[_S]}{A}"
if A in kwlist:A=f"{C.__box_config()[_S]}{A}"
return A
def _conversion_checks(A,item):
B=A._safe_attr(item)
if B in A._box_config[_C]:
C=[f"{item}({B})",f"{A._box_config[_C][B]}({B})"]
if A._box_config[_O].startswith('warn'):warnings.warn(f"Duplicate conversion attributes exist: {C}",BoxWarning)
else:raise BoxError(f"Duplicate conversion attributes exist: {C}")
def to_json(A,filename=_A,encoding=_I,errors=_M,**B):return _to_json(A.to_dict(),filename=filename,encoding=encoding,errors=errors,**B)
@classmethod
def from_json(E,json_string=_A,filename=_A,encoding=_I,errors=_M,**A):
D={}
for B in A.copy():
if B in BOX_PARAMETERS:D[B]=A.pop(B)
C=_from_json(json_string,filename=filename,encoding=encoding,errors=errors,**A)
if not isinstance(C,dict):raise BoxError(f"json data not returned as a dictionary, but rather a {type(C).__name__}")
return E(C,**D)
def to_yaml(A,filename=_A,default_flow_style=_B,encoding=_I,errors=_M,**B):return _to_yaml(A.to_dict(),filename=filename,default_flow_style=default_flow_style,encoding=encoding,errors=errors,**B)
@classmethod
def from_yaml(E,yaml_string=_A,filename=_A,encoding=_I,errors=_M,**A):
D={}
for B in A.copy():
if B in BOX_PARAMETERS:D[B]=A.pop(B)
C=_from_yaml(yaml_string=yaml_string,filename=filename,encoding=encoding,errors=errors,**A)
if not isinstance(C,dict):raise BoxError(f"yaml data not returned as a dictionary but rather a {type(C).__name__}")
return E(C,**D)
def to_toml(A,filename=_A,encoding=_I,errors=_M):return _to_toml(A.to_dict(),filename=filename,encoding=encoding,errors=errors)
@classmethod
def from_toml(D,toml_string=_A,filename=_A,encoding=_I,errors=_M,**B):
C={}
for A in B.copy():
if A in BOX_PARAMETERS:C[A]=B.pop(A)
E=_from_toml(toml_string=toml_string,filename=filename,encoding=encoding,errors=errors);return D(E,**C)