Source code for aiida.storage.psql_dos.models

# -*- coding: utf-8 -*-
###########################################################################
# Copyright (c), The AiiDA team. All rights reserved.                     #
# This file is part of the AiiDA code.                                    #
#                                                                         #
# The code is hosted on GitHub at https://github.com/aiidateam/aiida-core #
# For further information on the license, see the LICENSE.txt file        #
# For further information please visit http://www.aiida.net               #
###########################################################################
"""Module to define the database models for the SqlAlchemy backend."""
import sqlalchemy as sa
from sqlalchemy.orm import mapper

# SqlAlchemy does not set default values for table columns upon construction of a new instance, but will only do so
# when storing the instance. Any attributes that do not have a value but have a defined default, will be populated with
# this default. This does mean however, that before the instance is stored, these attributes are undefined, for example
# the UUID of a new instance. In Django this behavior is the opposite and more in intuitive because when one creates for
# example a `Node` instance in memory, it will already have a UUID. The following function call will force SqlAlchemy to
# behave the same as Django and set model attribute defaults upon instantiation. Note that this functionality used to be
# provided by the ``sqlalchemy_utils.force_instant_defaults`` utility function. However, this function's behavior was
# changed in v0.37.5, where the ``sqlalchemy_utils.listeners.instant_defaults_listener`` was changed to update the
# original ``kwargs`` passed to the constructor, with the default values from the column definitions. This broke the
# constructor of certain of our database models, e.g. `DbComment`, which needs to distinguish between the value of the
# ``mtime`` column being defined by the caller as opposed to the default. This is why we revert this change by copying
# the old implementation of the listener.


[docs]def instant_defaults_listener(target, _, __): """Loop over the columns of the target model instance and populate defaults.""" for key, column in sa.inspect(target.__class__).columns.items(): if hasattr(column, 'default') and column.default is not None: if callable(column.default.arg): setattr(target, key, column.default.arg(target)) else: setattr(target, key, column.default.arg)
sa.event.listen(mapper, 'init', instant_defaults_listener)