Skip to content

Commit 3efd6a1

Browse files
authored
Merge pull request #48 from akhundMurad/feat/core/35
**feat(factory): add reusable TypeID factories with caching**
2 parents 2d5efb2 + 7af0647 commit 3efd6a1

File tree

4 files changed

+97
-10
lines changed

4 files changed

+97
-10
lines changed

tests/test_factory.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import pytest
2+
3+
from typeid import TypeID, cached_typeid_factory, typeid_factory
4+
from typeid.errors import PrefixValidationException
5+
6+
7+
def test_typeid_factory_generates_typeid_with_prefix():
8+
gen = typeid_factory("user")
9+
tid = gen()
10+
11+
assert isinstance(tid, TypeID)
12+
assert tid.prefix == "user"
13+
14+
15+
def test_typeid_factory_returns_new_ids_each_time():
16+
gen = typeid_factory("user")
17+
a = gen()
18+
b = gen()
19+
20+
assert a != b
21+
22+
23+
def test_cached_typeid_factory_is_cached():
24+
a = cached_typeid_factory("user")
25+
b = cached_typeid_factory("user")
26+
c = cached_typeid_factory("order")
27+
28+
assert a is b
29+
assert a is not c
30+
31+
32+
def test_factory_invalid_prefix_propagates():
33+
gen = typeid_factory("BAD PREFIX")
34+
with pytest.raises(PrefixValidationException):
35+
gen()

typeid/__init__.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
from .factory import TypeIDFactory, cached_typeid_factory, typeid_factory
12
from .typeid import TypeID, from_string, from_uuid, get_prefix_and_suffix
23

3-
__all__ = ("TypeID", "from_string", "from_uuid", "get_prefix_and_suffix")
4+
__all__ = (
5+
"TypeID",
6+
"from_string",
7+
"from_uuid",
8+
"get_prefix_and_suffix",
9+
"TypeIDFactory",
10+
"typeid_factory",
11+
"cached_typeid_factory",
12+
)

typeid/factory.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
from dataclasses import dataclass
2+
from functools import lru_cache
3+
from typing import Callable
4+
5+
from .typeid import TypeID
6+
7+
8+
@dataclass(frozen=True, slots=True)
9+
class TypeIDFactory:
10+
"""
11+
Callable object that generates TypeIDs with a fixed prefix.
12+
13+
Example:
14+
user_id = TypeIDFactory("user")()
15+
"""
16+
17+
prefix: str
18+
19+
def __call__(self) -> TypeID:
20+
return TypeID(self.prefix)
21+
22+
23+
def typeid_factory(prefix: str) -> Callable[[], TypeID]:
24+
"""
25+
Return a zero-argument callable that generates TypeIDs with a fixed prefix.
26+
27+
Example:
28+
user_id = typeid_factory("user")()
29+
"""
30+
return TypeIDFactory(prefix)
31+
32+
33+
@lru_cache(maxsize=256)
34+
def cached_typeid_factory(prefix: str) -> Callable[[], TypeID]:
35+
"""
36+
Same as typeid_factory, but caches factories by prefix.
37+
38+
Use this if you create factories repeatedly at runtime.
39+
"""
40+
return TypeIDFactory(prefix)

typeid/typeid.py

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,34 @@
11
import uuid
22
import warnings
3-
from typing import Optional
3+
from typing import Generic, Optional, TypeVar
44

55
import uuid6
66

77
from typeid import base32
88
from typeid.errors import InvalidTypeIDStringException
99
from typeid.validation import validate_prefix, validate_suffix
1010

11+
PrefixT = TypeVar("PrefixT", bound=str)
1112

12-
class TypeID:
13-
def __init__(self, prefix: Optional[str] = None, suffix: Optional[str] = None) -> None:
13+
14+
class TypeID(Generic[PrefixT]):
15+
def __init__(self, prefix: Optional[PrefixT] = None, suffix: Optional[str] = None) -> None:
1416
suffix = _convert_uuid_to_b32(uuid6.uuid7()) if not suffix else suffix
1517
validate_suffix(suffix=suffix)
16-
if prefix:
18+
19+
if prefix is not None:
1720
validate_prefix(prefix=prefix)
1821

19-
self._prefix = prefix or ""
20-
self._suffix = suffix
22+
self._prefix: Optional[PrefixT] = prefix
23+
self._suffix: str = suffix
2124

2225
@classmethod
23-
def from_string(cls, string: str):
26+
def from_string(cls, string: str) -> "TypeID":
2427
prefix, suffix = get_prefix_and_suffix(string=string)
2528
return cls(suffix=suffix, prefix=prefix)
2629

2730
@classmethod
28-
def from_uuid(cls, suffix: uuid.UUID, prefix: Optional[str] = None):
31+
def from_uuid(cls, suffix: uuid.UUID, prefix: Optional[PrefixT] = None) -> "TypeID":
2932
suffix_str = _convert_uuid_to_b32(suffix)
3033
return cls(suffix=suffix_str, prefix=prefix)
3134

@@ -35,7 +38,7 @@ def suffix(self) -> str:
3538

3639
@property
3740
def prefix(self) -> str:
38-
return self._prefix
41+
return self._prefix or ""
3942

4043
@property
4144
def uuid(self) -> uuid6.UUID:

0 commit comments

Comments
 (0)