LoginSignup
3
3

More than 5 years have passed since last update.

[備忘録] GAE の dev_appserver で ctypes が import 出来ずはまった

Last updated at Posted at 2016-07-29

小一時間悩んだので備忘録として残しておきます。

結論

ちゃんと環境でサポートされているバージョンを使いましょう。(numpy なら 1.6.1)
https://cloud.google.com/appengine/docs/python/tools/built-in-libraries-27

あと dev_appserver.py の仕組みに少し詳しくなれました。

現象

サポートされているバージョンが 1.6.1 なのはさておき、Docker で構築中の GAE python の開発環境で numpy1.9.1 から 1.10.1 に上げたら以下のエラーが出るようになって困りました。

  File "/usr/local/google_appengine/google/appengine/tools/devappserver2/python/sandbox.py", line 705, in load_module
    module = self._find_and_load_module(fullname, fullname, [module_path])
  File "/usr/local/google_appengine/google/appengine/tools/devappserver2/python/sandbox.py", line 446, in _find_and_load_module
    return imp.load_module(fullname, source_file, path_name, description)
  File "/root/.pyenv/versions/2.7.12/lib/python2.7/site-packages/numpy/__init__.py", line 180, in <module>
    from . import add_newdocs
  ...
  File "/root/.pyenv/versions/2.7.12/lib/python2.7/site-packages/numpy/core/_internal.py", line 14, in <module>
    import ctypes
  File "/root/.pyenv/versions/2.7.12/lib/python2.7/ctypes/__init__.py", line 7, in <module>
    from _ctypes import Union, Structure, Array
  File "/usr/local/google_appengine/google/appengine/tools/devappserver2/python/sandbox.py", line 964, in load_module
    raise ImportError('No module named %s' % fullname)
ImportError: No module named _ctypes

原因

GAE python では主にセキュリティ的な理由C で書かれた python module の大部分は利用することができません。

local 開発環境で利用する dev_appserver.py でもこの制限を模擬するために(そうでないと local では動くのにデプロイしたら動かなくなる謎現象に悩まされるので)、上のエラーにも出てくる sandbox.py というモジュールが使われています。(このモジュール以外にもこういった制限を模擬するものがきっとあると思いますがそこまでは調べていないです)

具体的には sandbox.py の以下のコードで import が呼ばれた場合の hook を登録しています。

${GAE_HOME}/google/appengine/tools/devappserver2/python/sandbox.py

def _install_import_hooks(config, path_override_hook):
  """Install runtime's import hooks.

  These hooks customize the import process as per
  https://docs.python.org/2/library/sys.html#sys.meta_path .

  Args:
    config: An apphosting/tools/devappserver2/runtime_config.proto
        for this instance.
    path_override_hook: A hook for importing special appengine
        versions of select libraries from the libraries
        section of the current module's app.yaml file.
  """
  if not config.vm:
    enabled_library_regexes = [
        NAME_TO_CMODULE_WHITELIST_REGEX[lib.name] for lib in config.libraries
        if lib.name in NAME_TO_CMODULE_WHITELIST_REGEX]
    sys.meta_path = [
        StubModuleImportHook(),
        ModuleOverrideImportHook(_MODULE_OVERRIDE_POLICIES),
        CModuleImportHook(enabled_library_regexes),
        path_override_hook,
        PyCryptoRandomImportHook,
        PathRestrictingImportHook(enabled_library_regexes)]
  else:
    ...

sys.meta_path というのがこの hook を登録する場所にあたり
PEP 302 -- New Import Hooks で定義された find_module(fullname, path=None) というメソッドを持つオブジェクトが登録されます。

この find_modulemodule が発見された場合には loader が返され、その load_module(fullname) が呼ばれることで import が行われます。

注目すべきは CModuleImportHooksandbox.py をさらに見ていくとクラス定義を見つけることができます。

class CModuleImportHook(object):
  """An import hook implementing a C module (builtin or extensions) whitelist.

  CModuleImportHook implements the PEP 302 finder protocol where it returns
  itself as a loader for any builtin module that isn't whitelisted or part of an
  enabled third-party library. The loader implementation always raises
  ImportError.
  """

  def __init__(self, enabled_regexes):
    self._enabled_regexes = enabled_regexes

  @staticmethod
  def _module_type(fullname, path):
    _, _, submodule_name = fullname.rpartition('.')
    try:
      f, _, description = imp.find_module(submodule_name, path)
      _, _, file_type = description
    except ImportError:
      return None
    if f:
      f.close()
    return file_type

  def find_module(self, fullname, path=None):
    if (fullname in _WHITE_LIST_C_MODULES or
        any(regex.match(fullname) for regex in self._enabled_regexes)):
      return None
    if self._module_type(fullname, path) in [imp.C_EXTENSION, imp.C_BUILTIN]:
      return self
    return None

  def load_module(self, fullname):
    raise ImportError('No module named %s' % fullname)

このクラスは find_moduleload_module の 2 つのメソッドを持っています。 find_moduleC の native module だと判定すると self が返り、 self.load_module() が必ず ImportErrorraise するため import に失敗していました。

_WHITE_LIST_C_MODULES など幾つか例外は登録されていますが _ctypes は例外扱いされていません

numpy1.9.1ctypes を import する際に ImportError が出るとフォールバックしてくれていたのですが、 1.10.1 に挙げるとその挙動が無くなりこけるようになったという次第でした。

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