Skip to content

Commit c30cfa8

Browse files
committed
Implement optional per-env lockfiles in tox setup
1 parent aa21d83 commit c30cfa8

9 files changed

+593
-0
lines changed

.flake8

+10
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ extend-exclude =
2828
# Metadata of `pip wheel` cmd is autogenerated
2929
pip-wheel-metadata,
3030

31+
# IMPORTANT: avoid using ignore option, always use extend-ignore instead
32+
# Completely and unconditionally ignore the following errors:
33+
extend-ignore =
34+
# Legitimate cases, no need to "fix" these violations:
35+
WPS326 # "Found implicit string concatenation" -- nothing bad about this
36+
3137
# https://wemake-python-stylegui.de/en/latest/pages/usage/formatter.html
3238
#format = wemake
3339

@@ -43,6 +49,10 @@ per-file-ignores =
4349
# and subprocesses (import – S404; call – S603) in tests:
4450
cheroot/test/test_*.py: S101, S404, S603
4551

52+
# WPS102 Found incorrect module name pattern
53+
# This is not a regular module but a standalone script:
54+
bin/pip-wrapper: WPS102
55+
4656
# FIXME:
4757
cheroot/__init__.py: I003, WPS100, WPS412, WPS420, WPS422, WPS433
4858
cheroot/__main__.py: WPS130, WPS300

bin/pip-wrapper

+121
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
#! /usr/bin/env python
2+
"""A pip-wrapper that injects platform-specific constraints into pip."""
3+
from __future__ import print_function # noqa: WPS422
4+
5+
import functools
6+
import os
7+
import platform
8+
import subprocess # noqa: S404
9+
import sys
10+
11+
print_info = functools.partial(print, file=sys.stderr)
12+
13+
14+
def get_runtime_python_tag():
15+
"""Identify the Python tag of the current runtime.
16+
17+
:returns: Python tag.
18+
"""
19+
python_minor_ver = sys.version_info[:2]
20+
python_implementation = platform.python_implementation()
21+
22+
if python_implementation == 'PyPy':
23+
python_tag_prefix = 'py'
24+
else:
25+
python_tag_prefix = 'cp'
26+
27+
# pylint: disable=possibly-unused-variable
28+
python_minor_ver_tag = ''.join(map(str, python_minor_ver))
29+
30+
python_tag = (
31+
'{python_tag_prefix!s}{python_minor_ver_tag!s}'.
32+
format(**locals()) # noqa: WPS421
33+
)
34+
35+
if python_tag_prefix == 'cp' and python_implementation == 'CPython':
36+
print_info(
37+
'WARNING: The chosen Python interpreter tag "{python_tag}" '
38+
'may not match the current '
39+
'Python implementation "{python_implementation}"'.
40+
format(**locals()), # noqa: WPS421
41+
)
42+
43+
return python_tag
44+
45+
46+
def get_constraint_file_path(req_dir, toxenv, python_tag):
47+
"""Identify the constraints filename for the current environment.
48+
49+
:param req_dir: Requirements directory.
50+
:param toxenv: tox testenv.
51+
:param python_tag: Python tag.
52+
53+
:returns: Constraints filename for the current environment.
54+
"""
55+
sys_platform = sys.platform
56+
platform_machine = platform.machine()
57+
58+
if toxenv in {'py', 'python'}:
59+
toxenv = 'py{ver}'.format(ver=python_tag[2:])
60+
61+
if sys_platform == 'linux2':
62+
sys_platform = 'linux'
63+
64+
constraint_name = (
65+
'tox-{toxenv}-{python_tag}-{sys_platform}-{platform_machine}'.
66+
format(**locals()) # noqa: WPS421
67+
)
68+
return os.path.join(req_dir, os.path.extsep.join((constraint_name, 'txt')))
69+
70+
71+
def make_pip_cmd(pip_args, constraint_file_path):
72+
"""Inject a lockfile constraint into the pip command if present.
73+
74+
:param pip_args: pip arguments.
75+
:param constraint_file_path: Path to a ``constraints.txt``-compatible file.
76+
77+
:returns: pip command.
78+
"""
79+
pip_cmd = [sys.executable, '-m', 'pip'] + pip_args
80+
if os.path.isfile(constraint_file_path):
81+
pip_cmd += ['--constraint', constraint_file_path]
82+
else:
83+
print_info(
84+
'WARNING: The expected pinned constraints file for the current env'
85+
'does not exist (should be "{constraint_file_path}").'.
86+
format(**locals()), # noqa: WPS421
87+
)
88+
return pip_cmd
89+
90+
91+
def run_cmd(cmd):
92+
"""Invoke a shell command after logging it.
93+
94+
:param cmd: The command to invoke.
95+
"""
96+
print_info(
97+
'Invoking the following command: {cmd}'.
98+
format(cmd=' '.join(cmd)),
99+
)
100+
subprocess.check_call(cmd) # noqa: S603
101+
102+
103+
def main(argv):
104+
"""Invoke pip with the matching constraints file, if present.
105+
106+
:param argv: List of command-line arguments.
107+
"""
108+
constraint_file_path = get_constraint_file_path(
109+
req_dir=argv[1],
110+
toxenv=argv[0],
111+
python_tag=get_runtime_python_tag(),
112+
)
113+
pip_cmd = make_pip_cmd(
114+
pip_args=argv[2:],
115+
constraint_file_path=constraint_file_path,
116+
)
117+
run_cmd(pip_cmd)
118+
119+
120+
if __name__ == '__main__':
121+
main(sys.argv[1:])
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
tests.in
+202
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
#
2+
# This file is autogenerated by pip-compile
3+
# To update, run:
4+
#
5+
# pip-compile --allow-unsafe --output-file=requirements/tox-py27-cp27-linux-x86_64.txt requirements/tox-py27-cp27-linux-x86_64.in
6+
#
7+
atomicwrites==1.4.0
8+
# via pytest
9+
attrs==21.2.0
10+
# via pytest
11+
backports.functools-lru-cache==1.6.4
12+
# via
13+
# jaraco.functools
14+
# wcwidth
15+
certifi==2021.10.8
16+
# via requests
17+
cffi==1.15.0
18+
# via cryptography
19+
chardet==4.0.0
20+
# via
21+
# -r requirements/tox-py27-cp27-linux-x86_64.in
22+
# requests
23+
colorama==0.4.4
24+
# via pytest-watch
25+
configparser==4.0.2
26+
# via importlib-metadata
27+
contextlib2==0.6.0.post1
28+
# via
29+
# importlib-metadata
30+
# importlib-resources
31+
# jaraco.context
32+
# yg.lockfile
33+
# zipp
34+
coverage[toml]==5.5 ; python_version < "3.6" and python_version != "3.4"
35+
# via
36+
# -r requirements/tox-py27-cp27-linux-x86_64.in
37+
# pytest-cov
38+
cryptography==3.3.2
39+
# via
40+
# pyopenssl
41+
# trustme
42+
docopt==0.6.2
43+
# via pytest-watch
44+
enum34==1.1.10
45+
# via cryptography
46+
execnet==1.9.0
47+
# via pytest-xdist
48+
funcsigs==1.0.2
49+
# via
50+
# mock
51+
# pytest
52+
futures==3.3.0 ; python_version == "2.7"
53+
# via -r requirements/tox-py27-cp27-linux-x86_64.in
54+
idna==2.10
55+
# via
56+
# requests
57+
# trustme
58+
importlib-metadata==2.1.2
59+
# via
60+
# pluggy
61+
# pytest
62+
importlib-resources==3.3.1
63+
# via jaraco.text
64+
ipaddress==1.0.23
65+
# via
66+
# cryptography
67+
# trustme
68+
jaraco.apt==2.0
69+
# via jaraco.context
70+
jaraco.context==2.0
71+
# via -r requirements/tox-py27-cp27-linux-x86_64.in
72+
jaraco.functools==2.0
73+
# via
74+
# jaraco.text
75+
# tempora
76+
# yg.lockfile
77+
jaraco.text==3.2.0
78+
# via -r requirements/tox-py27-cp27-linux-x86_64.in
79+
mock==3.0.5
80+
# via pytest-mock
81+
more-itertools==5.0.0
82+
# via
83+
# jaraco.functools
84+
# pytest
85+
packaging==20.9
86+
# via
87+
# pytest
88+
# pytest-sugar
89+
pathlib2==2.3.6
90+
# via
91+
# importlib-metadata
92+
# importlib-resources
93+
# pytest
94+
pathtools==0.1.2
95+
# via watchdog
96+
pluggy==0.13.1
97+
# via pytest
98+
portend==2.6
99+
# via -r requirements/tox-py27-cp27-linux-x86_64.in
100+
py==1.11.0
101+
# via
102+
# pypytools
103+
# pytest
104+
# pytest-forked
105+
pycparser==2.21
106+
# via cffi
107+
pyopenssl==21.0.0
108+
# via -r requirements/tox-py27-cp27-linux-x86_64.in
109+
pyparsing==2.4.7
110+
# via packaging
111+
pypytools==0.6.2
112+
# via -r requirements/tox-py27-cp27-linux-x86_64.in
113+
pytest-cov==2.12.0 ; python_version != "3.4"
114+
# via -r requirements/tox-py27-cp27-linux-x86_64.in
115+
pytest-forked==1.3.0 ; sys_platform != "win32" and (python_version < "3.0" or python_version > "3.4")
116+
# via
117+
# -r requirements/tox-py27-cp27-linux-x86_64.in
118+
# pytest-xdist
119+
pytest-mock==2.0.0
120+
# via -r requirements/tox-py27-cp27-linux-x86_64.in
121+
pytest-rerunfailures==8.0 ; python_version <= "2.7"
122+
# via -r requirements/tox-py27-cp27-linux-x86_64.in
123+
pytest-sugar==0.9.4
124+
# via -r requirements/tox-py27-cp27-linux-x86_64.in
125+
pytest-watch==4.2.0
126+
# via -r requirements/tox-py27-cp27-linux-x86_64.in
127+
pytest-xdist==1.34.0
128+
# via -r requirements/tox-py27-cp27-linux-x86_64.in
129+
pytest==4.6.11
130+
# via
131+
# -r requirements/tox-py27-cp27-linux-x86_64.in
132+
# pytest-cov
133+
# pytest-forked
134+
# pytest-mock
135+
# pytest-rerunfailures
136+
# pytest-sugar
137+
# pytest-watch
138+
# pytest-xdist
139+
pytz==2021.3
140+
# via tempora
141+
requests-toolbelt==0.9.1
142+
# via -r requirements/tox-py27-cp27-linux-x86_64.in
143+
requests-unixsocket==0.2.0
144+
# via -r requirements/tox-py27-cp27-linux-x86_64.in
145+
requests==2.26.0
146+
# via
147+
# requests-toolbelt
148+
# requests-unixsocket
149+
scandir==1.10.0
150+
# via pathlib2
151+
singledispatch==3.7.0
152+
# via importlib-resources
153+
six==1.16.0
154+
# via
155+
# cryptography
156+
# jaraco.apt
157+
# jaraco.text
158+
# mock
159+
# more-itertools
160+
# pathlib2
161+
# pyopenssl
162+
# pytest
163+
# pytest-xdist
164+
# singledispatch
165+
# tempora
166+
tempora==1.14.1
167+
# via
168+
# portend
169+
# yg.lockfile
170+
termcolor==1.1.0
171+
# via pytest-sugar
172+
toml==0.10.2
173+
# via coverage
174+
trustme==0.9.0
175+
# via -r requirements/tox-py27-cp27-linux-x86_64.in
176+
typing==3.10.0.0
177+
# via importlib-resources
178+
urllib3==1.26.7 ; python_version != "3.4"
179+
# via
180+
# -r requirements/tox-py27-cp27-linux-x86_64.in
181+
# requests
182+
# requests-unixsocket
183+
watchdog==0.10.7 ; python_version < "3.6"
184+
# via
185+
# -r requirements/tox-py27-cp27-linux-x86_64.in
186+
# pytest-watch
187+
wcwidth==0.2.5
188+
# via pytest
189+
yg.lockfile==2.3
190+
# via jaraco.context
191+
zc.lockfile==2.0
192+
# via yg.lockfile
193+
zipp==1.2.0
194+
# via
195+
# importlib-metadata
196+
# importlib-resources
197+
198+
# The following packages are considered to be unsafe in a requirements file:
199+
setuptools==44.1.1
200+
# via
201+
# pytest-rerunfailures
202+
# zc.lockfile
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
tests.in

0 commit comments

Comments
 (0)