#!/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='camel_killer_box'
_F='conversion_box'
_E=True
_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=_E,frozen_box=_B,camel_killer_box=_B,conversion_box=_E,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,_F:conversion_box,_S:box_safe_prefix,_D:frozen_box,_G: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=_E,frozen_box=_B,camel_killer_box=_B,conversion_box=_E,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,_F:conversion_box,_S:box_safe_prefix,_D:frozen_box,_G: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[_F]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]=_E
	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[_F]:
					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,bypass_eval=_B):
		if not bypass_eval:return[(B,A[B])for B in A.keys()]
		return[(B,A.get(B,bypass_eval=_E))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[_F]: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=_E,**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[_G]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=_E)
			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[_F]:
				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[_G]:
			if A._box_config[_G]and isinstance(B,str):B=_camel_killer(B)
		if A._box_config[_F]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[_G]:
			if A._box_config[_G]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[_F]:
				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()[_G]: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)