_Z='keys'
_Y='box_settings'
_X='default_box_attr'
_W='Box is frozen'
_V='modify_tuples_box'
_U='box_safe_prefix'
_T='default_box_none_transform'
_S='__created'
_R='box_dots'
_Q='box_duplicates'
_P='ignore'
_O='.'
_N='strict'
_M='box_recast'
_L='box_intact_types'
_K='default_box'
_J='_'
_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):D='\\1_\\2';A=attr;A=str(A);B=_first_cap_re.sub(D,A);C=_all_cap_re.sub(D,B);return re.sub(' *_+',_J,C.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==_O:return A[:B],A[B+1:]
	raise BoxError('Could not split box dots properly')
def _get_box_config():return{_S:_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(_J)]
	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=_P,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({_K:default_box,_X:A.__class__ if C is NO_DEFAULT else C,_T:default_box_none_transform,_E:conversion_box,_U:box_safe_prefix,_D:frozen_box,_F:camel_killer_box,_V:modify_tuples_box,_Q:box_duplicates,_L:tuple(box_intact_types),_M:box_recast,_R:box_dots,_Y: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=_P,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({_K:default_box,_X:A.__class__ if E is NO_DEFAULT else E,_T:default_box_none_transform,_E:conversion_box,_U:box_safe_prefix,_D:frozen_box,_F:camel_killer_box,_V:modify_tuples_box,_Q:box_duplicates,_L:tuple(box_intact_types),_M:box_recast,_R:box_dots,_Y:box_settings or{}})
		if not A._box_config[_E]and A._box_config[_Q]!=_P: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[_K]and A._box_config[_T]: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[_S]=_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+_J;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[_K]and B._box_config[_T]:return B.__get_default(C)
				else:return _A
			if isinstance(A,dict)and not isinstance(A,Box):return Box(A,box_settings=B._box_config.get(_Y))
			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 __get_default(B,item):
		A=B._box_config[_X]
		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[_M]and B in A._box_config[_M]:
			try:return A._box_config[_M][B](C)
			except ValueError:raise BoxValueError(f"Cannot convert {C} to {A._box_config[_M][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[_L]and isinstance(A,B._box_config[_L]):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[_V],**B.__box_config())
			else:A=box.BoxList(A,box_class=B.__class__,**B.__box_config())
		elif B._box_config[_V]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[_R]and isinstance(A,str)and(_O 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[_K]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[_K]: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[_S]and A._box_config[_D]:raise BoxError(_W)
		if A._box_config[_R]and isinstance(B,str)and _O 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[_Q]!=_P: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[_S]:raise BoxError(_W)
		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(_W)
		if B not in A.keys()and A._box_config[_R]and isinstance(B,str)and _O in B:
			C,E=B.split(_O,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(_W)
		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,_Z):
				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[_L]and isinstance(v,A._box_config[_L])
			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,_Z):
				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+_J
		if isinstance(B,tuple):B=_J.join([str(A)for A in B])
		B=B.decode(_I,_P)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(_J)
		A=''.join(A)[:D+1]
		try:int(A[0])
		except (ValueError,IndexError):pass
		else:A=f"{C.__box_config()[_U]}{A}"
		if A in kwlist:A=f"{C.__box_config()[_U]}{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[_Q].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=_N,**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=_N,**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=_N,**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=_N,**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=_N):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=_N,**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)