Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

shared: add UTCDateTime column type #179

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 2 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
7 changes: 6 additions & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
#
# This file is part of Invenio.
# Copyright (C) 2015-2018 CERN.
# Copyright (C) 2022 Graz University of Technology.
# Copyright (C) 2022-2024 Graz University of Technology.
#
# Invenio is free software; you can redistribute it and/or modify it
# under the terms of the MIT License; see LICENSE file for more details.
Expand Down Expand Up @@ -316,3 +316,8 @@

# Autodoc configuraton.
autoclass_content = "both"

nitpick_ignore = [
("py:class", "TypeDecorator"),
("py:class", "sqlalchemy.sql.sqltypes.DateTime"),
]
29 changes: 29 additions & 0 deletions invenio_db/shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,20 @@
#
# This file is part of Invenio.
# Copyright (C) 2015-2018 CERN.
# Copyright (C) 2024 Graz University of Technology.
#
# Invenio is free software; you can redistribute it and/or modify it
# under the terms of the MIT License; see LICENSE file for more details.

"""Shared database object for Invenio."""

from datetime import datetime

from flask_sqlalchemy import SQLAlchemy as FlaskSQLAlchemy
from sqlalchemy import MetaData, event, util
from sqlalchemy.engine import Engine
from sqlalchemy.sql import text
from sqlalchemy.types import DateTime, TypeDecorator
from werkzeug.local import LocalProxy

NAMING_CONVENTION = util.immutabledict(
Expand All @@ -29,9 +33,34 @@
"""Default database metadata object holding associated schema constructs."""


class UTCDateTime(TypeDecorator):
"""Custom UTC datetime type."""

impl = DateTime

def process_bind_param(self, value, dialect):
"""Process value storing into database."""
if isinstance(value, datetime):
return value.replace(tzinfo=None)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor: I think here we should also check that value.tzinfo in (None, timezone.utc), and only in that case proceed. I would even go as far as raising an exception if it's not the case, since implicitly dropping the timezone info (or converting from local to UTC) is the behavior we want to avoid.

Needs to be tested in a couple of complex modules first (e.g. invenio-rdm-records), but I have a feeling that it'll also help us to catch some datetime-related bugs :)

return value

def process_result_value(self, value, dialect):
"""Process value retrieving from database."""
if isinstance(value, datetime):
return value.replace(tzinfo=None)
return value


class SQLAlchemy(FlaskSQLAlchemy):
"""Implement or overide extension methods."""

def __getattr__(self, name):
"""Get attr."""
if name == "UTCDateTime":
return UTCDateTime

return super().__getattr__(name)

def apply_driver_hacks(self, app, sa_url, options):
"""Call before engine creation."""
# Don't forget to apply hacks defined on parent object.
Expand Down