2012-12-03 11 views
6

Mam obecnie kolumnę zawierającą znaczniki HTML. Wewnątrz tego znacznika znajduje się znacznik czasu, który chcę zapisać w nowej kolumnie (aby móc zapytać o to). Mój pomysł był aby wykonać następujące czynności w jednym migracji:Używanie ORM SQLAlchemy wewnątrz migracji Alembic: jak to zrobić?

  1. Utwórz nowy, zerowalne kolumny dla danych
  2. używać ORM w celu wyciągnięcia HTML muszę przeanalizować
  3. Dla każdego wiersza
    1. parsowania HTML wyciągnąć znacznik czasu
    2. zaktualizować obiekt ORM

Ale kiedy próbuję uruchomić moją migrację, wydaje się, że utknęła w nieskończonej pętli. Oto co mam do tej pory:

def _extract_publication_date(html): 
    root = html5lib.parse(html, treebuilder='lxml', namespaceHTMLElements=False) 
    publication_date_string = root.xpath("//a/@data-datetime")[0] 
    return parse_date(publication_date) 


def _update_tip(tip): 
    tip.publication_date = _extract_publication_date(tip.rendered_html) 
    tip.save() 


def upgrade(): 
    op.add_column('tip', sa.Column('publication_date', sa.DateTime(timezone=True))) 
    tips = Tip.query.all() 
    map(tips, _update_tip) 


def downgrade(): 
    op.drop_column('tip', 'publication_date') 
+0

Skąd wiesz, że utknął w nieskończonej pętli? –

+1

Jeśli 'Tip.query' nie używa tej samej sesji co' op', to będą 2 transakcje, przy czym 'SELECT' utknął czekając na' ALTER TABLE', aby zatwierdzić. W każdym razie myślę, że jest czystsze, aby przenieść część ORM do jej własnego skryptu, aby uruchomić ją ręcznie po "aktualizacji alemicznej". – sayap

+0

@ X-Istence Nie wiem, że utknęła w nieskończonej pętli. I ** DO ** wiem, że polecenie nigdy nie wraca. –

Odpowiedz

1

dalej od komentarzy, możesz spróbować czegoś takiego:

import sqlalchemy as sa 


tip = sa.sql.table(
    'tip', 
    sa.sql.column('id', sa.Integer), 
    sa.sql.column('publication_date', sa.DateTime(timezone=True)), 
) 


def upgrade(): 
    mappings = [ 
     (x.id, _extract_publication_date(x.rendered_html)) 
     for x in Tip.query 
    ] 

    op.add_column('tip', sa.Column('publication_date', sa.DateTime(timezone=True))) 

    exp = sa.sql.case(value=tip.c.id, whens=(
     (op.inline_literal(id), op.inline_literal(publication_date)) 
     for id, publication_date in mappings.iteritems() 
    )) 

    op.execute(tip.update().values({'publication_date': exp})) 


def downgrade(): 
    op.drop_column('tip', 'publication_date') 
4

Co pracował dla mnie jest, aby sesję, wykonując następujące czynności:

connection = op.get_bind() 
Session = sa.orm.sessionmaker() 
session = Session(bind=connection) 
+0

Ten jeden * rodzaj * działał dla mnie, chociaż były błędy, że moje modele były już związane z sesją. – killthrush

4

Po odrobinie eksperymentów z użyciem odpowiedzi @ velochy, zdecydowałem się na coś podobnego do następującego przy używaniu SqlAlchemy w Alembic. Ten pracował dla mnie świetnie i prawdopodobnie mógłby służyć jako ogólne rozwiązanie dla kwestii PO za:

from sqlalchemy.orm.session import Session 
from alembic import op 

def upgrade(): 
    # Attach a sqlalchemy Session to the env connection 
    session = Session(bind=op.get_bind()) 

    # Perform arbitrarily-complex ORM logic 
    instance1 = Model1(foo='bar') 
    instance2 = Model2(monkey='banana') 

    # Add models to Session so they're tracked 
    session.add(instance1) 
    session.add(instance2) 

def downgrade(): 
    # Attach a sqlalchemy Session to the env connection 
    session = Session(bind=op.get_bind()) 

    # Perform ORM logic in downgrade (e.g. clear tables) 
    session.query(Model2).delete() 
    session.query(Model1).delete() 

Takie podejście wydaje się prawidłowo obsługiwać transakcje. Często podczas pracy nad tym generowałem wyjątki dla DB, a następnie przywracano je zgodnie z oczekiwaniami.

+1

Warto zauważyć, że ta technika może nie być zalecana, jeśli modele często się zmieniają. Gdy zmieniają się modele, może przerwać stare migracje, które zakładają, że modele mają określony kształt. – killthrush

Powiązane problemy