ImportError: DLL load failed: The specified module could not be found -- IBM DB2

Kyl*_*yle 1 db2 pyinstaller python-3.x

我知道有很多人有同样的问题,但这是我无法找到完全相同问题的情况。我正在使用 pyinstaller 构建一个可执行文件,但我不断收到 importError。我正在使用 ibm_db 包连接到 IBM DB2 数据库,并使用 pandas to_sql 方法插入到表中。在我添加 SQL 代码之前,我在我的程序上使用了 pyinstaller,所以我很确定它与我尝试连接到 DB2 有关系,但在我的生活中我无法弄清楚这一点。

我在运行 pyinstaller 时收到很多警告和信息消息,但没有看到任何错误。只有在尝试执行 pyinstaller 构建的可执行文件时才会收到错误消息。

我尝试在虚拟环境中运行它以尝试隔离问题,但我对虚拟环境不太熟悉,所以我不再尝试使用它。

Traceback (most recent call last):
  File "rebate_gui_sql.py", line 9, in <module>
  File "c:\users\dt24358\lib\site-packages\PyInstaller\loader\pyimod03_importers.py", line 627, in exec_module
    exec(bytecode, module.__dict__)
  File "site-packages\ibm_db.py", line 10, in <module>
  File "site-packages\ibm_db.py", line 9, in __bootstrap__
  File "imp.py", line 342, in load_dynamic
ImportError: DLL load failed: The specified module could not be found.
[11020] Failed to execute script rebate_gui_sql
Run Code Online (Sandbox Code Playgroud)

更新:2019 年 5 月 1 日来自下面的评论,这是我的简单程序

import pandas as pd
from tkinter import *
from tkinter import ttk
import ibm_db
import ibm_db_dbi as db
from sqlalchemy import create_engine

class Application(Frame):

    def __init__(self, master):
        ttk.Frame.__init__(self, master)  
        self.master = master
        self.run_process()

    def run_process(self):
        engine = create_engine("db2+ibm_db://userid:password@url:port/database")
        conn = engine.connect()
        print("Connected to " + str(engine))

        sql = '''
            Select *
            from rebteam.pd5
            fetch first row only
            '''

        df = pd.read_sql(sql, conn)
        print(df)

        df.to_csv(r'c:\users\dt24358\scripts\pricing tool\GUI_SQL\test.csv', index=False)
        self.result_label = Label(root, text="Select of PD5 Successful", bg="light green", width=80, justify=LEFT)
        self.result_label.grid(row=0,columnspan=2)

root=Tk()
root.title("Rebate Bid Data Upload")
root.configure(background="light green")
app = Application(root)
root.mainloop()
Run Code Online (Sandbox Code Playgroud)

mao*_*mao 7

此答案与这些版本相关:

python - up to 3.7 (but not higher)  
pyinstaller 3.4  
setuptools 41.0.1 
ibm_db 3.0.1
ibm_db_sa 0.3.4
sqlalchemy 1.3.3
Run Code Online (Sandbox Code Playgroud)

这里有单独的问题。

直接问题(ImportError)是加载失败 ibm_db.dll

发生 ImportError 是因为 pyinstaller 不会将外部(非 Python)库复制到包中,除非您明确要求这样做。

pyinstaller 也不会将 Db2 客户端复制到包中,除非您明确告诉它这样做,这意味着如果您部署内置可执行文件的目标主机名还没有预配置的预安装 Db2 客户端,那么您还将遇到加载 ibm_db 模块失败。

pyinstaller 选项--add-binary为某些类型的 ImportError 提供了一种解决方法,请参见下面的示例。如果您不使用 SQLAlchemy,请跳过此答案的那些部分。

pyinstaller 选项--add-data提供了在目标环境缺少 Db2 驱动程序时添加目录(例如用于添加 Db2 驱动程序的 clidriver 目录)的解决方法。

请注意,此答案不要求您使用 SQLAlchemy,如果您仅使用 ibm_db(或 ibm_db_dbi),则该答案也是相关的,在这种情况下,只需跳过 SQLAlchemy 部分。

如果您的 Python 脚本使用 SQLAlchemy 访问 Db2,那么您可能会在使用 pyinstaller 构建后在运行时看到第二个症状。运行时症状是:

“sqlalchemy.exc.NoSuchModuleError:无法加载插件:sqlalchemy.dialects:ibm_db_sa”

或者

“sqlalchemy.exc.NoSuchModuleError:无法加载插件:sqlalchemy.dialects:db2.ibm_db”

(取决于提供给 create_engine() 的 url 前缀)

此症状 sqlalchemy.exe.NoSuchModuleError 并非特定于 Db2,但在通过带有外部方言(Db2、teradata、snowflake、presto 等)的SQLAlchemy 使用时会影响其他数据库。使用 SQLAlchemy 内部方言的数据库可能开箱即用。

这是 SQLAlchemy 的一种解决方法,其他解决方法也是可能的。

SQLAlchemy 外部方言使用 pkg_resources entry_points 来允许 SQLAlchemy 利用它们,但是如果没有您的帮助,pyinstaller 还不能处理这些。这种入口点信息是一种关于模块的元数据。

此解决方法使用 pyinstaller 挂钩来收集相关模块的元数据,并告诉 pyinstaller 包含这些挂钩文件的目录(或目录)。对于带有 SQLAlchemy 的 Db2,需要三个钩子文件,hook-ibm_db.py, hook-ibm_db_sa.py, hook-sqlalchemy.py. 我选择将这些钩子文件放在与我的源文件 python 脚本相同的目录中。

这些文件中的每一个的内容都是微不足道的两行,内容的不同仅在于其中包含的模块名称。这是其中一个文件的示例hook-sqlalchemy.py(对于其他 2 个必需文件,只需适当替换模块名称):

from PyInstaller.utils.hooks  import  copy_metadata

datas = copy_metadata('sqlalchemy')
Run Code Online (Sandbox Code Playgroud)

ibm_db.dll通过 进行添加--add-binary method,您可以使用命令行选项来 pyinstaller 或编辑规范文件。

要单独处理 ibm_db.dll 的加载失败,只需使用 --add-binary 附加选项,如下所示:

pyinstaller -y --add-binary  %LOCALAPPDATA%\Programs\Python\Python37\Lib\site-packages\ibm_db_dlls\ibm_db.dll;.\ibm_db_dlls your_script.py
Run Code Online (Sandbox Code Playgroud)

如果要在包中包含 clidriver,请首先通过以下方式找到其位置的完全限定路径名:

pip show ibm_db

并在该命令的输出中看到包含Location:完全限定路径名第一部分的行,因此您附加\CLIDRIVER到该路径并在--add-data附加选项中使用它,如下所示:

--add-data="c:\path\to\clidriver;.\clidriver"

如果您在包中包含 clidriver,还有其他注意事项,请参阅下面的注释部分。

对于也使用 SQLAlchemy 的应用程序,您需要额外的步骤。

假设 ibm_db.dll 位于此目录中:

%LOCALAPPDATA%\programs\python\python37\lib\site-packages\ibm_db_dlls
Run Code Online (Sandbox Code Playgroud)

然后在 CMD.EXE shell 中创建一个变量以指向该位置:

> set ibm_db_path=%LOCALAPPDATA%\programs\python\python37\lib\site-packages\ibm_db_dlls
Run Code Online (Sandbox Code Playgroud)

对于 MS-Windows 批处理文件(使用 ^ 作为行继续符),处理上述两种解决方法的 pyinstaller 命令行示例是:

pyinstaller -y ^
--additional-hooks-dir=. ^
--hidden-import ibm_db_sa.ibm_db ^
--hidden-import ibm_db_dbi ^
--hidden-import ibm_db ^
--add-binary  %LOCALAPPDATA%\Programs\Python\Python37\Lib\site-packages\ibm_db_dlls\ibm_db.dll;.\ibm_db_dlls ^
 your_script.py
Run Code Online (Sandbox Code Playgroud)

笔记:

  • 如果你的 python 脚本显式地导入了 SQLAlchemy 模块,那么你不需要通过--hidden-import选项指定它们(购买你仍然需要 SQLAlchemy 在捆绑后运行的钩子)。

  • 对于高达 3.0.2 的 ibm_db 版本,ibm_db.dll需要ibm_db_dlls位于包中的子目录中,这就是在--add-binary选项上指定该目标的原因 。

  • 如果您正在为 Linux/Unix 构建,而不是 ^。像往常一样使用 \ 作为换行符。

  • 如果您打算将构建的可执行文件复制到新主机名,并且该新主机名尚未预先安装 Db2-client ,并且您不希望在目标上安装单独的 Db2-client,那么您可以捆绑 clidriver使用 pyinstaller 输出和--add-data上面显示的选项。

  • 如果您捆绑 clidriver,请注意,如果它们位于非默认位置,您可能还需要捆绑其配置文件(例如db2dsdriver.cfgdb2cli.ini),具体取决于您的代码是使用外部配置的 DSN 还是长连接字符串。如果您不捆绑此类配置文件(隐式或显式)并且您将构建环境部署到与构建环境不同的主机名,那么您将需要在目标主机名处重新配置这些文件。这些文件的默认位置在clidriver\cfg将通过--add-data前面提到的方式包含在内的目录中。

  • 如果您捆绑了 clidriver,并且如果您使用通过 TLS/SSL 到 Db2 的加密连接,请注意您可能还需要在运行 pyinstaller 构建时捆绑其他文件,例如证书、密钥库/存储文件等。

  • 如果您捆绑了 clidriver,请注意 IBM 每年都会通过错误修复和安全修复以及新功能刷新此组件几次,因此您可能需要定期刷新可执行文件,以防止它们因被及时冻结而成为安全漏洞旧版本。

  • 如果您捆绑了 clidriver 并且如果您需要odbcad32在目标主机名上使用来配置 Db2 DSN,那么在目标主机名上部署之后记得在目标主机名上运行clidriver\bin\db2cli install -setup命令。