LoginSignup
5
3

More than 5 years have passed since last update.

SpatiaLite with pyenv on Mac

Last updated at Posted at 2018-03-02

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が依存してるのでついてくる

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

    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をロードしてみよう

SQLiteConsole
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のソースをたどったら
sqlite3sqlite3.dbapi2_sqlite3

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できた。

$ /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

実際やってみると

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にオプションを付けるには以下で行けるらしい

$ 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

あれ・・・

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だったかなんだかが抜けてるかなんとか…

全部合わせて

$ 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
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

5
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
3