Masking property in Datastore - python

I have a field in my collection that I would like to mask for responses. Here is a code example of what I need to achieve:
class Entity(ndb.Model):
name = ndb.StringField()
#property
def name(self):
return self.name [:2] + "***"
Expected result: name in database: John, name returned with API: Jo***
When I'm trying with code above getting
TypeError: Cannot set non-property name when trying to create an Entity
Is there any smarter way to do it than masking name on every response?
Is it possible to modify values in _pre_get_hook() just for a response without changing a field in the database?

I think what you need is _post_get_hook, something along the lines of
def _post_get_hook(self,key,future):
self.name = self.name[:2]+"***"
The fact that the name is modified is worrisome if a put is done on the same entity. I am not sure how your response is being constructed. If there is a way to filter some of the fields, then you can have a separate property that is derived based on the name in the _post_get_hook which is safer.

Related

How to handle possible race-condition in overwritten Document.save method - Mongoengine

I am using flask-mongoengine and think I am running in some kind of race conditions while trying to overwrite the Document.save method.
My models (simplified) look like this:
class User(Document):
meta = {"collection": "users"}
name = StringField()
class Group(Document):
meta = {"collection": "groups"}
name = StringField()
class History(EmbeddedDocument):
key = StringField()
oldValue = StringField()
newValue = StringField()
class Asset(DynamicDocument):
meta = {"collection": "assets"}
c_id = SequenceField()
name = StringField()
history = ListField(EmbeddedDocumentField(History))
user = ReferenceField('User')
group = ReferenceField('Group', required=True, default=Group.objects.first())
def save(self, **kwargs):
for key, value in self._data.items():
history_update = History(
key=key,
oldValue="",
newValue=str(value)
)
self.history.append(history_update)
return super(Asset, self).save(**kwargs)
What I am trying to achieve is:
When a new Document of type Asset is created, add an entry of type History for each Key/Value pair of the document that changed. (Here from None to some value, I have similar code in the update method for changes on existing assets). This history list should be something like a changelog of the particular asset through its lifetime.
My problem with the current implementation is that:
c_id of type SequenceField is None in my for-loop.
str(value) for the User object gives me the correct user-object (or the result of my custom __str__ method) but str(value) for the Group object gives me DBRef('groups', '<mongoidstring>') and does not trigger my customer str method
When debugging with a breakpoint beforehand, these two errors do not occur. c_id has its correct value and my group object is a group object and not a DBRef object
I've tried saving the Document once before and then adding my history which at least gives me a correct c_id but the group is still a DBRef.
I do think the SequenceField is populated in parallel and therefore still None when I try to access it but not when I come through the debugger. But the DBRef still gives me headaches. And that I don't really see a way to properly implement my ChangeHistory through overwriting the save method. Any ideas how to properly handle this?
So I find an answer myself (somewhat).
SequenceFields are only populated during a save(). When overwritting the save method we first have to make a super.save to get the SequenceField value or we have to assume its value by the helper collection that is created by mongoengine. I took the easy route and just added an super(Asset, self).save() and the c_id is set corectly for the changes afterwards.
ReferenceFields are avalaible as DBRef until you first access it from the Document object.
Just add some kind of check beforehand to ensure its value is correctly resolved like:
assert self.group is not None
assert self.user is not None

Get properties of ndb model instance where Python name != datastore name

Goal: get "Python names" of attributes from an instance of Example, where the model was defined with a different datastore name
To provide some context, I have a custom to_dict() method for serializing an NDB Model. The core of the method is as follows:
for key, prop in self._properties.iteritems():
if hasattr(self, key):
value = getattr(self,key)
# do typical to_dict() stuff
If a model is defined as follows, everything is fine:
import Thing
class Example(ndb.Model):
things = ndb.KeyProperty(Thing, repeated=True)
However, there are issues if it defined where the the Python name is things but the datastore name is 'Thing':
# no import req'd
class Example(ndb.Model):
things = ndb.KeyProperty('Thing', repeated=True)
In the first scenario, the key from self._properties.iteritems() would be things. If I have an instance of Example, say example, then hasattr(example,'things') would evaluate to True.
In the second scenario, the key would be Thing and hasattr(example,'Thing') would evaluate to False, since the instance of Example has attributes defined by the Python name 'things'.
How can I get the properties of the instance? TIA.
ndb's own Model._to_dict method does it as follows (simplifying):
for prop in self._properties.itervalues():
name = prop._code_name
values[name] = prop._get_for_dict(self)
So: the name is taken from the _code_name of each property (not its key in self._properties, and the value is delegated to the property itself (via its _get_for_dict method) to allow further tweaking.
As a result, coding both of your examples as Example1 and Example2, whole their _properties.items() are respectively:
[('things', KeyProperty('things', repeated=True, kind='Thing'))]
[('Thing', KeyProperty('Thing', repeated=True))]
their ._to_dict(), as desired, both equal
{'things': [Key('Thing', 'roro')]}

How to check the existance of single Entity? Google App Engine, Python

Sorry for noobster question again.
But I'm trying to do some very easy stuff here, and I don't know how. Documentation gives me hints which do not work, or apply.
I recieve a POST request and grab a variable out of it. It says "name".
I have to search all over my entities Object (for example) and find out if there's one that has the same name. Is there's none, I must create a new Entity with this name. Easy it may look, but I keep Failing.
Would really appreciate any help.
My code currently is this one:
objects_qry = Object.query(Object.name == data["name"])
if (not objects_qry ):
obj = Object()
obj .name = data["name"]
obj .put()
class Object(ndb.Model):
name = ndb.StringProperty()
Using a query to perform this operation is really inefficient.
In addition your code is possibly unreliable, if name doesn't exist and you have two requests at the same time for name you could end up with two records. And you can't tell because your query only returns the first entity with the name property equal to some value.
Because you expect only one entity for name a query is expensive and inefficient.
So you have two choices you can use get_or_insert or just do a get, and if you have now value create a new entity.
Any way here is a couple of code samples using the name as part of the key.
name = data['name']
entity = Object.get_or_insert(name)
or
entity = Object.get_by_id(name)
if not entity:
entity = Object(id=name)
entity.put()
Calling .query just creates a query object, it doesn't execute it, so trying to evaluate is as a boolean is wrong. Query object have methods, fetch and get that, respectively, return a list of matching entities, or just one entity.
So your code could be re-written:
objects_qry = Object.query(Object.name == data["name"])
existing_object = objects_qry.get()
if not existing_object:
obj = Object()
obj.name = data["name"]
obj.put()
That said, Tim's point in the comments about using the ID instead of a property makes sense if you really care about names being unique - the code above wouldn't stop two simultaneous requests from creating entities with the same name.

Filter by an object in SQLAlchemy

I have a declared model where the table stores a "raw" path identifier of an object. I then have a #hybrid_property which allows directly getting and setting the object which is identified by this field (which is not another declarative model). Is there a way to query directly on this high level?
I can do this:
session.query(Member).filter_by(program_raw=my_program.raw)
I want to be able to do this:
session.query(Member).filter_by(program=my_program)
where my_program.raw == "path/to/a/program"
Member has a field program_raw and a property program which gets the correct Program instance and sets the appropriate program_raw value. Program has a simple raw field which identifies it uniquely. I can provide more code if necessary.
The problem is that currently, SQLAlchemy simply tries to pass the program instance as a parameter to the query, instead of its raw value. This results in a Error binding parameter 0 - probably unsupported type. error.
Either, SQLAlchemy needs to know that when comparing the program, it must use Member.program_raw and match that against the raw property of the parameter. Getting it to use Member.program_raw is done simply using #program.expression but I can't figure out how to translate the Program parameter correctly (using a Comparator?), and/or
SQLAlchemy should know that when I filter by a Program instance, it should use the raw attribute.
My use-case is perhaps a bit abstract, but imagine I stored a serialized RGB value in the database and had a property with a Color class on the model. I want to filter by the Color class, and not have to deal with RGB values in my filters. The color class has no problems telling me its RGB value.
Figured it out by reading the source for relationship. The trick is to use a custom Comparator for the property, which knows how to compare two things. In my case it's as simple as:
from sqlalchemy.ext.hybrid import Comparator, hybrid_property
class ProgramComparator(Comparator):
def __eq__(self, other):
# Should check for case of `other is None`
return self.__clause_element__() == other.raw
class Member(Base):
# ...
program_raw = Column(String(80), index=True)
#hybrid_property
def program(self):
return Program(self.program_raw)
#program.comparator
def program(cls):
# program_raw becomes __clause_element__ in the Comparator.
return ProgramComparator(cls.program_raw)
#program.setter
def program(self, value):
self.program_raw = value.raw
Note: In my case, Program('abc') == Program('abc') (I've overridden __new__), so I can just return a "new" Program all the time. For other cases, the instance should probably be lazily created and stored in the Member instance.

Property %s is required

I am write one easy program using GAE and python 2.7, but I met some problem while stored data into db. My code is below:
class MemberInfo(db.Model):
firstName = db.StringProperty(required=True)
class RegisterPageButtonDown(webapp2.RequestHandler):
def post(self):
memberInfo = MemberInfo()
memberInfo.firstName = self.request.get('firstName')
memberInfo.put()
The error raise in "memberInfo = MemberInfo()", it said "Property firstName is required". I am sure I put data in html form and the method is post, too.
I've been stuck in this problem for whole night, thanks for your reply.
You've set the firstName property to required, so when you instantiate an object you must provide that property with a value, e.g.:
memberInfo = MemberInfo(firstName = self.request.get('firstName'))
Alternatively, you can make firstName not required in your model.
The error is coming from the first line of the function, before you even get the value from the request. This is because you need to pass in that value when you instantiate the object.
firstName = self.request.get('firstName')
memberInfo = MemberInfo(firstName=firstName)
(Also note that normal naming conventions for Python mean that variables and properties are lower_case_with_underscore, not camelCase.)

Categories