Skip to content

Commit 88d058c

Browse files
authored
Merge pull request #5 from beheh/hashable
make compatible with sqlalchemy
2 parents 7a02841 + 039608a commit 88d058c

File tree

3 files changed

+74
-0
lines changed

3 files changed

+74
-0
lines changed

examples/sqlalchemy.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
from typing import Optional
2+
3+
from sqlalchemy import types
4+
from sqlalchemy.util import generic_repr
5+
from typeid import TypeID, from_uuid
6+
7+
8+
class TypeIDType(types.TypeDecorator):
9+
"""
10+
A SQLAlchemy TypeDecorator that allows storing TypeIDs in the database.
11+
The prefix will not be persisted, instead the database-native UUID field will be used.
12+
At retrieval time a TypeID will be constructed based on the configured prefix and the
13+
UUID value from the database.
14+
15+
Usage:
16+
# will result in TypeIDs such as "user_01h45ytscbebyvny4gc8cr8ma2"
17+
id = mapped_column(
18+
TypeIDType("user"),
19+
primary_key=True,
20+
default=lambda: TypeID("user")
21+
)
22+
"""
23+
impl = types.Uuid
24+
25+
cache_ok = True
26+
27+
prefix: Optional[str]
28+
29+
def __init__(self, prefix: Optional[str], *args, **kwargs):
30+
self.prefix = prefix
31+
super().__init__(*args, **kwargs)
32+
33+
def __repr__(self) -> str:
34+
# Customize __repr__ to ensure that auto-generated code e.g. from alembic includes
35+
# the right __init__ params (otherwise by default prefix will be omitted because
36+
# uuid.__init__ does not have such an argument).
37+
return generic_repr(
38+
self,
39+
to_inspect=TypeID(self.prefix),
40+
)
41+
42+
def process_bind_param(self, value: TypeID, dialect):
43+
if self.prefix is None:
44+
assert value.prefix is None
45+
else:
46+
assert value.prefix == self.prefix
47+
48+
return value.uuid
49+
50+
def process_result_value(self, value, dialect):
51+
return from_uuid(value, self.prefix)

tests/test_typeid.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,3 +101,15 @@ def test_construct_type_from_uuid_with_prefix() -> None:
101101
assert isinstance(typeid, TypeID)
102102
assert typeid.prefix == "prefix"
103103
assert isinstance(typeid.suffix, str)
104+
105+
106+
def test_hash_type_id() -> None:
107+
prefix = "plov"
108+
suffix = "00041061050r3gg28a1c60t3gf"
109+
110+
typeid_1 = TypeID(prefix=prefix, suffix=suffix)
111+
typeid_2 = TypeID(prefix=prefix, suffix=suffix)
112+
typeid_3 = TypeID(suffix=suffix)
113+
114+
assert hash(typeid_1) == hash(typeid_2)
115+
assert hash(typeid_3) != hash(typeid_1)

typeid/typeid.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ def suffix(self) -> str:
3636
def prefix(self) -> str:
3737
return self._prefix
3838

39+
@property
40+
def uuid(self) -> UUID:
41+
return _convert_b32_to_uuid(self.suffix)
42+
3943
def __str__(self) -> str:
4044
value = ""
4145
if self.prefix:
@@ -48,6 +52,9 @@ def __eq__(self, value: object) -> bool:
4852
return False
4953
return value.prefix == self.prefix and value.suffix == self.suffix
5054

55+
def __hash__(self) -> int:
56+
return hash((self.prefix, self.suffix))
57+
5158

5259
def from_string(string: str) -> TypeID:
5360
"""Consider TypeID.from_string instead."""
@@ -76,3 +83,7 @@ def get_prefix_and_suffix(string: str) -> tuple:
7683

7784
def _convert_uuid_to_b32(uuid_instance: UUID) -> str:
7885
return base32.encode(list(uuid_instance.bytes))
86+
87+
88+
def _convert_b32_to_uuid(b32: str) -> UUID:
89+
return UUID(bytes=bytes(base32.decode(b32)))

0 commit comments

Comments
 (0)