I have a base class BaseTemplateData which inherits from pydantic.BaseModel. Then, I want to have a property of the BaseTemplate class which stores a type of a child class of BaseTemplateData.
I'm doing the following, but I'm getting a mypy error saying Type variable "file.path.TemplateDataType" is unbound, when I'm explicitly passing a bound parameter to the TypeVar call.
I also would like to have another class BaseTemplate2 which property doesn't store the type itself, but an instance of a child class of BaseTemplateData. Would the approach be correct?
Any help would be much appreciated. Thanks!
from typing import Type, TypeVar
from pydantic import BaseModel
class BaseTemplateData(BaseModel):
"""
Base class for all templates.
"""
TemplateDataType = TypeVar("TemplateDataType", bound=BaseTemplateData)
class BaseTemplate(BaseModel):
"""
Template class for email templates
"""
data_model: Type[TemplateDataType]
class BaseTemplate2(BaseModel):
"""
Template class for email templates 2
"""
data_model: TemplateDataType
I'm not so familiar with pythons Typing and can't imagine what you trying to solve by this code. But I don't understand why you need TemplateDataType?
Your expectations are:
I want to have a property of the BaseTemplate class which stores a type of a child class of BaseTemplateData
I also would like to have another class BaseTemplate2 which property doesn't store the type itself, but an instance of a child class of BaseTemplateData
Okay:
from typing import Type
from pydantic import BaseModel
class BaseTemplateData(BaseModel):
"""
Base class for all templates.
"""
class ChildBaseTemplateData(BaseTemplateData):
"""
Child class for Base class.
"""
class BaseTemplate(BaseModel):
"""
Template class for email templates
"""
data_model: Type[BaseTemplateData]
class BaseTemplate2(BaseModel):
"""
Template class for email templates 2
"""
data_model: BaseTemplateData
a = BaseTemplate()
# it is okay. Because a.data_model is a "type of a child class of BaseTemplateData"
a.data_model = ChildBaseTemplateData
b = BaseTemplate2()
# it is okay too. Because b.data_model is "an instance of a child class of BaseTemplateData"
b.data_model = ChildBaseTemplateData()
c = BaseTemplate2()
# error: Incompatible types in assignment (expression has type "str", variable has type "BaseTemplateData")
c.data_model = "string"
Here is a good explanation about TypeVar
Also TypeVar is using with Generics.
Here is no Generics in your example
Proper usage of TypeVar will be something like:
from typing import Type, TypeVar, Generic
A = TypeVar("A", int, str)
class B(Generic[A]):
pass
# Ok
a = B[int]
# Error: error: Value of type variable "A" of "B" cannot be "float"
b = B[float]
Related
we are trying to add typehints to our django (3.2) project. We are using django-stubs (1.12.0).
We have an AbstractBaseModel with a custom BaseManager that we use for almost every other model. In some cases we also extend the custom Manager.
Example:
class BaseManager(models.Manager):
# ...
class AbstractBaseModel(models.Model):
# ...
objects = BaseManager()
# ...
class MyManager(BaseManager):
# ...
class MyModel(AbstractBaseModel):
# ...
objects = MyManager()
# ...
When I run this through mypy (0.982), I get this error on the objects assignment of MyModel:
Incompatible types in assignment (expression has type "MyManager[MyModel]",
base class "AbstractBaseModel" defined the type as
"AbstractBaseModel_BaseManager2[AbstractBaseModel]")
How would I add typehints to this?
Thank you!
As I said in my comment above, I could not reproduce that same error you described. It seems that in the django-stubs the BaseManager is defined as a covariant generic type over any Model (sub-)class.
So I would annotate the code you provided as follows:
from __future__ import annotations
from typing import TypeVar
from django.db import models
M = TypeVar("M", bound=models.Model, covariant=True)
class BaseManager(models.Manager[M]):
pass
class AbstractBaseModel(models.Model):
objects: BaseManager[AbstractBaseModel] = BaseManager()
class MyManager(BaseManager[M]):
pass
class MyModel(AbstractBaseModel):
objects: BaseManager[MyModel] = MyManager()
This passes mypy --strict without issues.
Versions used:
Python==3.10.7
Django==3.2
django-stubs==1.12.0
django-stubs-ext==0.5.0
mypy==0.982
mypy-extensions==0.4.3
I am new to fastapi and SQLModel, i was trying to implement some basic code from my existing lib, I have an Address Class
like
#dataclass
class Address(DataClassJsonMixin):
addr1: str
city: str
province: str
I simply want to create a class in SQLModel that connects to DB. I have only added a new column ID here. i am getting below error where i am not sure why is it asking for a config attribute.
class AddressMaster(SQLModel, Address):
id: int = Field(default=None, primary_key=True)
AttributeError: type object 'Address' has no attribute '__config__'
It's failing on config = getattr(base, "__config__") that has some information which I am not able to comprehand.
# Only one of the base classes (or the current one) should be a table model
# this allows FastAPI cloning a SQLModel for the response_model without
# trying to create a new SQLAlchemy, for a new table, with the same name, that
# triggers an error
try 1:
from sqlmodel import SQLModel, Field
from ...core import Address
from dataclasses import dataclass
#dataclass
class AddressDB(Address, SQLModel):
pass
# END AddressDB
class AddressMaster(AddressDB, table=True):
"""
Address Master Table
"""
id: int = Field(default=None, primary_key=True)
# END AddressMaster
Object Creation
objAd = AddressMaster.from_dict({"addr1": "Kashmir", "city": "Srinagar", "province": "Kashmir"})
There is an error in the semantics of your AdressMaster class.
If it is meant to be a class related to your DB. Then you have to specify in the first parameter either the class inheriting from a SQL model or from SQLmodel (And in this case, you should rewrite each attribute of your model within this class) directly. And it is necessary to pass it the argument table=True
class AddressMaster(Address, table=True):
id: int = Field(default=None, primary_key=True)
# Here the attributes will be inherited from your Adress class
# (provided that this one in its parentage is an inheritance link with a modelSQL)
Or
class AddressMaster(SQLModel, table=True):
id: int = Field(default=None, primary_key=True)
addr1: str
city: str
province: str
# Here, the class is independent from the other pydantic
# validation models since it inherits directly from SQLModel
In Try 1:
You are trying to pass two parameters to your AddressDB class, one of which is an SQLModel. However, SQLModel allows to override SQLAlchemy, and accepts as parameter only models from SQLAlchemy or Pydantic. During the initialization, it goes through the arguments passed in parameter and tries to call the method or attribute Config which exists in the pydantic and SQLAlchemy models. This is the source of your error since you pass in parameter a DataClassJsonMixin which has no Config method or attribute. This is the origin of your error.
How to solve it. You just have to not call DataClassJsonMixin which seems to me to encode / decode JSON data. However, this is a basic behavior of Pydantic (which is used behind SQLModel).
So if you use the first method shown above (i.e. inherited from a SQLModel), you just have to put your validation fields inside AddressDB and make this class inherit only from SQLModel
class AddressDB(SQLModel):
addr1: str
city: str
province: str
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
I have a Team model in my Django project. I create its custom model manager with QuerySet.as_manager().
class TeamQuerySet(models.QuerySet):
def active(self) -> "models.QuerySet[Team]":
return self.filter(is_active=True)
class Team(models.Model):
is_active = models.BooleanField()
objects = TeamQuerySet.as_manager()
When I try to execute Team.objects.active(), mypy gives the following error:
error: "Manager[Any]" has no attribute "active"
In [5]: Team.objects
Out[5]: <django.db.models.manager.ManagerFromTeamQuerySet at 0x10eee1f70>
If I was explicitly defining a TeamManager class, there would be not a problem. How can I hint the type of Django model field objects to a dynamically generated class?
Based on the Manager[Any] I assume you are already using django-stubs.
Unfortunately it looks like there's an open issue to make QuerySet.as_manager generic over the model it's attached to that has not been resolved yet.
Even if the PR addressing the issue got merged I'm afraid it wouldn't address your immediate issue because the as_manager needs to be generic over the generic QuerySet subclass used to create the manager in order for both .active to be available and attributes relating to Team be available.
In this regard this other PR, which is unfortunately quite stale, seems to properly address your issue.
I've worked around this with a little switch-a-roo for MyPy's sake:
_Q = TypeVar("_Q", bound="WorkflowQuerySet")
class WorkflowQuerySet(models.QuerySet["WorkflowModel"]):
"""
Queryset for workflow objects.
"""
def count_objects(self) -> int:
raise NotImplementedError
def latest_objects(self: _Q) -> _Q:
raise NotImplementedError
if TYPE_CHECKING:
# Create a type MyPy understands
class WorkflowManager(models.Manager["WorkflowModel"]):
def count_objects(self) -> int:
...
def latest_objects(self) -> _Q:
...
else:
WorkflowManager = WorkflowQuerySet.as_manager
class WorkflowModel(models.Model):
"""
A model that has workflow.
"""
objects = WorkflowManager()
Here is my answer using generics and typevar
from typing import Generic, TypeVar
from django.db import models
class BookQueryset(models.QuerySet['Book']):
...
class Book(models.Model):
objects: BookQueryset = BookQueryset.as_manager()
book = Book.objects.all()[0]
If you inspect book is type Book
I have an abstract base class which defines several class attributes which are types themselves (i.e., nested types); the attributes are overridden in concrete derived classes inheriting from the abstract class. The concrete derived classes are used with several other classes which are type-annotated as generics, where the type parameter has a type constraint that it must be derived from the abstract class. I want these other classes (generics) to refer to the class attributes (nested types) of the concrete classes. The following example should demonstrate the idea:
import typing
class Base:
A: typing.Type[Base]
class One(Base):
class A(Base): # overrides Base.A
pass
class Two(Base):
class A(One): # overrides Base.A
pass
T = typing.TypeVar('T', bound=Base)
class Foo(typing.Generic[T]): # the generic
def foo(self, a: typing.Type[T.A]) -> T: # <-- here, see T.A
...
f: Foo[Two] = Foo()
f.foo(Two.A) # line 22 is here
MyPy v0.701 reports an error on line 22:
Argument 1 to "foo" of "Foo" has incompatible type "Type[A]"; expected "Type[Two]"
MyPy seems to ignore the attribute reference at T.A. How do I make it understand that I am trying to refer to the type available via the attribute .A? The attribute is guaranteed to be available because the type variable T is constrained to Base, which has A. Note that extending the set of generic types is not an option; I could elaborate on this but the explanation would be too specific to my application.
UPDATE: MyPy v0.711 with --new-semantic-analyzer yields a more comprehensible error message:
Name 'T.A' is not defined
I'm not sure if this helps or not but if you parameterize the argument bound to Base it works:
class Base:
A: typing.Type['Base']
class One(Base):
class A(Base): # overrides Base.A
pass
class Two(Base):
class A(One): # overrides Base.A
pass
T = typing.TypeVar('T', bound='Base')
A = typing.TypeVar('A', bound='Base')
class Foo(typing.Generic[T, A]): # the generic
def foo(self, a: typing.Type[A]) -> T: # <-- here, see T.A
pass
f: Foo[Two, Two.A] = Foo()
f.foo(Two.A) # line 22 is here