11from typing import Any , ClassVar , Self , override
22
33from box import Box
4+ from box .box import _camel_killer # type: ignore[attr-defined] # noqa: PLC2701
45
56from mpt_api_client .http .types import Response
67from mpt_api_client .models .meta import Meta
78
89ResourceData = dict [str , Any ]
910
11+ _box_safe_attributes : list [str ] = ["_box_config" , "_attribute_mapping" ]
1012
11- class Model :
13+
14+ class MptBox (Box ):
15+ """python-box that preserves camelCase keys when converted to json."""
16+
17+ def __init__ (self , * args , attribute_mapping : dict [str , str ] | None = None , ** _ ): # type: ignore[no-untyped-def]
18+ attribute_mapping = attribute_mapping or {}
19+ self ._attribute_mapping = attribute_mapping
20+ super ().__init__ (
21+ * args ,
22+ camel_killer_box = False ,
23+ default_box = False ,
24+ default_box_create_on_get = False ,
25+ )
26+
27+ @override
28+ def __setitem__ (self , key , value ): # type: ignore[no-untyped-def]
29+ mapped_key = self ._prep_key (key )
30+ super ().__setitem__ (mapped_key , value ) # type: ignore[no-untyped-call]
31+
32+ @override
33+ def __setattr__ (self , item : str , value : Any ) -> None :
34+ if item in _box_safe_attributes :
35+ return object .__setattr__ (self , item , value )
36+
37+ super ().__setattr__ (item , value ) # type: ignore[no-untyped-call]
38+ return None
39+
40+ @override
41+ def __getattr__ (self , item : str ) -> Any :
42+ if item in _box_safe_attributes :
43+ return object .__getattribute__ (self , item )
44+ return super ().__getattr__ (item ) # type: ignore[no-untyped-call]
45+
46+ @override
47+ def to_dict (self ) -> dict [str , Any ]: # noqa: WPS210
48+ reverse_mapping = {
49+ mapped_key : original_key for original_key , mapped_key in self ._attribute_mapping .items ()
50+ }
51+ out_dict = {}
52+ for parsed_key , item_value in super ().to_dict ().items ():
53+ original_key = reverse_mapping [parsed_key ]
54+ out_dict [original_key ] = item_value
55+ return out_dict
56+
57+ def _prep_key (self , key : str ) -> str :
58+ try :
59+ return self ._attribute_mapping [key ]
60+ except KeyError :
61+ self ._attribute_mapping [key ] = _camel_killer (key )
62+ return self ._attribute_mapping [key ]
63+
64+
65+ class Model : # noqa: WPS214
1266 """Provides a resource to interact with api data using fluent interfaces."""
1367
1468 _data_key : ClassVar [str | None ] = None
15- _safe_attributes : ClassVar [list [str ]] = ["meta" , "_resource_data" ]
69+ _safe_attributes : ClassVar [list [str ]] = ["meta" , "_box" ]
70+ _attribute_mapping : ClassVar [dict [str , str ]] = {}
1671
1772 def __init__ (self , resource_data : ResourceData | None = None , meta : Meta | None = None ) -> None :
1873 self .meta = meta
19- self ._resource_data = Box (resource_data or {}, camel_killer_box = True , default_box = False )
74+ self ._box = MptBox (
75+ resource_data or {},
76+ attribute_mapping = self ._attribute_mapping ,
77+ )
78+
79+ @override
80+ def __repr__ (self ) -> str :
81+ class_name = self .__class__ .__name__
82+ return f"<{ class_name } { self .id } >"
2083
2184 @classmethod
2285 def new (cls , resource_data : ResourceData | None = None , meta : Meta | None = None ) -> Self :
@@ -25,15 +88,15 @@ def new(cls, resource_data: ResourceData | None = None, meta: Meta | None = None
2588
2689 def __getattr__ (self , attribute : str ) -> Box | Any :
2790 """Returns the resource data."""
28- return self ._resource_data .__getattr__ (attribute ) # type: ignore[no-untyped-call]
91+ return self ._box .__getattr__ (attribute )
2992
3093 @override
3194 def __setattr__ (self , attribute : str , attribute_value : Any ) -> None :
3295 if attribute in self ._safe_attributes :
3396 object .__setattr__ (self , attribute , attribute_value )
3497 return
3598
36- self ._resource_data .__setattr__ (attribute , attribute_value ) # type: ignore[no-untyped-call]
99+ self ._box .__setattr__ (attribute , attribute_value )
37100
38101 @classmethod
39102 def from_response (cls , response : Response ) -> Self :
@@ -55,8 +118,8 @@ def from_response(cls, response: Response) -> Self:
55118 @property
56119 def id (self ) -> str :
57120 """Returns the resource ID."""
58- return str (self ._resource_data .get ("id" , "" )) # type: ignore[no-untyped-call]
121+ return str (self ._box .get ("id" , "" )) # type: ignore[no-untyped-call]
59122
60123 def to_dict (self ) -> dict [str , Any ]:
61124 """Returns the resource as a dictionary."""
62- return self ._resource_data .to_dict ()
125+ return self ._box .to_dict ()
0 commit comments