Skip to content

Fixed PrivateAttr defaults ignored on database-loaded instances#1818

Draft
xr843 wants to merge 2 commits intofastapi:mainfrom
xr843:fix/149-private-attr-defaults
Draft

Fixed PrivateAttr defaults ignored on database-loaded instances#1818
xr843 wants to merge 2 commits intofastapi:mainfrom
xr843:fix/149-private-attr-defaults

Conversation

@xr843
Copy link

@xr843 xr843 commented Mar 18, 2026

Summary

Fixes #149

  • When SQLAlchemy reconstructs a model instance from a database query (bypassing __init__), it calls __new__ which invokes init_pydantic_private_attrs(). That function was setting __pydantic_private__ = None instead of initializing it with the private attribute defaults.
  • This caused AttributeError when accessing PrivateAttr fields on database-loaded instances.
  • The fix introspects the class's __private_attributes__ dict and calls get_default() on each entry to properly initialize __pydantic_private__, mirroring what Pydantic's own BaseModel.__init__ does.

Test plan

  • Create a SQLModel with PrivateAttr(default=...) fields
  • Save an instance to the database
  • Load the instance back from the database
  • Verify that accessing the private attribute returns the default value instead of raising AttributeError

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 4.6 (1M context) noreply@anthropic.com

When SQLAlchemy reconstructs a model instance from a database query
(bypassing __init__), it goes through __new__ which calls
init_pydantic_private_attrs(). That function was setting
__pydantic_private__ = None instead of initializing it with the
private attribute defaults. This caused AttributeError when accessing
PrivateAttr fields on database-loaded instances.

The fix introspects the class's __private_attributes__ dict and calls
get_default() on each entry, mirroring what Pydantic's own __init__
does.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@YuriiMotov YuriiMotov marked this pull request as draft March 18, 2026 07:45
@YuriiMotov
Copy link
Member

@xr843, please, review and test changes before opening PR for review

- Used getattr() instead of direct attribute access to satisfy mypy's
  union-attr check on InstanceOrType.
- Added regression tests verifying PrivateAttr with default and
  default_factory work correctly on database-loaded instances.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@xr843
Copy link
Author

xr843 commented Mar 18, 2026

Thank you for the feedback @YuriiMotov. I've addressed the issues:

  1. Fixed mypy error: Used getattr() instead of direct __private_attributes__ access to satisfy mypy's union-attr check on the InstanceOrType union type.

  2. Added regression tests (tests/test_private_attr.py):

    • test_private_attr_default_preserved_after_db_load — verifies PrivateAttr(default=...) works on DB-loaded instances
    • test_private_attr_default_factory_preserved_after_db_load — verifies PrivateAttr(default_factory=...) works on DB-loaded instances

Both tests confirm the fix resolves the AttributeError that previously occurred when accessing private attributes on instances reconstructed from the database (bypassing __init__).

Ready for re-review when you have a chance.

@github-actions github-actions bot removed the waiting label Mar 18, 2026
@svlandeg
Copy link
Member

Ready for re-review when you have a chance.

Please review the CI errors as well.

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Pydantic.PrivateAttr default and default_factory are ignored by SQLModel

4 participants