I'd like to set for the ProofModel subclasses to have the type_ field set and immutable.
class ProofType(Enum):
JWS = "RsaVerificationKey2018"
HASH = "Sha2"
class ProofModel(EmbeddedDocument):
type_ = EnumField(ProofType, required=True)
created = DateTimeField(default=datetime.now(), required=True)
meta = {"allow_inheritance": True}
class JwsProofModel(ProofModel):
type_ = EnumField(ProofType, default=ProofType.JWS)
jws = StringField(required=True)
class ShaProofModel(ProofModel):
type_ = EnumField(ProofType, default=ProofType.HASH)
hash_ = StringField(required=True)
I could leave it as above but my need is to limit the ability to create a (for example) JwsProofModel having the type_ specified when instantiated
I see 2 options:
1)
(not released yet) You can abuse the choices parameter on the EnumField constructor to limit the allowed value to just 1 value.
class JwsProofModel(ProofModel):
type_ = EnumField(ProofType, default=ProofType.JWS, choices=[ProofType.JWS])
jws = StringField(required=True)
This feature is only available on the development version (future 0.23.2) but it should get released shortly
2)
As a workaround, use the custom per-field validation feature (doc)
def is_jws(value):
if value != ProofType.JWS:
raise ValidationError("wrong value")
class JwsProofModel(Document):
type_ = EnumField(ProofType, default=ProofType.JWS, validation=is_jws)
Related
I am using pynamodb in a Python Flask project and am starting to build my model(s) to define the objects that will be used with tables.
The documentation says you define a model as follows:
from pynamodb.models import Model
from pynamodb.attributes import UnicodeAttribute
class UserModel(Model):
"""
A DynamoDB User
"""
class Meta:
table_name = "dynamodb-user"
email = UnicodeAttribute(null=True)
first_name = UnicodeAttribute(range_key=True)
last_name = UnicodeAttribute(hash_key=True)
However I already have an existing class in another module, that I've already defined, see below:
class ActivityTask:
def __init__(self,task_name, sequence_order):
self.taskid = uuid.uuid4()
self.taskcreated = datetime.datetime.now()
self.taskname = task_name
self.sequenceorder = sequence_order
Is there a way for me to somehow "port" my existing ActivityTask class object so that I can use it as a model? As it already matches the schema for the DynamoDB table in question.
Here is a class I made to automatically generate a Pynamodb Model from a Marshmellow class :
class PynamodbModel:
def __init__(self, base_object):
self.base_object = base_object
attributes = self.make_attributes(self.base_object.schema)
meta_attr = {"table_name" : self.base_object.__name__}
attributes['Meta'] = type("Meta", (), meta_attr)
self.table : Model = type("Table", (Model,), attributes)
def convert_attr(self, attr):
if type(attr) == Nested:
atttr = attr.inner.nested
def make_attributes(self, schema_obj):
attributes = {}
for name, elem in schema_obj._declared_fields.items():
if name == 'id':
attributes[name] = attibute_conversion[type(elem)](hash_key=True)
elif type(elem) == List:
if type(elem.inner) == Nested:
attributes[name] = attibute_conversion[type(elem)](of=self.make_nested_attr(elem.inner.nested))
else:
attributes[name] = attibute_conversion[type(elem)]()
elif type(elem) == Nested:
attributes[name] = self.make_nested_attr(elem.nested)
else:
attributes[name] = attibute_conversion[type(elem)]()
return attributes
def make_nested_attr(self, nested_schema):
attributes = self.make_attributes(nested_schema)
return type(nested_schema.__class__.__name__, (MapAttribute,), attributes)
Go here to see the full example !
I basically just iterate over the Marshmellow Schema attributes and assign the correspondant Pynamodb attributes. Hope it help !
I have the following object model:
class Data(Model):
__keyspace__ = 'varilog'
__table_name__ = 'md_data'
id = columns.TimeUUID(partition_key=True, primary_key=True, required=True)
device = columns.Text(primary_key=True, required=True)
property = columns.Text(primary_key=True, required=True)
field = columns.Text(primary_key=True, required=True)
cyclestamp = columns.DateTime(static=True)
type = columns.Text(discriminator_column=True)
#text_value = columns.Text() # Will work
#value = columns.Text(db_field='text_value') # Will work but...
class DataText(Data):
__discriminator_value__ = 'str'
value = columns.Text(db_field='text_value') # Always None
#text_value = columns.Text() # Ok also
When I query an object, depending on the value of the type column, the correct object is returned (DataText in this example), however it's value is None while if I uncomment text_value I'll have the correct value.
It looks like db_field is not supported in a child class. Is this a bug?
For unknown reasons, I cannot assign a foreign key instance of Item_rarity table into Detailed_item table. Django throws an error:
Cannot assign "u'Basic'": "Detailed_item.rarity" must be a "Item_rarity" instance.
... But in Item_rarity dictionary "Basic" record exists - I can choose it from admin panel and create Detailed_item record manually.
I have defined models:
class Detailed_item(models.Model):
item_id = models.IntegerField(primary_key=True)
name = models.CharField(max_length=50)
level = models.IntegerField()
icon = models.CharField(max_length=150)
rarity = models.ForeignKey('Item_rarity')
general_type = models.ForeignKey('Item_type')
detailed_type = models.ForeignKey('Item_detailed_type')
class Item_rarity(models.Model):
name = models.CharField(max_length=15, primary_key=True)
class Item_type(models.Model):
name = models.CharField(max_length=15, primary_key=True)
class Item_detailed_type(models.Model):
name = models.CharField(max_length=20, primary_key=True)
In views, I try to populate it in this manner (inserting multiple items):
...
items = get_all_items() #get dict of items
for element in items:
tmp_det_type = ''
for key, val in element.iteritems():
#get 'detailed type' from inner dict
if key == "type":
tmp_det_type = val
item = Detailed_item(
item_id=element['id'],
name=element['name'],
level=element['level'],
icon=element['icon'],
rarity=element['rarity'], #error
general_type=element['type'],
detailed_type=tmp_det_type,
)
item.save()
...
I even tried to hard code "Basic" string, but it doesn't work either.
* Solved *
Next two entries, that is Item_type and Item_detailed_type were also invalid.
Correct code:
from app.models import Detailed_item, Item_rarity, Item_type, Item_detailed_type
...
items = get_all_items() #get dict of items
for element in items:
tmp_det_type = ''
for key, val in element.iteritems():
#get 'detailed type' from inner dict
if key == "type":
tmp_det_type = val
#create objects with string values
obj_rarity = Item_rarity(name=element['rarity'])
obj_item_type = Item_type(name=element['type'])
obj_item_detailed_type = Item_detailed_type(name=tmp_det_type)
item = Detailed_item(
item_id=element['id'],
name=element['name'],
level=element['level'],
icon=element['icon'],
rarity=obj_rarity,
general_type=obj_item_type,
detailed_type=obj_item_detailed_type,
)
item.save()
...
Item_rarity instance should be passed while storing Detailed_item object since Item_rarity is a foreign key related object in Detailed_item.
Its that you might have passed the Basic string instead of the <Basic Object> itself.
While creating an object in django using its ORM, any foreign_key related object should be provided with the instance itself instead of the id(pk) of the object, where as while fetching the data from the database you can use either of instance or the id(pk) of the instance.
class ParentModel(models.Model):
model_field = models.CharField(max_length=16)
class MyModel(models.Model):
some_field = models.ForeignKey('ParentModel')
parent_model = ParentModel.objects.create(model_field='some_data')
my_model = MyModel.objects.create(some_field=parent_model)
^^^^^^^^^^^^
Note here that the parent_model object itself is passed instead of the id
While fetching the data back,
parent_model = ParentModel.objects.get(model_field='some_data')
my_model = MyModel.objects.get(some_field=parent_model)
or
my_model = MyModel.objects.get(some_field=parent_model.id)
Both would work in case of data fetch.
You do not have to provide the related object on creation if you change the kwarg in to rarity_name:
item = Detailed_item(
item_id=element['id'],
name=element['name'],
level=element['level'],
icon=element['icon'],
rarity_name=element['rarity'], # no error
general_type=element['type'],
detailed_type=tmp_det_type,
)
I have only tested this with the regular id field (the auto pk) but it
should work with your primary key just fine.
E.g.
class SimpleModel(Model):
value = TextField(blank=True)
class ComplexModel(Model):
simple = ForeingKey(SimpleModel)
title = TextField(unique=True)
ComplexModel.objects.create(title='test', simple_id=1)
I am using an inherited modelling schema for my site, it has every media element under one common PolyModel base with every different element by themselves like so:
class STSeasonMedia(polymodel.PolyModel):
season = db.ReferenceProperty(STSeason,collection_name='related_media')
description = db.StringProperty()
visible = db.BooleanProperty(default=True)
priority = db.IntegerProperty(default=10)
So I want the "Inheriting" Models to have some other fields but also different default values, for example:
class STVideo(STSeasonMedia):
video_id = db.StringProperty()
provider = db.StringProperty()
priority = db.IntegerProperty(default = 100)
class STThumb(STSeasonMedia):
picture = db.ReferenceProperty(STPicture,collection_name='thumbs')
url = db.StringProperty()
size = db.StringProperty()
class STNote(STSeasonMedia):
content = db.TextProperty()
visible = db.BooleanProperty(default=False)
priority = db.IntegerProperty(default = 1)
Is there a way to set this different default values, they may change afterwards but in the beginning must by those values. Any idea?
I think your best solution may be to provide an __init__ method to your derived models. It can provide a modified default value for certain properties if none was provided by the user.
For example, your STVideo class, which wants a different default priority should be able to use this:
def __init__(self, priority=100, **kwargs):
super(STVideo, self).__init__(priority=priority, **kwargs)
I have a very simple User class definition:
class User(Base):
implements(interfaces.IUser)
__tablename__ = 'users'
#Fields description
id = Column(Integer, primary_key=True)
client_id = Column(Integer, ForeignKey('w2_client.id'))
client = relationship("Client", backref=backref('users', order_by=id))
I want to generate automatically a GUI to edit the object User (and other type of class). So I need to get all the meta data of the table, for example, I can do:
for c in User.__table__.columns:
print c.name, c.type, c.nullable, c.primary_key, c.foreign_keys
But I can not get any information about the relationship "client", the c.foreign_keys just shows me the table related to the foreign_keys but not the attribute "client" I've defined.
Please let me know if my question is not clear
It's true that is not readily available. I had to come up with my own function after some reverse-engineering.
Here is the metadata that I use. I little different than what you are are looking for, but perhaps you can use it.
# structure returned by get_metadata function.
MetaDataTuple = collections.namedtuple("MetaDataTuple",
"coltype, colname, default, m2m, nullable, uselist, collection")
def get_metadata_iterator(class_):
for prop in class_mapper(class_).iterate_properties:
name = prop.key
if name.startswith("_") or name == "id" or name.endswith("_id"):
continue
md = _get_column_metadata(prop)
if md is None:
continue
yield md
def get_column_metadata(class_, colname):
prop = class_mapper(class_).get_property(colname)
md = _get_column_metadata(prop)
if md is None:
raise ValueError("Not a column name: %r." % (colname,))
return md
def _get_column_metadata(prop):
name = prop.key
m2m = False
default = None
nullable = None
uselist = False
collection = None
proptype = type(prop)
if proptype is ColumnProperty:
coltype = type(prop.columns[0].type).__name__
try:
default = prop.columns[0].default
except AttributeError:
default = None
else:
if default is not None:
default = default.arg(None)
nullable = prop.columns[0].nullable
elif proptype is RelationshipProperty:
coltype = RelationshipProperty.__name__
m2m = prop.secondary is not None
nullable = prop.local_side[0].nullable
uselist = prop.uselist
if prop.collection_class is not None:
collection = type(prop.collection_class()).__name__
else:
collection = "list"
else:
return None
return MetaDataTuple(coltype, str(name), default, m2m, nullable, uselist, collection)
def get_metadata(class_):
"""Returns a list of MetaDataTuple structures.
"""
return list(get_metadata_iterator(class_))
def get_metadata_map(class_):
rv = {}
for metadata in get_metadata_iterator(class_):
rv[metadata.colname] = metadata
return rv
But it doesn't have the primary key. I use a separate function for that.
mapper = class_mapper(ORMClass)
pkname = str(mapper.primary_key[0].name)
Perhaps I should put the primary key name in the metadata.