Open JLHasson opened 3 years ago
Tried some other options for achieving this, with a pydantic model as the PrivateAttr
. The solution as shown in https://github.com/tiangolo/sqlmodel/issues/147 doens't work either. Interestingly, running this in a notebook yields the expected result for B3
.
Environment: Ubuntu 18.04.6 LTS (GNU/Linux 5.4.0-87-generic x86_64) Python 3.8.12 pydantic==1.9.0 sqlmodel==0.0.6 ipykernel==6.9.0
from typing import Optional
from pydantic import BaseModel, PrivateAttr
from sqlmodel import Field, SQLModel
class A(BaseModel):
some_attr: int = 2
class B1(SQLModel, table=True):
_a: A = PrivateAttr(default_factory=A)
id: Optional[int] = Field(default=None, primary_key=True)
class B2(SQLModel, table=True):
_a: A = PrivateAttr(default=A())
id: Optional[int] = Field(default=None, primary_key=True)
class B3(SQLModel, table=True):
_a: A = PrivateAttr()
id: Optional[int] = Field(default=None, primary_key=True)
# from https://pydantic-docs.helpmanual.io/usage/models/#private-model-attributes
def __init__(self, **data):
super().__init__(**data)
self._a = A()
class B4(SQLModel, table=True):
_a: A = PrivateAttr()
id: Optional[int] = Field(default=None, primary_key=True)
@property
def a(self):
return self._a
for B in [B1, B2, B3, B4]:
b = B()
try:
print(b._a.some_attr)
except Exception as e:
print(e)
Result in plain python:
'ModelPrivateAttr' object has no attribute 'some_attr'
'ModelPrivateAttr' object has no attribute 'some_attr'
'ModelPrivateAttr' object has no attribute 'some_attr'
'ModelPrivateAttr' object has no attribute 'some_attr'
Result in a notebook:
'ModelPrivateAttr' object has no attribute 'some_attr'
'ModelPrivateAttr' object has no attribute 'some_attr'
2
'ModelPrivateAttr' object has no attribute 'some_attr'
Any solution for this? I just ran into the same problem
It appears that we are missing private attribute initialization. I have raised a PR to fix this.
Great! I ended up adding 8 columns to my SQL table because of this bug.., finally able to clean up after this merge
same issue here
I have a similar related issue. I have a PrivateAttr field on my model so it is not persisted with a public getter for clarity. If I construct the model myself the getter works but if I retrieve from the database with select
then accessing the getter raises:
File "/Volumes/Crucial X8/Projects/Portal/.venv/lib/python3.12/site-packages/pydantic/main.py", line 807, in __getattr__
return self.__pydantic_private__[item] # type: ignore
│ │ └ '_anonymous'
│ └ <member '__pydantic_private__' of 'BaseModel' objects>
â”” User(id=1, username='testuser')
TypeError: 'NoneType' object is not subscriptable
The model
class User(SQLModel, table=True):
__tablename__ = "user"
id: int | None = Field(default=None, primary_key=True, gt=0)
username: str = Field(index=True, max_length=32)
email: str
password: str = Field(repr=False)
superuser: bool = Field(default=False)
_anonymous: bool = PrivateAttr(default=False)
@property
def is_anonymous(self) -> bool:
return self._anonymous
If I change my repository method to the following it works fine. Obviously not a viable solution going forward but serves to prove the point.
def find_by_username(self, username: str) -> User | None:
stmt = select(User).where(User.username == username)
result = self.session.exec(stmt)
r = result.one_or_none()
u = User(**r.model_dump())
return u
I have a similar related issue. I have a PrivateAttr field on my model so it is not persisted with a public getter for clarity. If I construct the model myself the getter works but if I retrieve from the database with
select
then accessing the getter raises:File "/Volumes/Crucial X8/Projects/Portal/.venv/lib/python3.12/site-packages/pydantic/main.py", line 807, in __getattr__ return self.__pydantic_private__[item] # type: ignore │ │ └ '_anonymous' │ └ <member '__pydantic_private__' of 'BaseModel' objects> └ User(id=1, username='testuser') TypeError: 'NoneType' object is not subscriptable
The model
class User(SQLModel, table=True): __tablename__ = "user" id: int | None = Field(default=None, primary_key=True, gt=0) username: str = Field(index=True, max_length=32) email: str password: str = Field(repr=False) superuser: bool = Field(default=False) _anonymous: bool = PrivateAttr(default=False) @property def is_anonymous(self) -> bool: return self._anonymous
If I change my repository method to the following it works fine. Obviously not a viable solution going forward but serves to prove the point.
def find_by_username(self, username: str) -> User | None: stmt = select(User).where(User.username == username) result = self.session.exec(stmt) r = result.one_or_none() u = User(**r.model_dump()) return u
Furthermore, if you update the object and add it to the session sqlmodel considers it a new instance entirely and you will see a unique constraint error for the duplicate ID.
First Check
Commit to Help
Example Code
Description
As far as I can tell SQLModel is ignoring the
default
anddefault_factory
parameters ofpydantic.PrivateAttr
. The example I've given above reproduces on my system. The output can be seen here:As you can see the field is not set to
None
, and instead is an empty instance ofpydantic.fields.ModelPrivateAttr
.Operating System
macOS
Operating System Details
No response
SQLModel Version
0.0.4
Python Version
3.9.5
Additional Context
No response