Add dynamic property with class in Python metaclass - python

I use mongoengine with django rest framework. My model:
import mongoengine as mongo
class Plan(mongo.Document):
slug = mongo.StringField(max_length=255, primary_key=True)
subplans = mongo.ListField(mongo.EmbeddedDocumentField('self'))
I'm need serializer that be looks like this:
class PlanSerializer(serializers.DocumentSerializer):
subplans = PlanSerializer(many=True, required=False)
class Meta:
model = Plan
But that incorrect for Python. So I use metaclass for adding subplans field dynamicly:
class AddSubplanAttrMetaclass(type):
def __new__(cls, name, bases, dct):
# this code is incorrect because PlanSerializer not in globals
class_obj = globals()[name]
dct['subplans'] = class_obj(many=True, required=False)
return super(AddSubplanAttrMetaclass, cls).__new__(cls, name, bases, dct)
class PlanSerializer(serializers.DocumentSerializer, metaclass=AddSubplanAttrMetaclass):
class Meta:
model = Plan
How I can set PlanSerializer class to property inside __new__ method of metaclass?

The problem you have there is that when you try to either use the line
subplans = PlanSerializer(many=True, required=False) and when trying with the metaclass, the line class_obj = globals()[name] when your PlanSerializerclass itself was not defined yet. (Check my answer at How is super() in Python 3 implemented?)
The correct way to do that in the metaclass would be to call the superclass's new first - that returns you the actual class object, and then call that object - something along:
class AddSubplanAttrMetaclass(type):
def __new__(metacls, name, bases, dct):
# this code is incorrect because PlanSerializer not in globals
class_obj = super(AddSubplanAttrMetaclass, cls).__new__(metacls, name, bases, dct)
class_obj.subplans = class_obj(many=True, required=False)
return class_obj
But that is both not needed, and might still have issues - as not all the class initialization is completed while you are still inside the metaclass's __new__ (or even __init__) methods. For example, if the __init__ method of PlanSerializer itself would make use of super, that call would fail - super can only be used after class has been fully initialized.
However, you don't need a metaclass at all for that - you probably can simply set the subplans attribute as a descriptor - and retrieve the attribute lazily.
class PlanSerializer(serializers.DocumentSerializer):
class Meta:
model = Plan
PlanSerializer.subplans = PlanSerializer(many=True, required=False)
I said probably because this won't work if Mongo needs the attribute to be set when initializing the class itself - if that is the case, you can try resorting to a descriptor object. A descriptor is simply an object that implements the __get__ method, like below. That is usually done with the #property decorator, but that would not work for class level attributes, which you need for this case.
class PlanSerializer(serializers.DocumentSerializer):
class Subplans(object):
serializer = None
def __get__(self, instance, owner):
if not self.serializer:
self.serializer = PlanSerializer(many=True, required=False)
return self.serializer
subplans = Subplans()
class Meta:
model = Plan
In that way the usage of the call to the Subplans class is delayed to when it is actually used, instead of the time of parsing the class body, and it should work.

Related

How to delete constant in subclass?

Let's say I have a class something like the following:
class PostSerializer(serializers.HyperlinkedModelSerializer):
updated_at = serializers.DateTimeField()
def __init__(self, *args, **kwargs):
init = super().__init__(*args, **kwargs)
return init
I want to create a subclass of the PostSerializer class and I'd like to remove the updated_at constant property from the subclass-ed class.
class PostWithoutUpdatedAtSerializer(serializers.HyperlinkedModelSerializer):
# something to remove the updated_at property ?
def somefunc(self);
pass
I use a framework for example django so generally I cannot simply remove the property from the parent class, I need to subclass them. And of course obviously I need to "delete" the property, I cannot do updated_at = None, it's not a deleting.
How is it possible? Thanks.
It's not directly possible, since the attribute doesn't exist on your derived class at all (it does on the superclass), so there's nothing to remove or reassign.
Instead, the framework you're using (Django REST Framework, my magic ball tells me), uses a metaclass that inspects the class definition for field objects and puts them into cls._declared_fields on the class (along with any fields from the superclass(es)).
The real fields for your serializer instance are acquired by get_fields(), which by default just copies _declared_fields.
In other words, if your Django REST Framework serializer subclass should not serialize that field, customize get_fields():
def get_fields(self):
fields = super().get_fields()
fields.pop("updated_at", None) # remove field if it's there
return fields

flask_sqlalchemy: why instance created by Model.query.filter_by().first() doesn't run __init__() in model

I have a User model like this:
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key = True)
username = db.Column(db.String(64), unique=True, index=True
# ...
def __init__(self, **kwargs):
pass
When the instance is created like:user = User(username='john'), the constructor is called successfully.
But if the instance is returned by user = User.query.filter_by(username=john).first(), the constructor won't be called anyway.
And I have checked that the instance returned by the query is an instance of User, so the constructor should be called when the query is done. Because to my understanding, the constructor is called every time when an instance is created.
The question is: why this happens and what to do to call __init__ when the user is returned by the query.
I just ran accross this same problem (constructor not being called), and reading through SQLAlchemy documentation, that's exactly how it's supposed to behave. When an instance is being created from a Query object, it doesn't call the __init__ method. Instead, it calls methods marked as reconstructor (decorator).
To mark a method as reconstructor, declare a parameterless method and decorate it with the method from your SQLAlchemy instance:
class User(UserMixin, db.Model):
#db.reconstructor
def reload(self):
# Will be run when loading from a Query object
From the documentation:
Any method may be tagged as the reconstructor(), even the __init__ method. SQLAlchemy will call the reconstructor method with no arguments.
So, if your class has initialization code, or if you call super(), you can call your reconstructor from your __init__:
class User(UserMixin, db.Model):
def __init__(self, **kwargs):
super(User, self).__init__(**kwargs)
self.reload()
#db.reconstructor
def reload(self):
# Process data from model
That way, reload() will be called upon custom initialization and when queried.

Class reference inside class scope in python

Some Context
I have the following django/python snippet:
from rest_framework import serializers
from .models import Profile, Task
class Serializable():
types = {}
def __init__(self, objectid):
self.object = self.types[objectid][0]
self.serializer = self.types[objectid][1]
def serialized(self):
instances = self.object.objects.all()
serialized = self.serializer(instances, many=True)
return serialized
class ProfileSerializer(serializers.ModelSerializer):
class Meta:
oid = 'profile'
model = Profile
fields = ['login', 'status']
Serializable.types[oid] = [model, <class-reference>]
class TaskSerializer(serializers.ModelSerializer):
class Meta:
oid = 'task'
model = Task
fields = ['description', 'date', 'owner']
Serializable.types[oid] = [model, <class-reference>]
I am using Django with the rest_framework library installed. One of the interesting features I am using is ModelSerializers (ModelSerializers Documentation), which save quite a lot of code repetition. I want Serializable.types variable to be populated on runtime (when all the serializer classes are declared). The whole point of this is that I will not have to update my views whens a new type of model is included. For example, I would print the json representation of my model instances like this:
class QueryObject(APIView):
permission_classes = (AllowAny,)
def get(self, request, *args, **kwargs):
oid = request.GET['oid']
serializable= Serializable(oid)
json = serializable.serialized
return JsonResponse(json)
The Problem
The major problem is in the last line of each Serializer class.
Serializable.types[oid] = [model, <class-reference>]
I've tried putting the name of the class, ProfileSerializer for example, to no avail. I've tried doing the same outside of the Meta class, such as:
class ProfileSerializer(serializers.ModelSerializer):
class Meta:
oid = 'profile'
model = Profile
fields = ['login', 'status']
Serializable.types[Meta.oid] = [Meta.model, ProfileSerializer]
also not successful. Not sure what else to do, which is why I'm hoping the community can help me on this one.
This is actually a case for defining a metaclass.
I've never actually found a source of information which gives a complete, clear and satisfactory explanation as to what metaclasses are or how they work. I will try to enhance this answer with such information if required but for the time being I am going to stick to a solution for your present problem. I am assuming python 3.
Define an additional class, thus:
class ModelSerializerMeta(serializers.SerializerMetaclass):
def __init__(cls, class_name, base_classes, attributes):
super(ModelSerialiserMeta, cls).__init__(class_name, base_classes, attributes)
Serializer.types[cls.Meta.oid] = [cls.Meta.model, cls]
Then use this as the metaclass of your Serializers, e.g.
class ProfileSerializer(serializers.ModelSerializer, metaclass=ModelSerializerMeta):
class Meta:
oid = 'profile'
model = Profile
fields = ['login', 'status']
Better yet, create some superclass for all your model serializers, assign the metaclass there, make all of your serializers inherit from that superclass which will then use the metaclass throughout.
Metaclasses are definitely the right answer unless your code can require python >= 3.6. Starting with 3.6 there is a new feature called the __init_subclass__ hook.
So you can do something like
class foo:
#classmethod
def __init_subclass__(cls, *args, **kwargs):
Serializers.register_class(cls)
Whenever a child of Foo is defined, the __init_subclass__ method on Foo will be called, passing in the child class reference as cls.

How to create mixin to use #property in classes

I've got the following class
class UserProfile(models.Model):
# ...
#property
def get_fields(self):
return [(field.name, field.value_to_string(self)) for field in UserProfile._meta.fields]
I want to use that property on all my classes. So I want to make a mixin.
class HelperMixin(object):
#property
def get_fields(self):
return [(field.name, field.value_to_string(self)) for field in UserProfile._meta.fields]
Trouble is, the property has class specific code in it. How do I generalize the code to work in other classes?
And what is the difference when I use it like this:
class UserProfile(models.Model, HelperMixin)
vs. like this
class UserProfile(HelperMixin, models.Model)
Use self._meta.fields, class members are available on instances as well:
class HelperMixin(object):
#property
def get_fields(self):
return [(field.name, field.value_to_string(self)) for field in self._meta.fields]
The differense between class UserProfile(models.Model, HelperMixin) and class UserProfile(HelperMixin, models.Model) lies in the MRO (method resolution order). It would seem natural to have the mixin before the base class, but as long as the base and the mixin does not have members with the same name it makes no difference.

Django Pickle Field Access to its model instance on load

I have a custom django field subclass for storing my own pickled classes. Is there any way I could set a model attribute that points to the model instance on my pickled class on each load from the database?
So far my best guess is in the unpickling process, inside the to_python method, but I'm not sure if the Field has a reference to the model instance or class.
EDIT 1: The model reference inside of the to_python method is indeed a reference to the class, not the instance
Figured it out!
I overrode the model's __init__ method like this:
class MyModel(models.Model):
def __init__(self, *args, **kwargs):
# Don't do any extra looping or anything in here because this gets called
# at least once for every row in each query of this table
self._meta.fields[2].model_instance = self
super(MyModel, self).__init__(*args, **kwargs)
field1 = models.TextField()
field2 = models.PickleField()
field3 = models.DateTimeField()
Then in my field subclass:
def to_python(self, value):
# logic and unpickling, then right before your return:
if hasattr(self, 'model_instance'): # avoid AttributeError if list, dict, etc.
value.model_instance = self.model_instance
return value

Categories