Cant apply flask db migrate when using PyODBC w/ SQL Server. Error: Neither DSN nor SERVER keyword supplied (0). Connecting via pyodbc.connect works

Cant apply flask db migrate when using PyODBC w/ SQL Server. Error: Neither DSN nor SERVER keyword supplied (0). Connecting via pyodbc.connect works

我正在尝试使用 Flask SQLAlchemy 连接到托管在 Azure 上的 SQL 数据库。当我直接使用 pyodbc.connect 时一切正常,如下所示:

import pyodbc
# I got the ODBC connection string directly from Azure's connection strings for the db.

odbc_string = 'Driver={ODBC Driver 17 for SQL Server};Server=tcp:sql-server-for-capstone.database.windows.net,1433;Database=MastersCapstone;Uid={my username};Pwd={my super secure password here};Encrypt=yes;TrustServerCertificate=no;Connection Timeout=30;'
conn = pyodbc.connect(odbc_string)
# Works!

但是当我尝试使用 Flask SQLAlchemy 进行连接时,我得到:

sqlalchemy.exc.OperationalError: (pyodbc.OperationalError) ('08001', '[08001] [Microsoft][ODBC Driver 17 for SQL Server]Neither DSN nor SERVER keyword supplied (0) (SQLDriverConnect)')

我用来通过 SQLAlchemy 连接的配置如下所示:

odbc_string = 'Driver={ODBC Driver 17 for SQL Server};Server=tcp:sql-server-for-capstone.database.windows.net,1433;Database=MastersCapstone;Uid={my username};Pwd={my super secure password here};Encrypt=yes;TrustServerCertificate=no;Connection Timeout=30;'
params = urllib.parse.quote_plus(odbc_string)
sqlalchemy_db_uri = f"mssql+pyodbc:///?odbc_connect={params}"
class Config:
    # Database
    SQLALCHEMY_DATABASE_URI = sqlalchemy_db_uri

然后我在目录的 __init__.py 文件中像这样初始化应用程序:

from flask import Flask
from flask_migrate import Migrate
from flask_sqlalchemy import SQLAlchemy

from src.python.server.config import Config


db = SQLAlchemy()
migrate = Migrate(compare_type=True)


def create_app():
    app = Flask(__name__)
    app.config.from_object(Config)
    db.init_app(app)
    migrate.init_app(app, db)

    with app.app_context():
        from src.python.server import routes, models
        return app

最后,当我在终端中 运行 以下内容时出现错误:

flask db migrate

请注意,当我使用 PostgreSQL 数据库时一切正常。有谁知道为什么会这样?我被困住了!

附加信息: 终端中 运行ning odbcinst -j 的输出:

unixODBC 2.3.7
DRIVERS............: /usr/local/etc/odbcinst.ini
SYSTEM DATA SOURCES: /usr/local/etc/odbc.ini
FILE DATA SOURCES..: /usr/local/etc/ODBCDataSources
USER DATA SOURCES..: /Users/jordan/.odbc.ini
SQLULEN Size.......: 8
SQLLEN Size........: 8
SQLSETPOSIROW Size.: 8

整个堆栈跟踪:

Traceback (most recent call last):
  File "/Users/jordan/Coding/survey-app/venv/lib/python3.8/site-packages/sqlalchemy/engine/base.py", line 2285, in _wrap_pool_connect
    return fn()
  File "/Users/jordan/Coding/survey-app/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 303, in unique_connection
    return _ConnectionFairy._checkout(self)
  File "/Users/jordan/Coding/survey-app/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 773, in _checkout
    fairy = _ConnectionRecord.checkout(pool)
  File "/Users/jordan/Coding/survey-app/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 492, in checkout
    rec = pool._do_get()
  File "/Users/jordan/Coding/survey-app/venv/lib/python3.8/site-packages/sqlalchemy/pool/impl.py", line 238, in _do_get
    return self._create_connection()
  File "/Users/jordan/Coding/survey-app/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 308, in _create_connection
    return _ConnectionRecord(self)
  File "/Users/jordan/Coding/survey-app/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 437, in __init__
    self.__connect(first_connect_check=True)
  File "/Users/jordan/Coding/survey-app/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 657, in __connect
    pool.logger.debug("Error on connect(): %s", e)
  File "/Users/jordan/Coding/survey-app/venv/lib/python3.8/site-packages/sqlalchemy/util/langhelpers.py", line 68, in __exit__
    compat.raise_(
  File "/Users/jordan/Coding/survey-app/venv/lib/python3.8/site-packages/sqlalchemy/util/compat.py", line 178, in raise_
    raise exception
  File "/Users/jordan/Coding/survey-app/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 652, in __connect
    connection = pool._invoke_creator(self)
  File "/Users/jordan/Coding/survey-app/venv/lib/python3.8/site-packages/sqlalchemy/engine/strategies.py", line 114, in connect
    return dialect.connect(*cargs, **cparams)
  File "/Users/jordan/Coding/survey-app/venv/lib/python3.8/site-packages/sqlalchemy/engine/default.py", line 490, in connect
    return self.dbapi.connect(*cargs, **cparams)
pyodbc.OperationalError: ('08001', '[08001] [Microsoft][ODBC Driver 17 for SQL Server]Neither DSN nor SERVER keyword supplied (0) (SQLDriverConnect)')

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/Users/jordan/Coding/survey-app/venv/bin/flask", line 8, in <module>
    sys.exit(main())
  File "/Users/jordan/Coding/survey-app/venv/lib/python3.8/site-packages/flask/cli.py", line 967, in main
    cli.main(args=sys.argv[1:], prog_name="python -m flask" if as_module else None)
  File "/Users/jordan/Coding/survey-app/venv/lib/python3.8/site-packages/flask/cli.py", line 586, in main
    return super(FlaskGroup, self).main(*args, **kwargs)
  File "/Users/jordan/Coding/survey-app/venv/lib/python3.8/site-packages/click/core.py", line 782, in main
    rv = self.invoke(ctx)
  File "/Users/jordan/Coding/survey-app/venv/lib/python3.8/site-packages/click/core.py", line 1259, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/Users/jordan/Coding/survey-app/venv/lib/python3.8/site-packages/click/core.py", line 1259, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/Users/jordan/Coding/survey-app/venv/lib/python3.8/site-packages/click/core.py", line 1066, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/Users/jordan/Coding/survey-app/venv/lib/python3.8/site-packages/click/core.py", line 610, in invoke
    return callback(*args, **kwargs)
  File "/Users/jordan/Coding/survey-app/venv/lib/python3.8/site-packages/click/decorators.py", line 21, in new_func
    return f(get_current_context(), *args, **kwargs)
  File "/Users/jordan/Coding/survey-app/venv/lib/python3.8/site-packages/flask/cli.py", line 426, in decorator
    return __ctx.invoke(f, *args, **kwargs)
  File "/Users/jordan/Coding/survey-app/venv/lib/python3.8/site-packages/click/core.py", line 610, in invoke
    return callback(*args, **kwargs)
  File "/Users/jordan/Coding/survey-app/venv/lib/python3.8/site-packages/flask_migrate/cli.py", line 89, in migrate
    _migrate(directory, message, sql, head, splice, branch_label, version_path,
  File "/Users/jordan/Coding/survey-app/venv/lib/python3.8/site-packages/flask_migrate/__init__.py", line 96, in wrapped
    f(*args, **kwargs)
  File "/Users/jordan/Coding/survey-app/venv/lib/python3.8/site-packages/flask_migrate/__init__.py", line 210, in migrate
    command.revision(config, message, autogenerate=True, sql=sql,
  File "/Users/jordan/Coding/survey-app/venv/lib/python3.8/site-packages/alembic/command.py", line 214, in revision
    script_directory.run_env()
  File "/Users/jordan/Coding/survey-app/venv/lib/python3.8/site-packages/alembic/script/base.py", line 489, in run_env
    util.load_python_file(self.dir, "env.py")
  File "/Users/jordan/Coding/survey-app/venv/lib/python3.8/site-packages/alembic/util/pyfiles.py", line 98, in load_python_file
    module = load_module_py(module_id, path)
  File "/Users/jordan/Coding/survey-app/venv/lib/python3.8/site-packages/alembic/util/compat.py", line 184, in load_module_py
    spec.loader.exec_module(module)
  File "<frozen importlib._bootstrap_external>", line 783, in exec_module
  File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
  File "migrations/env.py", line 96, in <module>
    run_migrations_online()
  File "migrations/env.py", line 81, in run_migrations_online
    with connectable.connect() as connection:
  File "/Users/jordan/Coding/survey-app/venv/lib/python3.8/site-packages/sqlalchemy/engine/base.py", line 2218, in connect
    return self._connection_cls(self, **kwargs)
  File "/Users/jordan/Coding/survey-app/venv/lib/python3.8/site-packages/sqlalchemy/engine/base.py", line 103, in __init__
    else engine.raw_connection()
  File "/Users/jordan/Coding/survey-app/venv/lib/python3.8/site-packages/sqlalchemy/engine/base.py", line 2317, in raw_connection
    return self._wrap_pool_connect(
  File "/Users/jordan/Coding/survey-app/venv/lib/python3.8/site-packages/sqlalchemy/engine/base.py", line 2288, in _wrap_pool_connect
    Connection._handle_dbapi_exception_noconnection(
  File "/Users/jordan/Coding/survey-app/venv/lib/python3.8/site-packages/sqlalchemy/engine/base.py", line 1554, in _handle_dbapi_exception_noconnection
    util.raise_(
  File "/Users/jordan/Coding/survey-app/venv/lib/python3.8/site-packages/sqlalchemy/util/compat.py", line 178, in raise_
    raise exception
  File "/Users/jordan/Coding/survey-app/venv/lib/python3.8/site-packages/sqlalchemy/engine/base.py", line 2285, in _wrap_pool_connect
    return fn()
  File "/Users/jordan/Coding/survey-app/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 303, in unique_connection
    return _ConnectionFairy._checkout(self)
  File "/Users/jordan/Coding/survey-app/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 773, in _checkout
    fairy = _ConnectionRecord.checkout(pool)
  File "/Users/jordan/Coding/survey-app/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 492, in checkout
    rec = pool._do_get()
  File "/Users/jordan/Coding/survey-app/venv/lib/python3.8/site-packages/sqlalchemy/pool/impl.py", line 238, in _do_get
    return self._create_connection()
  File "/Users/jordan/Coding/survey-app/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 308, in _create_connection
    return _ConnectionRecord(self)
  File "/Users/jordan/Coding/survey-app/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 437, in __init__
    self.__connect(first_connect_check=True)
  File "/Users/jordan/Coding/survey-app/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 657, in __connect
    pool.logger.debug("Error on connect(): %s", e)
  File "/Users/jordan/Coding/survey-app/venv/lib/python3.8/site-packages/sqlalchemy/util/langhelpers.py", line 68, in __exit__
    compat.raise_(
  File "/Users/jordan/Coding/survey-app/venv/lib/python3.8/site-packages/sqlalchemy/util/compat.py", line 178, in raise_
    raise exception
  File "/Users/jordan/Coding/survey-app/venv/lib/python3.8/site-packages/sqlalchemy/pool/base.py", line 652, in __connect
    connection = pool._invoke_creator(self)
  File "/Users/jordan/Coding/survey-app/venv/lib/python3.8/site-packages/sqlalchemy/engine/strategies.py", line 114, in connect
    return dialect.connect(*cargs, **cparams)
  File "/Users/jordan/Coding/survey-app/venv/lib/python3.8/site-packages/sqlalchemy/engine/default.py", line 490, in connect
    return self.dbapi.connect(*cargs, **cparams)
sqlalchemy.exc.OperationalError: (pyodbc.OperationalError) ('08001', '[08001] [Microsoft][ODBC Driver 17 for SQL Server]Neither DSN nor SERVER keyword supplied (0) (SQLDriverConnect)')
(Background on this error at: http://sqlalche.me/e/e3q8)

我能够通过解决方法解决这个问题。

我编辑了 migrations/env.py 文件:

import os
import urllib

DB_CREDENTIALS = os.environ.get("ENV_DB_CREDENTIALS")
SQLALCHEMY_DATABASE_URI = (
    "mssql+pyodbc:///?odbc_connect=%s" % urllib.parse.quote_plus(DB_CREDENTIALS)
)

config.set_main_option(
    "sqlalchemy.url",
    SQLALCHEMY_DATABASE_URI.replace("%", "%%"),
)

我也在某处读到 Trusted_Connection=Yes 问题是出于某种原因,不确定为什么。