2 votes

Comment empêcher sql alchemy d'insérer la valeur None dans un champ ?

La migration des alambics script :

def upgrade():
    uuid_gen = saexp.text("UUID GENERATE V1MC()")
    op.create_table(
        'foo',
        sa.Column('uuid', UUID, primary_key=True, server_default=uuid_gen),
        sa.Column(
            'inserted',
            sa.DateTime(timezone=True),
            server_default=sa.text("not null now()"))
        sa.Column('data', sa.Text)
    )

C'est ma classe de base pour SQL Alchemy :

Class Foo(Base):
    __tablename__ = 'foo'
    inserted = Column(TIMESTAMP)
    uuid = Column(UUID, primary_key=True)
    data = Column(TEXT)

Il dispose d'un mode d'insertion statique :

@staticmethod
def insert(session, jsondata):
  foo = Foo()
  foo.data = jsondata['data']
  if 'inserted' in jsondata:
      foo.inserted = jsondata['inserted']
  if 'uuid' in jsondata:
      foo.uuid = jsondata['uuid']
  session.add(foo)
  return foo

le but des 2 if est de simplifier les tests. de cette façon, je peux "injecter" un uuid et une date insérée, pour obtenir des données prédictibles pour mes tests.

Lorsque vous essayez d'insérer des données

foo = Foo()
foo.insert(session, {"data": "foo bar baz"})
session.commit()

Je reçois un IntegrityError :

[SQL: 'INSERT INTO foo (inserted, data) VALUES (%(inserted)s, %(data)s) RETURNING foo.uuid'] [parameters: {'data': 'foo bar baz', 'inserted': None}]

ce qui me semble normal car l'insertion viole la contrainte "not-null" dans la base de données postgres.

Comment empêcher sql alchemy d'insérer la valeur None dans le champ inséré ?

En jouant et en testant, j'ai découvert que si la colonne "insérée" est définie comme clé primaire, sql alchemy n'inclut pas le champ dans la déclaration d'insertion.

def upgrade():
    uuid_gen = saexp.text("UUID GENERATE V1MC()")
    op.create_table(
        'foo',
        sa.Column('uuid', UUID, primary_key=True, server_default=uuid_gen),
        sa.Column(
            'inserted',
            primary_key=True,
            sa.DateTime(timezone=True),
            server_default=sa.text("not null now()"))
        sa.Column('data', sa.Text)
    )

Mais ce n'est pas ce que je veux.

1voto

exhuma Points 3619

Le problème principal est le server_default qui manque dans le inserted membre de la classe Foo . Il n'est présent que dans le script d'alembic. Notez que les définitions de l'alambic sont uniquement utilisé lors de l'exécution des migrations. Ils font no affecter l'application. Pour cette raison, c'est une bonne idée de copier exactement les mêmes définitions de l'alembic script vers votre application (ou vice-versa).

Parce qu'aucune valeur n'est définie dans la définition du modèle, sqlalchemy semble fixer cette valeur à None lorsque la classe est instanciée. Ceci sera alors envoyé à la DB qui se plaindra. Pour résoudre ce problème, il faut soit définir default o server_default sur la définition du modèle (la classe héritant de Base ).

Quelques notes/questions supplémentaires :

  • Où se trouve UUID GENERATE V1MC() proviennent-ils ? Le site documents officiels semblent différentes. Je l'ai remplacé par func.uuid_generate_v1mc() .
  • Le site server_default valeur dans votre cas contient not null ce qui est incorrect. Vous devez définir nullable=False sur l'attribut de votre colonne (voir ci-dessous).

alembic script

# revision identifiers, used by Alembic.
revision = THIS_IS_DIFFERENT_ON_EACH_INSTANCE!  # '1b7e145f2138'
down_revision = None
branch_labels = None
depends_on = None

from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects.postgresql import UUID

def upgrade():
    op.create_table(
        'foo',
        sa.Column('uuid', UUID, primary_key=True,
                server_default=sa.func.uuid_generate_v1mc()),
        sa.Column(
            'inserted',
            sa.DateTime(timezone=True),
            nullable=False,
            server_default=sa.text("now()")),
        sa.Column('data', sa.Text)
    )

def downgrade():
    op.drop_table('foo')

tester.py

from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, create_engine, func
from sqlalchemy.orm import scoped_session, sessionmaker
from sqlalchemy.dialects.postgresql import (
    TEXT,
    TIMESTAMP,
    UUID,
)

engine = create_engine('postgresql://michel@/michel')
Session = scoped_session(sessionmaker(autocommit=False,
                                      autoflush=False,
                                      bind=engine))
Base = declarative_base()

class Foo(Base):
    __tablename__ = 'foo'
    inserted = Column(TIMESTAMP, nullable=False,
                      server_default=func.now())
    uuid = Column(UUID, primary_key=True,
                  server_default=func.uuid_generate_v1mc()),
    data = Column(TEXT)

    @staticmethod
    def insert(session, jsondata):
        foo = Foo()
        foo.data = jsondata['data']
        if 'inserted' in jsondata:
            foo.inserted = jsondata['inserted']
        if 'uuid' in jsondata:
            foo.uuid = jsondata['uuid']
        session.add(foo)
        return foo

if __name__ == '__main__':
    session = Session()
    Foo.insert(session, {"data": "foo bar baz"})
    session.commit()
    session.close()

sortie après exécution

[9:43:54] michel@BBS-nexus  [1 background job(s)] 
/home/users/michel/tmp› psql -c "select * from foo"
                 uuid                 |           inserted            |    data     
--------------------------------------+-------------------------------+-------------
 71f5fd32-0602-11e6-aebb-27be4bbac26e | 2016-04-19 09:43:45.297191+02 | foo bar baz
(1 row)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X