Я использую 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)
у меня похожая проблема, может кто подскажет? stackoverflow.com/questions/67185813/…
Поскольку ''
и 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: я обновил ответ проверенным решением, которое работает для значений по умолчанию, отличных от ''
и 0
, используя default_factory
и lambda
Это не объясняет, почему default='john doe'
не работает, но, честно говоря, отвечает на мой первоначальный вопрос. Я попробую и отмечу как ответ, если это сработает.
По крайней мере, одна из причин, по которой default=
не работает, заключается в том, что классы данных устанавливают значение по умолчанию в качестве атрибута класса, а классическое сопоставление SQLA не настраивает инструменты для существующих атрибутов класса.
Печально то, что используется классическое отображение, чтобы избежать инвазивных изменений в текущей кодовой базе (не наследование от Base). Но теперь вам нужно изменить кодовую базу, чтобы она работала со значениями по умолчанию. (Я знаю, что это все еще агрессивно, поскольку инструментарий прикрепляет таблицу и другую метаинформацию к классу).
Сопоставление изменяет класс, который вы сопоставляете. Таким образом, атрибуты класса данных перезаписываются столбцами из таблицы, а свойства описаны в сопоставлении. Вы увидите больше проблем, когда объявите
@dataclass(frozen=True)
Вы должны использовать значения по умолчанию в объявлении столбца, и, возможно, объявленная модель вам больше подходит, она похожа на класс данных.
Если вы хотите отделить инфраструктуру/БД от кода вашего домена, вам нужен другой подход. Для этого я предлагаю рассматривать ваш класс данных как интерфейс для модели БД. Таким образом, модель БД будет реализовывать поведение, описанное вашим DTO. Но чтобы это заработало, вам понадобится репозиторий, который может справиться с этим. Я описываю похожий случай в этом блоге: 4 способа сопоставления объекта значения в SQLAlchemy
Разве это не из-за разницы между
default
иserver_default
?