我正在使用Alembic和SQL Alchemy。使用SQL Alchemy,我倾向于遵循一种模式,即我不将连接字符串与版本化代码一起存储。相反,我的文件secret.py
包含任何机密信息。我在.gitignore
中抛出了这个文件名,所以它不会在GitHub上结束。
这种模式运行良好,但现在我开始使用Alembic进行迁移。看来我无法隐藏连接字符串。而是在alembic.ini中,将连接字符串设置为configuration parameter:
# the 'revision' command, regardless of autogenerate
# revision_environment = false
sqlalchemy.url = driver://user:pass@localhost/dbname
# Logging configuration
[loggers]
keys = root,sqlalchemy,alembi
我担心我会不小心为我的数据库提交一个包含用户名/密码信息的文件。我宁愿将这个连接字符串存储在一个地方,避免意外将其提交给版本控制的风险。
我有哪些选择?
答案 0 :(得分:38)
我昨天遇到了同样的问题,并找到了以下解决方案。
我在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(...)
基本上会覆盖sqlalchemy.url
文件的[alembic]
部分中的alembic.ini
密钥。在我的配置中,我只是将其留下黑色。
答案 1 :(得分:13)
Alembic documentation建议将@RiggsFolly
与数据库网址(而不是modifying sqlalchemy.url in code)一起使用。
此外,您应修改run_migrations_offline以使用新URL。 Allan Simon在他的博客上有an example,但总的来说,将env.py修改为:
提供一个共享函数以某种方式获取URL(这里它来自命令行):
create_engine
在离线模式下使用网址:
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
使用def run_migrations_offline():
...
url = get_url()
context.configure(
url=url, target_metadata=target_metadata, literal_binds=True)
...
代替create_engine
在线模式使用网址:
engine_from_config
答案 2 :(得分:8)
所以看起来有效的是在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
确实文档seems to imply这没关系:
sqlalchemy.url - 通过SQLAlchemy连接到数据库的URL。实际上,该密钥仅在env.py文件中引用,该文件特定于“通用”配置;可由开发人员自定义的文件。多数据库配置可以在此处响应多个键,或者可以引用文件的其他部分。
答案 3 :(得分:1)
为了避免提交用户/密码,我想出的最简单的方法是: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"))
...
答案 4 :(得分:0)
另一个解决方案是创建模板alembic.ini.dist文件并使用您的版本化代码跟踪它,同时忽略VCS中的alembic.ini。
请勿在alembic.ini.dist中添加任何机密信息:
sqlalchemy.url = ...
将代码部署到平台时,将alembic.ini.dist复制到alembic.ini(您的VCS不会跟踪此文件)并使用平台的凭据修改alembic.ini。
答案 5 :(得分:0)
As Doug T. said您可以编辑env.py以提供除ini文件以外的其他位置的网址。您可以将额外的url
参数传递给engine_from_config
函数,而不是创建新引擎(kwargs稍后会合并到从ini文件中获取的选项)。在这种情况下你可以,例如将加密密码存储在ini文件中,并通过存储在ENV变量中的密码在运行时解密。
connectable = engine_from_config(
config.get_section(config.config_ini_section),
prefix='sqlalchemy.',
poolclass=pool.NullPool,
url=some_decrypted_endpoint)
答案 6 :(得分:0)
我一直在寻找如何管理多个数据库的方法
这就是我所做的。我有两个数据库:日志和 ohlc
根据doc, 我已经设置了像这样的淡啤酒
alembic init --template multidb
databases = logs, ohlc
[logs]
sqlalchemy.url = postgresql://botcrypto:botcrypto@localhost/logs
[ohlc]
sqlalchemy.url = postgresql://botcrypto:botcrypto@localhost/ohlc
[...]
# 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')
[...]
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
希望它可以帮助您!
答案 7 :(得分:0)
我在这里尝试了所有答案,但是没有用。然后,我尝试自己处理,如下所示:
.ini文件:
# A generic, single database configuration.
[alembic]
# path to migration scripts
script_location = alembic
# template used to generate migration files
file_template = %%(rev)s_%%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d_%%(minute).2d_%%(second).2d
# timezone to use when rendering the date
# within the migration file as well as the filename.
# string value is passed to dateutil.tz.gettz()
# leave blank for localtime
# timezone =
# 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
databases = auth_engine
[auth_engine]
sqlalchemy.url = mysql+mysqldb://{}:{}@{}:{}/auth_db
# Logging configuration
[loggers]
keys = root,sqlalchemy,alembic
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = WARN
handlers = console
qualname =
[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy.engine
[logger_alembic]
level = INFO
handlers =
qualname = alembic
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S
.env文件(位于我项目的根文件夹中):
DB_USER='root'
DB_PASS='12345678'
DB_HOST='127.0.0.1'
DB_PORT='3306'
env.py文件:
from __future__ import with_statement
import os
import re
import sys
from logging.config import fileConfig
from sqlalchemy import engine_from_config
from sqlalchemy import pool
from alembic import context
DB_USER = os.getenv("DB_USER")
DB_PASS = os.getenv("DB_PASS")
DB_HOST = os.getenv("DB_HOST")
DB_PORT = os.getenv("DB_PORT")
# 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)
# gather section names referring to different
# databases. These are named "engine1", "engine2"
# in the sample .ini file.
db_names = config.get_main_option('databases')
# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
sys.path.append(os.path.join(os.path.dirname(__file__), "../../../"))
from db_models.auth_db import auth_db_base
target_metadata = {
'auth_engine': auth_db_base.auth_metadata
}
# other values from the config, defined by the needs of env.py,
# can be acquired:
# my_important_option = config.get_main_option("my_important_option")
# ... etc.
def run_migrations_offline():
"""Run migrations in 'offline' mode.
This configures the context with just a URL
and not an Engine, though an Engine is acceptable
here as well. By skipping the Engine creation
we don't even need a DBAPI to be available.
Calls to context.execute() here emit the given string to the
script output.
"""
engines = {}
for name in re.split(r',\s*', db_names):
engines[name] = rec = {}
section = context.config.get_section(name)
url = section['sqlalchemy.url'].format(DB_USER, DB_PASS, DB_HOST, DB_PORT)
section['sqlalchemy.url'] = url
rec['url'] = url
# rec['url'] = context.config.get_section_option(name, "sqlalchemy.url")
for name, rec in engines.items():
print("Migrating database %s" % name)
file_ = "%s.sql" % name
print("Writing output to %s" % file_)
with open(file_, 'w') as buffer:
context.configure(url=rec['url'], output_buffer=buffer,
target_metadata=target_metadata.get(name),
compare_type=True,
compare_server_default=True
)
with context.begin_transaction():
context.run_migrations(engine_name=name)
def run_migrations_online():
"""Run migrations in 'online' mode.
In this scenario we need to create an Engine
and associate a connection with the context.
"""
engines = {}
for name in re.split(r',\s*', db_names):
engines[name] = rec = {}
section = context.config.get_section(name)
url = section['sqlalchemy.url'].format(DB_USER, DB_PASS, DB_HOST, DB_PORT)
section['sqlalchemy.url'] = url
rec['engine'] = engine_from_config(
section,
prefix='sqlalchemy.',
poolclass=pool.NullPool)
for name, rec in engines.items():
engine = rec['engine']
rec['connection'] = conn = engine.connect()
rec['transaction'] = conn.begin()
try:
for name, rec in engines.items():
print("Migrating database %s" % name)
context.configure(
connection=rec['connection'],
upgrade_token="%s_upgrades" % name,
downgrade_token="%s_downgrades" % name,
target_metadata=target_metadata.get(name),
compare_type=True,
compare_server_default=True
)
context.run_migrations(engine_name=name)
for rec in engines.values():
rec['transaction'].commit()
except:
for rec in engines.values():
rec['transaction'].rollback()
raise
finally:
for rec in engines.values():
rec['connection'].close()
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()
希望可以帮助别人。
答案 8 :(得分:0)
由于我们正在从本地计算机运行迁移,因此我也遇到了这个问题。我的解决方案是将环境部分放在存储数据库配置(减去凭据)的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
直接连接到您的config / secret store API并直接拉出用户名/密码,但这增加了第三个政党依赖性。
答案 9 :(得分:0)
from alembic.config import Config
alembic_cfg = Config()
alembic_cfg.set_main_option("sqlalchemy.url", getenv('PG_URI'))
答案 10 :(得分:0)
在MultiDB设置的情况下(SingleDB也一样),你可以使用config.set_section_option('section_name', 'variable_name', 'db_URL') 修改 alembic.ini 文件中数据库 URL 的值。
例如:
alembic.init
[engine1]
sqlalchemy.url =
[engine2]
sqlalchemy.url =
那么,
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'))
答案 11 :(得分:-1)
在env.py
中添加
config.set_main_option('sqlalchemy.url', os.environ['DB_URL'])
之后
config = context.config
喜欢
config = context.config
config.set_main_option('sqlalchemy.url', os.environ['DB_URL'])
然后像这样执行:
DB_URL="mysql://atuamae:de4@127.0.0.1/db" \
alembic upgrade head