Skip to content

Commit

Permalink
Merge pull request #5 from beheh/hashable
Browse files Browse the repository at this point in the history
make compatible with sqlalchemy
  • Loading branch information
akhundMurad authored Oct 2, 2023
2 parents 7a02841 + 039608a commit 88d058c
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 0 deletions.
51 changes: 51 additions & 0 deletions examples/sqlalchemy.py
Original file line number Diff line number Diff line change
@@ -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)
12 changes: 12 additions & 0 deletions tests/test_typeid.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,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)
11 changes: 11 additions & 0 deletions typeid/typeid.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,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:
Expand All @@ -48,6 +52,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:
"""Consider TypeID.from_string instead."""
Expand Down Expand Up @@ -76,3 +83,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)))

0 comments on commit 88d058c

Please sign in to comment.