Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion advanced_alchemy/alembic/templates/asyncio/script.py.mako
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ from typing import TYPE_CHECKING

import sqlalchemy as sa
from alembic import op
from advanced_alchemy.types import EncryptedString, EncryptedText, GUID, ORA_JSONB, DateTimeUTC, StoredObject, PasswordHash
from advanced_alchemy.types import EncryptedString, EncryptedText, GUID, ORA_JSONB, DateTimeUTC, StoredObject, PasswordHash, FernetBackend
from advanced_alchemy.types.encrypted_string import PGCryptoBackend
from advanced_alchemy.types.password_hash.argon2 import Argon2Hasher
from advanced_alchemy.types.password_hash.passlib import PasslibHasher
from advanced_alchemy.types.password_hash.pwdlib import PwdlibHasher
from sqlalchemy import Text # noqa: F401
${imports if imports else ""}
if TYPE_CHECKING:
Expand All @@ -25,6 +29,12 @@ sa.ORA_JSONB = ORA_JSONB
sa.EncryptedString = EncryptedString
sa.EncryptedText = EncryptedText
sa.StoredObject = StoredObject
sa.PasswordHash = PasswordHash
sa.Argon2Hasher = Argon2Hasher
sa.PasslibHasher = PasslibHasher
sa.PwdlibHasher = PwdlibHasher
sa.FernetBackend = FernetBackend
sa.PGCryptoBackend = PGCryptoBackend

# revision identifiers, used by Alembic.
revision = ${repr(up_revision)}
Expand Down
12 changes: 11 additions & 1 deletion advanced_alchemy/alembic/templates/sync/script.py.mako
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ from typing import TYPE_CHECKING

import sqlalchemy as sa
from alembic import op
from advanced_alchemy.types import EncryptedString, EncryptedText, GUID, ORA_JSONB, DateTimeUTC, StoredObject, PasswordHash
from advanced_alchemy.types import EncryptedString, EncryptedText, GUID, ORA_JSONB, DateTimeUTC, StoredObject, PasswordHash, FernetBackend
from advanced_alchemy.types.encrypted_string import PGCryptoBackend
from advanced_alchemy.types.password_hash.argon2 import Argon2Hasher
from advanced_alchemy.types.password_hash.passlib import PasslibHasher
from advanced_alchemy.types.password_hash.pwdlib import PwdlibHasher
from sqlalchemy import Text # noqa: F401
${imports if imports else ""}
if TYPE_CHECKING:
Expand All @@ -25,6 +29,12 @@ sa.ORA_JSONB = ORA_JSONB
sa.EncryptedString = EncryptedString
sa.EncryptedText = EncryptedText
sa.StoredObject = StoredObject
sa.PasswordHash = PasswordHash
sa.Argon2Hasher = Argon2Hasher
sa.PasslibHasher = PasslibHasher
sa.PwdlibHasher = PwdlibHasher
sa.FernetBackend = FernetBackend
sa.PGCryptoBackend = PGCryptoBackend

# revision identifiers, used by Alembic.
revision = ${repr(up_revision)}
Expand Down
5 changes: 5 additions & 0 deletions advanced_alchemy/types/encrypted_string.py
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,11 @@ def __init__(
self.backend = backend()
self.length = length

def __repr__(self) -> str:
"""Return a string representation of the EncryptedString."""
key_repr = self.key.__name__ if callable(self.key) else repr(self.key)
return f"EncryptedString(key={key_repr}, backend={self.backend.__class__.__name__}, length={self.length})"

@property
def python_type(self) -> type[str]:
"""Returns the Python type for this type decorator.
Expand Down
4 changes: 4 additions & 0 deletions advanced_alchemy/types/password_hash/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,10 @@ def __init__(self, backend: "HashingBackend", length: int = 128) -> None:
super().__init__(length=length)
self.backend = backend

def __repr__(self) -> str:
"""Return a string representation of the PasswordHash."""
return f"PasswordHash(backend=sa.{self.backend.__class__.__name__}(), length={self.length})"

@property
def python_type(self) -> "type[str]":
"""Returns the Python type for this type decorator.
Expand Down
37 changes: 36 additions & 1 deletion tests/integration/test_password_hash.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
from sqlalchemy.orm import Mapped, Session, mapped_column, sessionmaker

from advanced_alchemy.base import BigIntBase
from advanced_alchemy.types import PasswordHash
from advanced_alchemy.types import EncryptedString, PasswordHash
from advanced_alchemy.types.encrypted_string import FernetBackend, PGCryptoBackend
from advanced_alchemy.types.password_hash.argon2 import Argon2Hasher
from advanced_alchemy.types.password_hash.base import HashedPassword
from advanced_alchemy.types.password_hash.passlib import PasslibHasher
Expand Down Expand Up @@ -194,3 +195,37 @@ async def test_password_hash_async(
await db_session.flush()
await db_session.refresh(user2)
assert user2.argon2_password is None


def test_password_hash_repr() -> None:
"""Test __repr__() method for PasswordHash with different backends."""
# Test Argon2Hasher backend
argon2_hash = PasswordHash(backend=Argon2Hasher(), length=128)
assert repr(argon2_hash) == "PasswordHash(backend=sa.Argon2Hasher(), length=128)"

# Test PasslibHasher backend
passlib_hash = PasswordHash(backend=PasslibHasher(context=CryptContext(schemes=["argon2"])), length=256)
assert repr(passlib_hash) == "PasswordHash(backend=sa.PasslibHasher(), length=256)"

# Test PwdlibHasher backend
pwdlib_hash = PasswordHash(backend=PwdlibHasher(hasher=PwdlibArgon2Hasher()), length=512)
assert repr(pwdlib_hash) == "PasswordHash(backend=sa.PwdlibHasher(), length=512)"


def test_encrypted_string_repr() -> None:
"""Test __repr__() method for EncryptedString with different backends."""
# Test FernetBackend (default)
enc_str_fernet = EncryptedString(key="test_key", backend=FernetBackend, length=100)
assert repr(enc_str_fernet) == "EncryptedString(key='test_key', backend=FernetBackend, length=100)"

# Test PGCryptoBackend
enc_str_pgcrypto = EncryptedString(key=b"test_bytes_key", backend=PGCryptoBackend, length=200)
assert repr(enc_str_pgcrypto) == "EncryptedString(key=b'test_bytes_key', backend=PGCryptoBackend, length=200)"

# Test with callable key
def get_key() -> str:
return "dynamic_key"

# The repr should include the callable object itself
enc_str_callable = EncryptedString(key=get_key, backend=FernetBackend)
assert repr(enc_str_callable) == "EncryptedString(key=get_key, backend=FernetBackend, length=None)"