sqlmodel icon indicating copy to clipboard operation
sqlmodel copied to clipboard

__init_subclass__ not working as expected with SQLModel

Open LCBerndsen opened this issue 3 years ago • 4 comments

First Check

  • [X] I added a very descriptive title to this issue.
  • [x] I used the GitHub search to find a similar issue and didn't find it.
  • [X] I searched the SQLModel documentation, with the integrated search.
  • [X] I already searched in Google "How to X in SQLModel" and didn't find any information.
  • [X] I already read and followed all the tutorial in the docs and didn't find an answer.
  • [X] I already checked if it is not related to SQLModel but to Pydantic.
  • [X] I already checked if it is not related to SQLModel but to SQLAlchemy.

Commit to Help

  • [X] I commit to help with one of those options 👆

Example Code

#WORKING EXAMPLE
from pydantic import BaseModel, root_validator, ValidationError

class Philosopher(BaseModel):
    def __init_subclass__(cls, default_name=None, **kwargs):
        super().__init_subclass__(**kwargs)
        cls.default_name = default_name

    @root_validator(pre=True)
    def default_name_validator(cls, values):
        print(cls.default_name)
        if values["name"] != cls.default_name:
            raise ValidationError("name not correct")
        return values


class AustralianPhilosopher(Philosopher, default_name="Bruce"):
    name: str


class GermanPhilosopher(Philosopher, default_name="Nietzsche"):
    name: str


Bruce = AustralianPhilosopher(name="Bruce")
Nietzsche = GermanPhilosopher(name="Nietzsche")
Error = GermanPhilosopher(name="test")

# DESIRED:
class Philosopher(SQLModel):
    def __init_subclass__(cls, default_name=None, **kwargs):
        super().__init_subclass__(**kwargs)
        cls.default_name = default_name

    @root_validator(pre=True)
    def default_name_validator(cls, values):
        print(cls.default_name) # -> Now this is None
        if values["name"] != cls.default_name:
            raise ValidationError("name not correct")
        return values


class AustralianPhilosopher(Philosopher, default_name="Bruce"):
    name: str


class GermanPhilosopher(Philosopher, default_name="Nietzsche"):
    name: str


Bruce = AustralianPhilosopher(name="Bruce")
Nietzsche = GermanPhilosopher(name="Nietzsche")
Error = GermanPhilosopher(name="test")

Description

Building forward from this Stackoverflow issue, I create a base class that has a __init_subclass__ method. In this baseclass, I want to have a root_validator for all classes that subclass this baseclass. This seems to work when I have pydantic's BaseModel as the Base for my Philosopher class, but my actual goal is to do this with SQLModel's Base. If I do so, the default_name attribute of cls is None.

.

Operating System

macOS

Operating System Details

No response

SQLModel Version

sqlmodel = "0.0.6"

Python Version

3.10.3

Additional Context

Workaround I could do a dirty fix by setting title = "Bruce" and title ="Nietzsche" instead of default_name. Because then I would be able to call it using cls.config.title but that does not seem like a responsible fix


class Philosopher(SQLModel):
    def __init_subclass__(cls, default_name=None, **kwargs):
        super().__init_subclass__(**kwargs)
        cls.default_name = default_name

    @root_validator(pre=True)
    def default_name_validator(cls, values):
        print(cls.__config__.title)
        if values["name"] != cls.__config__.title:
            raise ValidationError("name not correct")
        return values


class AustralianPhilosopher(Philosopher, title="Bruce"):
    name: str


class GermanPhilosopher(Philosopher, title="Nietzsche"):
    name: str


Bruce = AustralianPhilosopher(name="Bruce")
Nietzsche = GermanPhilosopher(name="Nietzsche")
Error = GermanPhilosopher(name="test")

LCBerndsen avatar Aug 03 '22 09:08 LCBerndsen

Is there a good reason why not to do it this way?

from pydantic import root_validator, ValidationError
from sqlmodel import SQLModel


class Philosopher(SQLModel):
    __default_name__ = None

    @root_validator(pre=True)
    def default_name_validator(cls, values):
        print(cls.__default_name__)
        if values["name"] != cls.__default_name__:
            raise ValidationError("name not correct")
        return values


class AustralianPhilosopher(Philosopher):
    __default_name__ = "Bruce"

    name: str


class GermanPhilosopher(Philosopher):
    __default_name__ = "Nietzsche"

    name: str


Bruce = AustralianPhilosopher(name="Bruce")
Nietzsche = GermanPhilosopher(name="Nietzsche")
Error = GermanPhilosopher(name="test")

meirdev avatar Aug 05 '22 14:08 meirdev

Is there a good reason why not to do it this way?

from pydantic import root_validator, ValidationError
from sqlmodel import SQLModel


class Philosopher(SQLModel):
    __default_name__ = None

    @root_validator(pre=True)
    def default_name_validator(cls, values):
        print(cls.__default_name__)
        if values["name"] != cls.__default_name__:
            raise ValidationError("name not correct")
        return values


class AustralianPhilosopher(Philosopher):
    __default_name__ = "Bruce"

    name: str


class GermanPhilosopher(Philosopher):
    __default_name__ = "Nietzsche"

    name: str


Bruce = AustralianPhilosopher(name="Bruce")
Nietzsche = GermanPhilosopher(name="Nietzsche")
Error = GermanPhilosopher(name="test")

Thank you, this is actually a better solution to the problem than my hack. Though, I still hope (for sake of clean code) it would be possible through __init_subclass_ method.

LCBerndsen avatar Aug 05 '22 14:08 LCBerndsen

Currently, only Pydantic config options are allowed to pass by SQLModel's metaclass, e.g. extra=Extra.allow, so your default_name will never reach the __init_subclass__ method and its default value (None) will apply.

byrman avatar Sep 05 '22 07:09 byrman