version-query 1.4.0__tar.gz → 1.5.2__tar.gz
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.
- {version-query-1.4.0 → version-query-1.5.2}/MANIFEST.in +0 -1
- {version-query-1.4.0/version_query.egg-info → version-query-1.5.2}/PKG-INFO +7 -6
- {version-query-1.4.0 → version-query-1.5.2}/README.rst +1 -1
- {version-query-1.4.0 → version-query-1.5.2}/pyproject.toml +12 -1
- {version-query-1.4.0 → version-query-1.5.2}/requirements.txt +0 -1
- version-query-1.5.2/requirements_test.txt +4 -0
- {version-query-1.4.0 → version-query-1.5.2}/setup.py +3 -2
- version-query-1.5.2/test/test_packaging.py +7 -0
- {version-query-1.4.0 → version-query-1.5.2}/test/test_query.py +3 -2
- {version-query-1.4.0 → version-query-1.5.2}/version_query/git_query.py +6 -4
- {version-query-1.4.0 → version-query-1.5.2/version_query.egg-info}/PKG-INFO +7 -6
- {version-query-1.4.0 → version-query-1.5.2}/version_query.egg-info/SOURCES.txt +7 -2
- {version-query-1.4.0 → version-query-1.5.2}/version_query.egg-info/requires.txt +0 -1
- {version-query-1.4.0 → version-query-1.5.2}/version_query.egg-info/top_level.txt +1 -0
- version-query-1.4.0/requirements_test.txt +0 -6
- version-query-1.4.0/setup_boilerplate.py +0 -393
- version-query-1.4.0/test/test_setup.py +0 -364
- {version-query-1.4.0 → version-query-1.5.2}/LICENSE +0 -0
- {version-query-1.4.0 → version-query-1.5.2}/NOTICE +0 -0
- {version-query-1.4.0 → version-query-1.5.2}/setup.cfg +0 -0
- {version-query-1.4.0 → version-query-1.5.2}/test/__init__.py +0 -0
- {version-query-1.4.0 → version-query-1.5.2}/test/examples.py +0 -0
- {version-query-1.4.0 → version-query-1.5.2}/test/test_git.py +0 -0
- {version-query-1.4.0 → version-query-1.5.2}/test/test_version.py +0 -0
- {version-query-1.4.0 → version-query-1.5.2}/test/test_with_git_repo.py +0 -0
- {version-query-1.4.0 → version-query-1.5.2}/version_query/__init__.py +0 -0
- {version-query-1.4.0 → version-query-1.5.2}/version_query/__main__.py +0 -0
- {version-query-1.4.0 → version-query-1.5.2}/version_query/_version.py +0 -0
- {version-query-1.4.0 → version-query-1.5.2}/version_query/main.py +0 -0
- {version-query-1.4.0 → version-query-1.5.2}/version_query/py.typed +0 -0
- {version-query-1.4.0 → version-query-1.5.2}/version_query/py_query.py +0 -0
- {version-query-1.4.0 → version-query-1.5.2}/version_query/query.py +0 -0
- {version-query-1.4.0 → version-query-1.5.2}/version_query/version.py +0 -0
- {version-query-1.4.0 → version-query-1.5.2}/version_query.egg-info/dependency_links.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: version-query
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.5.2
|
|
4
4
|
Summary: Zero-overhead package versioning for Python.
|
|
5
5
|
Home-page: https://github.com/mbdevpl/version-query
|
|
6
6
|
Download-URL:
|
|
@@ -18,6 +18,7 @@ Classifier: Natural Language :: English
|
|
|
18
18
|
Classifier: Operating System :: MacOS :: MacOS X
|
|
19
19
|
Classifier: Operating System :: Microsoft :: Windows
|
|
20
20
|
Classifier: Operating System :: POSIX :: Linux
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
21
22
|
Classifier: Programming Language :: Python :: 3.9
|
|
22
23
|
Classifier: Programming Language :: Python :: 3.10
|
|
23
24
|
Classifier: Programming Language :: Python :: 3.11
|
|
@@ -27,7 +28,7 @@ Classifier: Topic :: Software Development :: Version Control :: Git
|
|
|
27
28
|
Classifier: Topic :: System :: Software Distribution
|
|
28
29
|
Classifier: Topic :: Utilities
|
|
29
30
|
Classifier: Typing :: Typed
|
|
30
|
-
Requires-Python: >=3.
|
|
31
|
+
Requires-Python: >=3.8
|
|
31
32
|
Description-Content-Type: text/x-rst; charset=UTF-8
|
|
32
33
|
License-File: LICENSE
|
|
33
34
|
License-File: NOTICE
|
|
@@ -62,7 +63,7 @@ Zero-overhead package versioning for Python.
|
|
|
62
63
|
:alt: grade from Codacy
|
|
63
64
|
|
|
64
65
|
.. image:: https://img.shields.io/github/license/mbdevpl/version-query.svg
|
|
65
|
-
:target: https://github.com/mbdevpl/version-query/blob/v1.
|
|
66
|
+
:target: https://github.com/mbdevpl/version-query/blob/v1.5.2/NOTICE
|
|
66
67
|
:alt: license
|
|
67
68
|
|
|
68
69
|
Package versioning toolkit for Python which relies on git tags, git history traversal
|
|
@@ -470,10 +471,10 @@ using version-query without any issues.
|
|
|
470
471
|
Requirements
|
|
471
472
|
============
|
|
472
473
|
|
|
473
|
-
Python version 3.
|
|
474
|
+
Python version 3.8 or later.
|
|
474
475
|
|
|
475
|
-
Python libraries as specified in `requirements.txt <https://github.com/mbdevpl/version-query/blob/v1.
|
|
476
|
+
Python libraries as specified in `requirements.txt <https://github.com/mbdevpl/version-query/blob/v1.5.2/requirements.txt>`_.
|
|
476
477
|
|
|
477
|
-
Building and running tests additionally requires packages listed in `requirements_test.txt <https://github.com/mbdevpl/version-query/blob/v1.
|
|
478
|
+
Building and running tests additionally requires packages listed in `requirements_test.txt <https://github.com/mbdevpl/version-query/blob/v1.5.2/requirements_test.txt>`_.
|
|
478
479
|
|
|
479
480
|
Tested on Linux, OS X and Windows.
|
|
@@ -1,10 +1,21 @@
|
|
|
1
1
|
[build-system]
|
|
2
|
-
requires = [
|
|
2
|
+
requires = [
|
|
3
|
+
'boilerplates[setup] ~= 0.1',
|
|
4
|
+
'GitPython ~= 3.1',
|
|
5
|
+
'packaging >= 23.0',
|
|
6
|
+
'semver ~= 2.13'
|
|
7
|
+
]
|
|
3
8
|
|
|
4
9
|
[tool.flake8]
|
|
5
10
|
max-line-length = 100
|
|
6
11
|
max-doc-length = 100
|
|
7
12
|
|
|
13
|
+
[[tool.mypy.overrides]]
|
|
14
|
+
module = [
|
|
15
|
+
'semver'
|
|
16
|
+
]
|
|
17
|
+
ignore_missing_imports = true
|
|
18
|
+
|
|
8
19
|
[tool.pydocstyle]
|
|
9
20
|
ignore = [
|
|
10
21
|
'D102', 'D103', 'D105', 'D107',
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"""Setup script for version_query package."""
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
import boilerplates.setup
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
class Package(
|
|
6
|
+
class Package(boilerplates.setup.Package):
|
|
7
7
|
"""Package metadata."""
|
|
8
8
|
|
|
9
9
|
name = 'version-query'
|
|
@@ -20,6 +20,7 @@ class Package(setup_boilerplate.Package):
|
|
|
20
20
|
'Operating System :: MacOS :: MacOS X',
|
|
21
21
|
'Operating System :: Microsoft :: Windows',
|
|
22
22
|
'Operating System :: POSIX :: Linux',
|
|
23
|
+
'Programming Language :: Python :: 3.8',
|
|
23
24
|
'Programming Language :: Python :: 3.9',
|
|
24
25
|
'Programming Language :: Python :: 3.10',
|
|
25
26
|
'Programming Language :: Python :: 3.11',
|
|
@@ -10,6 +10,8 @@ import sys
|
|
|
10
10
|
import tempfile
|
|
11
11
|
import unittest
|
|
12
12
|
|
|
13
|
+
from boilerplates.packaging_tests import run_module
|
|
14
|
+
|
|
13
15
|
from version_query.version import VersionComponent, Version
|
|
14
16
|
from version_query.git_query import query_git_repo, predict_git_repo
|
|
15
17
|
from version_query.py_query import query_metadata_json, query_pkg_info, query_package_folder
|
|
@@ -18,7 +20,6 @@ from version_query.query import \
|
|
|
18
20
|
from .examples import \
|
|
19
21
|
PY_LIB_DIR, GIT_REPO_EXAMPLES, METADATA_JSON_EXAMPLE_PATHS, PKG_INFO_EXAMPLE_PATHS, \
|
|
20
22
|
PACKAGE_FOLDER_EXAMPLES
|
|
21
|
-
from .test_setup import run_module
|
|
22
23
|
|
|
23
24
|
_LOG = logging.getLogger(__name__)
|
|
24
25
|
|
|
@@ -61,7 +62,7 @@ class Tests(unittest.TestCase):
|
|
|
61
62
|
|
|
62
63
|
def _query_test_case(self, paths, query_function):
|
|
63
64
|
for path in paths:
|
|
64
|
-
if any(_ in path.parts for _ in IGNORED_FOLDER_NAMES):
|
|
65
|
+
if any(_ in path.parts for _ in IGNORED_FOLDER_NAMES) or not path.exists():
|
|
65
66
|
continue
|
|
66
67
|
with self.subTest(path=path, query_function=query_function):
|
|
67
68
|
_LOG.debug('testing %s() on %s', query_function.__name__, path)
|
|
@@ -46,7 +46,8 @@ def _latest_git_version_tag_on_branches(
|
|
|
46
46
|
t.Optional[git.objects.Commit], t.Optional[git.TagReference], t.Optional[Version],
|
|
47
47
|
int]]:
|
|
48
48
|
_LOG.log(logging.NOTSET, 'entering %i branches...', len(commit.parents))
|
|
49
|
-
results
|
|
49
|
+
results: t.List[t.Tuple[
|
|
50
|
+
t.Optional[git.objects.Commit], t.Optional[git.TagReference], Version, int]] = []
|
|
50
51
|
main_commit_distance = None
|
|
51
52
|
for parent in commit.parents:
|
|
52
53
|
try:
|
|
@@ -57,7 +58,7 @@ def _latest_git_version_tag_on_branches(
|
|
|
57
58
|
if main_commit_distance is None:
|
|
58
59
|
main_commit_distance = result[3]
|
|
59
60
|
if result[2] is not None:
|
|
60
|
-
results.append(result)
|
|
61
|
+
results.append(result) # type: ignore
|
|
61
62
|
if not results:
|
|
62
63
|
if main_commit_distance is None:
|
|
63
64
|
raise ValueError(f'reached max commit distance {MAX_COMMIT_DISTANCE}'
|
|
@@ -73,8 +74,9 @@ MAX_COMMIT_DISTANCE = 999
|
|
|
73
74
|
|
|
74
75
|
|
|
75
76
|
def _latest_git_version_tag(
|
|
76
|
-
repo: git.Repo, assume_if_none: bool = False,
|
|
77
|
-
|
|
77
|
+
repo: git.Repo, assume_if_none: bool = False,
|
|
78
|
+
base_commit: t.Optional[git.objects.Commit] = None, commit_distance: int = 0,
|
|
79
|
+
skip_commits: t.Optional[t.Set[git.objects.Commit]] = None) -> t.Tuple[
|
|
78
80
|
t.Optional[git.objects.Commit], t.Optional[git.TagReference], t.Optional[Version], int]:
|
|
79
81
|
"""Return (commit, tag at that commit if any, latest version, distance from the version)."""
|
|
80
82
|
version_tags = _git_version_tags(repo)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: version-query
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.5.2
|
|
4
4
|
Summary: Zero-overhead package versioning for Python.
|
|
5
5
|
Home-page: https://github.com/mbdevpl/version-query
|
|
6
6
|
Download-URL:
|
|
@@ -18,6 +18,7 @@ Classifier: Natural Language :: English
|
|
|
18
18
|
Classifier: Operating System :: MacOS :: MacOS X
|
|
19
19
|
Classifier: Operating System :: Microsoft :: Windows
|
|
20
20
|
Classifier: Operating System :: POSIX :: Linux
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
21
22
|
Classifier: Programming Language :: Python :: 3.9
|
|
22
23
|
Classifier: Programming Language :: Python :: 3.10
|
|
23
24
|
Classifier: Programming Language :: Python :: 3.11
|
|
@@ -27,7 +28,7 @@ Classifier: Topic :: Software Development :: Version Control :: Git
|
|
|
27
28
|
Classifier: Topic :: System :: Software Distribution
|
|
28
29
|
Classifier: Topic :: Utilities
|
|
29
30
|
Classifier: Typing :: Typed
|
|
30
|
-
Requires-Python: >=3.
|
|
31
|
+
Requires-Python: >=3.8
|
|
31
32
|
Description-Content-Type: text/x-rst; charset=UTF-8
|
|
32
33
|
License-File: LICENSE
|
|
33
34
|
License-File: NOTICE
|
|
@@ -62,7 +63,7 @@ Zero-overhead package versioning for Python.
|
|
|
62
63
|
:alt: grade from Codacy
|
|
63
64
|
|
|
64
65
|
.. image:: https://img.shields.io/github/license/mbdevpl/version-query.svg
|
|
65
|
-
:target: https://github.com/mbdevpl/version-query/blob/v1.
|
|
66
|
+
:target: https://github.com/mbdevpl/version-query/blob/v1.5.2/NOTICE
|
|
66
67
|
:alt: license
|
|
67
68
|
|
|
68
69
|
Package versioning toolkit for Python which relies on git tags, git history traversal
|
|
@@ -470,10 +471,10 @@ using version-query without any issues.
|
|
|
470
471
|
Requirements
|
|
471
472
|
============
|
|
472
473
|
|
|
473
|
-
Python version 3.
|
|
474
|
+
Python version 3.8 or later.
|
|
474
475
|
|
|
475
|
-
Python libraries as specified in `requirements.txt <https://github.com/mbdevpl/version-query/blob/v1.
|
|
476
|
+
Python libraries as specified in `requirements.txt <https://github.com/mbdevpl/version-query/blob/v1.5.2/requirements.txt>`_.
|
|
476
477
|
|
|
477
|
-
Building and running tests additionally requires packages listed in `requirements_test.txt <https://github.com/mbdevpl/version-query/blob/v1.
|
|
478
|
+
Building and running tests additionally requires packages listed in `requirements_test.txt <https://github.com/mbdevpl/version-query/blob/v1.5.2/requirements_test.txt>`_.
|
|
478
479
|
|
|
479
480
|
Tested on Linux, OS X and Windows.
|
|
@@ -6,8 +6,13 @@ pyproject.toml
|
|
|
6
6
|
requirements.txt
|
|
7
7
|
requirements_test.txt
|
|
8
8
|
setup.py
|
|
9
|
-
setup_boilerplate.py
|
|
10
9
|
./test/__init__.py
|
|
10
|
+
./test/examples.py
|
|
11
|
+
./test/test_git.py
|
|
12
|
+
./test/test_packaging.py
|
|
13
|
+
./test/test_query.py
|
|
14
|
+
./test/test_version.py
|
|
15
|
+
./test/test_with_git_repo.py
|
|
11
16
|
./version_query/__init__.py
|
|
12
17
|
./version_query/__main__.py
|
|
13
18
|
./version_query/_version.py
|
|
@@ -19,8 +24,8 @@ setup_boilerplate.py
|
|
|
19
24
|
./version_query/version.py
|
|
20
25
|
test/examples.py
|
|
21
26
|
test/test_git.py
|
|
27
|
+
test/test_packaging.py
|
|
22
28
|
test/test_query.py
|
|
23
|
-
test/test_setup.py
|
|
24
29
|
test/test_version.py
|
|
25
30
|
test/test_with_git_repo.py
|
|
26
31
|
version_query.egg-info/PKG-INFO
|
|
@@ -1,393 +0,0 @@
|
|
|
1
|
-
"""Below code is generic boilerplate and normally should not be changed.
|
|
2
|
-
|
|
3
|
-
See the implementation of setup_boilerplate.Package for available options.
|
|
4
|
-
Also, some fields have default values. See DEFAULT_* constants below for those values.
|
|
5
|
-
|
|
6
|
-
To avoid setup script boilerplate, create "setup.py" file with the minimal contents as given
|
|
7
|
-
below and modify it according to the specifics of your package.
|
|
8
|
-
Note: string limiters need to be adjusted.
|
|
9
|
-
|
|
10
|
-
"" "Setup script." ""
|
|
11
|
-
|
|
12
|
-
import setup_boilerplate
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class Package(setup_boilerplate.Package):
|
|
16
|
-
"" "Package metadata." ""
|
|
17
|
-
|
|
18
|
-
name = ''
|
|
19
|
-
description = ''
|
|
20
|
-
url = 'https://github.com/mbdevpl/...'
|
|
21
|
-
classifiers = [
|
|
22
|
-
'Development Status :: 1 - Planning',
|
|
23
|
-
'Programming Language :: Python :: 3.7',
|
|
24
|
-
'Programming Language :: Python :: 3.8',
|
|
25
|
-
'Programming Language :: Python :: 3.9',
|
|
26
|
-
'Programming Language :: Python :: 3.10',
|
|
27
|
-
'Programming Language :: Python :: 3 :: Only']
|
|
28
|
-
keywords = []
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
if __name__ == '__main__':
|
|
32
|
-
Package.setup()
|
|
33
|
-
"""
|
|
34
|
-
|
|
35
|
-
import logging
|
|
36
|
-
import pathlib
|
|
37
|
-
import runpy
|
|
38
|
-
import sys
|
|
39
|
-
import typing as t
|
|
40
|
-
|
|
41
|
-
import docutils.frontend
|
|
42
|
-
import docutils.nodes
|
|
43
|
-
import docutils.parsers.rst
|
|
44
|
-
import docutils.utils
|
|
45
|
-
import setuptools
|
|
46
|
-
|
|
47
|
-
__version__ = '2023.03.09'
|
|
48
|
-
|
|
49
|
-
_LOG = logging.getLogger(__name__)
|
|
50
|
-
|
|
51
|
-
HERE = pathlib.Path(__file__).resolve().parent
|
|
52
|
-
|
|
53
|
-
DEFAULT_URL = 'https://github.com/mbdevpl'
|
|
54
|
-
DEFAULT_AUTHOR = 'Mateusz Bysiek'
|
|
55
|
-
DEFAULT_AUTHOR_EMAIL = 'mateusz.bysiek@gmail.com'
|
|
56
|
-
DEFAULT_LICENSE_STR = 'Apache License 2.0'
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
def find_version(
|
|
60
|
-
package_name: str, version_module_name: str = '_version',
|
|
61
|
-
version_variable_name: str = 'VERSION') -> str:
|
|
62
|
-
"""Simulate behaviour of "from package_name._version import VERSION", and return VERSION.
|
|
63
|
-
|
|
64
|
-
To avoid importing whole package only to read the version, just module containing the version
|
|
65
|
-
is imported. Therefore relative imports in that module will break the setup.
|
|
66
|
-
|
|
67
|
-
:param package_name: name of the package
|
|
68
|
-
:param version_module_name: name of the module containing the version
|
|
69
|
-
:param version_variable_name: name of the variable containing the version
|
|
70
|
-
:return: version string
|
|
71
|
-
"""
|
|
72
|
-
version_module_path = f'{package_name.replace("-", "_")}/{version_module_name}.py'
|
|
73
|
-
version_module_vars = runpy.run_path(version_module_path)
|
|
74
|
-
return version_module_vars[version_variable_name]
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
def find_packages(root_directory: str = '.') -> t.List[str]:
|
|
78
|
-
"""Find packages to pack.
|
|
79
|
-
|
|
80
|
-
:param root_directory: directory to start searching from
|
|
81
|
-
:return: list of packages
|
|
82
|
-
"""
|
|
83
|
-
exclude = ['test', 'test.*'] if ('bdist_wheel' in sys.argv or 'bdist' in sys.argv) else []
|
|
84
|
-
packages_list = setuptools.find_packages(root_directory, exclude=exclude)
|
|
85
|
-
return packages_list
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
def parse_requirements(
|
|
89
|
-
requirements_path: str = 'requirements.txt') -> t.List[str]:
|
|
90
|
-
"""Read contents of requirements.txt file and return data from its relevant lines.
|
|
91
|
-
|
|
92
|
-
Only non-empty and non-comment lines are relevant.
|
|
93
|
-
|
|
94
|
-
:param requirements_path: path to the requirements file if custom one is used
|
|
95
|
-
:return: list of requirements
|
|
96
|
-
"""
|
|
97
|
-
requirements = []
|
|
98
|
-
with HERE.joinpath(requirements_path).open(encoding='utf-8') as requirements_file:
|
|
99
|
-
for requirement in [line.strip() for line in requirements_file.read().splitlines()]:
|
|
100
|
-
if not requirement or requirement.startswith('#'):
|
|
101
|
-
continue
|
|
102
|
-
requirements.append(requirement)
|
|
103
|
-
return requirements
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
def partition_version_classifiers(
|
|
107
|
-
classifiers: t.Sequence[str], version_prefix: str = 'Programming Language :: Python :: ',
|
|
108
|
-
only_suffix: str = ' :: Only'
|
|
109
|
-
) -> t.Tuple[t.List[t.Tuple[int, ...]], t.List[t.Tuple[int, ...]]]:
|
|
110
|
-
"""Find version number classifiers in given list and partition them into 2 groups.
|
|
111
|
-
|
|
112
|
-
The 2 groups being:
|
|
113
|
-
1. minimum versions, any version at least as high this is compatible
|
|
114
|
-
2. only versions, this specific version is compatible, but nothing is said about other versions
|
|
115
|
-
|
|
116
|
-
:param classifiers: sequence of trove classifiers to be analysed
|
|
117
|
-
:param version_prefix: prefix that identifies a version classifier
|
|
118
|
-
:param only_suffix: prefix that identifies an "only version" classifier
|
|
119
|
-
:return: parsed and partitioned version tuples
|
|
120
|
-
"""
|
|
121
|
-
versions_min: t.List[t.Tuple[int, ...]] = []
|
|
122
|
-
versions_only: t.List[t.Tuple[int, ...]] = []
|
|
123
|
-
for classifier in classifiers:
|
|
124
|
-
version = classifier.replace(version_prefix, '')
|
|
125
|
-
versions = versions_min
|
|
126
|
-
if version.endswith(only_suffix):
|
|
127
|
-
version = version.replace(only_suffix, '')
|
|
128
|
-
versions = versions_only
|
|
129
|
-
try:
|
|
130
|
-
versions.append(tuple(int(_) for _ in version.split('.')))
|
|
131
|
-
except ValueError:
|
|
132
|
-
pass
|
|
133
|
-
return versions_min, versions_only
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
def find_required_python_version(
|
|
137
|
-
classifiers: t.Sequence[str], version_prefix: str = 'Programming Language :: Python :: ',
|
|
138
|
-
only_suffix: str = ' :: Only') -> t.Optional[str]:
|
|
139
|
-
"""Determine the minimum required Python version.
|
|
140
|
-
|
|
141
|
-
:param classifiers: sequence of trove classifiers to be analysed
|
|
142
|
-
:param version_prefix: prefix that identifies a version classifier
|
|
143
|
-
:param only_suffix: prefix that identifies an "only version" classifier
|
|
144
|
-
:return: minimum required Python version string, if any
|
|
145
|
-
"""
|
|
146
|
-
versions_min, versions_only = partition_version_classifiers(
|
|
147
|
-
classifiers, version_prefix, only_suffix)
|
|
148
|
-
if len(versions_only) > 1:
|
|
149
|
-
raise ValueError(f'more than one "{only_suffix}" version encountered in {versions_only}')
|
|
150
|
-
only_version = None
|
|
151
|
-
if len(versions_only) == 1:
|
|
152
|
-
only_version = versions_only[0]
|
|
153
|
-
for version in versions_min:
|
|
154
|
-
if version[:len(only_version)] != only_version:
|
|
155
|
-
raise ValueError(f'the "{only_suffix}" version {only_version}'
|
|
156
|
-
f' is inconsistent with version {version}')
|
|
157
|
-
min_supported_version = None
|
|
158
|
-
for version in versions_min:
|
|
159
|
-
if min_supported_version is None or \
|
|
160
|
-
(len(version) >= len(min_supported_version) and version < min_supported_version):
|
|
161
|
-
min_supported_version = version
|
|
162
|
-
if min_supported_version is None:
|
|
163
|
-
if only_version is not None:
|
|
164
|
-
return '.'.join([str(_) for _ in only_version])
|
|
165
|
-
else:
|
|
166
|
-
return '>=' + '.'.join([str(_) for _ in min_supported_version])
|
|
167
|
-
return None
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
def parse_rst(text: str) -> docutils.nodes.document:
|
|
171
|
-
"""Parse text assuming it's an RST markup."""
|
|
172
|
-
parser = docutils.parsers.rst.Parser()
|
|
173
|
-
components = (docutils.parsers.rst.Parser,)
|
|
174
|
-
settings = docutils.frontend.OptionParser(components=components).get_default_values()
|
|
175
|
-
document = docutils.utils.new_document('<rst-doc>', settings=settings)
|
|
176
|
-
parser.parse(text, document)
|
|
177
|
-
return document
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
class RelativeRefFinder(docutils.nodes.NodeVisitor):
|
|
181
|
-
"""Find all relative references in a given docutils document that point to existing files."""
|
|
182
|
-
|
|
183
|
-
def __init__(self, root_dir: pathlib.Path, *args, **kwargs):
|
|
184
|
-
"""Initialize the RelativeRefFinder object."""
|
|
185
|
-
super().__init__(*args, **kwargs)
|
|
186
|
-
self.root_dir = root_dir
|
|
187
|
-
self.references: t.List[docutils.nodes.reference] = []
|
|
188
|
-
|
|
189
|
-
def visit_reference(self, node: docutils.nodes.reference) -> None:
|
|
190
|
-
"""Call for "reference" nodes."""
|
|
191
|
-
assert isinstance(node, docutils.nodes.TextElement), type(node)
|
|
192
|
-
_LOG.debug('RelativeRefFinder: examining reference %s', node)
|
|
193
|
-
if len(node.children) != 1 or 'refuri' not in node.attributes \
|
|
194
|
-
or node.attributes['refuri'].startswith(('http://', 'https://')):
|
|
195
|
-
return
|
|
196
|
-
# _LOG.debug(' RelativeRefFinder: reference passed initial check')
|
|
197
|
-
path = pathlib.Path(node.attributes['refuri'])
|
|
198
|
-
if path.is_absolute():
|
|
199
|
-
return
|
|
200
|
-
if '#' in path.name:
|
|
201
|
-
# reference points to a section in a file
|
|
202
|
-
# we ignore the section part when checking if file exists
|
|
203
|
-
path = path.with_name(path.name[:path.name.index('#')])
|
|
204
|
-
try:
|
|
205
|
-
self.root_dir.joinpath(path).resolve(strict=True)
|
|
206
|
-
except FileNotFoundError:
|
|
207
|
-
return
|
|
208
|
-
_LOG.debug('RelativeRefFinder: reference points to existing file')
|
|
209
|
-
self.references.append(node)
|
|
210
|
-
|
|
211
|
-
def unknown_visit(self, node: docutils.nodes.Node) -> None:
|
|
212
|
-
"""Call for unknown node types."""
|
|
213
|
-
return
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
def resolve_relative_rst_links(text: str, base_link: str) -> str:
|
|
217
|
-
"""Resolve all relative links in a given string representing an RST document.
|
|
218
|
-
|
|
219
|
-
Links are resolved only if they point to files existing in the project's working directory.
|
|
220
|
-
|
|
221
|
-
All links of form `link`_ become `link <absolute_link>`_.
|
|
222
|
-
|
|
223
|
-
And all link of the form :target: link become :target: absolute_link.
|
|
224
|
-
|
|
225
|
-
Where absolute_link is made by concatenating base_link to link with no separator added
|
|
226
|
-
in between. So in most cases base_link should end with a slash.
|
|
227
|
-
"""
|
|
228
|
-
document = parse_rst(text)
|
|
229
|
-
finder = RelativeRefFinder(HERE, document)
|
|
230
|
-
document.walk(finder)
|
|
231
|
-
for target in finder.references:
|
|
232
|
-
_LOG.info('resolve_relative_rst_links: resolving reference %s', target)
|
|
233
|
-
assert isinstance(target, docutils.nodes.TextElement), type(target)
|
|
234
|
-
refuri = target.attributes['refuri']
|
|
235
|
-
if 'name' in target.attributes:
|
|
236
|
-
name = target.attributes['name']
|
|
237
|
-
if name == refuri:
|
|
238
|
-
old_link = f'`<{refuri}>`_'
|
|
239
|
-
else:
|
|
240
|
-
old_link = f'`{name} <{refuri}>`_'
|
|
241
|
-
new_link = f'`{name} <{base_link}{refuri}>`_'
|
|
242
|
-
else:
|
|
243
|
-
old_link = f' :target: {refuri}'
|
|
244
|
-
new_link = f' :target: {base_link}{refuri}'
|
|
245
|
-
text = text.replace(old_link, new_link)
|
|
246
|
-
_LOG.info('resolve_relative_rst_links: replaced "%s" with "%s"', old_link, new_link)
|
|
247
|
-
return text
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
class Package:
|
|
251
|
-
"""Default metadata and behaviour for a Python package setup script."""
|
|
252
|
-
|
|
253
|
-
root_directory: str = '.'
|
|
254
|
-
"""Root directory of the source code of the package, relative to the setup.py file location."""
|
|
255
|
-
|
|
256
|
-
name: str
|
|
257
|
-
"""Package name."""
|
|
258
|
-
|
|
259
|
-
version: str
|
|
260
|
-
"""Package version as string.
|
|
261
|
-
|
|
262
|
-
If None, it will be obtained from "package_name._version.VERSION" variable.
|
|
263
|
-
"""
|
|
264
|
-
|
|
265
|
-
description: str
|
|
266
|
-
"""One line of text that shortly describes the package."""
|
|
267
|
-
|
|
268
|
-
long_description: str
|
|
269
|
-
"""Full description of the package, can be arbitrarily long.
|
|
270
|
-
|
|
271
|
-
If not set, it will be generated from readme.
|
|
272
|
-
"""
|
|
273
|
-
|
|
274
|
-
long_description_content_type: str
|
|
275
|
-
"""Content type of the long description.
|
|
276
|
-
|
|
277
|
-
Usually one of 'text/x-rst', 'text/markdown' or 'text/plain'.
|
|
278
|
-
|
|
279
|
-
If not set, it will be set according to readme file extension.
|
|
280
|
-
For this field to be automatically set, also long_description field cannot be set.
|
|
281
|
-
"""
|
|
282
|
-
|
|
283
|
-
url: str = DEFAULT_URL
|
|
284
|
-
download_url: str = ''
|
|
285
|
-
author: str = DEFAULT_AUTHOR
|
|
286
|
-
author_email: str = DEFAULT_AUTHOR_EMAIL
|
|
287
|
-
maintainer: str
|
|
288
|
-
maintainer_email: str
|
|
289
|
-
license_str: str = DEFAULT_LICENSE_STR
|
|
290
|
-
|
|
291
|
-
classifiers: t.List[str] = []
|
|
292
|
-
"""List of trove classifiers for the package.
|
|
293
|
-
|
|
294
|
-
List of valid classifiers: https://pypi.org/pypi?:action=list_classifiers
|
|
295
|
-
"""
|
|
296
|
-
|
|
297
|
-
keywords: t.List[str] = []
|
|
298
|
-
|
|
299
|
-
packages: t.List[str]
|
|
300
|
-
"""List of Python packages to be included in the distributed file.
|
|
301
|
-
|
|
302
|
-
Usually it's just one package per distributed file, but any number is supported.
|
|
303
|
-
|
|
304
|
-
If not set, determined with help of setuptools.
|
|
305
|
-
"""
|
|
306
|
-
|
|
307
|
-
package_data: t.Dict[str, t.List[str]] = {}
|
|
308
|
-
exclude_package_data: t.Dict[str, t.List[str]] = {}
|
|
309
|
-
|
|
310
|
-
install_requires: t.Optional[t.List[str]] = None
|
|
311
|
-
"""If None, determined using requirements.txt."""
|
|
312
|
-
|
|
313
|
-
extras_require: t.Mapping[str, t.List[str]] = {}
|
|
314
|
-
"""A dictionary containing entries of type 'some_feature': ['requirement1', 'requirement2']."""
|
|
315
|
-
|
|
316
|
-
python_requires: t.Optional[str] = None
|
|
317
|
-
"""If None, determined from provided classifiers."""
|
|
318
|
-
|
|
319
|
-
entry_points: t.Mapping[str, t.List[str]] = {}
|
|
320
|
-
"""A dictionary used to enable automatic creation of console scripts, gui scripts and plugins.
|
|
321
|
-
|
|
322
|
-
Example entry:
|
|
323
|
-
'console_scripts': ['script_name = package.subpackage:function']
|
|
324
|
-
"""
|
|
325
|
-
|
|
326
|
-
test_suite: str = 'test'
|
|
327
|
-
|
|
328
|
-
@classmethod
|
|
329
|
-
def try_fields(cls, *names) -> t.Optional[t.Any]:
|
|
330
|
-
"""Return first existing of given class field names."""
|
|
331
|
-
for name in names:
|
|
332
|
-
if hasattr(cls, name):
|
|
333
|
-
return getattr(cls, name)
|
|
334
|
-
raise AttributeError((cls, names))
|
|
335
|
-
|
|
336
|
-
@classmethod
|
|
337
|
-
def parse_readme(cls, readme_filename: str = 'README.rst',
|
|
338
|
-
encoding: str = 'utf-8') -> t.Tuple[str, str]:
|
|
339
|
-
"""Parse readme and resolve relative links in it if it is feasible.
|
|
340
|
-
|
|
341
|
-
Links are resolved if readme is in rst format and the package is hosted on GitHub.
|
|
342
|
-
"""
|
|
343
|
-
readme_path = HERE.joinpath(readme_filename)
|
|
344
|
-
with readme_path.open(encoding=encoding) as readme_file:
|
|
345
|
-
long_description: str = readme_file.read()
|
|
346
|
-
|
|
347
|
-
if readme_path.suffix.lower() == '.rst' and cls.url.startswith('https://github.com/'):
|
|
348
|
-
base_url = f'{cls.url}/blob/v{cls.version}/'
|
|
349
|
-
long_description = resolve_relative_rst_links(long_description, base_url)
|
|
350
|
-
|
|
351
|
-
long_description_content_type = {'.rst': 'text/x-rst', '.md': 'text/markdown'}.get(
|
|
352
|
-
readme_path.suffix.lower(), 'text/plain')
|
|
353
|
-
long_description_content_type += '; charset=UTF-8'
|
|
354
|
-
|
|
355
|
-
return long_description, long_description_content_type
|
|
356
|
-
|
|
357
|
-
@classmethod
|
|
358
|
-
def prepare(cls) -> None:
|
|
359
|
-
"""Fill in possibly missing package metadata."""
|
|
360
|
-
if not hasattr(cls, 'version'):
|
|
361
|
-
cls.version = find_version(cls.name)
|
|
362
|
-
if not hasattr(cls, 'long_description'):
|
|
363
|
-
cls.long_description, cls.long_description_content_type = cls.parse_readme()
|
|
364
|
-
if not hasattr(cls, 'maintainer'):
|
|
365
|
-
cls.maintainer = cls.author
|
|
366
|
-
if not hasattr(cls, 'maintainer_email'):
|
|
367
|
-
cls.maintainer_email = cls.author_email
|
|
368
|
-
if not hasattr(cls, 'packages'):
|
|
369
|
-
cls.packages = find_packages(cls.root_directory)
|
|
370
|
-
if cls.install_requires is None:
|
|
371
|
-
cls.install_requires = parse_requirements()
|
|
372
|
-
if cls.python_requires is None:
|
|
373
|
-
cls.python_requires = find_required_python_version(cls.classifiers)
|
|
374
|
-
|
|
375
|
-
@classmethod
|
|
376
|
-
def setup(cls) -> None:
|
|
377
|
-
"""Call setuptools.setup with correct arguments."""
|
|
378
|
-
cls.prepare()
|
|
379
|
-
setuptools.setup(
|
|
380
|
-
name=cls.name, version=cls.version, description=cls.description,
|
|
381
|
-
long_description=cls.long_description,
|
|
382
|
-
long_description_content_type=cls.long_description_content_type,
|
|
383
|
-
url=cls.url, download_url=cls.download_url,
|
|
384
|
-
author=cls.author, author_email=cls.author_email,
|
|
385
|
-
maintainer=cls.maintainer,
|
|
386
|
-
maintainer_email=cls.maintainer_email,
|
|
387
|
-
license=cls.license_str, classifiers=cls.classifiers, keywords=cls.keywords,
|
|
388
|
-
packages=cls.packages, package_dir={'': cls.root_directory},
|
|
389
|
-
include_package_data=True,
|
|
390
|
-
package_data=cls.package_data, exclude_package_data=cls.exclude_package_data,
|
|
391
|
-
install_requires=cls.install_requires, extras_require=cls.extras_require,
|
|
392
|
-
python_requires=cls.python_requires,
|
|
393
|
-
entry_points=cls.entry_points, test_suite=cls.test_suite)
|
|
@@ -1,364 +0,0 @@
|
|
|
1
|
-
"""Tests for setup scripts."""
|
|
2
|
-
|
|
3
|
-
import importlib
|
|
4
|
-
import itertools
|
|
5
|
-
import logging
|
|
6
|
-
import os
|
|
7
|
-
import pathlib
|
|
8
|
-
import runpy
|
|
9
|
-
import subprocess
|
|
10
|
-
import sys
|
|
11
|
-
import tempfile
|
|
12
|
-
import types
|
|
13
|
-
import typing as t
|
|
14
|
-
import unittest
|
|
15
|
-
|
|
16
|
-
__version__ = '2023.03.09'
|
|
17
|
-
|
|
18
|
-
_LOG = logging.getLogger(__name__)
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def expand_args_by_globbing_items(*args: str) -> t.Tuple[str, ...]:
|
|
22
|
-
"""Expand a list of glob expressions."""
|
|
23
|
-
cwd = pathlib.Path.cwd()
|
|
24
|
-
expanded_args = []
|
|
25
|
-
for arg in args:
|
|
26
|
-
if '*' not in arg:
|
|
27
|
-
expanded_args.append(arg)
|
|
28
|
-
continue
|
|
29
|
-
expanded_arg = [str(_.relative_to(cwd)) for _ in cwd.glob(arg)]
|
|
30
|
-
assert expanded_arg, arg
|
|
31
|
-
_LOG.debug('expanded arg "%s" to %s', arg, expanded_arg)
|
|
32
|
-
expanded_args += expanded_arg
|
|
33
|
-
_LOG.debug('expanded args to %s', expanded_args)
|
|
34
|
-
return tuple(expanded_args)
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def run_program(*args, glob: bool = False) -> None:
|
|
38
|
-
"""Run subprocess with given args. Use path globbing for each arg that contains an asterisk."""
|
|
39
|
-
if glob:
|
|
40
|
-
args = expand_args_by_globbing_items(*args)
|
|
41
|
-
try:
|
|
42
|
-
subprocess.run(args, check=True)
|
|
43
|
-
except subprocess.CalledProcessError as err:
|
|
44
|
-
raise AssertionError(f'execution of {args} failed') from err
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
def run_pip(*args, **kwargs) -> None:
|
|
48
|
-
run_program(sys.executable, '-m', 'pip', *args, **kwargs)
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
def run_module(name: str, *args, run_name: str = '__main__') -> None:
|
|
52
|
-
backup_sys_argv = sys.argv
|
|
53
|
-
sys.argv = [name + '.py'] + list(args)
|
|
54
|
-
runpy.run_module(name, run_name=run_name)
|
|
55
|
-
sys.argv = backup_sys_argv
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
def import_module(name: str = 'setup') -> types.ModuleType:
|
|
59
|
-
setup_module = importlib.import_module(name)
|
|
60
|
-
return setup_module
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
def import_module_member(module_name: str, member_name: str) -> t.Any:
|
|
64
|
-
module = import_module(module_name)
|
|
65
|
-
return getattr(module, member_name)
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
CLASSIFIERS_LICENSES = (
|
|
69
|
-
'License :: OSI Approved :: Python License (CNRI Python License)',
|
|
70
|
-
'License :: OSI Approved :: Python Software Foundation License',
|
|
71
|
-
'License :: Other/Proprietary License',
|
|
72
|
-
'License :: Public Domain')
|
|
73
|
-
|
|
74
|
-
CLASSIFIERS_PYTHON_VERSIONS = tuple("""Programming Language :: Python
|
|
75
|
-
Programming Language :: Python :: 3
|
|
76
|
-
Programming Language :: Python :: 3.0
|
|
77
|
-
Programming Language :: Python :: 3.5
|
|
78
|
-
Programming Language :: Python :: 3 :: Only""".splitlines())
|
|
79
|
-
|
|
80
|
-
CLASSIFIERS_PYTHON_IMPLEMENTATIONS = tuple("""Programming Language :: Python :: Implementation
|
|
81
|
-
Programming Language :: Python :: Implementation :: CPython
|
|
82
|
-
Programming Language :: Python :: Implementation :: Jython
|
|
83
|
-
Programming Language :: Python :: Implementation :: PyPy
|
|
84
|
-
Programming Language :: Python :: Implementation :: Stackless""".splitlines())
|
|
85
|
-
|
|
86
|
-
CLASSIFIERS_VARIOUS = (
|
|
87
|
-
'Framework :: IPython',
|
|
88
|
-
'Topic :: Scientific/Engineering',
|
|
89
|
-
'Topic :: Sociology',
|
|
90
|
-
'Topic :: Security :: Cryptography',
|
|
91
|
-
'Topic :: Software Development :: Libraries :: Python Modules',
|
|
92
|
-
'Topic :: Software Development :: Version Control :: Git',
|
|
93
|
-
'Topic :: System',
|
|
94
|
-
'Topic :: Utilities')
|
|
95
|
-
|
|
96
|
-
CLASSIFIERS_LICENSES_TUPLES = tuple((_,) for _ in CLASSIFIERS_LICENSES) + ((),)
|
|
97
|
-
|
|
98
|
-
CLASSIFIERS_PYTHON_VERSIONS_COMBINATIONS = tuple((_,) for _ in CLASSIFIERS_PYTHON_VERSIONS)
|
|
99
|
-
|
|
100
|
-
CLASSIFIERS_PYTHON_IMPLEMENTATIONS_TUPLES = tuple((_,) for _ in CLASSIFIERS_PYTHON_IMPLEMENTATIONS)
|
|
101
|
-
|
|
102
|
-
CLASSIFIERS_VARIOUS_COMBINATIONS = tuple(itertools.combinations(
|
|
103
|
-
CLASSIFIERS_VARIOUS, len(CLASSIFIERS_VARIOUS) - 1)) + (CLASSIFIERS_VARIOUS,)
|
|
104
|
-
|
|
105
|
-
ALL_CLASSIFIERS_VARIANTS = [
|
|
106
|
-
licenses + versions + implementations + various
|
|
107
|
-
for licenses in CLASSIFIERS_LICENSES_TUPLES
|
|
108
|
-
for versions in CLASSIFIERS_PYTHON_VERSIONS_COMBINATIONS
|
|
109
|
-
for implementations in CLASSIFIERS_PYTHON_IMPLEMENTATIONS_TUPLES
|
|
110
|
-
for various in CLASSIFIERS_VARIOUS_COMBINATIONS]
|
|
111
|
-
|
|
112
|
-
LINK_EXAMPLES = [
|
|
113
|
-
(None, 'setup.py', True), ('this file', 'setup.py', True), (None, 'test/test_setup.py', True),
|
|
114
|
-
(None, 'test/test_setup.py#L98', True), ('line 5 of this file', 'setup.py#L5', True),
|
|
115
|
-
(None, 'http://site.com', False), (None, '../something/else', False), (None, 'no.thing', False),
|
|
116
|
-
(None, '/my/abs/path', False), ('test dir', 'test', True)]
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
def get_package_folder_name():
|
|
120
|
-
"""Attempt to guess the built package name."""
|
|
121
|
-
cwd = pathlib.Path.cwd()
|
|
122
|
-
directories = [
|
|
123
|
-
path for path in cwd.iterdir() if pathlib.Path(cwd, path).is_dir()
|
|
124
|
-
and pathlib.Path(cwd, path, '__init__.py').is_file() and path.name != 'test']
|
|
125
|
-
assert len(directories) == 1, directories
|
|
126
|
-
return directories[0].name
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
class UnitTests(unittest.TestCase):
|
|
130
|
-
"""Test basic functionalities of the setup boilerplate."""
|
|
131
|
-
|
|
132
|
-
def test_find_version(self):
|
|
133
|
-
find_version = import_module_member('setup_boilerplate', 'find_version')
|
|
134
|
-
result = find_version(get_package_folder_name())
|
|
135
|
-
self.assertIsInstance(result, str)
|
|
136
|
-
|
|
137
|
-
def test_find_packages(self):
|
|
138
|
-
find_packages = import_module_member('setup_boilerplate', 'find_packages')
|
|
139
|
-
results = find_packages()
|
|
140
|
-
self.assertIsInstance(results, list)
|
|
141
|
-
for result in results:
|
|
142
|
-
self.assertIsInstance(result, str)
|
|
143
|
-
|
|
144
|
-
def test_requirements(self):
|
|
145
|
-
parse_requirements = import_module_member('setup_boilerplate', 'parse_requirements')
|
|
146
|
-
results = parse_requirements()
|
|
147
|
-
self.assertIsInstance(results, list)
|
|
148
|
-
self.assertTrue(all(isinstance(result, str) for result in results), msg=results)
|
|
149
|
-
|
|
150
|
-
def test_requirements_empty(self):
|
|
151
|
-
parse_requirements = import_module_member('setup_boilerplate', 'parse_requirements')
|
|
152
|
-
with tempfile.NamedTemporaryFile('w', delete=False) as reqs_file:
|
|
153
|
-
pass
|
|
154
|
-
results = parse_requirements(reqs_file.name)
|
|
155
|
-
self.assertIsInstance(results, list)
|
|
156
|
-
self.assertEqual(len(results), 0)
|
|
157
|
-
os.remove(reqs_file.name)
|
|
158
|
-
|
|
159
|
-
def test_requirements_comments(self):
|
|
160
|
-
parse_requirements = import_module_member('setup_boilerplate', 'parse_requirements')
|
|
161
|
-
reqs = ['# comment', 'numpy', '', '# another comment', 'scipy', '', '# one more comment']
|
|
162
|
-
with tempfile.NamedTemporaryFile('w', delete=False) as reqs_file:
|
|
163
|
-
for req in reqs:
|
|
164
|
-
print(req, file=reqs_file)
|
|
165
|
-
results = parse_requirements(reqs_file.name)
|
|
166
|
-
self.assertIsInstance(results, list)
|
|
167
|
-
self.assertGreater(len(results), 0)
|
|
168
|
-
self.assertLess(len(results), len(reqs))
|
|
169
|
-
os.remove(reqs_file.name)
|
|
170
|
-
|
|
171
|
-
def test_python_versions(self):
|
|
172
|
-
find_required_python_version = import_module_member(
|
|
173
|
-
'setup_boilerplate', 'find_required_python_version')
|
|
174
|
-
for variant in ALL_CLASSIFIERS_VARIANTS:
|
|
175
|
-
with self.subTest(variant=variant):
|
|
176
|
-
result = find_required_python_version(variant)
|
|
177
|
-
if result is not None:
|
|
178
|
-
self.assertIsInstance(result, str)
|
|
179
|
-
|
|
180
|
-
def test_python_versions_combined(self):
|
|
181
|
-
find_required_python_version = import_module_member(
|
|
182
|
-
'setup_boilerplate', 'find_required_python_version')
|
|
183
|
-
classifiers = [
|
|
184
|
-
'Programming Language :: Python :: 3 :: Only',
|
|
185
|
-
'Programming Language :: Python :: 3.5']
|
|
186
|
-
req = find_required_python_version(classifiers)
|
|
187
|
-
self.assertEqual(req, '>=3.5')
|
|
188
|
-
|
|
189
|
-
def test_python_versions_reversed(self):
|
|
190
|
-
find_required_python_version = import_module_member(
|
|
191
|
-
'setup_boilerplate', 'find_required_python_version')
|
|
192
|
-
classifiers = [
|
|
193
|
-
'Programming Language :: Python :: 3.4',
|
|
194
|
-
'Programming Language :: Python :: 3.5',
|
|
195
|
-
'Programming Language :: Python :: 3.6']
|
|
196
|
-
req = find_required_python_version(classifiers)
|
|
197
|
-
self.assertEqual(req, '>=3.4')
|
|
198
|
-
req = find_required_python_version(reversed(classifiers))
|
|
199
|
-
self.assertEqual(req, '>=3.4')
|
|
200
|
-
|
|
201
|
-
def test_python_versions_none(self):
|
|
202
|
-
find_required_python_version = import_module_member(
|
|
203
|
-
'setup_boilerplate', 'find_required_python_version')
|
|
204
|
-
result = find_required_python_version([])
|
|
205
|
-
self.assertIsNone(result)
|
|
206
|
-
|
|
207
|
-
def test_python_versions_many_only(self):
|
|
208
|
-
find_required_python_version = import_module_member(
|
|
209
|
-
'setup_boilerplate', 'find_required_python_version')
|
|
210
|
-
classifiers = [
|
|
211
|
-
'Programming Language :: Python :: 2 :: Only',
|
|
212
|
-
'Programming Language :: Python :: 3 :: Only']
|
|
213
|
-
with self.assertRaises(ValueError):
|
|
214
|
-
find_required_python_version(classifiers)
|
|
215
|
-
|
|
216
|
-
def test_python_versions_conflict(self):
|
|
217
|
-
find_required_python_version = import_module_member(
|
|
218
|
-
'setup_boilerplate', 'find_required_python_version')
|
|
219
|
-
classifier_variants = [
|
|
220
|
-
['Programming Language :: Python :: 2.7',
|
|
221
|
-
'Programming Language :: Python :: 3 :: Only'],
|
|
222
|
-
['Programming Language :: Python :: 2 :: Only',
|
|
223
|
-
'Programming Language :: Python :: 3.0']]
|
|
224
|
-
for classifiers in classifier_variants:
|
|
225
|
-
with self.assertRaises(ValueError):
|
|
226
|
-
find_required_python_version(classifiers)
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
class PackageTests(unittest.TestCase):
|
|
230
|
-
"""Test methods of Package class."""
|
|
231
|
-
|
|
232
|
-
def test_try_fields(self):
|
|
233
|
-
package = import_module_member('setup_boilerplate', 'Package')
|
|
234
|
-
|
|
235
|
-
class Package(package): # pylint: disable=too-few-public-methods
|
|
236
|
-
name = 'package name'
|
|
237
|
-
description = 'package description'
|
|
238
|
-
self.assertEqual(Package.try_fields('name', 'description'), 'package name')
|
|
239
|
-
self.assertEqual(Package.try_fields('bad_field', 'description'), 'package description')
|
|
240
|
-
with self.assertRaises(AttributeError):
|
|
241
|
-
self.assertIsNone(Package.try_fields())
|
|
242
|
-
with self.assertRaises(AttributeError):
|
|
243
|
-
Package.try_fields('bad_field', 'another_bad_field')
|
|
244
|
-
|
|
245
|
-
def test_parse_readme(self):
|
|
246
|
-
package = import_module_member('setup_boilerplate', 'Package')
|
|
247
|
-
|
|
248
|
-
class Package(package): # pylint: disable=too-few-public-methods
|
|
249
|
-
name = 'package name'
|
|
250
|
-
description = 'package description'
|
|
251
|
-
version = '1.2.3.4'
|
|
252
|
-
url = 'https://github.com/example'
|
|
253
|
-
|
|
254
|
-
with tempfile.NamedTemporaryFile('w', suffix='.md', delete=False) as temp_file:
|
|
255
|
-
temp_file.write('test test test')
|
|
256
|
-
result, content_type = Package.parse_readme(temp_file.name)
|
|
257
|
-
os.remove(temp_file.name)
|
|
258
|
-
self.assertIsInstance(result, str)
|
|
259
|
-
self.assertIsInstance(content_type, str)
|
|
260
|
-
|
|
261
|
-
prefix = 'https://github.com/example/blob/v1.2.3.4/'
|
|
262
|
-
for name, link, done in LINK_EXAMPLES:
|
|
263
|
-
name = '' if name is None else name + ' '
|
|
264
|
-
text = f'Please see `{name}<{link}>`_ for details.'
|
|
265
|
-
with tempfile.NamedTemporaryFile('w', suffix='.rst', delete=False) as temp_file:
|
|
266
|
-
temp_file.write(text)
|
|
267
|
-
result, content_type = Package.parse_readme(temp_file.name)
|
|
268
|
-
os.remove(temp_file.name)
|
|
269
|
-
self.assertIsInstance(result, str)
|
|
270
|
-
self.assertIsInstance(content_type, str)
|
|
271
|
-
if not done:
|
|
272
|
-
self.assertEqual(result, text)
|
|
273
|
-
continue
|
|
274
|
-
if name == '':
|
|
275
|
-
name = link + ' '
|
|
276
|
-
self.assertIn(f'`{name}<{prefix}{link}>`_', result)
|
|
277
|
-
|
|
278
|
-
def test_prepare(self):
|
|
279
|
-
package = import_module_member('setup_boilerplate', 'Package')
|
|
280
|
-
|
|
281
|
-
version_ = '1.2.3.4.5.6.7'
|
|
282
|
-
long_description_ = 'long package description'
|
|
283
|
-
|
|
284
|
-
class Package(package): # pylint: disable=too-few-public-methods, missing-docstring
|
|
285
|
-
name = 'package name'
|
|
286
|
-
version = version_
|
|
287
|
-
description = 'package description'
|
|
288
|
-
long_description = long_description_
|
|
289
|
-
packages = []
|
|
290
|
-
install_requires = []
|
|
291
|
-
python_requires = ''
|
|
292
|
-
|
|
293
|
-
self.assertEqual(Package.version, version_)
|
|
294
|
-
self.assertEqual(Package.long_description, long_description_)
|
|
295
|
-
Package.prepare()
|
|
296
|
-
self.assertEqual(Package.version, version_)
|
|
297
|
-
self.assertEqual(Package.long_description, long_description_)
|
|
298
|
-
|
|
299
|
-
del Package.long_description
|
|
300
|
-
del Package.packages
|
|
301
|
-
del Package.install_requires
|
|
302
|
-
del Package.python_requires
|
|
303
|
-
Package.prepare()
|
|
304
|
-
|
|
305
|
-
del Package.version
|
|
306
|
-
with self.assertRaises(FileNotFoundError):
|
|
307
|
-
Package.prepare()
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
@unittest.skipUnless(os.environ.get('TEST_PACKAGING') or os.environ.get('CI'),
|
|
311
|
-
'skipping packaging tests for actual package')
|
|
312
|
-
class IntegrationTests(unittest.TestCase):
|
|
313
|
-
"""Test if the boilerplate can actually create a valid package."""
|
|
314
|
-
|
|
315
|
-
pkg_name = get_package_folder_name()
|
|
316
|
-
|
|
317
|
-
def test_build_binary(self):
|
|
318
|
-
run_module('setup', 'bdist')
|
|
319
|
-
self.assertTrue(os.path.isdir('dist'))
|
|
320
|
-
|
|
321
|
-
def test_build_wheel(self):
|
|
322
|
-
run_module('setup', 'bdist_wheel')
|
|
323
|
-
self.assertTrue(os.path.isdir('dist'))
|
|
324
|
-
|
|
325
|
-
def test_build_source(self):
|
|
326
|
-
run_module('setup', 'sdist', '--formats=gztar,zip')
|
|
327
|
-
self.assertTrue(os.path.isdir('dist'))
|
|
328
|
-
|
|
329
|
-
def test_install_code(self):
|
|
330
|
-
with tempfile.TemporaryDirectory() as temporary_folder:
|
|
331
|
-
run_pip('install', '--prefix', temporary_folder, '.')
|
|
332
|
-
self.assertFalse(pathlib.Path(temporary_folder).exists())
|
|
333
|
-
|
|
334
|
-
def test_install_source_tar(self):
|
|
335
|
-
find_version = import_module_member('setup_boilerplate', 'find_version')
|
|
336
|
-
version = find_version(self.pkg_name)
|
|
337
|
-
with tempfile.TemporaryDirectory() as temporary_folder:
|
|
338
|
-
run_pip(
|
|
339
|
-
'install', '--prefix', temporary_folder, f'dist/*-{version}.tar.gz', glob=True)
|
|
340
|
-
self.assertFalse(pathlib.Path(temporary_folder).exists())
|
|
341
|
-
|
|
342
|
-
def test_install_source_zip(self):
|
|
343
|
-
find_version = import_module_member('setup_boilerplate', 'find_version')
|
|
344
|
-
version = find_version(self.pkg_name)
|
|
345
|
-
with tempfile.TemporaryDirectory() as temporary_folder:
|
|
346
|
-
run_pip('install', '--prefix', temporary_folder, f'dist/*-{version}.zip', glob=True)
|
|
347
|
-
self.assertFalse(pathlib.Path(temporary_folder).exists())
|
|
348
|
-
|
|
349
|
-
def test_install_wheel(self):
|
|
350
|
-
find_version = import_module_member('setup_boilerplate', 'find_version')
|
|
351
|
-
version = find_version(self.pkg_name)
|
|
352
|
-
with tempfile.TemporaryDirectory() as temporary_folder:
|
|
353
|
-
run_pip(
|
|
354
|
-
'install', '--prefix', temporary_folder, f'dist/*-{version}-*.whl', glob=True)
|
|
355
|
-
self.assertFalse(pathlib.Path(temporary_folder).exists())
|
|
356
|
-
|
|
357
|
-
def test_pip_error(self):
|
|
358
|
-
with self.assertRaises(AssertionError):
|
|
359
|
-
run_pip('wrong_pip_command')
|
|
360
|
-
|
|
361
|
-
def test_setup_do_nothing_or_error(self):
|
|
362
|
-
run_module('setup', 'wrong_setup_command', run_name='__not_main__')
|
|
363
|
-
with self.assertRaises(SystemExit):
|
|
364
|
-
run_module('setup', 'wrong_setup_command')
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|