Environment
- MacOS Catalina 10.15.7
- Python 3.7.8, 3.8.6, 3.9.7
Directory structure:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
concurrent-api-client
├── .python-version
├── README.md
├── MANIFEST.in
├── requirements.in
├── requirements.testing.in
├── requirements.txt
├── setup.py
├── setup.cfg
├── tox.ini
├── src
│ └── api_client
│ ├── __init__.py
│ ├── api_client.py
│ └── ...
└── tests
├── __init__.py
├── test_api_formatter.py
└── ...
|
Python interpreter for multiple python versions
Ref. tox InterpreterNotFound error with pyenv
Revisit setup.py
for testing
setup.py
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
|
from glob import glob
from os.path import basename
from os.path import splitext
from setuptools import find_packages
from setuptools import setup
def parse_requirements(filename):
""" Given a filename, strip empty lines and those beginning with # """
output = []
with open(filename, 'r') as f:
for line in f:
sline = line.strip()
if sline and not line.startswith('#'):
output.append(sline)
return output
def find_modules():
found = []
for pattern in ['src/*.py']:
# for pattern in ['src/*.py', 'src/*.json']:
found.extend([splitext(basename(path))[0] for path in glob(pattern)])
return found
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=find_modules(),
include_package_data=True,
python_requires='>=3.7',
install_requires=parse_requirements("requirements.txt"),
tests_require=parse_requirements("requirements.testing.txt"),
setup_requires=['pytest-runner'],
)
|
packages
, packages_dir
, find_modules
: specifies files under src
directory are packaged.
setup_requires
: enabling pytest
in setup.py
test_require
: a list of modules for testing.
- For above case, before parsing
requirements.testing.txt
, pip-compile requirements.testing.in
creates requirements.testing.txt
tox.ini
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
[tox]
envlist = py37, py38, py39
indexserver =
default = https://pypi.org/simple
[testenv]
deps=
-rrequirements.txt
commands=
pytest -rsfp
[testenv:pip-compile]
deps=
pip-tools==6.4.0
pip==21.2.3
commands=
pip-compile -i https://pypi.org/simple requirements.in requirements.testing.in -o requirements.txt -v
|
tox.ini
is put in same dir as setup.py
- [tox]
- envlist: python versions
- index server : pip remote repository
- [testenv]
- a setting for for test environment
- deps: libraries to install to test environment
- commands: command to execute (in this case, pytest)
- [testenv:pip-compile]
- Run this to
pip-compile
your requirements*.in
files into requirements.txt
. This also gets the requirements from requirements.testing.in
because requirements.in
contains a reference to requirements.testing.in
.
1
2
3
4
5
|
$ cd concurrent-api-client
$ pyenv local 3.7.8, 3.8.6, 3.9.7 # this set multiple python interpreters
$ pip-compile requirements.testing.in # this output requirements.testing.txt
$ tox -e pip-compile # this generates requirements.txt
$ tox -r # -r option force recreation of virtual environments, then run test in tox virtual environment per python versions
|
Tip
tor -rvv
is sometime useful (-vv mode turns off output redirection for package installation)
Trouble shooting
Problem 1. tox
fails to create virtual environment
Problem
tox -r
fails to create virtual environment because the tool cannot read requirements.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
$ tox -rvv
using tox.ini: /Users/tatsuro.homma/repo/tatoflam/concurrent-api-client/tox.ini (pid 68529)
...
ERROR: Command errored out with exit status 1:
command: /Users/a-user/repo/tatoflam/concurrent-api-client/.tox/py39/bin/python -c 'import io, os, sys, setuptools, tokenize; sys.argv[0] = '"'"'/private/var/folders/q0/g44k3tvx0t7cz98pw1f3tfhw0000gq/T/pip-req-build-pchbcgxt/setup.py'"'"'; __file__='"'"'/private/var/folders/q0/g44k3tvx0t7cz98pw1f3tfhw0000gq/T/pip-req-build-pchbcgxt/setup.py'"'"';f = getattr(tokenize, '"'"'open'"'"', open)(__file__) if os.path.exists(__file__) else io.StringIO('"'"'from setuptools import setup; setup()'"'"');code = f.read().replace('"'"'\r\n'"'"', '"'"'\n'"'"');f.close();exec(compile(code, __file__, '"'"'exec'"'"'))' egg_info --egg-base /private/var/folders/q0/g44k3tvx0t7cz98pw1f3tfhw0000gq/T/pip-pip-egg-info-jlxkkci2
cwd: /private/var/folders/q0/g44k3tvx0t7cz98pw1f3tfhw0000gq/T/pip-req-build-pchbcgxt/
Complete output (7 lines):
Traceback (most recent call last):
File "<string>", line 1, in <module>
File "/private/var/folders/q0/g44k3tvx0t7cz98pw1f3tfhw0000gq/T/pip-req-build-pchbcgxt/setup.py", line 71, in <module>
install_requires=parse_requirements("requirements.txt"),
File "/private/var/folders/q0/g44k3tvx0t7cz98pw1f3tfhw0000gq/T/pip-req-build-pchbcgxt/setup.py", line 32, in parse_requirements
with open(filename, 'r') as f:
FileNotFoundError: [Errno 2] No such file or directory: 'requirements.txt'
----------------------------------------
|
tox.ini
1
2
3
4
5
6
7
8
9
10
11
12
|
# content of: tox.ini , put in same dir as setup.py
[tox]
envlist = py39
indexserver =
default = https://pypi.org/simple
[testenv]
# install pytest in the virtualenv where commands will be executed
deps=
-rrequirements.txt
commands=
{envbindir}/pytest
|
Solution
Add requirements.txt
in MANIFEST.in
1
2
3
|
concurrent-api-client
├── MANIFEST.in
├──...
|
MANIFEST.in
1
|
include requirements.txt
|
Adding & removing files to & from the source distribution is done by writing a MANIFEST.in file at the project root. (Ref. PyPA | python packaging guide - using manifest.in
Then pip
run by tox
can find requirements.txt
in a package.
Problem 2. tox
fails by InvocationError (cannot load pytest-runner)
Problem
tox -r
fails by InvocationError
1
2
3
4
5
|
$ tox
...
distutils.errors.DistutilsError: Command '['/a-user/repo/tatoflam/concurrent-api-client/.tox/py39/bin/python', '-m', 'pip', '--disable-pip-version-check', 'wheel', '--no-deps', '-w', '/var/folders/q0/g44k3tvx0t7cz98pw1f3tfhw0000gq/T/tmppg3oyf_8', '--quiet', 'pytest-runner']' returned non-zero exit status 1.
...
ERROR: invocation failed (exit code 1), logfile: /Users/a-user/repo/tatoflam/concurrent-api-client/.tox/py39/log/py39-2.log
|
The cause is pypi repository is set us unintended location (https://any-repo, instead of default : https://pypi.org/simple).
tox.ini
1
2
|
commands=
pip-compile requirements.in requirements.testing.in -o requirements.txt -v
|
setup.py
1
2
3
4
5
|
setup(
setup_requires=[
'pytest-runner',
]
)
|
Solution
For solving this, Firstly, I recreated requirements.txt
and installed packages to tox virtual environments with pip-compile -i https://pypi.org/simple
option.
tox.ini
1
2
|
commands=
pip-compile -i https://pypi.org/simple requirements.in requirements.testing.in -o requirements.txt -v
|
However, this does not perfectly solve the error. Instead, creating setup.cfg
file works.
setup.cfg
1
2
|
[easy_install]
index-url = https://pypi.org/simple
|
Then run following command.
1
2
|
$ tox -e pip-compile # this creates requirements.txt from requirements.in and requirements.testing.in.
$ tox -r
|
Or, simply removing pytest-runner
from setup_requires
attribute in setup().
Problem 3. tox
fails by InvocationError (site-package does not include configuration file)
Problem
tox -r
fails by InvocationError by FileNotFoundError for .json configuration file.
1
2
3
|
$ tox -r
...
E FileNotFoundError: [Errno 2] No such file or directory: '/Users/tatsuro.homma/repo/tatoflam/concurrent-api-client/.tox/py39/lib/python3.9/site-packages/api_client/config/logging.json'
|
Solution
As well as Problem 1, files not in python module are not included in site-package
directory, that is created by pip install
command. For adding it, write the path to MANIFEST.in
.
MANIFEST.in
1
2
3
4
|
include requirements.txt
include requirements.testing.txt
include README*
include src/api_client/config/*.json
|
Then tox -r
works!
Sources
See finalized codes(tox.ini
and setup.py
) in github
References