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
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ repos:
- id: unasyncd
additional_dependencies: ["ruff"]
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: "v0.15.14"
rev: "v0.15.15"
hooks:
# Run the linter.
- id: ruff
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@ offering:
- Integrated support for UUID6 and UUID7 ([`uuid-utils`](https://github.com/aminalaee/uuid-utils) when installed, otherwise native in Python 3.14+, with UUID4 fallback on older versions).
- Integrated support for Nano ID using [`fastnanoid`](https://github.com/oliverlambson/fastnanoid) (install with the `nanoid` extra)
- Custom encrypted text type with multiple backend support including [`pgcrypto`](https://www.postgresql.org/docs/current/pgcrypto.html) for PostgreSQL and the Fernet implementation from [`cryptography`](https://cryptography.io/en/latest/) for other databases
- Custom password hashing type with multiple backend support including [`Argon2`](https://github.com/P-H-C/phc-winner-argon2), [`Passlib`](https://passlib.readthedocs.io/en/stable/), and [`Pwdlib`](https://pwdlib.readthedocs.io/en/stable/) with automatic salt generation
- Custom password hashing type with multiple backend support including [`Argon2`](https://github.com/P-H-C/phc-winner-argon2), [`Passlib`](https://passlib.readthedocs.io/en/stable/), and [`Pwdlib`](https://pwdlib.readthedocs.io/en/stable/) with automatic salt generation and rehash-on-verify for transparent hash upgrades on login
- Custom TOTP shared-secret type that stores an authenticator seed encrypted at rest and verifies time-based one-time passwords via [`pyotp`](https://github.com/pyauth/pyotp) (install with the `pyotp` extra)
- Custom one-time-code type that stores email/SMS verification codes hashed in JSON with built-in expiry, single-use redemption, attempt lockout, and a secure code generator
- Pre-configured base classes with audit columns UUID or Big Integer primary keys and
a [sentinel column](https://docs.sqlalchemy.org/en/20/core/connections.html#configuring-sentinel-columns).
- Synchronous and asynchronous repositories featuring:
Expand Down
10 changes: 10 additions & 0 deletions advanced_alchemy/_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,18 @@
# ---------------------------------------------------------------------------
# Feature-detection flags (use ``find_spec`` to avoid eagerly importing).
# ---------------------------------------------------------------------------
ARGON2_INSTALLED = find_spec("argon2") is not None
ATTRS_INSTALLED = find_spec("attrs") is not None
CATTRS_INSTALLED = find_spec("cattrs") is not None
CRYPTOGRAPHY_INSTALLED = find_spec("cryptography") is not None
LITESTAR_INSTALLED = find_spec("litestar") is not None
MSGSPEC_INSTALLED = find_spec("msgspec") is not None
NUMPY_INSTALLED = find_spec("numpy") is not None
ORJSON_INSTALLED = find_spec("orjson") is not None
PASSLIB_INSTALLED = find_spec("passlib") is not None
PWDLIB_INSTALLED = find_spec("pwdlib") is not None
PYDANTIC_INSTALLED = find_spec("pydantic") is not None
PYOTP_INSTALLED = find_spec("pyotp") is not None
SQLMODEL_INSTALLED = find_spec("sqlmodel") is not None


Expand Down Expand Up @@ -375,14 +380,19 @@ class EmptyEnum(enum.Enum):


__all__ = (
"ARGON2_INSTALLED",
"ATTRS_INSTALLED",
"ATTRS_NOTHING_STUB",
"CATTRS_INSTALLED",
"CRYPTOGRAPHY_INSTALLED",
"LITESTAR_INSTALLED",
"MSGSPEC_INSTALLED",
"NUMPY_INSTALLED",
"ORJSON_INSTALLED",
"PASSLIB_INSTALLED",
"PWDLIB_INSTALLED",
"PYDANTIC_INSTALLED",
"PYOTP_INSTALLED",
"SQLMODEL_INSTALLED",
"UNSET",
"UNSET_STUB",
Expand Down
23 changes: 8 additions & 15 deletions advanced_alchemy/alembic/templates/asyncio/script.py.mako
Original file line number Diff line number Diff line change
Expand Up @@ -7,31 +7,22 @@ Create Date: ${create_date}
"""

import warnings
from typing import TYPE_CHECKING, Any
from typing import TYPE_CHECKING

import sqlalchemy as sa
from alembic import op
from advanced_alchemy.types import Bool, EncryptedString, EncryptedText, GUID, JsonB, ORA_JSONB, DateTimeUTC, StoredObject, PasswordHash, FernetBackend
from advanced_alchemy.types import Bool, EncryptedString, EncryptedText, GUID, JsonB, ORA_JSONB, DateTimeUTC, StoredObject, PasswordHash, FernetBackend, TOTPSecret, OneTimeCode
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 ""}
try:
from advanced_alchemy.types.password_hash.argon2 import Argon2Hasher
except ImportError:
Argon2Hasher = Any # type: ignore
try:
from advanced_alchemy.types.password_hash.passlib import PasslibHasher
except ImportError:
PasslibHasher = Any # type: ignore
try:
from advanced_alchemy.types.password_hash.pwdlib import PwdlibHasher
except ImportError:
PwdlibHasher = Any # type: ignore

if TYPE_CHECKING:
from collections.abc import Sequence

__all__ = ["downgrade", "upgrade", "schema_upgrades", "schema_downgrades", "data_upgrades", "data_downgrades"]
__all__ = ("downgrade", "upgrade", "schema_upgrades", "schema_downgrades", "data_upgrades", "data_downgrades")

sa.GUID = GUID
sa.Bool = Bool
Expand All @@ -47,6 +38,8 @@ sa.PasslibHasher = PasslibHasher
sa.PwdlibHasher = PwdlibHasher
sa.FernetBackend = FernetBackend
sa.PGCryptoBackend = PGCryptoBackend
sa.TOTPSecret = TOTPSecret
sa.OneTimeCode = OneTimeCode

# revision identifiers, used by Alembic.
revision = ${repr(up_revision)}
Expand Down
23 changes: 8 additions & 15 deletions advanced_alchemy/alembic/templates/sync/script.py.mako
Original file line number Diff line number Diff line change
Expand Up @@ -7,31 +7,22 @@ Create Date: ${create_date}
"""

import warnings
from typing import TYPE_CHECKING, Any
from typing import TYPE_CHECKING

import sqlalchemy as sa
from alembic import op
from advanced_alchemy.types import Bool, EncryptedString, EncryptedText, GUID, JsonB, ORA_JSONB, DateTimeUTC, StoredObject, PasswordHash, FernetBackend
from advanced_alchemy.types import Bool, EncryptedString, EncryptedText, GUID, JsonB, ORA_JSONB, DateTimeUTC, StoredObject, PasswordHash, FernetBackend, TOTPSecret, OneTimeCode
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 ""}
try:
from advanced_alchemy.types.password_hash.argon2 import Argon2Hasher
except ImportError:
Argon2Hasher = Any # type: ignore
try:
from advanced_alchemy.types.password_hash.passlib import PasslibHasher
except ImportError:
PasslibHasher = Any # type: ignore
try:
from advanced_alchemy.types.password_hash.pwdlib import PwdlibHasher
except ImportError:
PwdlibHasher = Any # type: ignore

if TYPE_CHECKING:
from collections.abc import Sequence

__all__ = ["downgrade", "upgrade", "schema_upgrades", "schema_downgrades", "data_upgrades", "data_downgrades"]
__all__ = ("downgrade", "upgrade", "schema_upgrades", "schema_downgrades", "data_upgrades", "data_downgrades")

sa.GUID = GUID
sa.Bool = Bool
Expand All @@ -47,6 +38,8 @@ sa.PasslibHasher = PasslibHasher
sa.PwdlibHasher = PwdlibHasher
sa.FernetBackend = FernetBackend
sa.PGCryptoBackend = PGCryptoBackend
sa.TOTPSecret = TOTPSecret
sa.OneTimeCode = OneTimeCode

# revision identifiers, used by Alembic.
revision = ${repr(up_revision)}
Expand Down
15 changes: 14 additions & 1 deletion advanced_alchemy/types/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""SQLAlchemy custom types for use with the ORM."""

from advanced_alchemy.types import encrypted_string, file_object, password_hash
from advanced_alchemy.types import encrypted_string, file_object, password_hash, totp
from advanced_alchemy.types.boolean import Bool
from advanced_alchemy.types.datetime import DateTimeUTC
from advanced_alchemy.types.encrypted_string import (
Expand All @@ -23,6 +23,12 @@
from advanced_alchemy.types.json import ORA_JSONB, JsonB
from advanced_alchemy.types.mutables import MutableList
from advanced_alchemy.types.password_hash.base import HashedPassword, PasswordHash
from advanced_alchemy.types.password_hash.one_time_code import (
HashedOneTimeCode,
OneTimeCode,
generate_one_time_code,
)
from advanced_alchemy.types.totp import TOTPProvider, TOTPSecret, generate_totp_secret
from advanced_alchemy.types.vector import Vector

__all__ = (
Expand All @@ -39,17 +45,24 @@
"FernetBackend",
"FileObject",
"FileObjectList",
"HashedOneTimeCode",
"HashedPassword",
"JsonB",
"MutableList",
"OneTimeCode",
"PasswordHash",
"StorageBackend",
"StorageBackendT",
"StorageRegistry",
"StoredObject",
"TOTPProvider",
"TOTPSecret",
"Vector",
"encrypted_string",
"file_object",
"generate_one_time_code",
"generate_totp_secret",
"password_hash",
"storages",
"totp",
)
Loading