LoginSignup
0

More than 1 year has passed since last update.

posted at

[python]importについて理解したい件―pytest, aws lambdaで使う

TL;DR

tests/conftest.py でデプロイ用のlambdaディレクトリをpathに追加する必要がある。

sys.path.append(os.path.join(os.dirname(__file__), '../{lambdaコードのディレクトリ}')

はじめに

pytestでテストを実行した際に、インポートエラーに遭遇した。
importについての理解が浅いせいで、アホみたいな勘違いをして時間を浪費したのでメモる。
sys.path.append は定義していたのだが、通すpathが間違っていたので苦労した。

ディレクトリ構成

test_app.ppy[app.py](http://app.py) をテストするときに mypkg をimportしたいケースを考えます。
パッケージ(ディレクトリ?)にはアンダースコア(_)は非推奨らしいので、 lambadasrc とした。また、ハイフン(-)を利用する場合、パッケージ化したときにimport文で読み込めなくなるので、命名について理解する必要がある。。。

.
├── lambadasrc
│   ├── app.py
│   └── mypkg
│       └── module1.py
└── tests
    ├── __init__.py
    ├── conftest.py
    └── test_app.py

エラー内容

pytestを実行するとエラー出力される。

.venv ❯ pytest tests -s

~
lambadasrc/app.py:8: in import_mypkg
    from mypkg.module1 import func
E   ModuleNotFoundError: No module named 'mypkg'

app.pyは下記。
lambadasrc ディレクトリは、lambdaのデプロイ単位のディレクトリと考えて、その直下からimportする。

from pprint import pprint
import sys
pprint(sys.path)

def import_mypkg():
    from mypkg.module1 import func
    return func

func = import_mypkg()

def lambada_handler(event, context):
    func()

なぜエラーになるかというと、 sys.pathlambdasrc へのPATHが通っていないため。(おそらく。。)

['/Users/hoge/import_pytest_202203',
 '/Users/hoge/import_pytest_202203/.venv/bin',
 '/Users/hoge/.pyenv/versions/3.9.7/lib/python39.zip',
 '/Users/hoge/.pyenv/versions/3.9.7/lib/python3.9',
 '/Users/hoge/.pyenv/versions/3.9.7/lib/python3.9/lib-dynload',
 '/Users/hoge/import_pytest_202203/.venv/lib/python3.9/site-packages']

pytestはtestのソースコードから上の階層にむかって __init__.py の有無でベースディレクトリを判断する仕様になっているので、 /Users/hoge/import_pytest_202203 が定義されていると理解。
しかし、 lambdasrc まではpathが通っていないため、import エラーが発生する。
なので、conftest.pyに sys.path.append をかく。

import sys
import os

sys.path.append(os.path.join(os.path.dirname(__file__), '../lambadasrc'))

sys.pathに下記が追加される。testを実行してもエラーは発生しなくなる。

'/Users/hoge/import_pytest_202203/tests/../lambadasrc',

勘違いしてたこと、色々調べてさらに沼ったこと

  • sys.path.append で、 lambadasrc/mypkg にpathを通していた
    • 何故かimportするパッケージまでpathが通っている必要があると思いこんでた。。。
  • __init__.py の有無
    • pytestでは、ベースディレクトリの判断のために必要
    • srcでは、レギュラーパッケージとして認識させる場合は__init__.pyが必要
    • 暗黙的な名前空間パッケージ(Implicit Namespace Packages)として利用するなら不要
      • まぁ、パフォーマンス的に定義してほうがよいという記事もあるが、、
    • __init__.py__all__ 定義している情報も見かけ、更に混乱した。。。
      • __all__の用途は、from~importのワイルドカードを制御するために使用。また、途中のmodule名を省略するために利用しているケースも観測
      • from mypkg.hoge from Hoge → from mypkg from Hoge(hogeを省略)
    • setup.py(公式では、pyproject.toml,setup.cfg)を使用して、ローカルモジュールとしてpathを通す解決策もあり?
      • pip install -e . で編集モードでパッケージ作成
      • 上記試したが、解決できなかった
      • おそらく、本来はパッケージ開発にpytestをインテグレーションするトピックだと推測。(src/とtest/のディレクトリ構成もパッケージ開発のためのトピックか?)
      • https://docs.pytest.org/en/latest/explanation/goodpractices.html#src-layout
  • namespaceもよくわかってない
  • デバッグと単純なRunだと、挙動が違いみたい?

他の対応案?

  • pytest コマンド実行でなく、 python -m pytest 実行だとカレントディレクトリをsys.pathに追加するっぽい
  • importlib がちょっと気になる

importlib: new in pytest-6.0, this mode uses [importlib](https://docs.python.org/3/library/importlib.html#module-importlib) to import test modules. This gives full control over the import process, and doesn’t require changing [sys.path](https://docs.python.org/3/library/sys.html#sys.path).

終わりに

今回のエラーについてググると、ヒットする記事の内容に微妙に差異があるので自分のエラー原因を特定するのに苦労した。ググり力を高めていきたい。

参考

pytest import mechanisms and sys.path/PYTHONPATH - pytest documentation

Good Integration Practices - pytest documentation

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
What you can do with signing up
0