(Примечание: в соответствии с призывом Stack Overflow обогатить сообщество, отвечая на собственные вопросы, особенно после вкладывая значительные усилия в устранение неполадок, я предоставляю ответ ниже. Я с радостью приветствую дальнейшие комментарии, дополнительные идеи или альтернативные подходы! Я бы также хотел признательность Ф. Казелли за ценную помощь в Форум SQLAlchemy, который помог мне сузить поиск. за решение.)
Здравствуйте, я пытаюсь создать столбец COMPUTED непосредственно в определении ORM, используя mapped_column() в SqlAlchemy 2.0, но мне это не удалось (я проверил Google и попытался найти ответ в документации).
Я знаю, что для базовой модели достаточно объявить таблицу следующим образом (пример Toy, где "area" — ВЫЧИСЛЯЕМЫЙ столбец):
square = Table(
"square",
metadata_obj,
Column("id", Integer, primary_key=True),
Column("side", Integer),
Column("area", Integer, Computed("side * side")),
)
Это эквивалентно следующему оператору SQL DDL (Postgresql >12):
CREATE TABLE square (
id SERIAL NOT NULL,
side INTEGER,
area INTEGER GENERATED ALWAYS AS (side * side) STORED,
PRIMARY KEY (id)
)
Однако, пытаясь сделать эквивалент в ORM, используя Mapped[] и mapped_column(), я не могу найти решение.
Я предполагал, что что-то вроде следующего может сработать, но это не так:
class Square(Base):
...
area: Mapped[int] = mapped_column(Computed("side*side"))
До этого я пробовал и другие конфигурации, например, передачу Computed() как server_default= и использование FetchedValue, но во всех случаях возникали ошибки.
Я также пришел к @hybrid_property, но понимаю, что это не то же самое (поскольку мне нужно, чтобы вычисляемый столбец был частью определения DDL таблицы, а @hybrid_property, похоже, оставляет вычисления во время выбора, но не как DDL).
Я был бы признателен, если бы кто-нибудь помог мне правильно использовать mapped_column() с Computed()!






Мы обнаружили, что недостающей частью был только параметр:
init=False
Итак, чтобы выполнить следующую работу:
area: Mapped[int] = mapped_column(Computed("side*side"))
его необходимо изменить на:
area: Mapped[int] = mapped_column(Computed("side*side"), init=False)
Оказывается, мой класс Base является производным от MappedAsDataclass, а создание метода __init__() класса данных не было обработано должным образом, как того требует этот тип класса Base.
По-видимому, механизм MappedAsDataclass не исключает автоматически столбец Computed() из метода __init__(), поэтому мы должны явно установить init=False в определении mapped_column().
Подводя итог, в моем случае решением было объявить столбец area следующим образом:
class Base(MappedAsDataclass, DeclarativeBase):
pass
class Square(Base):
...
# for computed column, use init=False if Base is a MappedAsDataclass
area: Mapped[int] = mapped_column(Computed("side*side"), init=False)
...
Спасибо Ф. Казелли за помощь в сужении поиска решения в форум sqlalchemy!!
Просто для справки в будущем (и, возможно, чтобы помочь в поиске решения кому-то с похожим случаем), ошибка, которую я получил, когда не включил
init=Falseдля столбцаComputed()в класс, производный отMappedAsDataclass, выглядит следующим образом:raise exc.InvalidRequestError (sqlalchemy.exc.InvalidRequestError: Python dataclasses error encountered when creating dataclass for 'Square': TypeError("non-default argument 'area' follows default argument")