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
$ 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
$ brew update
$ brew install spatialite-tools
$ brew install gdal
GEOS/PROJやらはGDALが依存してるのでついてくる
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
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をロードしてみよう
sqlite> SELECT load_extension('/usr/local/lib/mod_spatialite.dylib')
Error: no such function: load_extension
$ sqlite3 --version
3.16.0
$ which sqlite3
/usr/bin/sqlite3
あー
とりまsqlite3
パッケージにのコネクションにenable_load_extension
がないのでsqlite3
のソースをたどったら
sqlite3
→ sqlite3.dbapi2
→ _sqlite3
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
できた。
$ /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
sqlite3
パッケージをHomebrewのやつとリンクさせるには以下で行けるらしい
$ 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
実際やってみると
>>> 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にオプションを付けるには以下で行けるらしい
$ PYTHON_CONFIGURE_OPTS="<configurations>" pyenv install 3.6.2
これだけでやったら
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
あれ・・・
>>> 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だったかなんだかが抜けてるかなんとか…
全部合わせて
$ 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
>>> 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
- DjangoProject: Installing SpatiaLite
- MacPorts: python*: Allow loading SQLite extensions
- https://github.com/pyenv/pyenv: Link to a specific version of sqlite3 when building Python 3 versions #333
- https://github.com/pyenv/pyenv: How do I pass custom configure when using pyenv install? #86