How Generic class inheritance works in python? - python

I have the following pieces of code:
ModelType = TypeVar("ModelType", bound=Base)
CreateSchemaType = TypeVar("CreateSchemaType", bound=BaseModel)
UpdateSchemaType = TypeVar("UpdateSchemaType", bound=BaseModel)
class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]):
def __init__(self, model: ModelType):
self.model = model
class CRUDUser(CRUDBase[User, UserCreate, UserUpdate]):
pass
Although I know the purpose of this code, I'm a bit confused about how it works underneath.
CRUDBase inherits from Generic but how do you get the typevars? ie [ModelType, CreateSchemaType, UpdateSchemaType] which is in python? a generic builder?
Similarly CRUDUser inherits from CRUDBase (and in turn from Generic) and receives the same "list" [User, UserCreate, UserUpdate].

Related

Access child class variable in parent class with no instances

I have a class that other classes herit from:
class BaseManager:
model = Base
#classmethod
def get_by_id(cls, db: Session, id: int):
return db.query(cls.model).filter(cls.model.id == id).one()
class UserManager(BaseManager):
model = User
And it is used this way:
user = UserManager.get_by_id(db=db, id=user_id)
But the UserManager is ignoring the model defined in it, using the Base defined on BaseManager. How can I proceed?
I have already saw implementations such as django forms using metaclass and for example this question over here Access child class variable in parent class but I think that they don't apply to this context, i tried and did not succeed.

Changing pydantic model Field() arguments with class variables for Fastapi

I'm a little new to tinkering with class inheritance in python, particularly when it comes down to using class attributes. In this case I am using a class attribute to change an argument in pydantic's Field() function. This wouldn't be too hard to do if my class contained it's own constructor, however, my class User1 is inheriting this from pydantic's BaseModel.
The idea is that I would like to be able to change the class attribute prior to creating the instance.
Please see some example code below:
from pydantic import Basemodel, Field
class User1(BaseModel):
_set_ge = None # create class attribute
item: float = Field(..., ge=_set_ge)
# avoid overriding BaseModel's __init__
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
User1._set_ge = 0 # setting the class attribute to a new value
instance = User1(item=-1)
print(instance) # item=-1.0
When creating the instance using instance = User1(item=-1) I would expect a validation error to be thrown, but it instead passes validation and simply returns the item value.
If I had my own constructor there would be little issue in changing the _set_ge, but as User1 inheriting this constructor from BaseModel, things are a little more complicated.
The eventual aim is to add this class to a fastapi endpoint as follows:
from fastapi import Fastapi
from schemas import User1
class NewUser1(User1):
pass
NewUser1._set_ge = 0
#app.post("/")
def endpoint(request: NewUser1):
return User1.item
To reduce code duplication, I aimed to use this method to easily change Field() arguments. If there is a better way, I'd be glad to consider that too.
This question is quite closely related to this unanswered one.
In the end, the #validator proposal by #hernán-alarcón is probably the best way to do this. For example:
from pydantic import Basemodel, Field, NumberNotGeError
from typing import ClassVar
class User(BaseModel):
_set_ge = ClassVar[float] # added the ClassVar typing to make clearer, but the underscore should be sufficient
item: float = Field(...)
#validator('item')
def limits(cls, v):
limit_number = cls._set_ge
if v >= limit_number:
return v
else:
raise NumberNotGeError(limit_value=limit_number)
class User1(User)
_set_ge = 0 # setting the class attribute to a new value
instance = User1(item=-1) # raises the error

Add a dynamically generated Model to a models.py in an Django project

I am generating a Django model based on an abstract model class AbstractAttr and a normal model (let's say Foo).
I want my foo/models.py to look like this:
from bar.models import Attrs
# ...
class Foo(models.Model):
....
attrs = Attrs()
In the Attrs class which mimics a field I have a contribute_to_class that generates the required model using type(). The generated model c is called FooAttr.
Everything works. If I migrate, I see FooAttr appear in the proper table.
EXCEPT FOR ONE THING.
I want to be able to from foo.models import FooAttr. Somehow my generated FooAttr class is not bound to the models.py file in which it is generated.
If I change my models.py to this:
class Foo(models.Model):
# ...
FooAttr = generate_foo_attr_class(...)
it works, but this is not what I want (for example, this forces the dev to guess the generate class name).
Is what I want possible, define the class somewhat like in the first example AND bind it to the specific models.py module?
The project (pre-Alpha) is here (in develop branch):
https://github.com/zostera/django-mav
Some relevant code:
def create_model_attribute_class(model_class, class_name=None, related_name=None, meta=None):
"""
Generate a value class (derived from AbstractModelAttribute) for a given model class
:param model_class: The model to create a AbstractModelAttribute class for
:param class_name: The name of the AbstractModelAttribute class to generate
:param related_name: The related name
:return: A model derives from AbstractModelAttribute with an object pointing to model_class
"""
if model_class._meta.abstract:
# This can't be done, because `object = ForeignKey(model_class)` would fail.
raise TypeError("Can't create attrs for abstract class {0}".format(model_class.__name__))
# Define inner Meta class
if not meta:
meta = {}
meta['app_label'] = model_class._meta.app_label
meta['db_tablespace'] = model_class._meta.db_tablespace
meta['managed'] = model_class._meta.managed
meta['unique_together'] = list(meta.get('unique_together', [])) + [('attribute', 'object')]
meta.setdefault('db_table', '{0}_attr'.format(model_class._meta.db_table))
# The name of the class to generate
if class_name is None:
value_class_name = '{name}Attr'.format(name=model_class.__name__)
else:
value_class_name = class_name
# The related name to set
if related_name is None:
model_class_related_name = 'attrs'
else:
model_class_related_name = related_name
# Make a type for our class
value_class = type(
str(value_class_name),
(AbstractModelAttribute,),
dict(
# Set to same module as model_class
__module__=model_class.__module__,
# Add a foreign key to model_class
object=models.ForeignKey(
model_class,
related_name=model_class_related_name
),
# Add Meta class
Meta=type(
str('Meta'),
(object,),
meta
),
))
return value_class
class Attrs(object):
def contribute_to_class(self, cls, name):
# Called from django.db.models.base.ModelBase.__new__
mav_class = create_model_attribute_class(model_class=cls, related_name=name)
cls.ModelAttributeClass = mav_class
I see you create the model from within models.py, so I think you should be able to add it to the module's globals. How about this:
new_class = create_model_attribute_class(**kwargs)
globals()[new_class.__name__] = new_class
del new_class # no need to keep original around
Thanks all for thinking about this. I have updated the source code of the project at GitHub and added more tests. See https://github.com/zostera/django-mav
Since the actual generation of the models is done outside of foo/models.py (it takes place in mav/models.py, it seems Pythonically impossible to link the model to foo/models.py. Also, after rethinking this, it seems to automagically for Python (explicit is better, no magic).
So my new strategy is to use simple functions, a decorator to make it easy to add mav, and link the generated models to mac/attrs.py, so I can universally from mav.attrs import FooAttr. I also link the generated class to the Foo model as Foo._mav_class.
(In this comment, Foo is of course used as an example model that we want to add model-attribute-value to).

How to access the meta attributes of a superclass in Python?

I have some code like this for Django-Tastypie:
class SpecializedResource(ModelResource):
class Meta:
authentication = MyCustomAuthentication()
class TestResource(SpecializedResource):
class Meta:
# the following style works:
authentication = SpecializedResource.authentication
# but the following style does not:
super(TestResource, meta).authentication
I would like to know what would be the right method of accessing meta attributes of the superclass without hard-coding the name of the superclass.
In your example it seems that you are trying to override the attribute of the meta of the super class. Why not use meta inheritance?
class MyCustomAuthentication(Authentication):
pass
class SpecializedResource(ModelResource):
class Meta:
authentication = MyCustomAuthentication()
class TestResource(SpecializedResource):
class Meta(SpecializedResource.Meta):
# just inheriting from parent meta
pass
print Meta.authentication
Output:
<__main__.MyCustomAuthentication object at 0x6160d10>
so that the TestResource's meta are inheriting from parent meta (here the authentication attribute).
Finally answering the question:
If you really want to access it (for example to append stuff to a parent list and so on), you can use your example:
class TestResource(SpecializedResource):
class Meta(SpecializedResource.Meta):
authentication = SpecializedResource.Meta.authentication # works (but hardcoding)
or without hard coding the super class:
class TestResource(SpecializedResource):
class Meta(SpecializedResource.Meta):
authentication = TestResource.Meta.authentication # works (because of the inheritance)

Django model polymorphism, using proxy inheritance

This might sound like a duplicate, but I don't think it is.
I need to do something a bit similar to what the asker did there : django model polymorphism with proxy inheritance
My parent needs to implement a set of methods, let's call them MethodA(), MethodB(). These methods will never be used directly, they will always be called through child models (but no, abstract class is not the way to go for various reasons).
But this is where it becomes trickier :
Each child model inherits from a specific module (moduleA, moduleB), they all implement the same method names but do something different. The calls are made through the parent model, and are redirected to the childs depending on the values of a field
Since I guess it's not very clear, here is some pseudo-code to help you understand
from ModuleA import CustomClassA
from ModuleB import CustomClassB
class ParentModel(models.Model):
TYPE_CHOICES = (
('ChildModelA', 'A'),
('ChildModelB', 'B'),
)
#some fields
type = models.CharField(max_length=1, choices=TYPE_CHOICES)
def __init__(self, *args, **kwargs):
super(ParentModel, self).__init__(*args, **kwargs)
if self.type:
self.__class__ = getattr(sys.modules[__name__], self.type)
def MethodA():
some_method()
def MethodB():
some_other_method()
class ChildModelA(ParentModel, CustomClassA):
class Meta:
proxy = True
class ChildModelB(ParentModel, CustomClassB):
class Meta:
proxy = True
In ModuleA :
class CustomClassA():
def some_method():
#stuff
def some_other_method():
#other stuff
In ModuleB :
class CustomClassB():
def some_method():
#stuff
def some_other_method():
#other stuff
Right now, the problem is that the class change works, but it does not inherit from ChildModelA or B.
Is this even possible? If yes, how can I make it work, and if no, how could I do this elegantly, without too much repetition?
A proxy model must inherit from exactly one non-abstract model class. It seems that both CustomClass and ParentModel are non-abstract. I would suggest to make CustomClass abstract since no attributes are defined.
This is explained in dept here: https://docs.djangoproject.com/en/3.2/topics/db/models/#proxy-models

Categories

Resources