check-python-versions 0.23.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- check_python_versions/__init__.py +16 -0
- check_python_versions/__main__.py +7 -0
- check_python_versions/cli.py +479 -0
- check_python_versions/parsers/__init__.py +1 -0
- check_python_versions/parsers/classifiers.py +81 -0
- check_python_versions/parsers/ini.py +81 -0
- check_python_versions/parsers/poetry_version_spec.py +276 -0
- check_python_versions/parsers/python.py +262 -0
- check_python_versions/parsers/requires_python.py +239 -0
- check_python_versions/parsers/yaml.py +221 -0
- check_python_versions/sources/__init__.py +1 -0
- check_python_versions/sources/all.py +23 -0
- check_python_versions/sources/appveyor.py +191 -0
- check_python_versions/sources/base.py +87 -0
- check_python_versions/sources/github.py +162 -0
- check_python_versions/sources/manylinux.py +96 -0
- check_python_versions/sources/pyproject.py +244 -0
- check_python_versions/sources/setup_py.py +201 -0
- check_python_versions/sources/tox.py +265 -0
- check_python_versions/sources/travis.py +213 -0
- check_python_versions/utils.py +142 -0
- check_python_versions/versions.py +158 -0
- check_python_versions-0.23.0.dist-info/METADATA +434 -0
- check_python_versions-0.23.0.dist-info/RECORD +28 -0
- check_python_versions-0.23.0.dist-info/WHEEL +5 -0
- check_python_versions-0.23.0.dist-info/entry_points.txt +2 -0
- check_python_versions-0.23.0.dist-info/licenses/LICENSE +674 -0
- check_python_versions-0.23.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,87 @@
|
|
1
|
+
from typing import Callable, Optional
|
2
|
+
|
3
|
+
from ..utils import FileLines, FileOrFilename
|
4
|
+
from ..versions import SortedVersionList
|
5
|
+
|
6
|
+
|
7
|
+
ExtractorFn = Callable[[FileOrFilename], Optional[SortedVersionList]]
|
8
|
+
UpdaterFn = Callable[[FileOrFilename, SortedVersionList], Optional[FileLines]]
|
9
|
+
|
10
|
+
|
11
|
+
class Source:
|
12
|
+
"""Source for information about supported Pythons.
|
13
|
+
|
14
|
+
Examples of sources are: setup.py, CI configuration files.
|
15
|
+
|
16
|
+
A source has a ``title``, a typical ``filename`` (which can be a
|
17
|
+
glob pattern).
|
18
|
+
|
19
|
+
A source knows how to extract the list of supported Python versions
|
20
|
+
from a file, and it can know how to update the list of versions.
|
21
|
+
|
22
|
+
A source knows whether it's possible/typical to include PyPy support
|
23
|
+
for packages that want to support PyPy.
|
24
|
+
"""
|
25
|
+
|
26
|
+
def __init__(
|
27
|
+
self,
|
28
|
+
*,
|
29
|
+
title: Optional[str] = None,
|
30
|
+
filename: str,
|
31
|
+
extract: ExtractorFn,
|
32
|
+
update: Optional[UpdaterFn] = None,
|
33
|
+
check_pypy_consistency: bool,
|
34
|
+
has_upper_bound: bool,
|
35
|
+
) -> None:
|
36
|
+
self.title = title or filename
|
37
|
+
self.filename = filename
|
38
|
+
self.extract = extract
|
39
|
+
self.update = update
|
40
|
+
self.check_pypy_consistency = check_pypy_consistency
|
41
|
+
self.has_upper_bound = has_upper_bound
|
42
|
+
|
43
|
+
def for_file(
|
44
|
+
self,
|
45
|
+
pathname: str,
|
46
|
+
versions: SortedVersionList,
|
47
|
+
relpath: str,
|
48
|
+
) -> 'SourceFile':
|
49
|
+
title = relpath if self.title == self.filename else self.title
|
50
|
+
source = SourceFile(
|
51
|
+
title=title,
|
52
|
+
filename=self.filename,
|
53
|
+
extract=self.extract,
|
54
|
+
update=self.update,
|
55
|
+
check_pypy_consistency=self.check_pypy_consistency,
|
56
|
+
has_upper_bound=self.has_upper_bound,
|
57
|
+
pathname=pathname,
|
58
|
+
versions=versions,
|
59
|
+
)
|
60
|
+
return source
|
61
|
+
|
62
|
+
|
63
|
+
class SourceFile(Source):
|
64
|
+
"""A concrete source file with information about supported Pythons.
|
65
|
+
|
66
|
+
Some sources (GitHub Actions) can have multiple files matching a
|
67
|
+
glob pattern. Each of those gets its own ``SourceFile`` instance.
|
68
|
+
"""
|
69
|
+
|
70
|
+
def __init__(
|
71
|
+
self,
|
72
|
+
*,
|
73
|
+
title: Optional[str] = None,
|
74
|
+
filename: str,
|
75
|
+
extract: ExtractorFn,
|
76
|
+
update: Optional[UpdaterFn] = None,
|
77
|
+
check_pypy_consistency: bool,
|
78
|
+
has_upper_bound: bool,
|
79
|
+
pathname: str,
|
80
|
+
versions: SortedVersionList,
|
81
|
+
) -> None:
|
82
|
+
super().__init__(
|
83
|
+
title=title, filename=filename, extract=extract, update=update,
|
84
|
+
check_pypy_consistency=check_pypy_consistency,
|
85
|
+
has_upper_bound=has_upper_bound)
|
86
|
+
self.pathname = pathname
|
87
|
+
self.versions = versions
|
@@ -0,0 +1,162 @@
|
|
1
|
+
"""
|
2
|
+
Support for GitHub Actions.
|
3
|
+
|
4
|
+
GitHub Actions are very flexible, so this code is going to make some
|
5
|
+
simplifying assumptions:
|
6
|
+
|
7
|
+
- you use a matrix strategy
|
8
|
+
- on 'python-version' that contains python versions, or
|
9
|
+
- on 'config' that contains lists of [python_version, tox_env]
|
10
|
+
"""
|
11
|
+
|
12
|
+
from typing import Optional, Set, Union
|
13
|
+
|
14
|
+
import yaml
|
15
|
+
|
16
|
+
from .base import Source
|
17
|
+
from ..parsers.yaml import quote_string, update_yaml_list
|
18
|
+
from ..sources.tox import toxenv_for_version
|
19
|
+
from ..utils import FileLines, FileOrFilename, open_file
|
20
|
+
from ..versions import SortedVersionList, Version
|
21
|
+
|
22
|
+
|
23
|
+
GHA_WORKFLOW_FILE = '.github/workflows/tests.yml'
|
24
|
+
GHA_WORKFLOW_GLOB = '.github/workflows/*.yml'
|
25
|
+
|
26
|
+
|
27
|
+
def get_gha_python_versions(
|
28
|
+
filename: FileOrFilename = GHA_WORKFLOW_FILE,
|
29
|
+
) -> Optional[SortedVersionList]:
|
30
|
+
"""Extract supported Python versions from a GitHub workflow."""
|
31
|
+
with open_file(filename) as fp:
|
32
|
+
conf = yaml.safe_load(fp)
|
33
|
+
|
34
|
+
versions: Set[Version] = set()
|
35
|
+
had_matrix = False
|
36
|
+
for job_name, job in conf.get('jobs', {}).items():
|
37
|
+
matrix = job.get('strategy', {}).get('matrix', {})
|
38
|
+
if 'python-version' in matrix:
|
39
|
+
had_matrix = True
|
40
|
+
versions.update(
|
41
|
+
e for e in map(parse_gh_ver, matrix['python-version']) if e)
|
42
|
+
if 'config' in matrix:
|
43
|
+
had_matrix = True
|
44
|
+
versions.update(
|
45
|
+
parse_gh_ver(c[0])
|
46
|
+
for c in matrix['config']
|
47
|
+
if isinstance(c, list)
|
48
|
+
)
|
49
|
+
if 'include' in matrix:
|
50
|
+
for extra in matrix['include']:
|
51
|
+
if 'python-version' in extra:
|
52
|
+
had_matrix = True
|
53
|
+
versions.add(parse_gh_ver(extra['python-version']))
|
54
|
+
|
55
|
+
if not had_matrix:
|
56
|
+
return None
|
57
|
+
return sorted(set(versions))
|
58
|
+
|
59
|
+
|
60
|
+
def parse_gh_ver(v: Union[str, float]) -> Version:
|
61
|
+
"""Parse Python versions used for actions/setup-python@v2.
|
62
|
+
|
63
|
+
This format is not fully well documented. There's support for
|
64
|
+
specifying things like
|
65
|
+
|
66
|
+
- "3.x" (latest minor in Python 3.x; currently 3.9)
|
67
|
+
- "3.7" (latest bugfix in Python 3.7)
|
68
|
+
- "3.7.2" (specific version to be downloaded and installed)
|
69
|
+
- "pypy2"/"pypy3"
|
70
|
+
- "pypy-2.7"/"pypy-3.6"
|
71
|
+
- "pypy-3.7-v7.3.3"
|
72
|
+
|
73
|
+
https://github.com/actions/python-versions/blob/main/versions-manifest.json
|
74
|
+
contains a list of supported CPython versions that can be downloaded
|
75
|
+
and installed; this includes prereleases, but doesn't include PyPy.
|
76
|
+
"""
|
77
|
+
v = str(v)
|
78
|
+
if v.startswith(('pypy3', 'pypy-3')):
|
79
|
+
return Version.from_string('PyPy3')
|
80
|
+
elif v.startswith(('pypy2', 'pypy-2')):
|
81
|
+
return Version.from_string('PyPy')
|
82
|
+
else:
|
83
|
+
return Version.from_string(v)
|
84
|
+
|
85
|
+
|
86
|
+
def update_gha_python_versions(
|
87
|
+
filename: FileOrFilename,
|
88
|
+
new_versions: SortedVersionList,
|
89
|
+
) -> FileLines:
|
90
|
+
"""Update supported Python versions in a GitHub workflow file.
|
91
|
+
|
92
|
+
Does not touch the file but returns a list of lines with new file contents.
|
93
|
+
"""
|
94
|
+
with open_file(filename) as fp:
|
95
|
+
orig_lines = fp.readlines()
|
96
|
+
fp.seek(0)
|
97
|
+
conf = yaml.safe_load(fp)
|
98
|
+
new_lines = orig_lines
|
99
|
+
|
100
|
+
def keep_old_version(value: str) -> bool:
|
101
|
+
"""Determine if a Python version line should be preserved."""
|
102
|
+
parsed = yaml.safe_load(value)
|
103
|
+
ver = parse_gh_ver(parsed)
|
104
|
+
if ver == Version.from_string('PyPy'):
|
105
|
+
return any(v.major == 2 for v in new_versions)
|
106
|
+
if ver == Version.from_string('PyPy3'):
|
107
|
+
return any(v.major == 3 for v in new_versions)
|
108
|
+
return False
|
109
|
+
|
110
|
+
def keep_old_config(value: str) -> bool:
|
111
|
+
"""Determine if a Python version line should be preserved."""
|
112
|
+
parsed = yaml.safe_load(value)
|
113
|
+
if isinstance(parsed, list) and len(parsed) == 2:
|
114
|
+
ver = parse_gh_ver(parsed[0])
|
115
|
+
toxenv = str(parsed[1])
|
116
|
+
else:
|
117
|
+
return True
|
118
|
+
if ver == Version.from_string('PyPy'):
|
119
|
+
return any(v.major == 2 for v in new_versions)
|
120
|
+
if ver == Version.from_string('PyPy3'):
|
121
|
+
return any(v.major == 3 for v in new_versions)
|
122
|
+
return toxenv != toxenv_for_version(ver)
|
123
|
+
|
124
|
+
for job_name, job in conf.get('jobs', {}).items():
|
125
|
+
matrix = job.get('strategy', {}).get('matrix', {})
|
126
|
+
if 'python-version' in matrix:
|
127
|
+
quote_style = ''
|
128
|
+
if all(isinstance(v, str) for v in matrix['python-version']):
|
129
|
+
quote_style = '"'
|
130
|
+
yaml_new_versions = [
|
131
|
+
quote_string(str(v), quote_style)
|
132
|
+
for v in new_versions
|
133
|
+
]
|
134
|
+
new_lines = update_yaml_list(
|
135
|
+
new_lines,
|
136
|
+
('jobs', job_name, 'strategy', 'matrix', 'python-version'),
|
137
|
+
yaml_new_versions, filename=fp.name,
|
138
|
+
keep=keep_old_version,
|
139
|
+
)
|
140
|
+
if 'config' in matrix:
|
141
|
+
yaml_configs = []
|
142
|
+
for v in new_versions:
|
143
|
+
quoted_ver = quote_string(str(v), '"')
|
144
|
+
toxenv = quote_string(toxenv_for_version(v), '"')
|
145
|
+
yaml_configs.append(f"[{quoted_ver + ',':<8} {toxenv}]")
|
146
|
+
new_lines = update_yaml_list(
|
147
|
+
new_lines,
|
148
|
+
('jobs', job_name, 'strategy', 'matrix', 'config'),
|
149
|
+
yaml_configs, filename=fp.name,
|
150
|
+
keep=keep_old_config,
|
151
|
+
)
|
152
|
+
|
153
|
+
return new_lines
|
154
|
+
|
155
|
+
|
156
|
+
GitHubActions = Source(
|
157
|
+
filename=GHA_WORKFLOW_GLOB,
|
158
|
+
extract=get_gha_python_versions,
|
159
|
+
update=update_gha_python_versions,
|
160
|
+
check_pypy_consistency=True,
|
161
|
+
has_upper_bound=True,
|
162
|
+
)
|
@@ -0,0 +1,96 @@
|
|
1
|
+
"""
|
2
|
+
Support for .manylinux-install.sh. This is a shell script used by multiple
|
3
|
+
ZopeFoundation packages that builds manylinux wheels inside
|
4
|
+
quay.io/pypa/manylinux* Docker images.
|
5
|
+
|
6
|
+
The script loops over all installed Pythons, checks if each is a supported
|
7
|
+
version using a series of `if` statements, then builds wheels for each
|
8
|
+
supported versions. This looks like ::
|
9
|
+
|
10
|
+
for PYBIN in /opt/python/*/bin; do
|
11
|
+
if [[ "${PYBIN}" == *"cp27"* ]] || \
|
12
|
+
[[ "${PYBIN}" == *"cp34"* ]] || \
|
13
|
+
[[ "${PYBIN}" == *"cp35"* ]] || \
|
14
|
+
[[ "${PYBIN}" == *"cp36"* ]] || \
|
15
|
+
[[ "${PYBIN}" == *"cp37"* ]]; then
|
16
|
+
"${PYBIN}/pip" install -e /io/
|
17
|
+
"${PYBIN}/pip" wheel /io/ -w wheelhouse/
|
18
|
+
rm -rf /io/build /io/*.egg-info
|
19
|
+
fi
|
20
|
+
done
|
21
|
+
|
22
|
+
"""
|
23
|
+
|
24
|
+
import re
|
25
|
+
|
26
|
+
from .base import Source
|
27
|
+
from ..utils import FileLines, FileOrFilename, open_file, warn
|
28
|
+
from ..versions import SortedVersionList, Version, VersionList
|
29
|
+
|
30
|
+
|
31
|
+
MANYLINUX_INSTALL_SH = '.manylinux-install.sh'
|
32
|
+
|
33
|
+
|
34
|
+
def get_manylinux_python_versions(
|
35
|
+
filename: FileOrFilename = MANYLINUX_INSTALL_SH,
|
36
|
+
) -> SortedVersionList:
|
37
|
+
"""Extract supported Python versions from .manylinux-install.sh."""
|
38
|
+
magic = re.compile(r'.*\[\[ "\$\{PYBIN\}" == \*"cp(\d)(\d+)"\* \]\]')
|
39
|
+
versions = []
|
40
|
+
with open_file(filename) as fp:
|
41
|
+
for line in fp:
|
42
|
+
m = magic.match(line)
|
43
|
+
if m:
|
44
|
+
v = Version.from_string('{}.{}'.format(*m.groups()))
|
45
|
+
versions.append(v)
|
46
|
+
return sorted(set(versions))
|
47
|
+
|
48
|
+
|
49
|
+
def update_manylinux_python_versions(
|
50
|
+
filename: FileOrFilename,
|
51
|
+
new_versions: VersionList,
|
52
|
+
) -> FileLines:
|
53
|
+
"""Update supported Python versions in .manylinux_install_sh.
|
54
|
+
|
55
|
+
Does not touch the file but returns a list of lines with new file contents.
|
56
|
+
"""
|
57
|
+
magic = re.compile(r'.*\[\[ "\$\{PYBIN\}" == \*"cp(\d)(\d)"\* \]\]')
|
58
|
+
with open_file(filename) as f:
|
59
|
+
orig_lines = f.readlines()
|
60
|
+
lines = iter(enumerate(orig_lines))
|
61
|
+
for n, line in lines:
|
62
|
+
m = magic.match(line)
|
63
|
+
if m:
|
64
|
+
start = n
|
65
|
+
break
|
66
|
+
else:
|
67
|
+
warn(f'Failed to understand {f.name}')
|
68
|
+
return orig_lines
|
69
|
+
for n, line in lines:
|
70
|
+
m = magic.match(line)
|
71
|
+
if not m:
|
72
|
+
end = n
|
73
|
+
break
|
74
|
+
else:
|
75
|
+
warn(f'Failed to understand {f.name}')
|
76
|
+
return orig_lines
|
77
|
+
|
78
|
+
indent = ' ' * 4
|
79
|
+
conditions = f' || \\\n{indent} '.join(
|
80
|
+
f'[[ "${{PYBIN}}" == *"cp{ver.major}{ver.minor}"* ]]'
|
81
|
+
for ver in new_versions
|
82
|
+
)
|
83
|
+
new_lines = orig_lines[:start] + (
|
84
|
+
f'{indent}if {conditions}; then\n'
|
85
|
+
).splitlines(True) + orig_lines[end:]
|
86
|
+
|
87
|
+
return new_lines
|
88
|
+
|
89
|
+
|
90
|
+
Manylinux = Source(
|
91
|
+
filename=MANYLINUX_INSTALL_SH,
|
92
|
+
extract=get_manylinux_python_versions,
|
93
|
+
update=update_manylinux_python_versions,
|
94
|
+
check_pypy_consistency=False,
|
95
|
+
has_upper_bound=True,
|
96
|
+
)
|
@@ -0,0 +1,244 @@
|
|
1
|
+
"""
|
2
|
+
Support for pyproject.toml.
|
3
|
+
|
4
|
+
There are several build tools that use pyproject.toml to specify metadata.
|
5
|
+
Some of them use the PEP 621::
|
6
|
+
|
7
|
+
[project]
|
8
|
+
classifiers = [
|
9
|
+
...
|
10
|
+
"Programming Language :: Python :: 3.8",
|
11
|
+
...
|
12
|
+
]
|
13
|
+
requires-python = ">= 3.8"
|
14
|
+
|
15
|
+
check-python-versions also supports old-style Flit and Poetry metadata::
|
16
|
+
|
17
|
+
[tool.flit.metadata]
|
18
|
+
classifiers = [
|
19
|
+
...
|
20
|
+
"Programming Language :: Python :: 3.8",
|
21
|
+
...
|
22
|
+
]
|
23
|
+
requires-python = ">= 3.8"
|
24
|
+
|
25
|
+
[tool.poetry]
|
26
|
+
classifiers = [
|
27
|
+
...
|
28
|
+
"Programming Language :: Python :: 3.8",
|
29
|
+
...
|
30
|
+
]
|
31
|
+
|
32
|
+
[tool.poetry.dependencies]
|
33
|
+
python = "^3.8"
|
34
|
+
|
35
|
+
"""
|
36
|
+
from typing import TYPE_CHECKING, Any, List, Optional, Tuple, Union
|
37
|
+
|
38
|
+
import tomlkit
|
39
|
+
from tomlkit import TOMLDocument, dumps, load
|
40
|
+
|
41
|
+
from check_python_versions.parsers.poetry_version_spec import (
|
42
|
+
compute_poetry_spec,
|
43
|
+
detect_poetry_version_spec_style,
|
44
|
+
)
|
45
|
+
from check_python_versions.utils import file_name
|
46
|
+
|
47
|
+
from .base import Source
|
48
|
+
from ..parsers.classifiers import (
|
49
|
+
get_versions_from_classifiers,
|
50
|
+
update_classifiers,
|
51
|
+
)
|
52
|
+
from ..parsers.poetry_version_spec import parse_poetry_version_constraint
|
53
|
+
from ..parsers.requires_python import (
|
54
|
+
compute_python_requires,
|
55
|
+
detect_style,
|
56
|
+
parse_python_requires,
|
57
|
+
)
|
58
|
+
from ..utils import FileLines, FileOrFilename, open_file, warn
|
59
|
+
from ..versions import SortedVersionList
|
60
|
+
|
61
|
+
|
62
|
+
PYPROJECT_TOML = 'pyproject.toml'
|
63
|
+
|
64
|
+
|
65
|
+
if TYPE_CHECKING:
|
66
|
+
from tomlkit.container import Container
|
67
|
+
from tomlkit.items import Item
|
68
|
+
|
69
|
+
|
70
|
+
def traverse(document: TOMLDocument, path: str, default: Any = None) -> Any:
|
71
|
+
obj: Union[Container, Item] = document
|
72
|
+
for step in path.split('.'):
|
73
|
+
if not isinstance(obj, dict):
|
74
|
+
# complain
|
75
|
+
return default
|
76
|
+
if step not in obj:
|
77
|
+
return default
|
78
|
+
obj = obj[step]
|
79
|
+
return obj
|
80
|
+
|
81
|
+
|
82
|
+
def _get_pyproject_toml_classifiers(
|
83
|
+
filename: FileOrFilename = PYPROJECT_TOML,
|
84
|
+
) -> Tuple[TOMLDocument, str, Optional[List[str]]]:
|
85
|
+
"""Extract the list of PyPI classifiers from a pyproject.toml"""
|
86
|
+
|
87
|
+
with open_file(filename) as fp:
|
88
|
+
document = load(fp)
|
89
|
+
|
90
|
+
for path in 'project', 'tool.flit.metadata', 'tool.poetry':
|
91
|
+
classifiers = traverse(document, f"{path}.classifiers")
|
92
|
+
if classifiers is not None:
|
93
|
+
break
|
94
|
+
|
95
|
+
if classifiers is None:
|
96
|
+
return document, path, None
|
97
|
+
|
98
|
+
if not isinstance(classifiers, list):
|
99
|
+
warn(f'The value specified for {path}.classifiers in {fp.name}'
|
100
|
+
' is not an array')
|
101
|
+
# Returning None means that pyproject.toml doesn't have metadata.
|
102
|
+
# Returning [] is likely to cause a mismatch with other
|
103
|
+
# metadata sources, making the problem more noticeable.
|
104
|
+
return document, path, []
|
105
|
+
|
106
|
+
if not all(isinstance(s, str) for s in classifiers):
|
107
|
+
warn(f'The value specified for {path}.classifiers in {fp.name}'
|
108
|
+
' is not an array of strings')
|
109
|
+
# Returning None means that pyproject.toml doesn't have metadata.
|
110
|
+
# Returning [] is likely to cause a mismatch with other
|
111
|
+
# metadata sources, making the problem more noticeable.
|
112
|
+
return document, path, []
|
113
|
+
|
114
|
+
return document, path, classifiers
|
115
|
+
|
116
|
+
|
117
|
+
def get_supported_python_versions(
|
118
|
+
filename: FileOrFilename = PYPROJECT_TOML,
|
119
|
+
) -> Optional[SortedVersionList]:
|
120
|
+
"""Extract supported Python versions from classifiers in pyproject.toml."""
|
121
|
+
|
122
|
+
_d, _p, classifiers = _get_pyproject_toml_classifiers(filename)
|
123
|
+
|
124
|
+
if classifiers is None:
|
125
|
+
return None
|
126
|
+
|
127
|
+
return get_versions_from_classifiers(classifiers)
|
128
|
+
|
129
|
+
|
130
|
+
def _get_pyproject_toml_requires_python(
|
131
|
+
filename: FileOrFilename = PYPROJECT_TOML,
|
132
|
+
) -> Tuple[TOMLDocument, str, Optional[str]]:
|
133
|
+
|
134
|
+
with open_file(filename) as fp:
|
135
|
+
document = load(fp)
|
136
|
+
|
137
|
+
for path in (
|
138
|
+
"project.requires-python",
|
139
|
+
"tool.flit.metadata.requires-python",
|
140
|
+
"tool.poetry.dependencies.python",
|
141
|
+
):
|
142
|
+
python_requires = traverse(document, path)
|
143
|
+
if python_requires is not None:
|
144
|
+
break
|
145
|
+
|
146
|
+
if python_requires is None:
|
147
|
+
return document, path, None
|
148
|
+
|
149
|
+
if not isinstance(python_requires, str):
|
150
|
+
warn(f'The value specified for {path} in {fp.name} is not a string')
|
151
|
+
return document, path, None
|
152
|
+
|
153
|
+
return document, path, python_requires
|
154
|
+
|
155
|
+
|
156
|
+
def get_python_requires(
|
157
|
+
filename: FileOrFilename = PYPROJECT_TOML,
|
158
|
+
) -> Optional[SortedVersionList]:
|
159
|
+
"""Extract Python versions from require-python in pyproject.toml."""
|
160
|
+
|
161
|
+
_d, path, python_requires = _get_pyproject_toml_requires_python(filename)
|
162
|
+
|
163
|
+
if python_requires is None:
|
164
|
+
return None
|
165
|
+
|
166
|
+
if path == 'tool.poetry.dependencies.python':
|
167
|
+
return parse_poetry_version_constraint(
|
168
|
+
python_requires, path, filename=file_name(filename))
|
169
|
+
else:
|
170
|
+
return parse_python_requires(
|
171
|
+
python_requires, path, filename=file_name(filename))
|
172
|
+
|
173
|
+
|
174
|
+
def update_supported_python_versions(
|
175
|
+
filename: FileOrFilename,
|
176
|
+
new_versions: SortedVersionList,
|
177
|
+
) -> Optional[FileLines]:
|
178
|
+
"""Update classifiers in a pyproject.toml.
|
179
|
+
|
180
|
+
Does not touch the file but returns a list of lines with new file contents.
|
181
|
+
"""
|
182
|
+
|
183
|
+
document, path, classifiers = _get_pyproject_toml_classifiers(filename)
|
184
|
+
|
185
|
+
if classifiers is None:
|
186
|
+
return None
|
187
|
+
|
188
|
+
new_classifiers = update_classifiers(classifiers, new_versions)
|
189
|
+
|
190
|
+
table = traverse(document, path)
|
191
|
+
table['classifiers'] = a = tomlkit.array().multiline(True)
|
192
|
+
a.extend(new_classifiers)
|
193
|
+
|
194
|
+
return dumps(document).splitlines(True)
|
195
|
+
|
196
|
+
|
197
|
+
def update_python_requires(
|
198
|
+
filename: FileOrFilename,
|
199
|
+
new_versions: SortedVersionList,
|
200
|
+
) -> Optional[FileLines]:
|
201
|
+
"""Update python dependency in a pyproject.toml, if it's defined there.
|
202
|
+
|
203
|
+
Does not touch the file but returns a list of lines with new file contents.
|
204
|
+
"""
|
205
|
+
|
206
|
+
document, path, python_requires = _get_pyproject_toml_requires_python(
|
207
|
+
filename)
|
208
|
+
|
209
|
+
if python_requires is None:
|
210
|
+
return None
|
211
|
+
|
212
|
+
if path == 'tool.poetry.dependencies.python':
|
213
|
+
new_python_spec = compute_poetry_spec(
|
214
|
+
new_versions, **detect_poetry_version_spec_style(python_requires))
|
215
|
+
|
216
|
+
table = traverse(document, path.rpartition('.')[0])
|
217
|
+
table['python'] = new_python_spec
|
218
|
+
else:
|
219
|
+
new_python_requires = compute_python_requires(
|
220
|
+
new_versions, **detect_style(python_requires))
|
221
|
+
|
222
|
+
table = traverse(document, path.rpartition('.')[0])
|
223
|
+
table['requires-python'] = new_python_requires
|
224
|
+
|
225
|
+
return dumps(document).splitlines(True)
|
226
|
+
|
227
|
+
|
228
|
+
PyProject = Source(
|
229
|
+
title=PYPROJECT_TOML,
|
230
|
+
filename=PYPROJECT_TOML,
|
231
|
+
extract=get_supported_python_versions,
|
232
|
+
update=update_supported_python_versions,
|
233
|
+
check_pypy_consistency=True,
|
234
|
+
has_upper_bound=True,
|
235
|
+
)
|
236
|
+
|
237
|
+
PyProjectPythonRequires = Source(
|
238
|
+
title='- python_requires',
|
239
|
+
filename=PYPROJECT_TOML,
|
240
|
+
extract=get_python_requires,
|
241
|
+
update=update_python_requires,
|
242
|
+
check_pypy_consistency=False,
|
243
|
+
has_upper_bound=False, # TBH it might have one!
|
244
|
+
)
|