My question does not really have much to do with sqlalchemy but rather with pure python.
I’d like to control the instantiation of sqlalchemy Model instances. This is a snippet from my code:
class Tag(db.Model):
__tablename__ = 'tags'
query_class = TagQuery
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(), unique=True, nullable=False)
def __init__(self, name):
self.name = name
I want to achieve that whenever an entry is instantiated (Tag('django')) that a new instance should be created only if there is not yet another tag with the name django inside the database. Otherwise, instead of initializing a new object, a reference to the already existent row inside the database should be returned by (Tag('django')).
As of now I am ensuring the uniqueness of tags inside the Post Model:
class Post(db.Model):
# ...
# code code code
# ...
def _set_tags(self, taglist):
"""Associate tags with this entry. The taglist is expected to be already
normalized without duplicates."""
# Remove all previous tags
self._tags = []
for tag_name in taglist:
exists = Tag.query.filter(Tag.name==tag_name).first()
# Only add tags to the database that don't exist yet
# TODO: Put this in the init method of Tag (if possible)
if not exists:
self._tags.append(Tag(tag_name))
else:
self._tags.append(exists)
It does its job but still I’d like to know how to ensure the uniqueness of tags inside the Tag class itself so that I could write the _set_tags method like this:
def _set_tags(self, taglist):
# Remove all previous tags
self._tags = []
for tag_name in taglist:
self._tags.append(Tag(tag_name))
While writing this question and testing I learned that I need to use the __new__ method. This is what I’ve come up with (it even passes the unit tests and I didn’t forget to change the _set_tags method):
class Tag(db.Model):
__tablename__ = 'tags'
query_class = TagQuery
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(), unique=True, nullable=False)
def __new__(cls, *args, **kwargs):
"""Only add tags to the database that don't exist yet. If tag already
exists return a reference to the tag otherwise a new instance"""
exists = Tag.query.filter(Tag.name==args[0]).first() if args else None
if exists:
return exists
else:
return super(Tag, cls).__new__(cls, *args, **kwargs)
What bothers me are two things:
First: I get a warning:
DeprecationWarning: object.__new__() takes no parameters
Second: When I write it like so I get errors (I also tried to rename the paramater name to n but it did not change anything) :
def __new__(cls, name):
"""Only add tags to the database that don't exist yet. If tag already
exists return a reference to the tag otherwise a new instance"""
exists = Tag.query.filter(Tag.name==name).first()
if exists:
return exists
else:
return super(Tag, cls).__new__(cls, name)
Errors (or similar):
TypeError: __new__() takes exactly 2 arguments (1 given)
I hope you can help me!
I use class method for that.
And then
As for
__new__, you should not confuse it with__init__. It is expected to be called w/out args, so even if your own constructor asks for some, you should not pass them to super/object unless you know that your super needs them. Typical invocation would be:However this will not work as expected in your case, since it calls
__init__automatically if__new__returns instance ofcls. You would need to use metaclass or add some checks in__init__.