首页 > 解决方案 > 如何防止使用相同值创建新 SQLAlchemy 对象的 UUID 主键

问题描述

我有一个 for 循环,它创建一个新的“行”对象,在将对象提交到 Postgres 数据库中的表之前用数据填充属性。我要插入的表(以及因此对象)采用 UUID 的主键,只有在第一次提交后所有新创建的行对象中的循环的第一次迭代后,这个值保持不变。

我对解决方案有点迷茫,但是我认为这可能与我处理数据库会话的方式有关。在写这篇文章时,我还注意到我在and函数中使用了相同的变量名 ( new_user) 。虽然 Python 会认为它们在不同的范围内(我认为),但我想知道 SQLALchemy 会话是否会?invite_usersinvite_user

请注意,我已经删除了很多我认为在问题上下文中多余的代码 - 主要是更多列等。此外,该invite_user函数在其他地方使用,因此invite_users仅用于批量“邀请”。

这是一个显示表类定义的开头和Column定义的片段:

from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.dialects import postgresql
import uuid

Base = declarative_base()

class User(Base):
    __tablename__ = "users"

    id = Column(postgresql.UUID(as_uuid=True), default=uuid.uuid4(), primary_key=True)
    email = Column(String, unique=True)

这是我用来创建和销毁会话的函数:

from contextlib import contextmanager
from sqlalchemy import create_engine

@contextmanager
def db_session(db_url):
    engine = create_engine(db_url, convert_unicode=True)
    connection = engine.connect()
    db_session = scoped_session(sessionmaker(autocommit=False, autoflush=True, bind=engine))
    yield db_session
    db_session.close()
    connection.close()

下面是遍历 JSON 对象数组的函数的一部分:

def invite_users(json_dict):
    exceptions = {}

    with db_session(environ['CONNECTION_STRING']) as session:
        for new_user in json_dict['users']:
            try:
                invite_user(
                    session,
                    info['email_address']
                )
            # I'm catching exceptions and storing them to handle them later
            except Exception as e:
                exceptions[user_info['email_address']] = e
                pass

这是invite_user将行添加到会话并尝试提交它的函数:

from project.database import * # this contains the User table class above
from project.exceptions import *

def invite_user(session, email):

    new_user = User(
        email=email
    )

    session.add(new_user)

    try:
        session.commit()
    except exc.IntegrityError as e:
        session.rollback()
        raise DuplicateViolation(f"User already invited") from None

因此,我遍历 dict ( json_dict['emails']) 中的电子邮件地址。然后,我将每封电子邮件连同当前数据库一起传递给邀请用户session。我这样做是为了避免每次invite_user调用都创建一个会话,因为从性能的角度来看,这似乎比sessioninvite_user函数中创建一个新的更明智,因为这将导致它们中的很多被创建和销毁。

我认为我的Column定义足以处理每次提交User行对象时新 UUID 的生成。但是,如果我向函数传递invite_users多个电子邮件地址,第一个用户会被添加一个新的 UUID,而第二个用户会被分配相同的 UUID。如果我通过它一个电子邮件地址一切都很好

我不想依赖查询数据库中的现有行。我完全依靠数据库约束来防止重复,异常处理用于向用户报告错误。

标签: pythonpostgresqlsqlalchemy

解决方案


Column参数状态的 sqlalchemy文档:default

表示此列的默认值的标量、Python 可调用或 ColumnElement 表达式,如果在插入的 VALUES 子句中未指定此列,则将在插入时调用。

因此,与其提供uuid.uuid4()将创建一个标量(常量)值的方法,不如只提供 callable uuid.uuid4,以便为每个插入调用它。


推荐阅读