From f5459ec6208e3d12b46e34dae23d78c7d300e622 Mon Sep 17 00:00:00 2001 From: Benedict Etzel Date: Tue, 19 Sep 2023 13:19:06 +0200 Subject: [PATCH 1/3] make TypeID hashable --- tests/test_typeid.py | 12 ++++++++++++ typeid/typeid.py | 3 +++ 2 files changed, 15 insertions(+) diff --git a/tests/test_typeid.py b/tests/test_typeid.py index 159601d..f59658d 100644 --- a/tests/test_typeid.py +++ b/tests/test_typeid.py @@ -81,3 +81,15 @@ def test_construct_type_from_uuid_with_prefix() -> None: assert isinstance(typeid, TypeID) assert typeid.prefix == "prefix" assert isinstance(typeid.suffix, str) + + +def test_hash_type_id() -> None: + prefix = "plov" + suffix = "00041061050r3gg28a1c60t3gf" + + typeid_1 = TypeID(prefix=prefix, suffix=suffix) + typeid_2 = TypeID(prefix=prefix, suffix=suffix) + typeid_3 = TypeID(suffix=suffix) + + assert hash(typeid_1) == hash(typeid_2) + assert hash(typeid_3) != hash(typeid_1) diff --git a/typeid/typeid.py b/typeid/typeid.py index 966cdeb..3ff3fb7 100644 --- a/typeid/typeid.py +++ b/typeid/typeid.py @@ -38,6 +38,9 @@ def __eq__(self, value: object) -> bool: return False return value.prefix == self.prefix and value.suffix == self.suffix + def __hash__(self) -> int: + return hash((self.prefix, self.suffix)) + def from_string(string: str) -> TypeID: prefix, suffix = get_prefix_and_suffix(string=string) From ebd731866a2755ebcd67116a7183fe14b4b70e3b Mon Sep 17 00:00:00 2001 From: Benedict Etzel Date: Mon, 25 Sep 2023 11:28:13 +0200 Subject: [PATCH 2/3] add TypeID.uuid property --- typeid/typeid.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/typeid/typeid.py b/typeid/typeid.py index 3ff3fb7..ba1ce4c 100644 --- a/typeid/typeid.py +++ b/typeid/typeid.py @@ -26,6 +26,10 @@ def suffix(self) -> str: def prefix(self) -> str: return self._prefix + @property + def uuid(self) -> UUID: + return _convert_b32_to_uuid(self.suffix) + def __str__(self) -> str: value = "" if self.prefix: @@ -69,3 +73,7 @@ def get_prefix_and_suffix(string: str) -> tuple: def _convert_uuid_to_b32(uuid_instance: UUID) -> str: return base32.encode(list(uuid_instance.bytes)) + + +def _convert_b32_to_uuid(b32: str) -> UUID: + return UUID(bytes=bytes(base32.decode(b32))) From 039608a1a55aecf8de16ef6f0996d92a95a16714 Mon Sep 17 00:00:00 2001 From: Benedict Etzel Date: Mon, 25 Sep 2023 11:28:13 +0200 Subject: [PATCH 3/3] add SQLAlchemy example --- examples/sqlalchemy.py | 51 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 examples/sqlalchemy.py diff --git a/examples/sqlalchemy.py b/examples/sqlalchemy.py new file mode 100644 index 0000000..64f0972 --- /dev/null +++ b/examples/sqlalchemy.py @@ -0,0 +1,51 @@ +from typing import Optional + +from sqlalchemy import types +from sqlalchemy.util import generic_repr +from typeid import TypeID, from_uuid + + +class TypeIDType(types.TypeDecorator): + """ + A SQLAlchemy TypeDecorator that allows storing TypeIDs in the database. + The prefix will not be persisted, instead the database-native UUID field will be used. + At retrieval time a TypeID will be constructed based on the configured prefix and the + UUID value from the database. + + Usage: + # will result in TypeIDs such as "user_01h45ytscbebyvny4gc8cr8ma2" + id = mapped_column( + TypeIDType("user"), + primary_key=True, + default=lambda: TypeID("user") + ) + """ + impl = types.Uuid + + cache_ok = True + + prefix: Optional[str] + + def __init__(self, prefix: Optional[str], *args, **kwargs): + self.prefix = prefix + super().__init__(*args, **kwargs) + + def __repr__(self) -> str: + # Customize __repr__ to ensure that auto-generated code e.g. from alembic includes + # the right __init__ params (otherwise by default prefix will be omitted because + # uuid.__init__ does not have such an argument). + return generic_repr( + self, + to_inspect=TypeID(self.prefix), + ) + + def process_bind_param(self, value: TypeID, dialect): + if self.prefix is None: + assert value.prefix is None + else: + assert value.prefix == self.prefix + + return value.uuid + + def process_result_value(self, value, dialect): + return from_uuid(value, self.prefix)