diff --git a/fastapi_users_db_sqlalchemy/__init__.py b/fastapi_users_db_sqlalchemy/__init__.py index 6889aab..f5d409c 100644 --- a/fastapi_users_db_sqlalchemy/__init__.py +++ b/fastapi_users_db_sqlalchemy/__init__.py @@ -20,9 +20,9 @@ from sqlalchemy.orm import joinedload from sqlalchemy.sql import Select -from fastapi_users_db_sqlalchemy.guid import GUID +from fastapi_users_db_sqlalchemy.generics import GUID -__version__ = "3.0.1" +__version__ = "3.0.2" class SQLAlchemyBaseUserTable: diff --git a/fastapi_users_db_sqlalchemy/access_token.py b/fastapi_users_db_sqlalchemy/access_token.py index 7c3de7e..3f5f264 100644 --- a/fastapi_users_db_sqlalchemy/access_token.py +++ b/fastapi_users_db_sqlalchemy/access_token.py @@ -2,11 +2,11 @@ from typing import Generic, Optional, Type from fastapi_users.authentication.strategy.db import A, AccessTokenDatabase -from sqlalchemy import Column, DateTime, ForeignKey, String, delete, select, update +from sqlalchemy import Column, ForeignKey, String, delete, select, update from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.declarative import declared_attr -from fastapi_users_db_sqlalchemy.guid import GUID +from fastapi_users_db_sqlalchemy.generics import GUID, TIMESTAMPAware class SQLAlchemyBaseAccessTokenTable: @@ -15,7 +15,7 @@ class SQLAlchemyBaseAccessTokenTable: __tablename__ = "accesstoken" token = Column(String(length=43), primary_key=True) - created_at = Column(DateTime(timezone=True), index=True, nullable=False) + created_at = Column(TIMESTAMPAware(timezone=True), index=True, nullable=False) @declared_attr def user_id(cls): diff --git a/fastapi_users_db_sqlalchemy/guid.py b/fastapi_users_db_sqlalchemy/generics.py similarity index 61% rename from fastapi_users_db_sqlalchemy/guid.py rename to fastapi_users_db_sqlalchemy/generics.py index 6e453ae..3b22ddc 100644 --- a/fastapi_users_db_sqlalchemy/guid.py +++ b/fastapi_users_db_sqlalchemy/generics.py @@ -1,19 +1,21 @@ import uuid +from datetime import datetime, timezone from pydantic import UUID4 +from sqlalchemy import CHAR, TIMESTAMP, TypeDecorator from sqlalchemy.dialects.postgresql import UUID -from sqlalchemy.types import CHAR, TypeDecorator class GUID(TypeDecorator): # pragma: no cover - """Platform-independent GUID type. + """ + Platform-independent GUID type. Uses PostgreSQL's UUID type, otherwise uses CHAR(36), storing as regular strings. """ class UUIDChar(CHAR): - python_type = UUID4 + python_type = UUID4 # type: ignore impl = UUIDChar cache_ok = True @@ -42,3 +44,20 @@ def process_result_value(self, value, dialect): if not isinstance(value, uuid.UUID): value = uuid.UUID(value) return value + + +class TIMESTAMPAware(TypeDecorator): # pragma: no cover + """ + MySQL and SQLite will always return naive-Python datetimes. + + We store everything as UTC, but we want to have + only offset-aware Python datetimes, even with MySQL and SQLite. + """ + + impl = TIMESTAMP + cache_ok = True + + def process_result_value(self, value: datetime, dialect): + if dialect.name != "postgresql": + return value.replace(tzinfo=timezone.utc) + return value diff --git a/pyproject.toml b/pyproject.toml index 0a614e0..3da409f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,7 +30,7 @@ classifiers = [ description-file = "README.md" requires-python = ">=3.7" requires = [ - "fastapi-users >= 9.1.0", + "fastapi-users >= 9.1.0,<10.0.0", "sqlalchemy[asyncio] >=1.4", ] diff --git a/requirements.txt b/requirements.txt index 626054b..da27282 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ -fastapi-users >= 9.1.0 +fastapi-users >= 9.1.0,<10.0.0 sqlalchemy[asyncio,mypy] >=1.4 diff --git a/setup.cfg b/setup.cfg index 1bc99a2..6cd16ae 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 3.0.1 +current_version = 3.0.2 commit = True tag = True diff --git a/tests/test_access_token.py b/tests/test_access_token.py index 34f356d..4122181 100644 --- a/tests/test_access_token.py +++ b/tests/test_access_token.py @@ -78,7 +78,10 @@ async def test_queries( # Update access_token_db.created_at = datetime.now(timezone.utc) - await sqlalchemy_access_token_db.update(access_token_db) + updated_access_token = await sqlalchemy_access_token_db.update(access_token_db) + assert updated_access_token.created_at.replace( + microsecond=0 + ) == access_token_db.created_at.replace(microsecond=0) # Get by token access_token_by_token = await sqlalchemy_access_token_db.get_by_token(