Skip to content

Commit 321b11b

Browse files
committed
feat: TransformingUnicodeAttribute now transforms python strings upon direct access
1 parent b7b1b74 commit 321b11b

File tree

3 files changed

+48
-2
lines changed

3 files changed

+48
-2
lines changed

fastapi_users_db_dynamodb/attributes.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
1+
from __future__ import annotations
2+
13
from collections.abc import Callable
4+
from typing import Any, TypeVar, overload
25

3-
from aiopynamodb.attributes import Attribute, UnicodeAttribute
6+
from aiopynamodb.attributes import _T, Attribute, UnicodeAttribute
47
from aiopynamodb.constants import STRING
58

69
from ._generics import UUID_ID
710

11+
_A = TypeVar("_A", bound="TransformingUnicodeAttribute")
12+
813

914
class GUID(Attribute[UUID_ID]):
1015
"""
@@ -63,6 +68,22 @@ def __init__(self, transform: Callable[[str], str] | None = None, **kwargs):
6368
super().__init__(**kwargs)
6469
self.transform = transform
6570

71+
@overload
72+
def __get__(self: _A, instance: None, owner: Any) -> _A: ...
73+
@overload
74+
def __get__(self: _A, instance: Any, owner: Any) -> _T: ... # type: ignore
75+
def __get__(self: _A, instance: Any, owner: Any) -> _A | _T: # type: ignore
76+
if instance:
77+
attr_name = instance._dynamo_to_python_attrs.get(
78+
self.attr_name, self.attr_name
79+
)
80+
value = instance.attribute_values.get(attr_name, None)
81+
if getattr(self, "transform", None) and value is not None:
82+
return self.transform(value) # type: ignore
83+
return value
84+
else:
85+
return self
86+
6687
def serialize(self, value):
6788
"""Serialize a value for later storage in DynamoDB.
6889

tests/conftest.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import hashlib
12
import uuid
23
from typing import Any
34

@@ -83,3 +84,13 @@ def user_id() -> UUID4:
8384
UUID4: The random UUIDv4 user id.
8485
"""
8586
return uuid.uuid4()
87+
88+
89+
def hash_string(string: str) -> str:
90+
"""A simple function that returns the SHA256 hash of a given string.
91+
Args:
92+
string (str): The string to hash.
93+
Returns:
94+
str: The SHA256 hash of the string.
95+
"""
96+
return hashlib.sha256(string.encode("utf-8")).hexdigest()

tests/test_misc.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@
44
from fastapi_users_db_dynamodb import DynamoDBBaseUserTable, config
55
from fastapi_users_db_dynamodb._generics import classproperty
66
from fastapi_users_db_dynamodb.access_token import DynamoDBBaseAccessTokenTable
7-
from fastapi_users_db_dynamodb.attributes import GUID
7+
from fastapi_users_db_dynamodb.attributes import GUID, TransformingUnicodeAttribute
88
from fastapi_users_db_dynamodb.tables import delete_tables, ensure_tables_exist
99

10+
from .conftest import hash_string
11+
1012

1113
class NotAModel:
1214
"""A class representing an invalid `Model`."""
@@ -23,6 +25,9 @@ class IncompleteModel(Model):
2325
class ValidModel(Model):
2426
"""A class representing a valid `Model`."""
2527

28+
attr = TransformingUnicodeAttribute(transform=str.lower)
29+
attr2 = TransformingUnicodeAttribute(transform=hash_string)
30+
2631
class Meta:
2732
"""The required `Meta` definitions for PynamoDB.
2833
@@ -128,3 +133,12 @@ def test_attributes(user_id):
128133

129134
assert id.deserialize(None) is None
130135
assert user_id == id.deserialize(user_id)
136+
137+
assert ValidModel().attr is None
138+
model = ValidModel(attr="TEST", attr2="TEST")
139+
assert model.attr == "test"
140+
model.attr = "ANOTHER TEST"
141+
assert model.attr == "another test"
142+
assert model.attr2 == hash_string("TEST")
143+
model.attr2 = "ANOTHER TEST"
144+
assert model.attr2 == hash_string("ANOTHER TEST")

0 commit comments

Comments
 (0)