Skip to content

Support hybrid_property, column_property, declared_attr #801

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 51 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
5471ece
Support hybrid_property
50Bytes-dev Feb 14, 2024
c363ce6
🎨 [pre-commit.ci] Auto format from pre-commit.com hooks
pre-commit-ci[bot] Feb 14, 2024
5bf07f8
fix
50Bytes-dev Feb 14, 2024
224c74b
🎨 [pre-commit.ci] Auto format from pre-commit.com hooks
pre-commit-ci[bot] Feb 14, 2024
3f82be3
fix
50Bytes-dev Feb 14, 2024
953c01d
Merge remote-tracking branch 'origin/main'
50Bytes-dev Feb 14, 2024
e5dad94
add declared_attr, column_property support
50Bytes-dev Mar 1, 2024
6bfff90
🎨 [pre-commit.ci] Auto format from pre-commit.com hooks
pre-commit-ci[bot] Mar 1, 2024
e5bb32a
Merge branch 'tiangolo:main' into main
50Bytes-dev Mar 1, 2024
5ade49a
fix tests
50Bytes-dev Mar 1, 2024
b6e2caf
fix tests
50Bytes-dev Mar 1, 2024
e0b201d
Update sqlmodel/main.py
50Bytes-dev Mar 7, 2024
7ac7889
Update sqlmodel/main.py
50Bytes-dev Mar 7, 2024
bb25d90
Update sqlmodel/main.py
50Bytes-dev Mar 7, 2024
8da2993
Merge branch 'tiangolo:main' into main
50Bytes-dev May 10, 2024
7a53368
fix
50Bytes-dev May 16, 2024
87d9c02
🎨 [pre-commit.ci] Auto format from pre-commit.com hooks
pre-commit-ci[bot] May 16, 2024
0fa2d40
fix
50Bytes-dev May 16, 2024
7f0aa44
Merge branch 'main' of github.com:50Bytes-dev/sqlmodel
50Bytes-dev May 16, 2024
2e76370
fix
50Bytes-dev May 28, 2024
a6b81de
🎨 [pre-commit.ci] Auto format from pre-commit.com hooks
pre-commit-ci[bot] May 28, 2024
23929c3
Merge branch 'tiangolo:main' into main
50Bytes-dev May 28, 2024
d5aa7dd
Merge branch 'tiangolo:main' into main
50Bytes-dev Jun 19, 2024
f4dadc5
fix
50Bytes-dev Jun 19, 2024
fba4308
🎨 [pre-commit.ci] Auto format from pre-commit.com hooks
pre-commit-ci[bot] Jun 19, 2024
4afc41b
fix list type
50Bytes-dev Jun 20, 2024
7799fe9
Merge branch 'main' of github.com:50Bytes-dev/sqlmodel
50Bytes-dev Jun 20, 2024
d06c75a
add validation_alias
50Bytes-dev Jun 20, 2024
c980f31
fix
50Bytes-dev Jun 20, 2024
74d3129
Merge remote-tracking branch 'upstream/main'
50Bytes-dev Nov 5, 2024
1fae234
🎨 [pre-commit.ci] Auto format from pre-commit.com hooks
pre-commit-ci[bot] Nov 5, 2024
88acd2a
fix
50Bytes-dev Nov 5, 2024
47b88bf
Merge branch 'main' of github.com:50Bytes-dev/sqlmodel
50Bytes-dev Nov 5, 2024
b08c757
🎨 [pre-commit.ci] Auto format from pre-commit.com hooks
pre-commit-ci[bot] Nov 5, 2024
9db90a5
fix
50Bytes-dev Nov 5, 2024
ecd3997
Merge branch 'main' of github.com:50Bytes-dev/sqlmodel
50Bytes-dev Nov 5, 2024
14c5aba
add association proxy support
50Bytes-dev Nov 5, 2024
44e0cbe
🎨 [pre-commit.ci] Auto format from pre-commit.com hooks
pre-commit-ci[bot] Nov 5, 2024
b1ae757
fix: update return type of col function to Column
50Bytes-dev Mar 4, 2025
0c96495
🎨 [pre-commit.ci] Auto format from pre-commit.com hooks
pre-commit-ci[bot] Mar 4, 2025
fdcd531
fix: extend sa_column type to include MappedSQLExpression
50Bytes-dev Mar 21, 2025
cf6b48b
Merge branch 'main' of github.com:50Bytes-dev/sqlmodel
50Bytes-dev Mar 21, 2025
b991725
fix
50Bytes-dev Mar 22, 2025
683b0c7
fix
50Bytes-dev Mar 22, 2025
e141966
Add comprehensive tests for Pydantic to SQLModel conversion and relat…
50Bytes-dev Jun 4, 2025
1229a44
refactor: streamline relationship update tests for Pydantic to SQLMod…
50Bytes-dev Jun 4, 2025
244069f
Fix: Correct relationship updates with forward references and test logic
google-labs-jules[bot] Jun 4, 2025
b5d7b2d
Merge pull request #1 from 50Bytes-dev/fix/relationship-update-forwar…
50Bytes-dev Jun 4, 2025
c875330
feat: enhance relationship handling for Pydantic to SQLModel conversi…
50Bytes-dev Jun 5, 2025
170b343
feat: enhance handling of association proxies in SQLModel initialization
50Bytes-dev Jul 3, 2025
c6f39f0
chore: add pdm.lock and .pdm-python to .gitignore
50Bytes-dev Jul 3, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,7 @@ site
*.db
.cache
.venv*
uv.lock
.timetracker
pdm.lock
.pdm-python
9 changes: 9 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -134,3 +134,12 @@ known-third-party = ["sqlmodel", "sqlalchemy", "pydantic", "fastapi"]
[tool.ruff.lint.pyupgrade]
# Preserve types, even if a file imports `from __future__ import annotations`.
keep-runtime-typing = true

[dependency-groups]
dev = [
"coverage>=7.2.7",
"dirty-equals>=0.7.1.post0",
"fastapi>=0.103.2",
"httpx>=0.24.1",
"pytest>=7.4.4",
]
1 change: 1 addition & 0 deletions sqlmodel/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@
from .main import Field as Field
from .main import Relationship as Relationship
from .main import SQLModel as SQLModel
from .main import SQLModelConfig as SQLModelConfig
from .orm.session import Session as Session
from .sql.expression import all_ as all_
from .sql.expression import and_ as and_
Expand Down
54 changes: 42 additions & 12 deletions sqlmodel/_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
)

from pydantic import VERSION as P_VERSION
from pydantic import BaseModel
from pydantic import BaseModel, ConfigDict
from pydantic.fields import FieldInfo
from typing_extensions import Annotated, get_args, get_origin

Expand Down Expand Up @@ -289,6 +289,10 @@ def sqlmodel_table_construct(
value = values.get(key, Undefined)
if value is not Undefined:
setattr(self_instance, key, value)
for key in self_instance.__sqlalchemy_association_proxies__:
value = values.get(key, Undefined)
if value is not Undefined:
setattr(self_instance, key, value)
# End SQLModel override
return self_instance

Expand Down Expand Up @@ -339,7 +343,20 @@ def sqlmodel_validate(
# Get and set any relationship objects
if is_table_model_class(cls):
for key in new_obj.__sqlmodel_relationships__:
value = getattr(use_obj, key, Undefined)
# Handle both dict and object access
if isinstance(use_obj, dict):
value = use_obj.get(key, Undefined)
else:
value = getattr(use_obj, key, Undefined)
if value is not Undefined:
setattr(new_obj, key, value)
# Get and set any association proxy objects
for key in new_obj.__sqlalchemy_association_proxies__:
# Handle both dict and object access
if isinstance(use_obj, dict):
value = use_obj.get(key, Undefined)
else:
value = getattr(use_obj, key, Undefined)
if value is not Undefined:
setattr(new_obj, key, value)
return new_obj
Expand Down Expand Up @@ -385,7 +402,7 @@ def sqlmodel_init(*, self: "SQLModel", data: Dict[str, Any]) -> None:
Representation as Representation,
)

class SQLModelConfig(BaseConfig): # type: ignore[no-redef]
class SQLModelConfig(ConfigDict): # type: ignore[no-redef]
table: Optional[bool] = None # type: ignore[misc]
registry: Optional[Any] = None # type: ignore[misc]

Expand All @@ -403,12 +420,12 @@ def set_config_value(
setattr(model.__config__, parameter, value) # type: ignore

def get_model_fields(model: InstanceOrType[BaseModel]) -> Dict[str, "FieldInfo"]:
return model.__fields__ # type: ignore
return model.model_fields

def get_fields_set(
object: InstanceOrType["SQLModel"],
) -> Union[Set[str], Callable[[BaseModel], Set[str]]]:
return object.__fields_set__
) -> Union[Set[str], property]:
return object.model_fields_set

def init_pydantic_private_attrs(new_object: InstanceOrType["SQLModel"]) -> None:
object.__setattr__(new_object, "__fields_set__", set())
Expand Down Expand Up @@ -479,7 +496,7 @@ def _calculate_keys(
# Do not include relationships as that would easily lead to infinite
# recursion, or traversing the whole database
return (
self.__fields__.keys() # noqa
self.model_fields.keys() # noqa
) # | self.__sqlmodel_relationships__.keys()

keys: AbstractSet[str]
Expand All @@ -492,7 +509,7 @@ def _calculate_keys(
# Do not include relationships as that would easily lead to infinite
# recursion, or traversing the whole database
keys = (
self.__fields__.keys() # noqa
self.model_fields.keys() # noqa
) # | self.__sqlmodel_relationships__.keys()
if include is not None:
keys &= include.keys()
Expand Down Expand Up @@ -548,16 +565,27 @@ def sqlmodel_validate(
setattr(m, key, value)
# Continue with standard Pydantic logic
object.__setattr__(m, "__fields_set__", fields_set)
# Handle non-Pydantic fields like relationships and association proxies
if getattr(cls.__config__, "table", False): # noqa
non_pydantic_keys = set(obj.keys()) - set(values.keys())
for key in non_pydantic_keys:
if (
hasattr(m, "__sqlmodel_relationships__")
and key in m.__sqlmodel_relationships__
):
setattr(m, key, obj[key])
elif (
hasattr(m, "__sqlalchemy_association_proxies__")
and key in m.__sqlalchemy_association_proxies__
):
setattr(m, key, obj[key])
m._init_private_attributes() # type: ignore[attr-defined] # noqa
return m

def sqlmodel_init(*, self: "SQLModel", data: Dict[str, Any]) -> None:
values, fields_set, validation_error = validate_model(self.__class__, data)
# Only raise errors if not a SQLModel model
if (
not is_table_model_class(self.__class__) # noqa
and validation_error
):
if not is_table_model_class(self.__class__) and validation_error: # noqa
raise validation_error
if not is_table_model_class(self.__class__):
object.__setattr__(self, "__dict__", values)
Expand All @@ -573,3 +601,5 @@ def sqlmodel_init(*, self: "SQLModel", data: Dict[str, Any]) -> None:
for key in non_pydantic_keys:
if key in self.__sqlmodel_relationships__:
setattr(self, key, data[key])
elif key in self.__sqlalchemy_association_proxies__:
setattr(self, key, data[key])
Loading