能否将Alembic连接字符串存储在alembic.ini之外?

129

我正在使用SQLAlchemy的Alembic。在SQLAlchemy中,我倾向于遵循一种模式,即不将连接字符串与版本化代码存储在一起。相反,我有一个名为secret.py的文件,其中包含任何机密信息。我把这个文件名放在我的.gitignore中,以便它不会出现在GitHub上。

这种模式很好用,但现在我开始使用Alembic进行迁移。看起来我无法隐藏连接字符串。相反,在alembic.ini中,您需要将连接字符串作为配置参数放置:

# the 'revision' command, regardless of autogenerate
# revision_environment = false

sqlalchemy.url = driver://user:pass@localhost/dbname

# Logging configuration
[loggers]
keys = root,sqlalchemy,alembi

我担心我会不小心提交一个包含数据库用户名/密码信息的文件。 我宁愿将连接字符串存储在一个地方,避免意外将其提交到版本控制中。

我有哪些选项?

15个回答

112

昨天我也遇到了同样的问题,并找到以下解决方案有效。

我在alembic/env.py中执行以下操作:

# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config

# this will overwrite the ini-file sqlalchemy.url path
# with the path given in the config of the main code
import config as ems_config
config.set_main_option('sqlalchemy.url', ems_config.config.get('sql', 'database'))

ems_config 是一个外部模块,保存着我的配置数据。

config.set_main_option(...) 本质上会覆盖 alembic.ini 文件中 [alembic] 部分的 sqlalchemy.url 键。在我的配置中,我只需将其留空即可。


我认为alembic.config是一个内部使用的API,当使用alembic时不应该使用它:https://alembic.sqlalchemy.org/en/latest/api/config.html "本节讨论了Alembic的内部API,涉及内部配置结构。" - Eric Stdlib

87

为避免提交我的用户/密码,我能想到的最简单的方法是:a)在alembic.ini文件中添加插值字符串,并b)在env.py中设置这些插值值。

alembic.ini

sqlalchemy.url = postgresql://%(DB_USER)s:%(DB_PASS)s@35.197.196.146/nozzle-website

env.py

import os

from logging.config import fileConfig

from sqlalchemy import engine_from_config
from sqlalchemy import pool

from alembic import context

# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config

# here we allow ourselves to pass interpolation vars to alembic.ini
# fron the host env
section = config.config_ini_section
config.set_section_option(section, "DB_USER", os.environ.get("DB_USER"))
config.set_section_option(section, "DB_PASS", os.environ.get("DB_PASS"))

...

2
这对我很有效,而且似乎比顶部答案更容易 - 有什么缺点吗? - mattnedrich
3
由于 env.py 现在依赖于正确设置的 env 变量,因此您可能需要编写一些防御性代码,并在未设置 DB_USER 或 DB_PASS 时提供有用的“使用”消息。 - TomDotTom
1
到目前为止最更新的解决方案。现在您可能已经在受保护的.env文件中设置密码并使用dotenv进行加载。因此,重用此env变量是有意义的。 如果您只写os.environ [“DB_USER”],则也可能不需要“防御性编码”,因为生成的键错误已经非常明确了。 - CheradenineZK
当密码中含有 '@' 字符时,似乎会很困难。 - npk
这项工作对我来说非常完美。谢谢。 - Pratik Sharma
显示剩余2条评论

39

Alembic文档建议使用数据库URL(而不是修改代码中的sqlalchemy.url)来使用create_engine

此外,您应该修改run_migrations_offline以使用新的URL。Allan Simon在他的博客上有一个例子,但总结起来,要修改env.py

  1. Provide a shared function to get the URL somehow (here it comes from the command line):

    def get_url():
        url = context.get_x_argument(as_dictionary=True).get('url')
        assert url, "Database URL must be specified on command line with -x url=<DB_URL>"
        return url
    
  2. Use the URL in offline mode:

    def run_migrations_offline():
        ...
        url = get_url()
        context.configure(
            url=url, target_metadata=target_metadata, literal_binds=True)
        ...
    
  3. Use the URL in online mode by using create_engine instead of engine_from_config:

    def run_migrations_online():
        ...
        connectable = create_engine(get_url())
        with connectable.connect() as connection:
        ...
    

这可以结合 https://alembic.sqlalchemy.org/en/latest/cookbook.html#sharing-a-connection-with-a-series-of-migration-commands-and-environments,使其成为一个很好的答案! - ericbn
我必须在env.py文件中添加这一行代码:from sqlalchemy import create_engine - Miguel Conde
谢谢,工作变体! - Nikolay Baranenko
对于异步使用情况(其中alembic的初始化方式为:alembic init -t async migrations),在**async def run_migrations_online()**中设置connectable = AsyncEngine(create_engine(get_url())) - VDR

11
所以看起来的解决方法是在env.py中重新实现引擎创建,这显然是一个进行此类定制的地方,而不是使用ini文件中的SQLAlchemy连接字符串。
engine = engine_from_config(
            config.get_section(config.config_ini_section),
            prefix='sqlalchemy.',
           poolclass=pool.NullPool)

您可以更换并指定自己的引擎配置:
import store
engine = store.engine

确实,文档似乎暗示这是可以的:

sqlalchemy.url - 通过SQLAlchemy连接到数据库的URL。实际上,此键仅在特定于“通用”配置的env.py文件中引用;这是一个可以由开发人员自定义的文件。多个数据库配置可能会在此处响应多个键,或者可能引用文件的其他部分。


1
使用最新版本的alembic(0.8.1),这将只影响“online”模式。run_migrations_offline也需要更改以使用不同的数据库URL。 - danio

6
我一直在寻找如何管理多数据库的方法。这是我所做的:我有两个数据库:logsohlc。根据文档,我已经设置好了Alembic。
alembic init --template multidb

alembic.ini

databases = logs, ohlc
[logs]
sqlalchemy.url = postgresql://botcrypto:botcrypto@localhost/logs
[ohlc]
sqlalchemy.url = postgresql://botcrypto:botcrypto@localhost/ohlc

env.py

[...]
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config

# Interpret the config file for Python logging.
# This line sets up loggers basically.
fileConfig(config.config_file_name)
logger = logging.getLogger('alembic.env')

# overwrite alembic.ini db urls from the config file
settings_path = os.environ.get('SETTINGS')
if settings_path:
    with open(settings_path) as fd:
        settings = conf.load(fd, context=os.environ) # loads the config.yml
    config.set_section_option("ohlc", "sqlalchemy.url", settings["databases"]["ohlc"])
    config.set_section_option("logs", "sqlalchemy.url", settings["databases"]["logs"])
else:
    logger.warning('Environment variable SETTINGS missing - use default alembic.ini configuration')
[...]

config.yml

databases:
    logs: postgresql://botcrypto:botcrypto@127.0.0.1:5432/logs
    ohlc: postgresql://botcrypto:botcrypto@127.0.0.1:5432/ohlc

使用方法

SETTINGS=config.yml alembic upgrade head

希望这可以帮到您!

4
在多数据库(与单数据库相同)的情况下,您可以使用 config.set_section_option('section_name', 'variable_name', 'db_URL') 来修改 alembic.ini 文件中数据库 URL 的值。
例如: alembic.init
[engine1]
sqlalchemy.url = 

[engine2]
sqlalchemy.url = 

Then,

env.py

config = context.config

config.set_section_option('engine1', 'sqlalchemy.url', os.environ.get('URL_DB1'))
config.set_section_option('engine2', 'sqlalchemy.url', os.environ.get('URL_DB2'))

4

2

我也遇到了这个问题,因为我们是从本地机器运行迁移。我的解决方案是在 alembic.ini 中放置环境部分,其中存储数据库配置(减去凭据):

[local]
host = localhost
db = dbname

[test]
host = x.x.x.x
db = dbname

[prod]
host = x.x.x.x
db = dbname

然后我将以下内容放入env.py中,以便用户可以选择他们的环境并提示输入凭据:

from alembic import context
from getpass import getpass

...

envs = ['local', 'test', 'prod']

print('Warning: Do not commit your database credentials to source control!')
print(f'Available migration environments: {", ".join(envs)}')

env = input('Environment: ')

if env not in envs:
    print(f'{env} is not a valid environment')
    exit(0)

env_config = context.config.get_section(env)
host = env_config['host']
db = env_config['db']

username = input('Username: ')
password = getpass()
connection_string = f'postgresql://{username}:{password}@{host}/{db}'

context.config.set_main_option('sqlalchemy.url', connection_string)

你应该将凭据存储在密码管理器中,整个团队都可以访问,或者使用可用的配置/秘密存储。但是,采用这种方法会暴露密码到本地剪贴板 - 更好的方法是让env.py直接连接到您的配置/秘密存储API,并直接提取用户名/密码,但这会增加第三方依赖项。

1
我想要使用SQLAlchemy和Redshift作为数据库。
另外,我不想为它们分别设置相同的配置文件。
所以,我决定像Laravel一样使用.env文件。
设置步骤如下。
首先,安装python-dotenv和其他相关的东西。
pip install python-dotenv alembic psycopg2-binary SQLAlchemy sqlalchemy-redshift

在alembic.ini文件中,我保持空白就像这样。
#sqlalchemy.url = driver://user:pass@localhost/dbname
sqlalchemy.url =

在env.py中,我添加了以下内容。 关键是你应该将这些放在函数之前。
from dotenv import load_dotenv
load_dotenv()
dialect = "redshift+psycopg2://"
url = dialect + os.getenv("REDSHIFT_USER") + ":" + os.getenv("REDSHIFT_PW") + "@" + os.getenv("REDSHIFT_HOST") + "/" + os.getenv("REDSHIFT_DATABASE")
config.set_main_option( 'sqlalchemy.url', url)

0

正如Doug T.所说的那样,您可以编辑env.py文件以提供来自ini文件以外的URL。而不是创建新引擎,您可以向engine_from_config函数传递一个额外的url参数(kwargs稍后将与从ini文件中获取的选项合并)。在这种情况下,您可以在ini文件中存储加密密码,并通过存储在ENV变量中的口令在运行时解密它。

connectable = engine_from_config(                 
    config.get_section(config.config_ini_section),
    prefix='sqlalchemy.',                         
    poolclass=pool.NullPool,                      
    url=some_decrypted_endpoint)                                   

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接