11# -*- coding: utf-8 -*-
2- """
3- .. module:: simple_model.v2
4- :platform: Unix
5- :synopsis: Simple decorator based models for easy data (de-)serialization and validation.
6-
7- .. moduleauthor:: Aljosha Friemann a.friemann@automate.wtf
8-
9- """
102
113import copy
124
179class ModelError (RuntimeError ):
1810 def __str__ (self ):
1911 def format_arg (arg ):
20- return '- attribute: {}\n value: "{}"\n exception: {}' .format (* arg ).strip ()
12+ return '- attribute: {}\n value: "{}"\n exception: {}' .format (
13+ arg [0 ].name if arg [0 ] else None , arg [1 ], arg [2 ]
14+ ).strip ()
2115
2216 return '{name}\n {errors}' .format (
2317 name = self .args [0 ],
2418 errors = '\n ' .join (
25- format_arg (arg ) for arg in self .args [1 ]
19+ format_arg (arg . args ) for arg in self .args [1 ]
2620 )
2721 )
2822
2923
30- class Model :
31- def __init__ (self , mutable = False , hide_unset = False , drop_unknown = False , ignore_unknown = True ):
24+ class Model ( object ) :
25+ def __init__ (self , mutable = True , hide_unset = False , drop_unknown = False , ignore_unknown = True ):
3226 self .mutable = mutable
3327 self .hide_unset = hide_unset
3428 self .drop_unknown = drop_unknown
3529 self .ignore_unknown = ignore_unknown
3630
3731 def __call__ (self , model ):
38- custom_init = getattr (model , '__init__' , None )
32+ old_init = getattr (model , '__init__' , None )
3933
40- def __init__ (cls , * args , ** kwargs ):
41- # make this instance memory independent from it's class.
42- cls .__attributes__ = copy .deepcopy (cls .__attributes__ )
43-
44- errors = []
34+ def new_init (cls , * args , ** kwargs ):
35+ exceptions = []
4536
4637 for attribute in cls .__attributes__ :
47- if attribute .is_set ( ):
48- # skip attributes that have already been set.
49- continue
38+ if hasattr ( cls , attribute .name ):
39+ if getattr ( cls , attribute . name ) is not Unset :
40+ continue
5041
51- value = kwargs .pop (attribute .name , kwargs .pop (
52- attribute .alias ,
53- Unset ))
42+ value = kwargs .pop (attribute .name ,
43+ kwargs .pop (attribute .alias , Unset )) # noqa: E128
5444
5545 try :
56- attribute .set ( value = value )
46+ attribute .fset ( cls , value )
5747 except (AttributeError , ValueError ) as e :
58- errors . append (( attribute , value , e ) )
59-
60- mutable = attribute . mutable or ( self . mutable and attribute . mutable is None )
48+ exception = AttributeError ( attribute , value , e )
49+ exception . __cause__ = None
50+ exceptions . append ( exception )
6151
62- prop = property (
63- fget = attribute .get ,
64- fset = attribute .set if mutable else None ,
65- fdel = attribute .unset if mutable else None ,
66- doc = attribute .help
67- )
68-
69- setattr (model , attribute .name , prop )
52+ if not self .mutable :
53+ setattr (model , attribute .name ,
54+ property (fget = getattr (model , attribute .name ).fget )
55+ )
7056
7157 if kwargs :
7258 if self .drop_unknown :
7359 kwargs = {}
7460 elif not self .ignore_unknown :
75- errors .extend (
76- (None , v , AttributeError ( 'Unknown attribute "%s"' % k )) for k , v in kwargs .items ()
61+ exceptions .extend (
62+ (AttributeError ( None , v , 'Unknown attribute "%s"' % k ) for k , v in kwargs .items () )
7763 )
7864
79- if errors :
80- raise ModelError (cls .__class__ .__name__ , errors )
65+ if exceptions :
66+ raise ModelError (cls .__class__ .__name__ , exceptions )
8167
82- if custom_init :
83- custom_init (cls , * args , ** kwargs )
68+ if old_init :
69+ old_init (cls , * args , ** kwargs )
8470
85- if custom_init is not None :
86- __init__ .__doc__ = custom_init .__doc__
71+ if old_init :
72+ new_init .__doc__ = old_init .__doc__
8773
88- def __getitem__ (cls , key ):
89- for attribute in cls .__attributes__ :
90- if attribute .name == key or attribute .alias == key :
74+ def getitem (cls , key ):
75+ for a in cls .__attributes__ :
76+ if a .name == key or a .alias == key :
9177 try :
92- return dict (attribute . get ( ))
78+ return dict (getattr ( cls , a . name ))
9379 except (ValueError , TypeError ):
94- return attribute . get ( )
80+ return getattr ( cls , a . name )
9581
9682 raise KeyError (key )
9783
98- model .__init__ = __init__
99- model .__getitem__ = __getitem__
84+ model .__init__ = new_init
85+ model .__getitem__ = getitem
86+
10087 model .__str__ = lambda cls : str (dict (cls ))
10188 model .__repr__ = lambda cls : str (dict (cls ))
10289 model .__ne__ = lambda cls , o : not cls .__eq__ (o )
103- model .__eq__ = lambda cls , o : (isinstance (o , cls .__class__ ) and dict (cls ) == dict (o ))
104- model .__contains__ = lambda cls , key : next ((a for a in cls .__attributes__ if a .name == key and (a .is_set () or not self .hide_unset )), Unset ) is not Unset
105- model .keys = lambda cls : sorted ([ a .alias or a .name for a in cls .__attributes__ if (a .is_set () or not self .hide_unset )])
90+ model .__eq__ = lambda cls , o : (issubclass (o .__class__ , cls .__class__ ) and dict (cls ) == dict (o ))
91+ model .__contains__ = lambda cls , key : getattr (cls , key ) not in [None , Unset ]
92+ model .keys = lambda cls : sorted (
93+ [a .alias or a .name for a in cls .__attributes__ if (
94+ getattr (cls , a .name ) not in [None , Unset ] or not self .hide_unset )]
95+ )
10696
10797 return model
10898
10999
110- class Attribute :
111- def __init__ (self , name , type , optional = False , nullable = False , default = None , fdefault = None , mutable = None , alias = None , help = None ):
100+ class Attribute ( object ) :
101+ def __init__ (self , name , type , optional = False , nullable = False , mutable = True , default = None , fdefault = None , alias = None , help = None , value_by_reference = False ):
112102 self .name = name
113103 self .type = type
114104 self .default = default
@@ -118,14 +108,10 @@ def __init__(self, name, type, optional=False, nullable=False, default=None, fde
118108 self .mutable = mutable
119109 self .alias = alias
120110 self .help = help
111+ self .value_by_reference = value_by_reference
121112
122- try :
123- if self .default is not None :
124- self .parse (self .default )
125- elif self .fdefault is not None :
126- self .parse (self .fdefault ())
127- except Exception as e :
128- raise ValueError ("Invalid default value(s) (%s/%s) for type %s" % (self .default , self .fdefault , self .type ), e )
113+ def __repr__ (self ):
114+ return str (vars (self ))
129115
130116 def __call__ (self , model ):
131117 if not hasattr (model , '__attributes__' ):
@@ -138,48 +124,52 @@ def __call__(self, model):
138124 finally :
139125 model .__attributes__ .add (self )
140126
127+ setattr (model , self .value_name , Unset )
128+
129+ prop = property (
130+ fget = self .fget ,
131+ fset = self .fset if self .mutable else None ,
132+ fdel = self .fdel if self .mutable else None ,
133+ doc = self .help
134+ )
135+
136+ setattr (model , self .name , prop )
137+
141138 return model
142139
143- def __repr__ (self ):
144- return str ( vars ( self ) )
140+ def fget (self , cls ):
141+ return getattr ( cls , self . value_name )
145142
146- def set (self , model = None , value = Unset ):
143+ def fset (self , cls , value ):
147144 if value is Unset :
148- try :
149- value = self .get_default ()
150- except AttributeError as e :
151- pass
145+ if self . default is not None :
146+ value = self .default
147+ elif self . fdefault is not None :
148+ value = self . fdefault ()
152149
153- try :
154- self .value = self .parse (value )
155- except NotImplementedError as e :
156- raise e
157- except Exception as e :
158- raise ValueError ("Invalid value for Attribute: %s" % value , e )
150+ setattr (cls , self .value_name , self .parse (value ))
159151
160- def get (self , model = None ):
161- try :
162- return self .value
163- except AttributeError as e :
164- if not self .optional :
165- raise e
152+ def fdel (self , cls ):
153+ setattr (cls , self .value_name , Unset )
166154
167- def unset (self , model = None ):
168- del self .value
155+ @property
156+ def value_name (self ):
157+ return '_%s' % self .name
169158
170159 def parse (self , value ):
171160 if value is Unset :
172161 if self .optional :
173162 return value
174163 else :
175- raise ValueError ( "Non-optional attribute set with non-value. " )
164+ raise AttributeError ( self , value , "Attribute is not optional " )
176165 elif value is None :
177166 if self .nullable :
178167 return None
179168 else :
180169 value = self .get_default ()
181170
182- value = copy .deepcopy (value )
171+ if not self .value_by_reference :
172+ value = copy .deepcopy (value )
183173
184174 if self .type is None :
185175 return value
@@ -189,18 +179,4 @@ def parse(self, value):
189179 except TypeError :
190180 return self .type (value )
191181
192- def is_set (self ):
193- try :
194- return self .value is not Unset
195- except AttributeError :
196- return False
197-
198- def get_default (self ):
199- if self .default is not None :
200- return self .default
201- elif self .fdefault is not None :
202- return self .fdefault ()
203- else :
204- raise AttributeError ("Attribute has no default" )
205-
206182# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 fenc=utf-8
0 commit comments