sqlmodel icon indicating copy to clipboard operation
sqlmodel copied to clipboard

Adding _init_private_attributes to SQLModel __init__ function.

Open alexisgaziello opened this issue 2 years ago β€’ 10 comments

Fixing:

Private attributes cannot be used in SQLModel. Attributes initialized with PrivateAttr cannot be found.

Proposed solution:

Add a missing line from Pydantic source code.

Note: the class method from_orm does have this initialization.

Related issues:

https://github.com/tiangolo/sqlmodel/issues/149 mentions this issue

Disclaimer

This is my first-ever contribution to an open-source project. I would appreciate any kind of feedback.

alexisgaziello avatar Oct 20 '22 19:10 alexisgaziello

πŸ“ Docs preview for commit 7a2b7af7b98f8d0e4722b7792acae10ddf9a9996 at: https://63519cbbc2f8c40065737d3f--sqlmodel.netlify.app

github-actions[bot] avatar Oct 20 '22 19:10 github-actions[bot]

Codecov Report

Base: 98.49% // Head: 98.50% // Increases project coverage by +0.00% :tada:

Coverage data is based on head (7a2b7af) compared to base (ea79c47). Patch coverage: 100.00% of modified lines in pull request are covered.

:exclamation: Current head 7a2b7af differs from pull request most recent head 844e21c. Consider uploading reports for the commit 844e21c to get more accurate results

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #472   +/-   ##
=======================================
  Coverage   98.49%   98.50%           
=======================================
  Files         185      186    +1     
  Lines        5856     5867   +11     
=======================================
+ Hits         5768     5779   +11     
  Misses         88       88           
Impacted Files Coverage Ξ”
tests/test_private_attributes.py 100.00% <100.00%> (ΓΈ)

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

:umbrella: View full report at Codecov.
:loudspeaker: Do you have feedback about the report comment? Let us know in this issue.

codecov[bot] avatar Oct 20 '22 19:10 codecov[bot]

Thanks for the fix! I've used it as a workaround in my project by adding it to the init method of my own model.

I noticed that when I loaded a model from the database the private attributes were not initialized because the init in SQLModel is not called. I had to call it separately on the model before being able to use the private attribute.

Have you tested whether your fix works when loading a model from the database?

t1mk1k avatar Nov 02 '22 17:11 t1mk1k

Thanks for the fix! I've used it as a workaround in my project by adding it to the init method of my own model.

I noticed that when I loaded a model from the database the private attributes were not initialized because the init in SQLModel is not called. I had to call it separately on the model before being able to use the private attribute.

Have you tested whether your fix works when loading a model from the database?

Do you mean in an example such as the one below?

test_id = uuid4()


class Hero(SQLModel, table=True):
    id: UUID = Field(default_factory=uuid4, primary_key=True)
    _private_hero_attribute: str = PrivateAttr(default="private hero value")

    metadata = MetaData()


with Session(engine) as session:
    hero_rusty_man = Hero(
        id=test_id,
    )
    session.add(hero_rusty_man)
    session.commit()

with Session(engine) as session:
    statement = select(Hero).where(Hero.id == test_id)
    hero = session.exec(statement).scalars().first()

    assert hero._private_hero_attribute == "private hero value"  # this fails

    hero = Hero.from_orm(hero)
    assert hero._private_hero_attribute == "private hero value"  # this doesn't fail

Indeed, when running SQLAlchemy, the private attributes don't get correctly initialized. Calling the method from_orm solves the problem for this particular case. Not ideal.

I haven't figured out if there is a way to "automate" this process. Maybe SQLAlchemy is using another init/factory_function?

alexisgaziello avatar Nov 03 '22 05:11 alexisgaziello

πŸ“ Docs preview for commit 844e21c717746da87e2fc4be0e7efa804b8cbce6 at: https://639ce0b204318b01e2823f64--sqlmodel.netlify.app

github-actions[bot] avatar Dec 16 '22 21:12 github-actions[bot]

Whats the status of this?

lucasgadams avatar Jan 26 '23 21:01 lucasgadams

Any chance we can get this merged?

lucas-labs avatar Jun 24 '23 08:06 lucas-labs

@lucas-labs the reality is that the fix doesn't fully work, as shown in https://github.com/tiangolo/sqlmodel/pull/472#issuecomment-1301647570

alexisgaziello avatar Jun 28 '23 17:06 alexisgaziello

Calling the method from_orm solves the problem for this particular case. Not ideal.

This is the way this works in Pydantic; the method is called from init, from_orm, and construct; we need to call it from any place where initialization is required. Could you release this fix? We have the entire infrastructure from a base model from where all objects inherit from a BaseModel to make it much simpler to handle connections. Still, without the ability to use private attributes, this makes it impossible to handle.

I imagine this is the case for many users since a model that represents a database object might need to have more often attributes that do not necessary link to the database itself and are used for internal logic.

AAraKKe avatar Feb 05 '24 18:02 AAraKKe

I think I am also running into this issue. I am retrieving an object from the database and want to copy it like described in the fastapi docs for partial updates.

object_from_db = session.exec(select_cmd).one_or_none()

copied_object = object_from_db.model_copy() 

The model_copy results in the following error: object has no attribute '__pydantic_extra__'. Did you mean: '__pydantic_private__'?

Is there a workaround I can use?

sharenz avatar Feb 10 '24 09:02 sharenz