File: box.py

package info (click to toggle)
python-dynaconf 3.1.7-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,116 kB
  • sloc: python: 12,959; makefile: 4
file content (327 lines) | stat: -rw-r--r-- 13,967 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
_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)