@@ -3363,6 +3363,7 @@ class StructuredProperty(Property):
33633363 """
33643364
33653365 _modelclass = None
3366+ _kwargs = None
33663367
33673368 def __init__ (self , modelclass , name = None , ** kwargs ):
33683369 super (StructuredProperty , self ).__init__ (name = name , ** kwargs )
@@ -3661,6 +3662,7 @@ class GenericProperty(Property):
36613662 """
36623663
36633664 _compressed = False
3665+ _kwargs = None
36643666
36653667 def __init__ (self , name = None , compressed = False , ** kwargs ):
36663668 if compressed : # Compressed implies unindexed.
@@ -3734,6 +3736,8 @@ class ComputedProperty(GenericProperty):
37343736 ... hash = ndb.model.ComputedProperty(_compute_hash, name='sha1')
37353737 """
37363738
3739+ _kwargs = None
3740+
37373741 def __init__ (
37383742 self , func , name = None , indexed = None , repeated = None , verbose_name = None
37393743 ):
@@ -5210,10 +5214,91 @@ def _post_put_hook(self, future):
52105214
52115215
52125216class Expando (Model ):
5213- __slots__ = ()
5217+ """Model subclass to support dynamic Property names and types.
5218+
5219+ Sometimes the set of properties is not known ahead of time. In such
5220+ cases you can use the Expando class. This is a Model subclass that
5221+ creates properties on the fly, both upon assignment and when loading
5222+ an entity from Cloud Datastore. For example::
5223+
5224+ >>> class SuperPerson(Expando):
5225+ name = StringProperty()
5226+ superpower = StringProperty()
5227+
5228+ >>> razorgirl = SuperPerson(name='Molly Millions',
5229+ superpower='bionic eyes, razorblade hands',
5230+ rasta_name='Steppin\' Razor',
5231+ alt_name='Sally Shears')
5232+ >>> elastigirl = SuperPerson(name='Helen Parr',
5233+ superpower='stretchable body')
5234+ >>> elastigirl.max_stretch = 30 # Meters
5235+
5236+ >>> print(razorgirl._properties.keys())
5237+ ['rasta_name', 'name', 'superpower', 'alt_name']
5238+ >>> print(elastigirl._properties)
5239+ {'max_stretch': GenericProperty('max_stretch'),
5240+ 'name': StringProperty('name'),
5241+ 'superpower': StringProperty('superpower')}
5242+
5243+ Note: You can inspect the properties of an expando instance using the
5244+ _properties attribute, as shown above. This property exists for plain Model instances
5245+ too; it is just not as interesting for those.
5246+ """
52145247
5215- def __init__ (self , * args , ** kwargs ):
5216- raise NotImplementedError
5248+ # Set this to False (in an Expando subclass or entity) to make
5249+ # properties default to unindexed.
5250+ _default_indexed = True
5251+
5252+ # Set this to True to write [] to Cloud Datastore instead of no property
5253+ _write_empty_list_for_dynamic_properties = None
5254+
5255+ def _set_attributes (self , kwds ):
5256+ for name , value in kwds .items ():
5257+ setattr (self , name , value )
5258+
5259+ def __getattr__ (self , name ):
5260+ prop = self ._properties .get (name )
5261+ if prop is None :
5262+ return super (Expando , self ).__getattribute__ (name )
5263+ return prop ._get_value (self )
5264+
5265+ def __setattr__ (self , name , value ):
5266+ if name .startswith ("_" ) or isinstance (
5267+ getattr (self .__class__ , name , None ), (Property , property )
5268+ ):
5269+ return super (Expando , self ).__setattr__ (name , value )
5270+ if isinstance (value , Model ):
5271+ prop = StructuredProperty (Model , name )
5272+ elif isinstance (value , dict ):
5273+ prop = StructuredProperty (Expando , name )
5274+ else :
5275+ prop = GenericProperty (
5276+ name ,
5277+ repeated = isinstance (value , (list , tuple )),
5278+ indexed = self ._default_indexed ,
5279+ write_empty_list = self ._write_empty_list_for_dynamic_properties ,
5280+ )
5281+ prop ._code_name = name
5282+ self ._properties [name ] = prop
5283+ prop ._set_value (self , value )
5284+
5285+ def __delattr__ (self , name ):
5286+ if name .startswith ("_" ) or isinstance (
5287+ getattr (self .__class__ , name , None ), (Property , property )
5288+ ):
5289+ return super (Expando , self ).__delattr__ (name )
5290+ prop = self ._properties .get (name )
5291+ if not isinstance (prop , Property ):
5292+ raise TypeError (
5293+ "Model properties must be Property instances; not %r" % prop
5294+ )
5295+ prop ._delete_value (self )
5296+ if name in super (Expando , self )._properties :
5297+ raise RuntimeError (
5298+ "Property %s still in the list of properties for the "
5299+ "base class." % name
5300+ )
5301+ del self ._properties [name ]
52175302
52185303
52195304def transactional (* args , ** kwargs ):
0 commit comments