Skip to content

Support hybrid_property, column_property, declared_attr#801

Open
50Bytes-dev wants to merge 66 commits intofastapi:mainfrom
50Bytes-dev:main
Open

Support hybrid_property, column_property, declared_attr#801
50Bytes-dev wants to merge 66 commits intofastapi:mainfrom
50Bytes-dev:main

Conversation

@50Bytes-dev
Copy link
Copy Markdown

    class Item(SQLModel, table=True):
        id: Optional[int] = Field(default=None, primary_key=True)
        value: float
        hero_id: int = Field(foreign_key="hero.id")
        hero: "Hero" = Relationship(back_populates="items")

    class Hero(SQLModel, table=True):
        id: Optional[int] = Field(default=None, primary_key=True)
        name: str
        items: list[Item] = Relationship(back_populates="hero")

        @hybrid_property
        def total_items(self):
            return sum([item.value for item in self.items], 0)

        @total_items.inplace.expression
        @classmethod
        def _total_items_expression(cls):
            return (
                select(func.coalesce(func.sum(Item.value), 0))
                .where(Item.hero_id == cls.id)
                .correlate(cls)
                .label("total_items")
            )

        @hybrid_property
        def status(self):
            return "active" if self.total_items > 0 else "inactive"

        @status.inplace.expression
        @classmethod
        def _status_expression(cls):
            return (
                select(case((cls.total_items > 0, "active"), else_="inactive"))
                .label("status")
            )

@50Bytes-dev 50Bytes-dev changed the title Support hybrid_property Support hybrid_property, column_property, declared_attr Mar 1, 2024
@50Bytes-dev
Copy link
Copy Markdown
Author

    class Item(SQLModel, table=True):
        id: Optional[int] = Field(default=None, primary_key=True)
        value: float
        hero_id: int = Field(foreign_key="hero.id")
        hero: "Hero" = Relationship(back_populates="items")

    class Hero(SQLModel, table=True):
        id: Optional[int] = Field(default=None, primary_key=True)
        name: str
        items: List[Item] = Relationship(back_populates="hero")

        @declared_attr
        def total_items(cls):
            return column_property(cls._total_items_expression())

        @classmethod
        def _total_items_expression(cls):
            return (
                select(func.coalesce(func.sum(Item.value), 0))
                .where(Item.hero_id == cls.id)
                .correlate_except(Item)
                .label("total_items")
            )

        @declared_attr
        def status(cls):
            return column_property(
                select(
                    case(
                        (cls._total_items_expression() > 0, "active"), else_="inactive"
                    )
                ).scalar_subquery()
            )

Copy link
Copy Markdown

@awoimbee awoimbee left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just small suggestions from a random outsider :p (some suggestions might be wrong).
All in all, this is clean and adds a lot of functionality, thanks !
You should look into this @tiangolo !

50Bytes-dev and others added 3 commits March 7, 2024 17:18
Co-authored-by: Arthur Woimbée <[email protected]>
Co-authored-by: Arthur Woimbée <[email protected]>
Co-authored-by: Arthur Woimbée <[email protected]>
@muazhari
Copy link
Copy Markdown

Please merge it asap :3. I need it, lol.

@mfkaroui
Copy link
Copy Markdown

hey can we get this merged in

@mfkaroui
Copy link
Copy Markdown

mfkaroui commented Apr 4, 2024

@tiangolo Could you please take a look at this MR. The functionality is very nice. Would be a nice addition. Doesn't seem to clash with overall design

@francomahl
Copy link
Copy Markdown

    class Item(SQLModel, table=True):
        id: Optional[int] = Field(default=None, primary_key=True)
        value: float
        hero_id: int = Field(foreign_key="hero.id")
        hero: "Hero" = Relationship(back_populates="items")

    class Hero(SQLModel, table=True):
        id: Optional[int] = Field(default=None, primary_key=True)
        name: str
        items: List[Item] = Relationship(back_populates="hero")

        @declared_attr
        def total_items(cls):
            return column_property(cls._total_items_expression())

        @classmethod
        def _total_items_expression(cls):
            return (
                select(func.coalesce(func.sum(Item.value), 0))
                .where(Item.hero_id == cls.id)
                .correlate_except(Item)
                .label("total_items")
            )

        @declared_attr
        def status(cls):
            return column_property(
                select(
                    case(
                        (cls._total_items_expression() > 0, "active"), else_="inactive"
                    )
                ).scalar_subquery()
            )

Following this example I am getting

pydantic.errors.PydanticUserError: A non-annotated attribute was detected: `total_items = <sqlalchemy.orm.decl_api.declared_attr object at 0x10e415350>`. All model fields require a type annotation; if `total_items` is not meant to be a field, you may be able to resolve this error by annotating it as a `ClassVar` or updating `model_config['ignored_types']`.

Even if I do def total_items(cls): -> int I still get the same error.
SQLModel version 0.0.16

But I think we can get the same result with computed_field and the property decorator

    from pydantic import computed_field

    class Hero(SQLModel, table=True):
        id: Optional[int] = Field(default=None, primary_key=True)
        name: str
        items: List[Item] = Relationship(back_populates="hero")

        @computed_field
        @property
        def total_items(cls) -> int:
            return len(self.items)

@raphaellaude
Copy link
Copy Markdown

Any movement to get this functionality in?

@50Bytes-dev
Copy link
Copy Markdown
Author

Hi, I am awaiting this feature as it would simplify my life a lot. I there any progress on this? Otherwise I need to go back to sql_alchemy
Cheers

Hi. Just use my repo https://github.com/50Bytes-dev/sqlmodel/tree/main

Thanks, no offence but I would prefer to stick with the official repro to keep everythin in line. Isn't there a chance for a merge any time soon?

This repository has long been abandoned, which is why I made a fork and am adding needed functionality there

@sakost
Copy link
Copy Markdown

sakost commented Aug 25, 2025

any updates on this?

@github-actions github-actions bot added the conflicts Automatically generated when a PR has a merge conflict label Sep 5, 2025
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Sep 5, 2025

This pull request has a merge conflict that needs to be resolved.

@Vizonex
Copy link
Copy Markdown

Vizonex commented Sep 25, 2025

I think I can go a bit further than what this pr had in mind. I was thinking about combining computed_feilds and hybrid_property column_property and declared_attr together. Does anyone have any tips and suggestions? I'll try and use what this pr had in mind as a blueprint.

…configuration and utility functions for SQLModel
@Vizonex
Copy link
Copy Markdown

Vizonex commented Oct 22, 2025

@50Bytes-dev Glad to see your pr is still alive, mind if I help you review it?

…columns by schema; update col function to return InstrumentedAttribute for better type safety
…Path and AliasChoices for enhanced flexibility
@Vizonex
Copy link
Copy Markdown

Vizonex commented Nov 21, 2025

@50Bytes-dev Have you tried adding some tests to the pytest to ensure that this implementation works?

@50Bytes-dev
Copy link
Copy Markdown
Author

@50Bytes-dev Have you tried adding some tests to the pytest to ensure that this implementation works?

Yes, I have basic tests. I use this PR in the production of my projects and make sure that everything works correctly.

@tstorek
Copy link
Copy Markdown

tstorek commented Nov 26, 2025

@50Bytes-dev thanks, for that piece of code. Is there any chance for release some time soon. I have a use case starting from next week and I'd like to omit a vanilla sqlalchemy workaround :)

@50Bytes-dev
Copy link
Copy Markdown
Author

@50Bytes-dev thanks, for that piece of code. Is there any chance for release some time soon. I have a use case starting from next week and I'd like to omit a vanilla sqlalchemy workaround :)

Release where? Personally, I just use the repository and don't plan to release anything anywhere. I recommend trying it this way too, it's quite convenient.

dependencies = [
  "sqlmodel",
]

[tool.uv.sources]
sqlmodel = { git = "https://github.com/50Bytes-dev/sqlmodel.git", rev = "833b8700e5006177d1a57fd8b3109bd625a358de" }

@tstorek
Copy link
Copy Markdown

tstorek commented Nov 26, 2025

@50Bytes-dev Okay, sorry. I thought that the PR is meant to be merged at some point.

@50Bytes-dev
Copy link
Copy Markdown
Author

@50Bytes-dev Okay, sorry. I thought that the PR is meant to be merged at some point.

I think that the main repository is dead and unlikely to be developed further, so I created my own fork where I implement all the necessary functions myself.

@ksaadDE
Copy link
Copy Markdown

ksaadDE commented Jan 15, 2026

I think that the main repository is dead

The main repo of what? FastAPI, SQLmodel?

If there is no PR, in SQLModel I assume, nobody can check it, thus it appears obv. dead. Though if you look at the commit history in main, all is fine

Looking for a method to introduce calculated column easily. Got to this to just read that lol

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

conflicts Automatically generated when a PR has a merge conflict feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.