SQLAlchemy с классом данных по умолчанию, не заполняющим базу данных postgres

Я использую dataclasses в сочетании с классической парадигмой сопоставления SQLAlchemy. Когда я определяю dataclass в сочетании со значениями по умолчанию для полей int и str, SQLAlchemy не заполняет поля int и str, но заполняет поля List и datetime. Например следующий код:

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 (столбцы name и age — это null):

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

Если я изменю класс Person, чтобы удалить значения по умолчанию из name и age, тогда данные будут правильно заполнены в 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 создается в версии класса «без значения по умолчанию», поля name и age правильно заполняются в памяти.

Как использовать классические сопоставления SQLAlchemy в сочетании с классами данных со значениями по умолчанию?

(Python 3.6, SQLAlchemy 1.2.16, PostgreSQL 11.2)

Разве это не из-за разницы между default и server_default?

EvertW 15.01.2021 09:52

у меня похожая проблема, может кто подскажет? stackoverflow.com/questions/67185813/…

greg 23.04.2021 01:05
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
4
2
6 595
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Поскольку '' и 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)

К сожалению, по какой-то причине использование параметра default функции field() не работает так, как мы могли ожидать (может быть ошибка бэкпорта 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 08.04.2019 19:48

@robbymurphy: я обновил ответ проверенным решением, которое работает для значений по умолчанию, отличных от '' и 0, используя default_factory и lambda

Tryph 09.04.2019 12:25

Это не объясняет, почему default='john doe' не работает, но, честно говоря, отвечает на мой первоначальный вопрос. Я попробую и отмечу как ответ, если это сработает.

robbymurphy 09.04.2019 15:56

По крайней мере, одна из причин, по которой default= не работает, заключается в том, что классы данных устанавливают значение по умолчанию в качестве атрибута класса, а классическое сопоставление SQLA не настраивает инструменты для существующих атрибутов класса.

Ilja Everilä 04.01.2020 17:26

Печально то, что используется классическое отображение, чтобы избежать инвазивных изменений в текущей кодовой базе (не наследование от Base). Но теперь вам нужно изменить кодовую базу, чтобы она работала со значениями по умолчанию. (Я знаю, что это все еще агрессивно, поскольку инструментарий прикрепляет таблицу и другую метаинформацию к классу).

Marti Nito 26.03.2020 18:37

Сопоставление изменяет класс, который вы сопоставляете. Таким образом, атрибуты класса данных перезаписываются столбцами из таблицы, а свойства описаны в сопоставлении. Вы увидите больше проблем, когда объявите

@dataclass(frozen=True)

Вы должны использовать значения по умолчанию в объявлении столбца, и, возможно, объявленная модель вам больше подходит, она похожа на класс данных.

Если вы хотите отделить инфраструктуру/БД от кода вашего домена, вам нужен другой подход. Для этого я предлагаю рассматривать ваш класс данных как интерфейс для модели БД. Таким образом, модель БД будет реализовывать поведение, описанное вашим DTO. Но чтобы это заработало, вам понадобится репозиторий, который может справиться с этим. Я описываю похожий случай в этом блоге: 4 способа сопоставления объекта значения в SQLAlchemy

Другие вопросы по теме