AttributeError: 'sqlite3.Connection' object has no attribute 'enable_load_extension' の対処法


MacでPythonからSQLite3の拡張機能(例えばsqlite-vss)を使う時にエラーが出て苦労したので、その解決方法と理由をまとめておきます。


# エラーの再現方法
Pythonでは、SQLite3の操作を標準モジュールからサポートしています。例えば、以下のようにすれば簡単にSQLite3のデータベースファイル('test.sqlite3')に接続できます。ファイルがない場合は、自動的にデータベースファイルが作成されます。
```python
import sqlite3
con = sqlite3.connect("test.sqlite3")
```
`con`変数は、データベースへの接続を表すインスタンスです。`con`から、SQLを実行し、テーブルの作成や、SELECT分でデータを抽出したりできるようになるわけですね。詳しくは以下の公式ドキュメントのチュートリアルをご覧ください。
https://docs.python.org/ja/3/library/sqlite3.html

SQLite3には、さまざまな拡張機能があります。例えば、ベクトル検索を可能にするsqlite-vssがあり、こういった拡張機能もPythonから使用することができます。sqlite-vssの場合は、以下のようにpipでsqlite-vssをインストールできます。(自分の環境を汚したく無い人は、venv等を使って管理しましょう)
```
pip install sqlite-vss
```
あくまでこれはPythonからsqlite-vssを操作するためのモジュールです。ただし、pip installした際に、sqlite-vss本体も自動でPythonの環境内に保存されるので、sqlite-vss本体を意識する必要はありません。

このような拡張機能を読み込むPythonコードの例を、sqlite-vssを題材に以下に示します。
```python:sqlite3_extension.py
import sqlite3
import sqlite_vss
con = sqlite3.connect("test.sqlite3")
con.enable_load_extension(True)
sqlite_vss.load(con)
```

これで、`con`からsqlite-vssを使用できるようになります。ポイントは、下部２行です。
- `con.enable_load_extensions(True)`: ここで、SQLiteの拡張機能を使うことを許可しています。
- `sqlite_vss.load(con)`: `con`で接続したSQLiteデータベースに拡張機能であるsqlite-vssをロードしています。

これにより、以降のコードで拡張機能を自由自在に使えるようになるはずです。

しかし、筆者が知る限りでは、 **Macbook上で、PyenvでインストールしたPythonで実行すると、** 以下のエラーが発生してしまいます。

```
Traceback (most recent call last):
  File "/Users/kawaguchi/qiita/sqlite3_extension.py", line 4, in <module>
    con.enable_load_extension(True)
AttributeError: 'sqlite3.Connection' object has no attribute 'enable_load_extension'
```

エラー文を読むと、拡張機能の使用を許可するための`enable_load_extension`メソッドが無いということがわかります。これはどういうことでしょうか？Pyenv＋venvでバージョンやパッケージ管理をしている場合、pipで拡張機能をインストールするだけで使えるようになることを期待していたのですが、残念ながらそうはなりませんでした。


# エラーの原因
このエラーの原因は、 **Macbookに最初からインストールされているSQLite3が拡張機能の使用を許可しておらず、Pythonがそちらを使っていること** でした。
どういうことか見ていきましょう。まず、以下のコマンドでSQLiteがどこにあるか確認してください。
```zsh
% which sqlite3
/usr/bin/sqlite3
```
`/usr/bin/sqlite3/`と表示されたのであれば、それはMacbookに最初から組み込まれているSQLite3です。実は、この最初から組み込まれているSQLite3は拡張機能をサポートしていません。以下のPythonのsqlite3モジュールの`enable_load_extension`メソッドの説明文にもそのようなことが書かれています。拡張機能をサポートしていないSQLite3があり、特にMacがその代表格のような書き方がされています。

> 注釈 sqlite3 モジュールは、デフォルトではロード可能な拡張機能をサポートするようにビルドされていません。一部のプラットフォーム(特に macOS)には、この機能なしでコンパイルされた SQLite ライブラリがあるためです。 ロード可能な拡張機能のサポートを得るには、 configure に --enable-loadable-sqlite-extensions オプションを渡す必要があります。

https://docs.python.org/ja/3/library/sqlite3.html#sqlite3.Connection.enable_load_extension

## 補足: エラーが起きない環境
エラーの解決方法を示す前に、筆者が確認した限りの、上述のエラーが起きなかった条件を書きます。
- Ubuntu20.04で同様のことをした場合。おそらくUbuntu等のLinuxでは起きない？
- condaの仮想環境で実施した場合

後者のcondaについてですが、色々と調べてみたところ仮想環境の中に拡張機能をサポートしているSQLiteを自動でインストールして、そちらを使うようになっているようでした（気が利く！）。手っ取り早くエラーを回避したいのであれば、condaを使いましょう。

# エラーの解決方法
エラーの解決には、以下の2点を実施する必要があります。

1. HomebrewでSQLite3をインストール
2. 特別な設定をしてからのPythonインタプリタのビルド

具体的にコマンドで書くと、以下のように実行すれば、上記のエラーを回避できます。

```zsh
brew install sqlite3
export LDFLAGS="-L/opt/homebrew/opt/sqlite/lib"
export CPPFLAGS="-I/opt/homebrew/opt/sqlite/include"
export PYTHON_CONFIGURE_OPTS="--enable-loadable-sqlite-extensions"
pyenv install 3.11.3
```
例では、Pythonの3.11.3をビルドしてインストールしていますが、所望のバージョンに置き換えてください。

どういう意味なのかは、1.と2.に分けて説明します。

## 1. HomebrewでSQLite3をインストール
Homebrewを使って、Macbookに最初からインストールされているSQLite3とは別のSQLite3をインストールできます。 **Homebrew版は、拡張機能が使えるSQLite3です。** 以下のコマンドでインストールできます。

```zsh
brew install sqlite3
```

実行すると、おそらく以下のような結果が出るかと思います。解説するので、びっくりしないようにしましょう。
```
sqlite is keg-only, which means it was not symlinked into /opt/homebrew,
because macOS already provides this software and installing another version in
parallel can cause all kinds of trouble.

If you need to have sqlite first in your PATH, run:
  echo 'export PATH="/opt/homebrew/opt/sqlite/bin:$PATH"' >> ~/.zshrc

For compilers to find sqlite you may need to set:
  export LDFLAGS="-L/opt/homebrew/opt/sqlite/lib"
  export CPPFLAGS="-I/opt/homebrew/opt/sqlite/include"

For pkg-config to find sqlite you may need to set:
  export PKG_CONFIG_PATH="/opt/homebrew/opt/sqlite/lib/pkgconfig"
==> Summary
:beer:  /opt/homebrew/Cellar/sqlite/3.42.0: 11 files, 4.5MB
==> Running `brew cleanup sqlite`...
Disable this behaviour by setting HOMEBREW_NO_INSTALL_CLEANUP.
Hide these hints with HOMEBREW_NO_ENV_HINTS (see `man brew`).
```

これ、何が起きているかというと、Macbookには元々SQLite3が入っているから、Homebrewで入れたけど、Homebrew版は明示的にじゃないと使えないようになっていることを説明しています。同一のパソコンに同じソフトウェアが入っているので、ややこしいですよね。なので、下手に設定を書き換えてないんです。

コマンドラインからHomebrew版SQLite3をコマンドで呼ぶためには、以下のコマンドを実行しろと書いてあります。
```zsh
echo 'export PATH="/opt/homebrew/opt/sqlite/bin:$PATH"' >> ~/.zshrc
```
この後にターミナルを再起動するか、.zshrcを実行すると使えるようになりますね。ただし、これは以降のPythonのビルドには必ずしも必要でない設定です。

大事なのは、以下の３行です。

```
For compilers to find sqlite you may need to set:
  export LDFLAGS="-L/opt/homebrew/opt/sqlite/lib"
  export CPPFLAGS="-I/opt/homebrew/opt/sqlite/include"
```
何かしらのソフトウェアをコンパイルする時には、`LDFLAGS`環境変数と`CPPFLAGS`環境変数を設定しろと言っています。これらが、後のPythonインタプリタのビルドに必要になってきます。

## 2. 特別な設定をしてからのPythonインタプリタのビルド
Pythonのインタプリタのビルド時、つまりpyenvでいうと特定のバージョンのPythonのインストールを行うのは以下のコマンドです。
```zsh
pyenv install 3.11.3 # 3.11.3はPythonインタプリタのバージョン
```
この時、Pythonインタプリタがビルドされるのですが、その際に標準モジュールのsqlite3もビルドされます。その際に、 **ある設定をしないと、Homebrew版ではなくてMacbookに最初から入っている拡張機能の使えないSQLite3を参照ようにビルドされてしまいます。** そのためこのPythonインタプリタのインストール時に、Homebrew版のSQLite3を参照するように教えてあげる必要があります。それが、上述したコマンド群のうちの、以下の部分です。

```
export LDFLAGS="-L/opt/homebrew/opt/sqlite/lib"
export CPPFLAGS="-I/opt/homebrew/opt/sqlite/include"
export PYTHON_CONFIGURE_OPTS="--enable-loadable-sqlite-extensions"
```
上２行は、Homebrew版SQLite3のインストール時にもそうしろと書いてありましたね。３行目の`PYTHON_CONFIGURE_OPTS`は、Pythonのビルド時のオプションをしていて、sqlite3の拡張機能を許可しています。(設定から推測しているだけなので、厳密には違うかもしれません)


# まとめ

というわけで、以下のコマンドを実行することでSQLite3の拡張機能を使えるようになります。
```zsh
brew install sqlite3
export LDFLAGS="-L/opt/homebrew/opt/sqlite/lib"
export CPPFLAGS="-I/opt/homebrew/opt/sqlite/include"
export PYTHON_CONFIGURE_OPTS="--enable-loadable-sqlite-extensions"
pyenv install 3.11.3
```

以下のPythonコードを実行してエラーが出なければOKです。(このコードでは、データベースファイルを作らずに、メモリ上にデータベースを作って接続しています。)
```python
import sqlite3
con = sqlite3.connect(':memory:')
con.enable_load_extension(True)
```

僕のようにこのエラーを解決するために、半日を潰してしまう人が少しでも減ることを願っております。