From f5fd8504b9f4a939a19d8d9f8428e8dee27b74ad Mon Sep 17 00:00:00 2001 From: "50bytes.dev" <50bytes.dev@gmail.com> Date: Thu, 24 Aug 2023 15:08:02 +0300 Subject: [PATCH 1/2] fix model_copy --- sqlmodel/main.py | 4 ++++ tests/test_model_copy.py | 49 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 tests/test_model_copy.py diff --git a/sqlmodel/main.py b/sqlmodel/main.py index a593a53..3981058 100644 --- a/sqlmodel/main.py +++ b/sqlmodel/main.py @@ -596,6 +596,10 @@ class SQLModel(BaseModel, metaclass=SQLModelMetaclass, registry=default_registry # in the Pydantic model so that when SQLAlchemy sets attributes that are # added (e.g. when querying from DB) to the __fields_set__, this already exists object.__setattr__(new_object, "__pydantic_fields_set__", set()) + if not hasattr(new_object, "__pydantic_extra__"): + object.__setattr__(new_object, "__pydantic_extra__", None) + if not hasattr(new_object, "__pydantic_private__"): + object.__setattr__(new_object, "__pydantic_private__", None) return new_object def __init__(__pydantic_self__, **data: Any) -> None: diff --git a/tests/test_model_copy.py b/tests/test_model_copy.py new file mode 100644 index 0000000..30f7e0a --- /dev/null +++ b/tests/test_model_copy.py @@ -0,0 +1,49 @@ +from typing import Optional + +import pytest +from pydantic import field_validator +from pydantic.error_wrappers import ValidationError +from sqlmodel import SQLModel, create_engine, Session, Field + + +def test_model_copy(clear_sqlmodel): + """Test validation of implicit and explict None values. + + # For consistency with pydantic, validators are not to be called on + # arguments that are not explicitly provided. + + https://github.com/tiangolo/sqlmodel/issues/230 + https://github.com/samuelcolvin/pydantic/issues/1223 + + """ + + class Hero(SQLModel, table=True): + id: Optional[int] = Field(default=None, primary_key=True) + name: str + secret_name: str + age: Optional[int] = None + + hero = Hero(name="Deadpond", secret_name="Dive Wilson", age=25) + + engine = create_engine("sqlite://") + + SQLModel.metadata.create_all(engine) + + with Session(engine) as session: + session.add(hero) + session.commit() + session.refresh(hero) + + model_copy = hero.model_copy(update={"name": "Deadpond Copy"}) + + assert model_copy.name == "Deadpond Copy" and \ + model_copy.secret_name == "Dive Wilson" and \ + model_copy.age == 25 + + db_hero = session.get(Hero, hero.id) + + db_copy = db_hero.model_copy(update={"name": "Deadpond Copy"}) + + assert db_copy.name == "Deadpond Copy" and \ + db_copy.secret_name == "Dive Wilson" and \ + db_copy.age == 25 From 45ab47266c1fd944e73013039efecbb6ca35d3a6 Mon Sep 17 00:00:00 2001 From: "50bytes.dev" <50bytes.dev@gmail.com> Date: Thu, 24 Aug 2023 18:41:34 +0300 Subject: [PATCH 2/2] fix --- sqlmodel/main.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/sqlmodel/main.py b/sqlmodel/main.py index 3981058..dd41cc9 100644 --- a/sqlmodel/main.py +++ b/sqlmodel/main.py @@ -649,7 +649,10 @@ class SQLModel(BaseModel, metaclass=SQLModelMetaclass, registry=default_registry # remove defaults so they don't get validated data = {} for key, value in validated: - field = cls.model_fields[key] + field = cls.model_fields.get(key) + + if field is None: + continue if ( hasattr(field, "default")