【问题标题】:SQLAlchemy with dataclass default not populating postgres database具有数据类默认值的 SQLAlchemy 不填充 postgres 数据库
【发布时间】:2019-08-29 19:45:49
【问题描述】:

我正在使用dataclasses 与 SQLAlchemy 经典映射范例相结合。当我定义dataclass 并结合intstr 字段的默认值时,SQLAlchemy 不会填充intstrs,但它会填充Listdatetime 字段。比如下面的代码:

from dataclasses import dataclass, field
from typing import List
from datetime import datetime
from sqlalchemy import create_engine, MetaData, Table, Column, Integer, String, ARRAY, TIMESTAMP
from sqlalchemy.orm import sessionmaker, mapper

metadata = MetaData()
person_table = \
    Table('people', metadata,
          Column('id', Integer, primary_key=True, autoincrement=True),
          Column('name', String(255)),
          Column('age', Integer),
          Column('hobbies', ARRAY(String)),
          Column('birthday', TIMESTAMP)
          )

@dataclass
class Person:
    id: int = None
    name: str = ''
    age: int = 0
    hobbies: List[str] = field(default_factory=list)
    birthday: datetime = field(default_factory=datetime)

mapper(Person, person_table)

engine = create_engine('postgresql://postgres@localhost:32771/test', echo=True)
metadata.create_all(engine)

session = sessionmaker(bind=engine)()
person = Person(id=None, name='Robby', age=33, hobbies=['golf', 'hiking'], birthday=datetime(1985, 7, 25))
session.add(person)
session.commit()

这会在内存中正确填充 person 对象,但 commit 操作会在 postgres 中生成以下数据(nameage 列是 null):

 id | name | age |    hobbies    |      birthday       
----+------+-----+---------------+---------------------
  1 |      |     | {golf,hiking} | 1985-07-25 00:00:00

如果我更改 Person 类以从 nameage 中删除默认值,那么数据会在 postgres 中正确填充:

@dataclass
class Person:
    id: int = None
    name: str
    age: int
    hobbies: List[str] = field(default_factory=list)
    birthday: datetime = field(default_factory=datetime)

注意,我已经验证,当在类的“非默认”版本中创建 person 对象时,nameage 字段在内存中被正确填充。

如何将 SQLAlchemy 经典映射与具有默认值的数据类结合使用?

(Python 3.6、SQLAlchemy 1.2.16、PostgreSQL 11.2)

【问题讨论】:

标签: python postgresql sqlalchemy


【解决方案1】:

由于''0 分别是str()int() 函数的默认返回值,您可以使用以下代码插入这些默认值:

@dataclass
class Person:
    id: int = None
    name: str = field(default_factory=str)
    age: int = field(default_factory=int)
    hobbies: List[str] = field(default_factory=list)
    birthday: datetime = field(default_factory=datetime)

不幸的是,由于某种原因,使用field() 函数的default 参数不能像我们预期的那样工作(可能是dataclasses 反向端口的错误或误解......)。但是您仍然可以使用default_factory 指定与''0 不同的值,使用lambda

@dataclass
class Person:
    id: int = None
    name: str = field(default_factory=lambda: 'john doe')
    age: int = field(default_factory=lambda: 77)
    hobbies: List[str] = field(default_factory=list)
    birthday: datetime = field(default_factory=datetime)

【讨论】:

  • 如果我声明name: str = field(default_factory=str),则此方法有效,但在name: str = field(default=20) 时无效。如果不处理更高级的字段使用,我会很满意,但默认值非常重要。
  • @robbymurphy:我使用经过测试的解决方案更新了答案,该解决方案适用于 ''0 以外的默认值,使用 default_factorylambda
  • 它没有解释为什么default='john doe' 不起作用,但公平地说,它回答了我原来的问题。如果可行,我会尝试并标记为答案。
  • default= 不起作用的至少一个原因是数据类将默认值设置为类属性,而 SQLA 经典映射不会在现有类属性上设置检测。
  • 可悲的是:使用经典映射来避免对当前代码库的侵入性更改(不是从 Base 继承)。但是现在,您需要更改代码库以使其使用默认值。 (我知道它仍然是侵入性的,因为仪器将表格和其他元信息附加到类)。
【解决方案2】:

映射会更改您映射的类。因此数据类属性被表中的列和映射中描述的属性覆盖。声明时会看到更多问题

@dataclass(frozen=True)

您应该在 Column 声明中使用默认值,并且声明的模型可能更适合您,它类似于 dataclass。

如果您想将基础架构/数据库从您的域代码中分离出来,那么您需要另一种方法。为此,我建议将您的数据类视为 DB 模型的接口。因此 DB 模型将实现 DTO 描述的行为。但要使其正常工作,您将需要一个可以处理此问题的存储库。 我在这篇博文中描述了一个类似的案例:4 ways of mapping Value Object in SQLAlchemy

【讨论】:

    猜你喜欢
    • 2013-02-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-11-30
    • 1970-01-01
    相关资源
    最近更新 更多