Как создать таблицу с интервалом в качестве первичного ключа в SQLAlchemy?

#python #postgresql #sqlalchemy

#python #postgresql #sqlalchemy

Вопрос:

Я пытаюсь создать таблицу для обработки частоты выставления счетов с помощью ORM SQLAlchemy, и, похоже, я не могу заставить ее быть счастливой

Следующее отлично работает в Postgres:

 create table test_interval(
    frequency interval primary key
);


insert into test_interval values ('1 MONTH'), ('1 YEAR');


select * from test_interval;
-- 0 years 1 mons 0 days 0 hours 0 mins 0.00 secs
-- 1 years 0 mons 0 days 0 hours 0 mins 0.00 secs

  

И сейчас я пытаюсь добиться того же в SQLAlchemy с помощью этого кода

 from typing import Any

from sqlalchemy import Column, Interval, PrimaryKeyConstraint
from sqlalchemy.ext.declarative import as_declarative, declared_attr


@as_declarative()
class Base:
    id: Any
    __name__: str

    # Generate __tablename__ automatically
    @declared_attr
    def __tablename__(cls) -> str:
        return cls.__name__.lower()



class BillingFrequency(Base):
    __tablename__ = "billing_frequency"
    # I've also tried this
    # __table_args__ = (PrimaryKeyConstraint("frequency"),)
    # frequency: Column(Interval(native=True), index=True, unique=True, nullable=False)
    frequency: Column(Interval(native=True), primary_key=True, nullable=False)


# seed.py
# -- I've not even managed to create the table so this is yet untested --
from sqlalchemy.orm import Session
from sqlalchemy.dialects.postgresql import insert

from app.models import BillingFrequency

def seed_billing(db: Session) -> None:
    # Monthy frequency
    stmt_month = insert(BillingFrequency).values(frequency="1 MONTH")
    stmt_month = stmt_month.on_conflict_do_nothing(
        index_elements=[BillingFrequency.frequency],
    )
    db.add(stmt_month)
    # Year frequency
    stmt_year = insert(BillingFrequency).values(frequency="1 YEAR")
    stmt_year = stmt_year.on_conflict_do_nothing(
        index_elements=[BillingFrequency.frequency],
    )
    db.add(stmt_year)
    db.commit()


  

Что приводит к следующей ошибке:

 sqlalchemy.exc.ArgumentError: Mapper mapped class BillingFrequency->billing_frequency could not assemble any primary key columns for mapped table 'billing_frequency'
  

И если я попытаюсь использовать первичный ключ, используя __table_args__ , я получаю следующую ошибку.

  KeyError: 'frequency'
  

Не уверен, как с этим справиться. Это довольно тривиально в чистом SQL, но ORM делает это болезненным.

Комментарии:

1. В вашем определении класса попробуйте frequency = вместо frequency:

2. Хе-хе, да, я понял это, потратив на это добрый час или два. Кажется глупым, когда вы видите свою ошибку. Я также допустил еще одну ошибку при заполнении. Я добавил ответ на случай, если кто-то увидит это и столкнется с той же проблемой.

Ответ №1:

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

Первая проблема заключается в том, что вы использовали ...: Column т.е. Как тип вместо ...= Column присвоения значения. Это то, что вызывает sqlalchemy.exc.ArgumentError и KeyError: 'frequency' , SQLAlchemy не знает, что столбец существует, поскольку он не просматривает аннотации типов для данных столбца.

Вторая ошибка, которую вы допустили, заключается в использовании db.add(…) для оператора, вместо этого вы должны использовать db.execute(…) . Вы получите следующую ошибку с db.add :

 AttributeError: 'Insert' object has no attribute '_sa_instance_state'

The above exception was the direct cause of the following exception:
...
sqlalchemy.orm.exc.UnmappedInstanceError: Class 'sqlalchemy.dialects.postgresql.dml.Insert' is not mapped

  

С этими изменениями ваш код должен выглядеть следующим образом:

 
from typing import Any

from sqlalchemy import Column, Interval, PrimaryKeyConstraint
from sqlalchemy.ext.declarative import as_declarative, declared_attr


@as_declarative()
class Base:
    id: Any
    __name__: str

    # Generate __tablename__ automatically
    @declared_attr
    def __tablename__(cls) -> str:
        return cls.__name__.lower()



class BillingFrequency(Base):
    __tablename__ = "billing_frequency"
    frequency = Column(Interval(native=True), primary_key=True, nullable=False)


# seed.py
# -- I've not even managed to create the table so this is yet untested --
from sqlalchemy.orm import Session
from sqlalchemy.dialects.postgresql import insert

from app.models import BillingFrequency

def seed_billing(db: Session) -> None:
    # Monthy frequency
    stmt_month = insert(BillingFrequency).values(frequency="1 MONTH")
    stmt_month = stmt_month.on_conflict_do_nothing(
        index_elements=[BillingFrequency.frequency],
    )
    db.execute(stmt_month)
    # Year frequency
    stmt_year = insert(BillingFrequency).values(frequency="1 YEAR")
    stmt_year = stmt_year.on_conflict_do_nothing(
        index_elements=[BillingFrequency.frequency],
    )
    db.execute(stmt_year)
    db.commit()