allow uuid_format add more tests and update docs

This commit is contained in:
collerek
2020-11-28 11:28:15 +01:00
parent 740bb29ea5
commit 2350111768
5 changed files with 64 additions and 9 deletions

View File

@ -251,11 +251,21 @@ You can use either `length` and `precision` parameters or `max_digits` and `deci
### UUID ### UUID
`UUID()` has no required parameters. `UUID(uuid_format: str = 'hex')` has no required parameters.
* Sqlalchemy column: `ormar.UUID` based on `sqlalchemy.CHAR` field * Sqlalchemy column: `ormar.UUID` based on `sqlalchemy.CHAR(36)` or `sqlalchemy.CHAR(32)` field (for string or hex format respectively)
* Type (used for pydantic): `uuid.UUID` * Type (used for pydantic): `uuid.UUID`
`uuid_format` parameters allow 'hex'(default) or 'string' values.
Depending on the format either 32 or 36 char is used in the database.
Sample:
* 'hex' format value = "c616ab438cce49dbbf4380d109251dce" (CHAR(32))
* 'string' value = "c616ab43-8cce-49db-bf43-80d109251dce" (CHAR(36))
When loaded it's always python UUID so you can compare it and compare two formats values between each other.
[relations]: ./relations.md [relations]: ./relations.md
[queries]: ./queries.md [queries]: ./queries.md
[pydantic]: https://pydantic-docs.helpmanual.io/usage/types/#constrained-types [pydantic]: https://pydantic-docs.helpmanual.io/usage/types/#constrained-types

View File

@ -1,7 +1,11 @@
# 0.6.0 # 0.5.4
* **Breaking** Changed `UUID` field that was trimmed to 32 chars (without dashes) instead of 36 (with dashes) * Allow to pass `uuid_format` (allowed 'hex'(default) or 'string') to `UUID` field to change the format in which it's saved.
to more in line with other libraries. By default field is saved in hex format (trimmed to 32 chars (without dashes)), but you can pass
format='string' to use 36 (with dashes) instead to adjust to existing db or other libraries.
Sample:
hex value = c616ab438cce49dbbf4380d109251dce
string value = c616ab43-8cce-49db-bf43-80d109251dce
# 0.5.3 # 0.5.3

View File

@ -318,6 +318,21 @@ class Decimal(ModelFieldFactory, decimal.Decimal):
class UUID(ModelFieldFactory, uuid.UUID): class UUID(ModelFieldFactory, uuid.UUID):
_type = uuid.UUID _type = uuid.UUID
def __new__( # type: ignore # noqa CFQ002
cls, *, uuid_format: str = "hex", **kwargs: Any
) -> Type[BaseField]:
kwargs = {
**kwargs,
**{
k: v
for k, v in locals().items()
if k not in ["cls", "__class__", "kwargs"]
},
}
return super().__new__(cls, **kwargs)
@classmethod @classmethod
def get_column_type(cls, **kwargs: Any) -> Any: def get_column_type(cls, **kwargs: Any) -> Any:
return sqlalchemy_uuid.UUID() uuid_format = kwargs.get("uuid_format", "hex")
return sqlalchemy_uuid.UUID(uuid_format=uuid_format)

View File

@ -15,6 +15,10 @@ class UUID(TypeDecorator): # pragma nocover
impl = CHAR impl = CHAR
def __init__(self, *args: Any, uuid_format: str = "hex", **kwargs: Any) -> None:
super().__init__(*args, **kwargs)
self.uuid_format = uuid_format
def _cast_to_uuid(self, value: Union[str, int, bytes]) -> uuid.UUID: def _cast_to_uuid(self, value: Union[str, int, bytes]) -> uuid.UUID:
if not isinstance(value, uuid.UUID): if not isinstance(value, uuid.UUID):
if isinstance(value, bytes): if isinstance(value, bytes):
@ -28,7 +32,11 @@ class UUID(TypeDecorator): # pragma nocover
return ret_value return ret_value
def load_dialect_impl(self, dialect: DefaultDialect) -> Any: def load_dialect_impl(self, dialect: DefaultDialect) -> Any:
return dialect.type_descriptor(CHAR(36)) return (
dialect.type_descriptor(CHAR(36))
if self.uuid_format == "string"
else dialect.type_descriptor(CHAR(32))
)
def process_bind_param( def process_bind_param(
self, value: Union[str, int, bytes, uuid.UUID, None], dialect: DefaultDialect self, value: Union[str, int, bytes, uuid.UUID, None], dialect: DefaultDialect
@ -37,7 +45,7 @@ class UUID(TypeDecorator): # pragma nocover
return value return value
if not isinstance(value, uuid.UUID): if not isinstance(value, uuid.UUID):
value = self._cast_to_uuid(value) value = self._cast_to_uuid(value)
return str(value) return str(value) if self.uuid_format == "string" else "%.32x" % value.int
def process_result_value( def process_result_value(
self, value: Optional[str], dialect: DefaultDialect self, value: Optional[str], dialect: DefaultDialect

View File

@ -36,6 +36,16 @@ class UUIDSample(ormar.Model):
test_text: str = ormar.Text() test_text: str = ormar.Text()
class UUIDSample2(ormar.Model):
class Meta:
tablename = "uuids2"
metadata = metadata
database = database
id: uuid.UUID = ormar.UUID(primary_key=True, default=uuid.uuid4, uuid_format='string')
test_text: str = ormar.Text()
class User(ormar.Model): class User(ormar.Model):
class Meta: class Meta:
tablename = "users" tablename = "users"
@ -150,6 +160,14 @@ async def test_uuid_column():
assert item2.id == item3.id assert item2.id == item3.id
assert isinstance(item3.id, uuid.UUID) assert isinstance(item3.id, uuid.UUID)
u3 = await UUIDSample2(**u1.dict()).save()
u1_2 = await UUIDSample.objects.get(pk=u3.id)
assert u1_2 == u1
u4 = await UUIDSample2.objects.get(pk=u3.id)
assert u3 == u4
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_model_crud(): async def test_model_crud():
@ -303,7 +321,7 @@ async def test_model_limit_with_filter():
await User.objects.create(name="Tom") await User.objects.create(name="Tom")
assert ( assert (
len(await User.objects.limit(2).filter(name__iexact="Tom").all()) == 2 len(await User.objects.limit(2).filter(name__iexact="Tom").all()) == 2
) )