Is it safe to override __hash__ on a peewee.Model object? - python

I recently noticed that my a bunch of peewee model objects that referred to the same data were not being recognized as equivalent, even though they contained the same data.
Is it safe to override __hash__ on these guys? It appears to work but I don't wan't this to come back and bite me unexpectedly in the future -- does hashing mess with anything like the internal state to record mapping that I should worry about?
class User(PowertailMeta):
name = CharField(unique=True)
password = CharField(null=False)
balance = FloatField(default=10.0)
cap = FloatField(default=60, constraints=[Check('cap >= 0')])
is_admin = BooleanField(default=False)
last_login = DateTimeField(default=datetime.now)
picture = CharField(default="porp")
def __hash__(self):
return hash(self.name) # since name is unique...
This passes trivial tests but I'm not sure what I might need to be looking for.

__hash__ has been recently added to peewee.Model, see https://github.com/coleifer/peewee/issues/879. It's yet unreleased, but I assume it will be in 2.8.1.
The implementation is rather simple:
def __hash__(self):
return hash((self.__class__, self._get_pk_value()))
(in https://github.com/coleifer/peewee/commit/4de894aeebf7245d4fb6c4f412c7a09a2c039d8a#diff-eb0556c6b1b9232ba053c4cea13ff075R4786)
So, it relies on the model class and your primary key. If you have other needs, I don't see any problem in overriding it. However, with your suggested solution, consider following:
class User(Model):
name = CharField(unique=True)
def __hash__(self):
return hash(self.name)
class User(Model):
label = CharField(unique=True)
def __hash__(self):
return hash(self.label)
These are two different models, even using different fields, but they would produce the same hash value.

Related

Is there a way to reference variable name as a string in that variable's declaration in Python?

For my Django application, I have a dictionary of field names (keys) mapped to help texts (values) (which I read in from a csv file). In models.py, I want to retrieve the appropriate help text for each field name.
If I have a sample dictionary like the following, with an entry for each field:
test_dict = {
'host_name': 'The name of a host',
...
}
And my models.py looks like this:
class Host_main(models.Model):
host_name = models.CharField(max_length=20, unique=True, help_text=test_dict['host_name'])
def __str__(self):
return self.host_name
Is there a way to call the variable name (host_name) dynamically in each help_text definition?
I do have the option to do something like the following:
from varname import nameof
host_name = models.CharField(max_length=20, unique=True, help_text=test_dict[nameof(host_name)])
But if possible, I'd like to reference the current variable name with something consistent to avoid typing out the field name a second time, like help_text=test_dict[nameof(**this**)] in pseudocode.
Thanks!
Let's simplify your question a little bit, and let's remove the django part first. Let's say you have a class with name A, then you can set an attribute on A with
class A: pass
A.foo = 'bar'
Alternatively, you can also do
setattr(A,'bar','baz')
You can verify that those are indeed equivalent by checking
print(A.foo) # bar
print(A.bar) # baz
So if you are defining a normal class, you can pretty much just loop through your dict and set things with setattr calls, in which case you have the control of the variable name.
And when we bring Django into the question, things are a bit more complicated...There are some very strange and magical things happening when you initialize a Model class. Basically it does a lookup on things already defined, and transforms all the defined fields through the pretty much public private _meta API. This means that adding class attributes outside of definition time won't work, and you might have to use a bit of a hack to construct a class directly through its metaclass (in case you are not familiar with Python's metaclasses, a class is basically an instance of its metaclass).
The basic syntax for dynamically constructing a class is through the type call with 3 arguments: type(name, bases, dict). In your example,
class Host_main(models.Model):
host_name = models.CharField(max_length=20, unique=True, help_text=test_dict['host_name'])
def __str__(self):
return self.host_name
is the equivalent of
def Host_main_str(self): # the name here doesn't really matter unless you care about the __name__ attribute of the function, which you can overwrite.
return self.host_name
Host_main = type('Host_main', (models.Model,), {
'__str__': Host_main_str,
'host_name': models.CharField(max_length=20, unique=True, help_text=test_dict['host_name'])
})
It's actually how django constructs all their QuerySet classes (there were some more crazy things django did with dynamic class constructions if I remember correctly but I couldn't find a good example)
So, you can do things like
attr_dict = {'__str__':'Host_main_str'}
for name, help_text in test_dict.values():
attr_dict[name] = models.CharField(max_length=20, unique=True, help_text=help_text)
Host_main = type('Host_main', (models.Model,), attr_dict)
if you really insist on loop through a dictionary.

Django model fields from constants?

How to define a Django model field in constant and use everywhere.
For example, if I have a model like:-
class Author(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=40)
email = models.EmailField()
And what I want to do is define constant for fields in Author model and provide the constant instead of field name in model like:-
KEY_FIRST_NAME = 'first_name'
KEY_LAST_NAME = 'last_name'
KEY_EMAIL = 'email'
And Author model should use the constant instead of exact key like:-
class Author(models.Model):
KEY_FIRST_NAME = models.CharField(max_length=30)
KEY_LAST_NAME = models.CharField(max_length=40)
KEY_EMAIL = models.EmailField()
How to do something like this, direct assignment to constant won't work here.
I want to store all the field name in constant, and everywhere when it required I want to use the constant instead of string field name.
The purpose of doing this is If there is any change in filed name in future version then I want to only change at one place and it should reflect on all the places.
If it is not possible or it will make code too complex as suggested by one approach by #dirkgroten than what can be the best practice to define the model field as constant and use them in other places (other than inside models like if we are referring those field for admin portal or any other place).
Short answer: you can't do this in Python, period (actually I don't think you could do so in any language but someone will certainly prove me wrong xD).
Now if we go back to your real "problem" - not having to change client code if your model's fields names are ever to change - you'd first need to tell whether you mean "the python attribute name" or "the underlying database field name".
For the second case, the database field name does not have to match the Python attribute name, Django models fields take a db_column argument to handle this case.
For the first case, I'd have to say that it's a very generic (and not new by any mean) API-design problem, and the usual answer is "you shouldn't change names that are part of your public API once it's been released". Now sh!t happens and sometimes you have to do it. The best solution here is then to use computed attributes redirecting the old name to the new one for the deprecation period and remove them once all the client code has been ported.
An example with your model, changing 'first_name' to 'firstname':
class Author(models.Model):
# assuming the database column name didn't change
# so we can also show how to us `db_column` ;)
firstname = models.CharField(
max_length=30,
db_column='first_name'
)
#property
def first_name(self):
# shoud issue a deprecation warning here
return self.firstname
#first_name.setter
def first_name(self, value):
# shoud issue a deprecation warning here
self.firstname = value
If you have a dozen fields to rename you will certainly want to write a custom descriptor (=> computed attribute) instead to keep it dry:
class Renamed(object):
def __init__(self, new_name):
self.new_name = new_name
def __get__(self, instance, cls):
if instance is None:
return self
# should issue a deprecation warning here
return getattr(instance, self.new_name)
def __set__(self, instance, value):
# should issue a deprecation warning here
setattr(instance, self.new_name, value)
class Author(models.Model):
firstname = models.CharField(
max_length=30,
db_column='first_name'
)
first_name = Renamed("firstname")
I think the following information could prove beneficial:
To achieve this you first need to think how you can define class parameters from strings. Hence, I came across a way to dynamically create derived classes from base classes: link
Particularly this answer is what I was looking for. You can dynamically create a class with the type() command.
From here on, search how to integrate that with Django. Unsurprisingly someone has tried that already - here.
In one of the answers they mention dynamic Django models. I haven't tried it, but it might be what you are searching for.

django simple history - using model methods?

I'm using django-simple-history:
http://django-simple-history.readthedocs.io/en/latest/
I have a model, which I would like to apply its methods on an historical instance. Example:
from simple_history.models import HistoricalRecords
class Person(models.Model):
firstname = models.CharField(max_length=20)
lastname = models.CharField(max_length=20)
history = HistoricalRecords()
def fullName(self):
return firstname + lastname
person = Person.objects.get(pk=1) # Person instance
for historyPerson in person.history:
historyPerson.fullName() # wont work.
Since the class HistoricalPerson does not inherit the methods of Person. But using Person methods actually make sense, since they share the same fields..
Any solution for this? I'd prefer something simple, not like duplicating every method in my models for the history instances..
I found another workaround (maybe it's just the addon had been updated and got this feature). It's based on the documentation: adding-additional-fields-to-historical-models
HistoricalRecords field accepts bases parameter which sets a class that history objects will inherit. But you can't just set bases=[Person] inside Person class description, because it's not yet initialized.
So I ended up with an abstract class, which is inherited by both Person class and HistoricalRecords field. So the example from the question would look like:
class AbstractPerson(models.Model):
class Meta:
abstract = True
firstname = models.CharField(max_length=20)
lastname = models.CharField(max_length=20)
def fullName(self):
return firstname + lastname
class Person(AbstractPerson):
history = HistoricalRecords(bases=[AbstractPerson])
And now history objects can use fullName method.
For anyone else having the same problem, I made it work by calling the method from the original class on the historical record object. So for the example in the question, a solution could be:
for historyPerson in person.history:
Person.fullName(historyPerson)
This works because methods are very much like functions in Python, except that when you call a method on an instance, the instance is implicitly passed as the first parameter for the method. So if you have a class like:
class Foo:
def method(self):
....
doing
f = Foo()
f.method()
is the same as:
f = Foo()
Foo.method(f)
I don't know exactly why simple-history does not copy the original model's methods though. One reason might be that since it allows you to exclude fields to be recorded, having the original methods might not make sense, since a method might not work if it uses fields that are not recorded in the historical record.

GAE require model property iff another has a certain value?

In GAE's db.Model properties, we have a required parameter that disallows an entity of that model from being created without a value for that property.
e.g.:
class user(db.Model):
isFromUK = db.BoolProperty(required = True)
fromCounty = db.StringProperty()
How can I do essentially required = True on fromCounty iff the isFromUK == True?
I am aware this may not be possible directly in GAE implementation (I have not found a way in docs) - but I wondered if there may be some simple way to implement this, perhaps with a #ClassMethod?
I have not had cause to use one before, so I am not sure if that would offer a solution.
This is how you would override .put() to do your special validation before continuing with the regular (ie. super-class' .put):
class user(db.Model):
...
def put(self, *args, **kw):
if self.isFromUK:
if not self.fromCountry:
raise ValueError("Need fromCountry if isFromUK..")
super(user, self).put(*args, **kwargs)

Django model class methods for predefined values

I'm working on some Django-code that has a model like this:
class Status(models.Model):
code = models.IntegerField()
text = models.CharField(maxlength=255)
There are about 10 pre-defined code/text-pairs that are stored in the database. Scattered around the codebase I see code like this:
status = Status.objects.get(code=0) # successful
status = Status.objects.get(code=1) # failed
I would rather have a method for each so that the code would look something like this instead:
status = Status.successful()
status = Status.failed()
etc...
Is this possible? I have looked in to the Manager-stuff but I haven't really found a way. Is it time to really RTFM?
In Java it would be a static method and in Ruby you would just define a method on self, but it's not that easy in Python, is it?
You should perhaps implement this by defining a custom manager for your class, and adding two manager methods on that manager (which I believe is the preferred way for adding table-level functionality for any model). However, another way of doing it is by throwing in two class methods on your class that query and return resulting objects, such as:
class Status(models.Model):
code = models.IntegerField()
text = models.CharField(maxlength=255)
#classmethod
def successful(cls):
return cls.objects.get(code=0)
#classmethod
def failed(cls):
return cls.objects.get(code=1)
Do note please that get() is likely to throw different exceptions, such as Status.DoesNotExist and MultipleObjectsReturned.
And for an example implementation of how to do the same thing using Django managers, you could do something like this:
class StatusManager(models.Manager):
def successful(self):
return self.get(code=1)
def failed(self):
return self.get(code=0)
class Status(models.Model):
code = models.IntegerField()
text = models.CharField(maxlength=255)
objects = StatusManager()
Where, you could do Status.objects.successful() and Status.objects.failed() to get what you desire.

Categories