SpatiaLite with pyenv on Mac

GeoDjangoを使うためのSpatial database。
本番用としてPostGISを使うときは問題なかったが、ローカルテストやCI用としてSQLite上で動かしたく、SpatiaLiteを使おうとするとmigrateでつまづく。
ドキュメント通りはいかなかったものを解決した話

# Environment
- macOS Sierra 10.12.6
- pyenv 1.1.4
- Python 3.6.2
- Django 1.11.6

# TL;DR
```console
$ LDFLAGS="-L/usr/local/opt/sqlite/lib -L/usr/local/opt/zlib/lib" CPPFLAGS="-I/usr/local/opt/sqlite/include -I/usr/local/opt/zlib/include" PYTHON_CONFIGURE_OPTS="--enable-loadable-sqlite-extensions" pyenv install 3.6.2
```

# 原因
- Pythonインストール時の`sqlite3`**パッケージ**がMacのbuilt-inのSQLiteとリンクされていた
- pyenvでインストールしたPythonが`--enable-loadable-sqlite-extensions`じゃなかった

# 事の顛末

## Installing SpatiaLite
https://docs.djangoproject.com/en/2.0/ref/contrib/gis/install/spatialite/#spatialite-macos

```console
$ brew update
$ brew install spatialite-tools
$ brew install gdal
```
GEOS/PROJやらはGDALが依存してるのでついてくる

```py3:settings.py
SPATIALITE_LIBRARY_PATH = '/usr/local/lib/mod_spatialite.dylib'
```

これだけだとそもそもextensionがロードできないのでエラーになる。
起こる場所はここ
https://github.com/django/django/blob/1.11.6/django/contrib/gis/db/backends/spatialite/base.py#L52

```py3
    def get_new_connection(self, conn_params):
        conn = super(DatabaseWrapper, self).get_new_connection(conn_params)
        # Enabling extension loading on the SQLite connection.
        try:
            conn.enable_load_extension(True)
        except AttributeError:
            raise ImproperlyConfigured(
                'The pysqlite library does not support C extension loading. '
                'Both SQLite and pysqlite must be configured to allow '
                'the loading of extensions to use SpatiaLite.')
        # Loading the SpatiaLite library extension on the connection, and returning
        # the created cursor.
        cur = conn.cursor(factory=SQLiteCursorWrapper)
        try:
            cur.execute("SELECT load_extension(%s)", (self.spatialite_lib,))
        except Exception as msg:
            new_msg = (
                'Unable to load the SpatiaLite library extension '
                '"%s" because: %s') % (self.spatialite_lib, msg)
            six.reraise(ImproperlyConfigured, ImproperlyConfigured(new_msg), sys.exc_info()[2])
        cur.close()
        return conn
```
最初はpysqliteに惑わされてそちら辺り調べたが、Python3ではそもそも`pysqlite`じゃなく`sqlite3`を使うので全く見当違いだった。


## SQLite extension

直接sqliteからextensionをロードしてみよう

```sql:SQLiteConsole
sqlite> SELECT load_extension('/usr/local/lib/mod_spatialite.dylib')
Error: no such function: load_extension
```
```console
$ sqlite3 --version
3.16.0
$ which sqlite3
/usr/bin/sqlite3
```
あー

とりま`sqlite3`パッケージにのコネクションに`enable_load_extension`がないので`sqlite3`のソースをたどったら
`sqlite3` → `sqlite3.dbapi2` → `_sqlite3`

```py3:IPython
In [1]: import sqlite3
In [2]: sqlite3.sqlite_version
Out[2]: '3.16.0'  # mac built-in

In [3]: import _sqlite3
In [4]: _sqlite3.__file__
Out[4]: '/PATH/TO/.venv/lib/python3.6/lib-dynload/_sqlite3.cpython-36m-darwin.so'
```
あー

**HomebrewでインストールされたSQLite3だと問題なく`load_extension`できた。**

```console
$ /usr/local/opt/sqlite3/bin/sqlite3 --version
3.22.0
```
ソースレベルじゃどうしようもないんで、djangojaの助言通りPythonのインストールからやり直し

## pyenv with configurations

### ググったら出たやつ1
https://trac.macports.org/ticket/49317
MacPortsは使ってないけどまあ`--enable-loadable-sqlite-extensions`これをつけないといけないらしい

### ググったら出たやつ2
https://github.com/pyenv/pyenv/issues/333
`sqlite３`パッケージをHomebrewのやつとリンクさせるには以下で行けるらしい

```console
$ LDFLAGS="-L/usr/local/opt/sqlite/lib -L/usr/local/opt/zlib/lib" CPPFLAGS="-I/usr/local/opt/sqlite/include -I/usr/local/opt/zlib/include" pyenv install 3.6.2
```
実際やってみると


```py3:InteractiveConsole
>>> import sqlite3
>>> sqlite3.sqlite_version
'3.22.0'  # やったか！？
>>> conn = sqlite3.connect('tmp.db')
>>> conn.enable_load_extension(True)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'sqlite3.Connection' object has no attribute 'enable_load_extension'
# やってない
```

### ググったら出たやつ3
https://github.com/pyenv/pyenv/issues/86
Pythonにオプションを付けるには以下で行けるらしい

```console
$ PYTHON_CONFIGURE_OPTS="<configurations>" pyenv install 3.6.2
```
これだけでやったら

```console
WARNING: The Python sqlite3 extension was not compiled. Missing the SQLite3 lib?
Installed Python-3.6.2 to /PATH/TO/.pyenv/versions/3.6.2
```
あれ・・・

```py3:InteractiveConsole
>>> import sqlite3
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/PATH/TO/.pyenv/versions/3.6.2/lib/python3.6/sqlite3/__init__.py", line 23, in <module>
    from sqlite3.dbapi2 import *
  File "/PATH/TO/.pyenv/versions/3.6.2/lib/python3.6/sqlite3/dbapi2.py", line 27, in <module>
    from _sqlite3 import *
ModuleNotFoundError: No module named '_sqlite3'
```

多分built-inのSQLite3にlibsqlite3-devだったかなんだかが抜けてるかなんとか…

### 全部合わせて

```console
$ LDFLAGS="-L/usr/local/opt/sqlite/lib -L/usr/local/opt/zlib/lib" CPPFLAGS="-I/usr/local/opt/sqlite/include -I/usr/local/opt/zlib/include" PYTHON_CONFIGURE_OPTS="--enable-loadable-sqlite-extensions" pyenv install 3.6.2
```

```py3:InteractiveConsole
>>> import sqlite3
>>> conn = sqlite3.connect('tmp.db')
>>> conn.enable_load_extension(True)
>>> conn.load_extension('/usr/local/lib/mod_spatialite.dylib')
```
問題なし
GeoDjangoのmigrate/test/runserverともに問題なかった



# 終わりに
- Homebrewで直接インストールしたPython3のときはわからん。pyenv使いましょう。
- Pythonパッケージの`sqlite3`とDBの`SQLite3`をはっきり区別して考えよう。
- 本番ではPostGISを使いましょう。

# References
- [Slack: djangoja](https://djangoja.slack.com)
- [DjangoProject: Installing SpatiaLite](https://docs.djangoproject.com/en/2.0/ref/contrib/gis/install/spatialite/#spatialite-macos)
- [MacPorts: python*: Allow loading SQLite extensions](https://trac.macports.org/ticket/49317)
- [https://github.com/pyenv/pyenv: Link to a specific version of sqlite3 when building Python 3 versions #333](https://github.com/pyenv/pyenv/issues/333)
- [https://github.com/pyenv/pyenv: How do I pass custom configure when using pyenv install? #86](https://github.com/pyenv/pyenv/issues/86)