diff --git a/docs/releases.md b/docs/releases.md index 22523bd..48b0a0e 100644 --- a/docs/releases.md +++ b/docs/releases.md @@ -1,3 +1,12 @@ +# 0.10.15 + +## 🐛 Fixes + +* Fix generating pydantic models tree with nested models (by @pawamoy - thanks!) [#278](https://github.com/collerek/ormar/issues/278) +* Fix missing f-string in warning about missing primary key field [#274](https://github.com/collerek/ormar/issues/274) +* Fix passing foreign key value as relation (additional guard, fixed already in the latest release) [#270](https://github.com/collerek/ormar/issues/270) + + # 0.10.14 ## ✨ Features diff --git a/ormar/__init__.py b/ormar/__init__.py index 69929d3..c9f8bc8 100644 --- a/ormar/__init__.py +++ b/ormar/__init__.py @@ -76,7 +76,7 @@ class UndefinedType: # pragma no cover Undefined = UndefinedType() -__version__ = "0.10.14" +__version__ = "0.10.15" __all__ = [ "Integer", "BigInteger", diff --git a/ormar/models/helpers/sqlalchemy.py b/ormar/models/helpers/sqlalchemy.py index 113dd98..3ad53f1 100644 --- a/ormar/models/helpers/sqlalchemy.py +++ b/ormar/models/helpers/sqlalchemy.py @@ -152,7 +152,7 @@ def sqlalchemy_columns_from_model_fields( if len(model_fields.keys()) == 0: model_fields["id"] = ormar.Integer(name="id", primary_key=True) logging.warning( - "Table {table_name} had no fields so auto " + f"Table {new_model.Meta.tablename} had no fields so auto " "Integer primary key named `id` created." ) validate_related_names_in_relations(model_fields, new_model) diff --git a/ormar/models/mixins/pydantic_mixin.py b/ormar/models/mixins/pydantic_mixin.py index da15048..863a95f 100644 --- a/ormar/models/mixins/pydantic_mixin.py +++ b/ormar/models/mixins/pydantic_mixin.py @@ -97,7 +97,7 @@ class PydanticMixin(RelationMixin): include=cls._skip_ellipsis(include, name), exclude=cls._skip_ellipsis(exclude, name), relation_map=cls._skip_ellipsis( - relation_map, field, default_return=dict() + relation_map, name, default_return=dict() ), ) if field.is_multi or field.virtual: diff --git a/ormar/models/mixins/save_mixin.py b/ormar/models/mixins/save_mixin.py index f96c3b9..b70ce6d 100644 --- a/ormar/models/mixins/save_mixin.py +++ b/ormar/models/mixins/save_mixin.py @@ -121,7 +121,7 @@ class SavePrepareMixin(RelationMixin, AliasMixin): f"model without pk set!" ) model_dict[field] = pk_value - elif field_value: # nested dict + elif isinstance(field_value, (list, dict)) and field_value: if isinstance(field_value, list): model_dict[field] = [ target.get(target_pkname) for target in field_value diff --git a/tests/test_inheritance_and_pydantic_generation/test_nested_models_pydantic.py b/tests/test_inheritance_and_pydantic_generation/test_nested_models_pydantic.py new file mode 100644 index 0000000..a96191c --- /dev/null +++ b/tests/test_inheritance_and_pydantic_generation/test_nested_models_pydantic.py @@ -0,0 +1,75 @@ +import databases +import sqlalchemy + +import ormar +from tests.settings import DATABASE_URL + +metadata = sqlalchemy.MetaData() +database = databases.Database(DATABASE_URL, force_rollback=True) + + +class BaseMeta(ormar.ModelMeta): + metadata = metadata + database = database + + +class Library(ormar.Model): + class Meta(BaseMeta): + pass + + id: int = ormar.Integer(primary_key=True) + name: str = ormar.String(max_length=100) + + +class Package(ormar.Model): + class Meta(BaseMeta): + pass + + id: int = ormar.Integer(primary_key=True) + library: Library = ormar.ForeignKey(Library, related_name="packages") + version: str = ormar.String(max_length=100) + + +class Ticket(ormar.Model): + class Meta(BaseMeta): + pass + + id: int = ormar.Integer(primary_key=True) + number: int = ormar.Integer() + status: str = ormar.String(max_length=100) + + +class TicketPackage(ormar.Model): + class Meta(BaseMeta): + pass + + id: int = ormar.Integer(primary_key=True) + status: str = ormar.String(max_length=100) + ticket: Ticket = ormar.ForeignKey(Ticket, related_name="packages") + package: Package = ormar.ForeignKey(Package, related_name="tickets") + + +def test_have_proper_children(): + TicketPackageOut = TicketPackage.get_pydantic(exclude={"ticket"}) + assert "package" in TicketPackageOut.__fields__ + PydanticPackage = TicketPackageOut.__fields__["package"].type_ + assert "library" in PydanticPackage.__fields__ + + +def test_casts_properly(): + payload = { + "id": 0, + "status": "string", + "ticket": {"id": 0, "number": 0, "status": "string"}, + "package": { + "version": "string", + "id": 0, + "library": {"id": 0, "name": "string"}, + }, + } + test_package = TicketPackage(**payload) + TicketPackageOut = TicketPackage.get_pydantic(exclude={"ticket"}) + parsed = TicketPackageOut(**test_package.dict()).dict() + assert "ticket" not in parsed + assert "package" in parsed + assert "library" in parsed.get("package") diff --git a/tests/test_model_definition/test_foreign_key_value_used_for_related_model.py b/tests/test_model_definition/test_foreign_key_value_used_for_related_model.py new file mode 100644 index 0000000..cc24cb4 --- /dev/null +++ b/tests/test_model_definition/test_foreign_key_value_used_for_related_model.py @@ -0,0 +1,81 @@ +import uuid +from typing import List, Optional + +import databases +import pytest +import sqlalchemy + +import ormar +from tests.settings import DATABASE_URL + +database = databases.Database(DATABASE_URL, force_rollback=True) +metadata = sqlalchemy.MetaData() + + +class BaseMeta(ormar.ModelMeta): + metadata = metadata + database = database + + +class PageLink(ormar.Model): + class Meta(BaseMeta): + tablename = "pagelinks" + + id: int = ormar.Integer(primary_key=True) + value: str = ormar.String(max_length=2048) + country: str = ormar.String(max_length=1000) + + +class Post(ormar.Model): + class Meta(BaseMeta): + tablename = "posts" + + id: int = ormar.Integer(primary_key=True) + title: str = ormar.String(max_length=500) + link: PageLink = ormar.ForeignKey( + PageLink, related_name="posts", ondelete="CASCADE" + ) + + +class Department(ormar.Model): + class Meta(BaseMeta): + pass + + id: uuid.UUID = ormar.UUID(primary_key=True, default=uuid.uuid4()) + name: str = ormar.String(max_length=100) + + +class Course(ormar.Model): + class Meta(BaseMeta): + pass + + id: int = ormar.Integer(primary_key=True) + name: str = ormar.String(max_length=100) + completed: bool = ormar.Boolean(default=False) + department: Optional[Department] = ormar.ForeignKey(Department) + + +@pytest.fixture(autouse=True, scope="module") +def create_test_database(): + engine = sqlalchemy.create_engine(DATABASE_URL) + metadata.create_all(engine) + yield + metadata.drop_all(engine) + + +@pytest.mark.asyncio +async def test_pass_int_values_as_fk(): + async with database: + async with database.transaction(force_rollback=True): + link = await PageLink(id=1, value="test", country="USA").save() + await Post.objects.create(title="My post", link=link.id) + post_check = await Post.objects.select_related("link").get() + assert post_check.link == link + + +@pytest.mark.asyncio +async def test_pass_uuid_value_as_fk(): + async with database: + async with database.transaction(force_rollback=True): + dept = await Department(name="Department test").save() + await Course(name="Test course", department=dept.id).save()