Я работаю над проектом с Flask, SQLAlchemy, Alembic и их оболочками для Flask (Flask-SQLAlchemy и Flask-Migrate). У меня четыре миграции:
1c5f54d4aa34 -> 4250dfa822a4 (head), Feed: Countries
312c1d408043 -> 1c5f54d4aa34, Feed: Continents
41984a51dbb2 -> 312c1d408043, Basic Structure
<base> -> 41984a51dbb2, Init Alembic
Когда я запускаю новую и чистую базу данных и пытаюсь запустить миграции, я получаю сообщение об ошибке:
vagrant@precise32:/vagrant$ python manage.py db upgrade
...
sqlalchemy.exc.ProgrammingError: (ProgrammingError) relation "continent" does not exist
...
Если я попрошу Flask-Migrate запустить все миграции, кроме последней, все заработает. Если после этого я снова запустил команду обновления, она сработает, то есть полностью обновит мою базу данных без единого изменения кода:
vagrant@precise32:/vagrant$ python manage.py db upgrade 312c1d408043
INFO [alembic.migration] Context impl PostgresqlImpl.
INFO [alembic.migration] Will assume transactional DDL.
INFO [alembic.migration] Running upgrade -> 41984a51dbb2, Init Alembic
INFO [alembic.migration] Running upgrade 41984a51dbb2 -> 312c1d408043, Basic Structure
vagrant@precise32:/vagrant$ python manage.py db upgrade
INFO [alembic.migration] Context impl PostgresqlImpl.
INFO [alembic.migration] Will assume transactional DDL.
INFO [alembic.migration] Running upgrade 312c1d408043 -> 1c5f54d4aa34, Feed: Continents
INFO [alembic.migration] Running upgrade 1c5f54d4aa34 -> 4250dfa822a4, Feed: Countries
TL; DR
Последняя миграция (Лента: Страны) запускает запросы к таблице, загруженной предыдущей (Лента: Континенты). Если у меня есть таблица континентов, созданная и загруженная, скрипты должны работать. Но это не так. Почему я должен останавливать процесс миграции между ними, чтобы перезапустить его с помощью другой команды? Я действительно этого не понимаю. Это какая-то команда, которую Alembic выполняет после серии миграций? Любые идеи?
На всякий случай
Мои модели определены следующим образом:
class Country(db.Model):
__tablename__ = 'country'
id = db.Column(db.Integer, primary_key=True)
alpha2 = db.Column(db.String(2), index=True, unique=True)
title = db.Column(db.String(140))
continent_id = db.Column(db.Integer, db.ForeignKey('continent.id'))
continent = db.relationship('Continent', backref='countries')
def __repr__(self):
return '<Country #{}: {}>'.format(self.id, self.title)
class Continent(db.Model):
__tablename__ = 'continent'
id = db.Column(db.Integer, primary_key=True)
alpha2 = db.Column(db.String(2), index=True, unique=True)
title = db.Column(db.String(140))
def __repr__(self):
return '<Continent #{}: {}>'.format(self.id, self.title)
Большое спасибо,
ОБНОВЛЕНИЕ 1. Метод обновления последних двух миграций
Как @Miguel спросил в комментарии, здесь есть методы обновления последних двух миграций:
Лента: континенты
def upgrade():
csv_path = app.config['BASEDIR'].child('migrations', 'csv', 'en')
csv_file = csv_path.child('continents.csv')
with open(csv_file) as file_handler:
csv = list(reader(file_handler))
csv.pop(0)
data = [{'alpha2': c[0].lower(), 'title': c[1]} for c in csv]
op.bulk_insert(Continent.__table__, data)
Фид: страны (зависит от таблицы, загруженной при последней миграции)
def upgrade():
# load countries iso3166.csv and build a dictionary
csv_path = app.config['BASEDIR'].child('migrations', 'csv', 'en')
csv_file = csv_path.child('iso3166.csv')
countries = dict()
with open(csv_file) as file_handler:
csv = list(reader(file_handler))
for c in csv:
countries[c[0]] = c[1]
# load countries-continents from country_continent.csv
csv_file = csv_path.child('country_continent.csv')
with open(csv_file) as file_handler:
csv = list(reader(file_handler))
country_continent = [{'country': c[0], 'continent': c[1]} for c in csv]
# loop
data = list()
for item in country_continent:
# get continent id
continent_guess = item['continent'].lower()
continent = Continent.query.filter_by(alpha2=continent_guess).first()
# include country
if continent is not None:
country_name = countries.get(item['country'], False)
if country_name:
data.append({'alpha2': item['country'].lower(),
'title': country_name,
'continent_id': continent.id})
CSV, который я использую, в основном следуют этим шаблонам:
continents.csv
...
AS, "Asia"
EU, "Europe"
NA, "North America"
...
iso3166.csv
...
CL,"Chile"
CM,"Cameroon"
CN,"China"
...
_country_continent.csv_
...
US,NA
UY,SA
UZ,AS
...
Таким образом, Feed: Continents питает таблицу континентов, а Feed: Country - таблицу стран. Но он должен запросить таблицу континентов, чтобы установить правильную связь между страной и континентом.
ОБНОВЛЕНИЕ 2. Кто-то из Reddit уже предложил объяснение и обходной путь
Я задал steps_upgrade/cmua7az">тот же вопрос на Reddit и themathemagician сказал:
Я сталкивался с этим раньше, и проблема в том, что миграции не выполняются индивидуально, а вместо этого алембический пакет объединяет их все (или все из них, которые необходимо запустить), а затем выполняет SQL. Это означает, что к моменту выполнения последней миграции таблицы еще не существуют, поэтому вы не можете выполнять запросы. Делает
from alembic import op def upgrade(): #migration stuff op.execute('COMMIT') #run queries
Это не самое элегантное решение (и это было для Postgres, команда может отличаться для других dbs), но у меня оно сработало. Кроме того, на самом деле это проблема не с Flask-Migrate, а с алембиком, поэтому, если вы хотите получить дополнительную информацию в Google, поищите алембик. Flask-Migrate - это просто оболочка для перегонки, которая легко работает с Flask-Script.
upgrade()
методы последних двух миграций? - person Miguel   schedule 14.12.2014