Alembic:'关系" public.alembic_version"不存在'使用`version_table_schema`时

时间:2016-04-09 02:07:18

标签: postgresql python-3.5 alembic

我正在为Alembic编写一些自定义代码,以便在我的开发环境中为项目保持数据库始终更新。该项目涉及一个包含以下内容的数据库:

  • 共享数据的public架构
  • 每个客户端的单个架构"数据库"
  • 一个模式,充当所有客户端模式(组织)的prototype

目前,我并不担心多个客户端架构,只保持publicprototype架构是最新的。我的env.py脚本非常适合public架构,但不适用于prototype,因为在使用public时,alembic正尝试使用prototype中的版本表。

所以,我认为我可以使用version_table_schema选项在public架构中维护一个版本表,在prototype架构中维护一个版本表。但是,一旦我开始使用它,我就会得到一个' 关系" public.alembic_version"不存在'我尝试升级时出错。

我看到的唯一区别是,当我使用version_table_schema设置为适当的架构时,生成的修订脚本实际上包含一行op.drop_table('alembic_version')。该行仅在version_table_schema正在使用时存在。

我希望我只是遗漏了一些小事。

以下是所有相关的源文件供参考。唯一的外部依赖应该是appcontextgetDeclarativeBaseappcontext仅用于配置,并且确实有效(数据库连接正常工作)。 getDeclarativeBase是一种动态获取模式的声明性基础(和相关元数据)的方法。基于调试输出,这似乎也正常工作。元数据对象本身在构造时与正确的模式相关联。

用于运行迁移的包装函数。 autoMigratePublic()autoMigrateOrgProto()在这种情况下是有问题的方法

""" A wrapper around Alembic that will assist with database migrations.

    Our database system is soemwhat complex to upgrade. There is a public
    schema which we use for shared things (such as authentication and job
    management). Then there is the org prototype.

    There are also individual schemas for each client org. These are trickier.

    http://stackoverflow.com/a/35211383/703040

    Also useful:

    https://github.com/zzzeek/alembic/blob/0e3319bb36f1612e41a8d7da5a48ce1ca33a0b2b/alembic/config.py#L476
"""

import logging
import os
import time

import alembic.config
import alembic.util

CONFIG_ORG_PROTO = os.path.join("migrations", "alembic_orgproto.ini")
CONFIG_PUBLIC = os.path.join("migrations", "alembic_public.ini")

log = logging.getLogger("sys.migrations")

def autoMigratePublic():
    try:
        freezePublic()
    except alembic.util.CommandError:
        log.warn("[public] Problem creating migration revision. Skipping as this sometimes just means that we are working with a new DB.")
    upgradePublic()
    return

def autoMigrateOrgProto():
    try:
        freezeOrgProto()
    except alembic.util.CommandError:
        log.warn("[orgproto] Problem creating migration revision. Skipping as this sometimes just means that we are working with a new DB.")
    upgradeOrgProto()
    return

def freezePublic():
    log.info("[public] Checking the database for revisions...")
    alembicArgs = [
        "--config", CONFIG_PUBLIC,
        "--raiseerr",
        "revision",
        "--autogenerate",
        "--message", "autogenerate {0}".format(makeRevisionName()),
    ]
    runAlembic(alembicArgs)
    return

def freezeOrgProto():
    log.info("[orgproto] Checking the database for revisions...")
    alembicArgs = [
        "--config", CONFIG_ORG_PROTO,
        "--raiseerr",
        "revision",
        "--autogenerate",
        "--message", "autogenerate {0}".format(makeRevisionName()),
    ]
    runAlembic(alembicArgs)
    return

def makeRevisionName():
    return time.strftime('%Y-%m-%d %H:%M:%S')

def upgradePublic():
    log.info("[public] Performing database upgrade...")
    alembicArgs = [
        "--config", CONFIG_PUBLIC,
        "--raiseerr",
        "upgrade",
        "head",
    ]
    runAlembic(alembicArgs)
    return

def upgradeOrgProto():
    log.info("[orgproto] Performing database upgrade...")
    alembicArgs = [
        "--config", CONFIG_ORG_PROTO,
        "--raiseerr",
        "upgrade",
        "head",
    ]
    runAlembic(alembicArgs)
    return

def upgradeOrg(schemaName):
    log.info("[%s] Performing database upgrade...", schemaName)
    alembicArgs = [
        "--config", CONFIG_ORG_PROTO,
        "--raiseerr",
        "upgrade",
        "head",
        "-x", "schema={0}".format(schemaName),
    ]
    runAlembic(alembicArgs)
    return

def runAlembic(args):
    return alembic.config.main(args)

用于执行迁移的类。此类在env.py文件中引用

import copy
import logging
import os
import re
import traceback

from logging.config import fileConfig

from sqlalchemy import create_engine

import core.context.appcontext
from core.database.declarative import getDeclarativeBase

logging.getLogger("alembic").setLevel(logging.DEBUG)

#==============================================================================
class Migrator(object):

    def __init__(self):
        from alembic import context
        self.context = context
        self.config = context.config
        self.log = logging.getLogger("sys.migrations")
        self.sys = core.context.appcontext.instance()
        self.schema = self.config.get_main_option("schema")
        if self.context.get_x_argument("schema"):
            self.schema = self.context.get_x_argument("schema")
        return

    def start(self):
        import core.database.tables  # Make sure the metadata is defined
        if self.context.is_offline_mode():
            self.log.error("[%s] Can't run migrations offline", self.schema)
            return
        self.doMigration()
        return

    def doMigration(self):
        targetMetadata = getDeclarativeBase(self.schema).metadata
        engine = create_engine(self.sys.getConnectionUrl(), echo=False)
        self.log.info("[%s] Engine:   %s", self.schema, engine)
        self.log.debug("[%s] Metadata: %s", self.schema, targetMetadata)
        for t in targetMetadata.sorted_tables:
            self.log.debug("    - %s", t)
        conn = engine.connect()
        try:
            self.context.configure(
                conn,
                version_table_schema=self.schema,
                target_metadata=targetMetadata,
                process_revision_directives=self.process_revision_directives,
            )
            with self.context.begin_transaction():
                self.context.run_migrations()
        finally:
            conn.close()
        return

    def process_revision_directives(self, context, revision, directives):
        """ Used to prevent creating empty migrations

            See: http://alembic.readthedocs.org/en/latest/cookbook.html#don-t-generate-empty-migrations-with-autogenerate
        """
        if self.config.cmd_opts.autogenerate:
            script = directives[0]
            if script.upgrade_ops.is_empty():
                self.log.debug("[%s] Auto-generated migration is empty. No migration file will be created.", self.schema)
                directives[:] = []
            else:
                self.log.info("[%s] Creating new auto-generated migration revision.", self.schema)
        return

示例env.py公开升级和原型升级都具有相同的内容

from migrations.migrator import Migrator
Migrator().start() 

示例配置ini 公共和原型迁移几乎使用完全相同的文件

# A generic, single database configuration.

[alembic]
# path to migration scripts
script_location = migrations/orgproto

# template used to generate migration files
# file_template = %%(rev)s_%%(slug)s

# max length of characters to apply to the
# "slug" field
#truncate_slug_length = 40

# set to 'true' to run the environment during
# the 'revision' command, regardless of autogenerate
# revision_environment = false

# set to 'true' to allow .pyc and .pyo files without
# a source .py file to be detected as revisions in the
# versions/ directory
# sourceless = false

# version location specification; this defaults
# to alembic/versions.  When using multiple version
# directories, initial revisions must be specified with --version-path
# version_locations = %(here)s/bar %(here)s/bat alembic/versions

# the output encoding used when revision files
# are written from script.py.mako
# output_encoding = utf-8

sqlalchemy.url = <Not Loaded>

schema = orgproto

Mako文件我使用默认设置。

"""${message}

Revision ID: ${up_revision}
Revises: ${down_revision | comma,n}
Create Date: ${create_date}

"""

# revision identifiers, used by Alembic.
revision = ${repr(up_revision)}
down_revision = ${repr(down_revision)}
branch_labels = ${repr(branch_labels)}
depends_on = ${repr(depends_on)}

from alembic import op
import sqlalchemy as sa
${imports if imports else ""}

def upgrade():
    ${upgrades if upgrades else "pass"}


def downgrade():
    ${downgrades if downgrades else "pass"}

2 个答案:

答案 0 :(得分:1)

在我们的例子中,帮助将 alembic_version 添加到 [alembic:exclude]migrations/alembic.ini 部分:

[alembic:exclude]
- tables = spatial_ref_sys,topology,layer
+ tables = spatial_ref_sys,topology,layer,alembic_version

migrations/env.py 中的下一个:

context.configure(
    version_table_schema='public',
    # other params
)

答案 1 :(得分:0)

我收到了解决此问题的sqlalchemy-alembic google group的回复。将其发布在此处,以防其他任何人:

  

您可能会遇到适用的非常令人困惑的架构规则   PostgreSQL的。看到   http://docs.sqlalchemy.org/en/rel_1_0/dialects/postgresql.html#remote-schema-table-introspection-and-postgresql-search-path   详情。简短的回答是“空白”模式和模式   “公共”是Python方面的两个不同的东西,导致了很多   混乱。

     

为了说服autogenerate完全不影响alembic_version   无论弹出的位置如何,您都可能需要创建一个排除项   使用include_object的规则:   http://alembic.readthedocs.org/en/latest/api/runtime.html?highlight=include_object#alembic.runtime.environment.EnvironmentContext.configure.params.include_object

def include_object(object, name, type_, reflected, compare_to): 
    if (type_ == "table" and name == 'alembic_version'): 
        return False 
    else: 
        return True