2014-07-08 17 views
11

Używam SQLite jako formatu pliku aplikacji (zobacz here dla tego, dlaczego chciałbyś to zrobić) dla mojej aplikacji opartej na PySide. Oznacza to, że gdy użytkownik korzysta z mojej aplikacji, ich dane są zapisywane w jednym pliku bazy danych na ich komputerze. Używam SQLAlchemy ORM do komunikowania się z bazami danych.Używanie Alembic API z wewnętrznego kodu aplikacji

Po wydaniu nowych wersji aplikacji mogę zmodyfikować schemat bazy danych. Nie chcę, aby użytkownicy musieli wyrzucać swoje dane za każdym razem, gdy zmieniam schemat, więc muszę przeprowadzić migrację ich baz danych do najnowszego formatu. Ponadto tworzę tymczasowe bazy danych bardzo dużo, aby zapisać podzbiory danych do wykorzystania w niektórych procesach zewnętrznych. Chcę utworzyć te bazy danych za pomocą programu alembic, aby zostały oznaczone odpowiednią wersją.

mam kilka pytań:

  • Czy istnieje sposób, aby wywołać alembic od wewnątrz mojego kodu Pythona? Myślę, że to dziwne, że trzeba użyć Popen do czystego modułu Pythona, ale dokumenty używają alembiku z wiersza poleceń. Głównie, muszę zmienić lokalizację bazy danych, gdziekolwiek znajduje się baza danych użytkownika.

  • Jeśli nie jest to możliwe, czy mogę określić nową lokalizację bazy danych z wiersza poleceń bez edytowania pliku .ini? To sprawiłoby, że dzwonienie na alembik przez Popen nic wielkiego.

  • widzę, że alembic zachowuje informacje o wersji pod prostej tabeli o nazwie alembic_version, z jednej kolumnie o nazwie version_num i jednym rzędzie określa wersję. Czy mogę dodać tabelę alembic_version do mojego schematu i wypełnić ją najnowszą wersją, gdy tworzę nowe bazy danych, więc nie ma narzutów? Czy to nawet dobry pomysł; czy powinienem używać alembiku do tworzenia wszystkich baz danych?

Mam alembik świetnie współpracujący z pojedynczą bazą danych, z której korzystam w swoim katalogu projektu. Chcę używać programu alembic do wygodnej migracji i tworzenia baz danych w dowolnych lokalizacjach, najlepiej za pośrednictwem jakiegoś interfejsu API języka Python, a nie wiersza poleceń. Ta aplikacja jest również zamrożona przez cx_Freeze, w przypadku, która robi różnicę.

Dzięki!

Odpowiedz

7

To bardzo szerokie pytanie, a realizacja twojego pomysłu będzie zależeć od Ciebie, ale jest to możliwe.

Możesz wywołać Alembic z kodu Pythona bez użycia poleceń, ponieważ jest on również zaimplementowany w Pythonie! Trzeba tylko odtworzyć to, co polecenia wykonują za kulisami.

Wprawdzie docs nie są w bardzo dobrym stanie, gdyż są to nadal stosunkowo wczesne wersje biblioteki, ale z trochę kopanie znajdziesz następujące:

  1. Tworzenie Config
  2. Użyj Config stworzyć ScriptDirectory
  3. Użyj Config i ScriptDirectory stworzyć EnvironmentContext
  4. Użyj EnvironmentContext stworzyć MigrationContext
  5. Większość poleceń korzystać z niektórych kombinacji metod z Config i MigrationContext

Pisałem o przedłużenie świadczenia tej programowej Alembic dostęp do bazy Kolba-sqlalchemy. Implementacja jest powiązana z Flask i Flask-SQLAlchemy, ale powinno być dobrym miejscem do rozpoczęcia. See Flask-Alembic here.

chodzi o ostatni punkt na temat tworzenia nowych baz danych, można użyć alembic tworzyć tabele, lub użyć metadata.create_all() następnie alembic stamp head (lub równoważny kod Pythona). Zalecam zawsze używanie ścieżki migracji do tworzenia tabel i ignorowanie surowego metadata.create_all().

Nie mam żadnego doświadczenia z cx_freeze, ale powinno być w porządku, o ile migracje są uwzględnione w dystrybucji, a ścieżka do tego katalogu w kodzie jest poprawna.

9

Oto czego się nauczyłem po podpinania się moje oprogramowanie do alembic:

Czy istnieje sposób, aby wywołać alembic od wewnątrz mojego kodu Pythona?

Tak. W chwili pisania tego głównego punktu odbioru alembiku jest alembic.config.main, więc można go importować i nazwać siebie, na przykład:

import alembic.config 
alembicArgs = [ 
    '--raiseerr', 
    'upgrade', 'head', 
] 
alembic.config.main(argv=alembicArgs) 

Zauważ, że alembic wygląda na migracje w bieżącym katalogu (tj os.getcwd ()). Zajmowałem się tym za pomocą os.chdir(migration_directory) przed wywołaniem alembik, ale może być lepsze rozwiązanie.


Czy mogę określić nową lokalizację bazy danych z wiersza poleceń bez edytowania pliku .ini?

Tak. Klucz leży w argumencie linii komend -x. Od alembic -h (o dziwo, nie był w stanie znaleźć odniesienie argument wiersza poleceń w docs):

optional arguments: 
-x X     Additional arguments consumed by custom env.py 
         scripts, e.g. -x setting1=somesetting -x 
         setting2=somesetting 

dzięki czemu można tworzyć własne parametr, np , a następnie przechwycić go w env.py:

alembic -x dbPath=/path/to/sqlite.db upgrade head

następnie na przykład w env.py:

def run_migrations_online(): 
    # get the alembic section of the config file 
    ini_section = config.get_section(config.config_ini_section) 

    # if a database path was provided, override the one in alembic.ini 
    db_path = context.get_x_argument(as_dictionary=True).get('dbPath') 
    if db_path: 
     ini_section['sqlalchemy.url'] = db_path 

    # establish a connectable object as normal 
    connectable = engine_from_config(
     iniSection, 
     prefix='sqlalchemy.', 
     poolclass=pool.NullPool) 

    # etc 

Oczywiście, można podać parametr -x korzystając argv w alembic.config.main też.

zgadzam się z @davidism temat korzystania migracje vs metadata.create_all() :)

2

Oto czysto programmical przykładem howto skonfigurować i nazwać alembic polecenia programowo.

katalog instalacyjny (dla łatwiejszego odczytu kodu)

.       # root dir 
|- alembic/    # directory with migrations 
|- tests/diy_alembic.py # example script 
|- alembic.ini   # ini file 

A oto diy_alembic.py

import os 
import argparse 
from alembic.config import Config 
from alembic import command 
import inspect 

def alembic_set_stamp_head(user_parameter): 
    # set the paths values 
    this_file_directory = os.path.dirname(os.path.abspath(inspect.stack()[0][1])) 
    root_directory  = os.path.join(this_file_directory, '..') 
    alembic_directory = os.path.join(root_directory, 'alembic') 
    ini_path   = os.path.join(root_directory, 'alembic.ini') 

    # create Alembic config and feed it with paths 
    config = Config(ini_path) 
    config.set_main_option('script_location', alembic_directory)  
    config.cmd_opts = argparse.Namespace() # arguments stub 

    # If it is required to pass -x parameters to alembic 
    x_arg = 'user_parameter=' + user_parameter 
    if not hasattr(config.cmd_opts, 'x'): 
     if x_arg is not None: 
      setattr(config.cmd_opts, 'x', []) 
      if isinstance(x_arg, list) or isinstance(x_arg, tuple): 
       for x in x_arg: 
        config.cmd_opts.x.append(x) 
      else: 
       config.cmd_opts.x.append(x_arg) 
     else: 
      setattr(config.cmd_opts, 'x', None) 

    #prepare and run the command 
    revision = 'head' 
    sql = False 
    tag = None 
    command.stamp(config, revision, sql=sql, tag=tag) 

    #upgrade command 
    command.upgrade(config, revision, sql=sql, tag=tag) 

Kod jest mniej lub bardziej wycięte z this Flask-Alembic file. Jest to dobre miejsce, aby przyjrzeć się innym komendom użycia i szczegółom.

Dlaczego to rozwiązanie? - Został napisany w celu stworzenia znaczników alembików, aktualizacji i obniżek podczas uruchamiania testów automatycznych.

  • Os.chdir (katalog_przechodzenia) zakłócał niektóre testy.
  • Chcieliśmy mieć JEDNEGO źródło tworzenia i manipulowania bazą danych. "Jeśli tworzymy skrzynki z alembikiem i zarządzamy nimi, to do testów również można użyć powłoki alembic, ale nie metadata.create_all()".
  • Nawet jeśli powyższy kod jest dłuższy niż 4 linie, alembik pokazał się jako dobrze kontrolowana bestia, jeśli jest napędzana w ten sposób.
Powiązane problemy