feat: use custom type to handle existing naive datetimes

This commit is contained in:
Iain Learmonth 2024-12-06 18:02:59 +00:00
parent e22abb383c
commit 39bdac1ecf
45 changed files with 210 additions and 84 deletions

View file

@ -7,6 +7,7 @@ from sqlalchemy.orm import Mapped, mapped_column
from app.brm.brn import BRN
from app.extensions import db
from app.models.types import AwareDateTime
class AbstractConfiguration(db.Model): # type: ignore
@ -14,9 +15,9 @@ class AbstractConfiguration(db.Model): # type: ignore
id: Mapped[int] = mapped_column(db.Integer, primary_key=True)
description: Mapped[str]
added: Mapped[datetime] = mapped_column(db.DateTime(timezone=True))
updated: Mapped[datetime] = mapped_column(db.DateTime(timezone=True))
destroyed: Mapped[Optional[datetime]] = mapped_column(db.DateTime(timezone=True), nullable=True)
added: Mapped[datetime] = mapped_column(AwareDateTime())
updated: Mapped[datetime] = mapped_column(AwareDateTime())
destroyed: Mapped[Optional[datetime]] = mapped_column(AwareDateTime(), nullable=True)
@property
@abstractmethod
@ -43,7 +44,7 @@ class Deprecation(db.Model): # type: ignore[name-defined,misc]
id: Mapped[int] = mapped_column(db.Integer, primary_key=True)
resource_type: Mapped[str]
resource_id: Mapped[int]
deprecated_at: Mapped[datetime] = mapped_column(db.DateTime(timezone=True))
deprecated_at: Mapped[datetime] = mapped_column(AwareDateTime())
meta: Mapped[Optional[Dict[str, Any]]] = mapped_column(db.JSON())
reason: Mapped[str]
@ -58,11 +59,11 @@ class AbstractResource(db.Model): # type: ignore
__abstract__ = True
id: Mapped[int] = mapped_column(db.Integer, primary_key=True)
added: Mapped[datetime] = mapped_column(db.DateTime(timezone=True))
updated: Mapped[datetime] = mapped_column(db.DateTime(timezone=True))
deprecated: Mapped[Optional[datetime]] = mapped_column(db.DateTime(timezone=True), nullable=True)
added: Mapped[datetime] = mapped_column(AwareDateTime())
updated: Mapped[datetime] = mapped_column(AwareDateTime())
deprecated: Mapped[Optional[datetime]] = mapped_column(AwareDateTime(), nullable=True)
deprecation_reason: Mapped[Optional[str]]
destroyed: Mapped[Optional[datetime]] = mapped_column(db.DateTime(timezone=True), nullable=True)
destroyed: Mapped[Optional[datetime]] = mapped_column(AwareDateTime(), nullable=True)
def __init__(self, *,
id: Optional[int] = None,

View file

@ -7,6 +7,7 @@ from sqlalchemy.orm import Mapped, mapped_column
from app.brm.brn import BRN
from app.extensions import db
from app.models import AbstractConfiguration
from app.models.types import AwareDateTime
class Activity(db.Model): # type: ignore
@ -14,7 +15,7 @@ class Activity(db.Model): # type: ignore
group_id: Mapped[Optional[int]]
activity_type: Mapped[str]
text: Mapped[str]
added: Mapped[datetime] = mapped_column(db.DateTime(timezone=True))
added: Mapped[datetime] = mapped_column(AwareDateTime())
def __init__(self, *,
id: Optional[int] = None,

View file

@ -6,6 +6,7 @@ from sqlalchemy.orm import Mapped, mapped_column
from app.extensions import db
from app.models.activity import Activity
from app.models.types import AwareDateTime
class AlarmState(enum.Enum):
@ -30,8 +31,8 @@ class Alarm(db.Model): # type: ignore
target: Mapped[str]
aspect: Mapped[str]
alarm_state: Mapped[AlarmState] = mapped_column(default=AlarmState.UNKNOWN)
state_changed: Mapped[datetime] = mapped_column(db.DateTime(timezone=True))
last_updated: Mapped[datetime] = mapped_column(db.DateTime(timezone=True))
state_changed: Mapped[datetime] = mapped_column(AwareDateTime())
last_updated: Mapped[datetime] = mapped_column(AwareDateTime())
text: Mapped[str]
@classmethod

View file

@ -7,6 +7,7 @@ from sqlalchemy.orm import Mapped, mapped_column
from app.brm.brn import BRN
from app.extensions import db
from app.models import AbstractConfiguration, AbstractResource
from app.models.types import AwareDateTime
class AutomationState(enum.Enum):
@ -19,8 +20,8 @@ class Automation(AbstractConfiguration):
short_name: Mapped[str]
state: Mapped[AutomationState] = mapped_column(default=AutomationState.IDLE)
enabled: Mapped[bool]
last_run: Mapped[Optional[datetime]] = mapped_column(db.DateTime(timezone=True))
next_run: Mapped[Optional[datetime]] = mapped_column(db.DateTime(timezone=True))
last_run: Mapped[Optional[datetime]] = mapped_column(AwareDateTime())
next_run: Mapped[Optional[datetime]] = mapped_column(AwareDateTime())
next_is_full: Mapped[bool]
logs = db.relationship("AutomationLogs", back_populates="automation")

View file

@ -8,6 +8,7 @@ from app.brm.brn import BRN
from app.extensions import db
from app.models import AbstractConfiguration, AbstractResource
from app.models.base import Pool
from app.models.types import AwareDateTime
class ProviderAllocation(enum.Enum):
@ -54,7 +55,7 @@ class BridgeConf(AbstractConfiguration):
class Bridge(AbstractResource):
conf_id: Mapped[int] = mapped_column(db.ForeignKey("bridge_conf.id"))
cloud_account_id: Mapped[int] = mapped_column(db.ForeignKey("cloud_account.id"))
terraform_updated: Mapped[Optional[datetime]] = mapped_column(db.DateTime(timezone=True), nullable=True)
terraform_updated: Mapped[Optional[datetime]] = mapped_column(AwareDateTime(), nullable=True)
nickname: Mapped[Optional[str]]
fingerprint: Mapped[Optional[str]]
hashed_fingerprint: Mapped[Optional[str]]

View file

@ -1,5 +1,5 @@
import enum
from typing import Any, Dict, List, TYPE_CHECKING
from typing import TYPE_CHECKING, Any, Dict, List
from sqlalchemy.orm import Mapped, mapped_column, relationship
@ -8,7 +8,6 @@ from app.extensions import db
from app.models import AbstractConfiguration
from app.models.mirrors import StaticOrigin
if TYPE_CHECKING:
from app.models.bridges import Bridge

View file

@ -16,6 +16,7 @@ from app.extensions import db
from app.models import AbstractConfiguration, AbstractResource, Deprecation
from app.models.base import Group, Pool
from app.models.onions import Onion
from app.models.types import AwareDateTime
country_origin = db.Table(
'country_origin',
@ -275,7 +276,7 @@ class Proxy(AbstractResource):
provider: Mapped[str] = mapped_column(db.String(20), nullable=False)
psg: Mapped[Optional[int]] = mapped_column(db.Integer, nullable=True)
slug: Mapped[Optional[str]] = mapped_column(db.String(20), nullable=True)
terraform_updated: Mapped[Optional[datetime]] = mapped_column(db.DateTime(timezone=True), nullable=True)
terraform_updated: Mapped[Optional[datetime]] = mapped_column(AwareDateTime(), nullable=True)
url: Mapped[Optional[str]] = mapped_column(db.String(255), nullable=True)
origin: Mapped[Origin] = relationship("Origin", back_populates="proxies")

View file

@ -7,6 +7,7 @@ from app.brm.brn import BRN
from app.extensions import db
from app.models import AbstractConfiguration, AbstractResource
from app.models.base import Group
from app.models.types import AwareDateTime
from app.util.onion import onion_hostname
@ -36,7 +37,7 @@ class Onion(AbstractConfiguration):
group_id: Mapped[int] = mapped_column(db.ForeignKey("group.id"))
domain_name: Mapped[str]
cert_expiry: Mapped[datetime] = mapped_column(db.DateTime(timezone=True))
cert_expiry: Mapped[datetime] = mapped_column(AwareDateTime())
cert_sans: Mapped[str]
onion_public_key: Mapped[bytes]
onion_private_key: Mapped[bytes]

21
app/models/types.py Normal file
View file

@ -0,0 +1,21 @@
from datetime import timezone
from sqlalchemy import DateTime, TypeDecorator
class AwareDateTime(TypeDecorator):
impl = DateTime(timezone=True)
cache_ok = True
def process_bind_param(self, value, dialect):
# Ensure the value is aware. If it's naive, assume UTC.
if value is not None and value.tzinfo is None:
value = value.replace(tzinfo=timezone.utc)
return value
def process_result_value(self, value, dialect):
# Ensure the value is aware. If it's naive, assume UTC.
if value is not None and value.tzinfo is None:
value = value.replace(tzinfo=timezone.utc)
return value