sqlmodel icon indicating copy to clipboard operation
sqlmodel copied to clipboard

Using hybrid_property of SQLAlchemy leads to CompileError

Open deZakelijke opened this issue 2 years ago • 11 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

from sqlalchemy.ext.hybrid import hybrid_property  
from typing import Optional  
from sqlmodel import SQLModel, Field, create_engine  
  
from datetime import datetime  
                                    
                                      
class ObjectTable(SQLModel, table=True):  
    object_id: Optional[int] = Field(primary_key=True, default=None)  
    first_detection_time: datetime = Field(index=True)  
    last_detection_time: datetime = Field(index=True)  
      
    @hybrid_property                                    
    def detection_time(self) -> float:  
        return (self.last_detection_time - self.first_detection_time).total_seconds()  
      
    class Config:                 
        arbitrary_types_allowed = True  
                                                                                       
     
if __name__ == "__main__":  
    engine = create_engine("sqlite:///database.db")  
    SQLModel.metadata.create_all(engine)

Description

I am trying to create a hybrid property in an SQLModel class to allow more complex querying. Following the steps as described in the sqlalchemy docs here: https://docs.sqlalchemy.org/en/14/orm/extensions/hybrid.html I assumed that this would work and create a valid table. However, this code gives the error: sqlalchemy.exc.CompileError: (in table 'objecttable', column detection_time'): Can't generate DDL for NullType(); did you forget to specify a type on this Column? At first, I assumed that a type hint was missing so I added the float return type to the hybrid_property. I am not sure what the problem is exactly but I assumed that this code would yield a valid table.

Operating System

Linux

Operating System Details

Ubuntu 20.04

SQLModel Version

0.0.6

Python Version

Python 3.8.10

Additional Context

No response

deZakelijke avatar Apr 14 '22 11:04 deZakelijke

I faced with the same issue

alexbojko avatar Apr 15 '22 15:04 alexbojko

me too. solved @alexbojko ...? please...

lhs950204 avatar May 14 '22 02:05 lhs950204

Nope. @tiangolo that would be really cool if you can take a look into it.

alexbojko avatar May 14 '22 14:05 alexbojko

Any update regarding this issue?

My model:

class Model(SQLModel: table=True):
    active_until: Optional[datetime.datetime]
    
    class Config:
        arbitrary_types_allowed = True
        
    @hybrid_property
    def is_active(self) -> bool:
        return self.active_until is not None and self.active_until > datetime.datetime.now()

On 0.0.8 I've got an error:

... python3.10/site-packages/sqlmodel/main.py", line 414, in get_sqlachemy_type
raise ValueError(f"The field {field.name} has no matching SQLAlchemy type")
ValueError: The field is_active has no matching SQLAlchemy type

kozickikarol avatar Sep 17 '22 19:09 kozickikarol

Same issue over here @kozickikarol, any workarounds?

Augustinio avatar Sep 19 '22 11:09 Augustinio

I also struggled with column_property and hybrid_property. Am now using workaround where just call custom code to add properties in code to my "read" objects.

Am also using normal Python properties where suitable, actually, wouldn't that is_active work as a plain @property?

antont avatar Oct 20 '22 09:10 antont

@tiangolo Bro is there any problem in pull request if it be merged it would really help :)

Elyasomer avatar Oct 17 '23 13:10 Elyasomer

I found a somewhat simple workaround: annotate the property as a class var (which pydantic will ignore)

from typing import ClassVar, Optional  

from datetime import datetime  

from sqlalchemy.ext.hybrid import hybrid_property  
from sqlmodel import SQLModel, Field, create_engine  
                                      

def _detection_time(self) -> float:
    return (self.last_detection_time - self.first_detection_time).total_seconds()  

class ObjectTable(SQLModel, table=True):  
    object_id: Optional[int] = Field(primary_key=True, default=None)  
    first_detection_time: datetime = Field(index=True)  
    last_detection_time: datetime = Field(index=True)  
      
    detection_time: ClassVar[float] = hybrid_property(_detection_time)
     
if __name__ == "__main__":  
    engine = create_engine("sqlite:///database.db")  
    SQLModel.metadata.create_all(engine)

NOTE: I think in this example you need to add an expression for this to work as expected in queries.

jjjacksn avatar Oct 30 '23 16:10 jjjacksn

@tiangolo any update on this yet? The above feels like a work-around (and I'm so far not able to hybrid props and comparators working using sqlmodel and fast api)

geudrik avatar Jan 24 '24 19:01 geudrik

@jjjacksn Could you please elaborate how to add an expression to use the detection_time in a query?

For instance:

select(ObjectTable).where(ObjectTable.detection_time > 1000)

christianholland avatar Feb 08 '24 10:02 christianholland

@geudrik use my PR #801

50Bytes-dev avatar Feb 14 '24 13:02 50Bytes-dev