From 304fe6aafcc922fe63df2ebde93ad580905bffc1 Mon Sep 17 00:00:00 2001 From: collerek Date: Wed, 30 Sep 2020 11:17:03 +0200 Subject: [PATCH 1/3] add uuid field and tests --- .coverage | Bin 53248 -> 53248 bytes ormar/__init__.py | 2 ++ ormar/fields/__init__.py | 2 ++ ormar/fields/model_fields.py | 11 ++++++ ormar/fields/sqlalchemy_uuid.py | 59 ++++++++++++++++++++++++++++++++ ormar/queryset/queryset.py | 3 +- tests/test_models.py | 33 ++++++++++++++++++ 7 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 ormar/fields/sqlalchemy_uuid.py diff --git a/.coverage b/.coverage index 2485861228f957321a3f20d9865554d979154796..2d8a6bb12748c7e76270cd3f6e685c33be3acda8 100644 GIT binary patch delta 2113 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 Any: + return sqlalchemy_uuid.UUID() diff --git a/ormar/fields/sqlalchemy_uuid.py b/ormar/fields/sqlalchemy_uuid.py new file mode 100644 index 0000000..fbf1388 --- /dev/null +++ b/ormar/fields/sqlalchemy_uuid.py @@ -0,0 +1,59 @@ +import uuid +from typing import Any, Optional, Union + +from sqlalchemy.dialects.postgresql import UUID as psqlUUID +from sqlalchemy.engine.default import DefaultDialect +from sqlalchemy.types import CHAR, TypeDecorator + + +class UUID(TypeDecorator): # pragma nocover + """Platform-independent GUID type. + + Uses Postgresql's UUID type, otherwise uses + CHAR(32), to store UUID. + + """ + + impl = CHAR + + def _cast_to_uuid(self, value: Union[str, int, bytes]) -> uuid.UUID: + if not isinstance(value, uuid.UUID): + if isinstance(value, bytes): + ret_value = uuid.UUID(bytes=value) + elif isinstance(value, int): + ret_value = uuid.UUID(int=value) + elif isinstance(value, str): + ret_value = uuid.UUID(value) + else: + ret_value = value + return ret_value + + def load_dialect_impl(self, dialect: DefaultDialect) -> Any: + if dialect.name == "postgresql": + return dialect.type_descriptor(psqlUUID()) + else: + return dialect.type_descriptor(CHAR(32)) + + def process_bind_param( + self, value: Union[str, int, bytes, uuid.UUID, None], dialect: DefaultDialect + ) -> Optional[str]: + if value is None: + return value + elif not isinstance(value, uuid.UUID): + value = self._cast_to_uuid(value) + if dialect.name == "postgresql": + return str(value) + else: + return "%.32x" % value.int + + def process_result_value( + self, value: Optional[str], dialect: DefaultDialect + ) -> Optional[uuid.UUID]: + if value is None: + return value + if dialect.name == "postgresql": + return uuid.UUID(value) + else: + if not isinstance(value, uuid.UUID): + return uuid.UUID(value) + return value diff --git a/ormar/queryset/queryset.py b/ormar/queryset/queryset.py index 1430b10..427e95a 100644 --- a/ormar/queryset/queryset.py +++ b/ormar/queryset/queryset.py @@ -261,14 +261,15 @@ class QuerySet: expr = self.table.insert() expr = expr.values(**new_kwargs) - # Execute the insert, and return a new model instance. instance = self.model(**kwargs) pk = await self.database.execute(expr) + pk_name = self.model_meta.pkname if pk_name not in kwargs and pk_name in new_kwargs: instance.pk = new_kwargs[self.model_meta.pkname] if pk and isinstance(pk, self.model.pk_type()): setattr(instance, self.model_meta.pkname, pk) + return instance async def bulk_create(self, objects: List["Model"]) -> None: diff --git a/tests/test_models.py b/tests/test_models.py index 79ca0ed..f33d855 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -6,6 +6,7 @@ import databases import pydantic import pytest import sqlalchemy +import uuid import ormar from ormar.exceptions import QueryDefinitionError, NoMatch @@ -25,6 +26,16 @@ class JsonSample(ormar.Model): test_json: ormar.JSON(nullable=True) +class UUIDSample(ormar.Model): + class Meta: + tablename = "uuids" + metadata = metadata + database = database + + id: ormar.UUID(primary_key=True, default=uuid.uuid4) + test_text: ormar.Text() + + class User(ormar.Model): class Meta: tablename = "users" @@ -113,6 +124,28 @@ async def test_json_column(): assert items[1].test_json == dict(aa=12) +@pytest.mark.asyncio +async def test_uuid_column(): + async with database: + async with database.transaction(force_rollback=True): + u1 = await UUIDSample.objects.create(test_text="aa") + u2 = await UUIDSample.objects.create(test_text="bb") + + items = await UUIDSample.objects.all() + assert len(items) == 2 + + assert isinstance(items[0].id, uuid.UUID) + assert isinstance(items[1].id, uuid.UUID) + + assert items[0].id in (u1.id, u2.id) + assert items[1].id in (u1.id, u2.id) + + assert items[0].id != items[1].id + + item = await UUIDSample.objects.filter(id=u1.id).get() + assert item.id == u1.id + + @pytest.mark.asyncio async def test_model_crud(): async with database: From f4a45381b0b7c27b848a4a966d0733094684d07d Mon Sep 17 00:00:00 2001 From: collerek Date: Wed, 30 Sep 2020 11:52:44 +0200 Subject: [PATCH 2/3] switch all together to char as postgres is failing --- .coverage | Bin 53248 -> 53248 bytes ormar/fields/sqlalchemy_uuid.py | 18 ++++-------------- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/.coverage b/.coverage index 2d8a6bb12748c7e76270cd3f6e685c33be3acda8..de993f7e8cb46ecfd46c4f95839865ae0d37eff8 100644 GIT binary patch delta 137 zcmV;40CxX?paX!Q1F%9b1vN1`F*vhFFTYS$U;q#K59$xx577_B53>)N4~-9C4^j_G z4paX!Q1F%9b1u-@{G&HkEFTYS$UjPsJ59$xx577_B53>)N4~-9C4^a6KmvvtW;)Krh)VvH$=8 diff --git a/ormar/fields/sqlalchemy_uuid.py b/ormar/fields/sqlalchemy_uuid.py index fbf1388..1ad7e65 100644 --- a/ormar/fields/sqlalchemy_uuid.py +++ b/ormar/fields/sqlalchemy_uuid.py @@ -1,7 +1,6 @@ import uuid from typing import Any, Optional, Union -from sqlalchemy.dialects.postgresql import UUID as psqlUUID from sqlalchemy.engine.default import DefaultDialect from sqlalchemy.types import CHAR, TypeDecorator @@ -29,10 +28,7 @@ class UUID(TypeDecorator): # pragma nocover return ret_value def load_dialect_impl(self, dialect: DefaultDialect) -> Any: - if dialect.name == "postgresql": - return dialect.type_descriptor(psqlUUID()) - else: - return dialect.type_descriptor(CHAR(32)) + return dialect.type_descriptor(CHAR(32)) def process_bind_param( self, value: Union[str, int, bytes, uuid.UUID, None], dialect: DefaultDialect @@ -41,19 +37,13 @@ class UUID(TypeDecorator): # pragma nocover return value elif not isinstance(value, uuid.UUID): value = self._cast_to_uuid(value) - if dialect.name == "postgresql": - return str(value) - else: - return "%.32x" % value.int + return "%.32x" % value.int def process_result_value( self, value: Optional[str], dialect: DefaultDialect ) -> Optional[uuid.UUID]: if value is None: return value - if dialect.name == "postgresql": + if not isinstance(value, uuid.UUID): return uuid.UUID(value) - else: - if not isinstance(value, uuid.UUID): - return uuid.UUID(value) - return value + return value From b7b23ae780291e07a34357387ee0d6d38efb4a1b Mon Sep 17 00:00:00 2001 From: collerek Date: Wed, 30 Sep 2020 11:57:25 +0200 Subject: [PATCH 3/3] fix elif after return --- ormar/fields/sqlalchemy_uuid.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ormar/fields/sqlalchemy_uuid.py b/ormar/fields/sqlalchemy_uuid.py index 1ad7e65..aca381a 100644 --- a/ormar/fields/sqlalchemy_uuid.py +++ b/ormar/fields/sqlalchemy_uuid.py @@ -35,7 +35,7 @@ class UUID(TypeDecorator): # pragma nocover ) -> Optional[str]: if value is None: return value - elif not isinstance(value, uuid.UUID): + if not isinstance(value, uuid.UUID): value = self._cast_to_uuid(value) return "%.32x" % value.int