I'm trying to create a base class that works for any CRUD in applications and I've seen the following implementation:
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: Type[ModelType]):
"""CRUD object with default methods to Create, Read, Update,
Delete (CRUD).
**Parameters**
* `model`: A SQLAlchemy model class
* `schema`: A Pydantic model (schema) class
"""
self.model = model
'...crud methods'`
Generic is a way to define an abstract class, containing the objects that are specified (in this case [ModelType,CreateSchemaType, UpdateSchemaType]) or what is the use of generic?
CodePudding user response:
If your question is about the purpose of typing.Generic
I would suggest you read through PEP 484. It has a section dedicated to user defined generic classes with some examples specifically for this, but the entire document is worthwhile reading IMHO. If you are unsure about the entire concept of generic types, the Wikipedia articles on parametric polymorphism and generic programming might even be worth skimming.
In very simple terms, you can use the Generic
base class to define your own classes in a generic way, i.e. parameterized with regards to one or more type arguments. These type parameters are constructed via typing.TypeVar
. It is worth noting that in most cases these things are only relevant for the purposes of type safety, which is not really enforced by Python itself. Instead, static type checkers (most IDEs have them built-in) deal with those things.
As to your example, it is entirely possible to define your class in a generic way and thus improve your own coding experience because you'll likely get useful auto-suggestions and warnings by your IDE. This only really becomes useful, if you use the type variables somewhere else throughout your class. Example:
from typing import Generic, TypeVar
class Base:
pass
ModelType = TypeVar("ModelType", bound=Base)
class CRUDBase(Generic[ModelType]):
def __init__(self, model: type[ModelType]):
self.model = model
def get_model_instance(self) -> ModelType:
return self.model()
class Sub(Base):
def foo(self) -> None:
print("hi mom")
if __name__ == "__main__":
crud_a = CRUDBase(Sub)
crud_b = CRUDBase(Base)
a = crud_a.get_model_instance()
b = crud_b.get_model_instance()
a.foo()
b.foo() # error
A good type checker like mypy
will know, that a
is of the Sub
type and thus has the foo
method, whereas b
is of the Base
type and therefore does not. And it will know so just from the way we annotated CRUDBase
.
PyCharm for example struggles with this (don't know why). Fortunately, we can help it out with explicit type annotations because we defined CRUDBase
as a generic type:
...
crud_a: CRUDBase[Sub] = CRUDBase(Sub)
crud_b: CRUDBase[Base] = CRUDBase(Base)
a = crud_a.get_model_instance()
b = crud_b.get_model_instance()
a.foo()
b.foo() # PyCharm marks `foo` and complains
Notice that so far there is nothing particularly "abstract" about our CRUDBase
. It is fully self-contained and functioning like this (albeit not very useful yet).
If you want a class to be an abstract base and not be directly instantiated, the abc
module provides you with the tools you need. You can define an abstract base class and abstract methods on it that all subclasses must implement. But I think it is clear now that this is a different concept. The idea is that an abstract class is not intended to be instantiated, only its subclasses are.
I hope this helps point you in a few useful directions.