Problem
On running pytest
with configuring strongly recommended directory structure in good practices in docs.pytest.org,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
setup.py
src/
mypkg/
__init__.py
app.py
view.py
tests/
__init__.py
foo/
__init__.py
test_view.py
bar/
__init__.py
test_view.py
|
In my sample case, the directory is like:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
concurrent-api-client
├── README.md
├── log
├── setup.py
├── src
│ └── api_client
│ ├── __init__.py
│ ├── api_client.py
│ ├── api_formatter.py
│ ├── exceptions.py
│ └── ...
│ └── ...
└── tests
├── __init__.py
├── test_api_formatter.py
└── test_weather_api.py
|
However, pytest cannot find the source package directory like:
1
|
E ModuleNotFoundError: No module named 'api_client'
|
Solution
Refering docs.pytest.org | good practices and linked blog post (ionl’s codelog | Packaging a python library) it seems writing setup.py
appropriately and run pip install -e .
solves this.
setup.py
for project (tweaked some code for a simple example)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
from glob import glob
from os.path import basename, splitext
from setuptools import find_packages
from setuptools import setup
def get_readme():
""" Get the README from the current directory. If there isn't one, return an empty string """
all_readmes = sorted(glob("README*"))
if len(all_readmes) > 1:
warnings.warn("There seems to be more than one README in this directory. Choosing the "
"first in lexicographic order.")
if len(all_readmes) > 0:
return open(all_readmes[0], 'r').read()
warnings.warn("There doesn't seem to be a README in this directory.")
return ""
setup(
name='concurrent-api-client',
version='1.0',
license='MIT',
description='An example for API client using python request library',
long_description="\n" + get_readme(),
author='tatoflam',
author_email='tatoflam@gamil.com',
url='https://github.com/tatoflam/concurrent-api-client',
packages=find_packages('src'),
package_dir={'': 'src'},
py_modules=[splitext(basename(path))[0] for path in glob('src/*.py')],
include_package_data=True,
zip_safe=False,
classifiers=[
# complete classifier list: http://pypi.python.org/pypi?%3Aaction=list_classifiers
'Development Status :: 4 - Beta',
'Intended Audience :: Developers',
'License :: OSI Approved :: MIT License',
'Operating System :: MacOS :: MacOS X',
'Programming Language :: Python :: 3',
'Topic :: Utilities',
'Topic :: Software Development :: Libraries :: Python Modules'
],
project_urls={
# 'Changelog': 'https://github.com/tatoflam/concurrent-api-client/blob/master/CHANGELOG.rst',
'Issue Tracker': 'https://github.com/tatoflam/concurrent-api-client/issues',
},
python_requires='>=3.7',
setup_requires=[
'pytest-runner',
]
)
|
After running pip insatll - e .
, sys.path gives the path to the package directory and pytest
can retrieve modules under src directory.
1
2
3
4
5
6
7
8
9
10
11
12
13
|
$ python
Python 3.9.7 (default, Sep 10 2021, 10:49:24)
[Clang 12.0.0 (clang-1200.0.32.29)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> import pprint
>>> pprint.pprint(sys.path)
['',
'/Users/a-user/.pyenv/versions/3.9.7/lib/python39.zip',
'/Users/a-user/.pyenv/versions/3.9.7/lib/python3.9',
'/Users/a-user/.pyenv/versions/3.9.7/lib/python3.9/lib-dynload',
'/Users/a-user/.pyenv/versions/3.9.7/lib/python3.9/site-packages',
'/Users/a-user/repo/tatoflam/python_api_client/src']
|
The other ways that I tried
- Make
__init__.py
to src or test directory
- Set environment variable:
PYTHONPATH
(e.g. export PYTHONPATH=.:~/repo/tatoflam/python_api_client:$PYTHONPATH
)
- Or append path
.
and ~/repo/tatoflam/python_api_client' to python
to sys.path
by sys.path.append()
pip insatll - e .
(without setting setup.py)
This can add path to the package directory, but did not solve the ModuleNotFoundError under the modules in src
directory. Even if locating modules without src directory like below structure, pytest
works, that’s not the case for recommended directory structure. So, I believe above solution is better.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
python_api_client
├── README.md
├── log
├── setup.py
├── api_client
│ ├── __init__.py
│ ├── api_client.py
│ ├── api_formatter.py
│ ├── exceptions.py
│ └── ...
│ └── ...
└── tests
├── __init__.py
├── test_api_formatter.py
└── test_weather_api.py
|
Sources
github
References