-
Notifications
You must be signed in to change notification settings - Fork 0
/
cv.py
executable file
·190 lines (145 loc) · 6.37 KB
/
cv.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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
#!/usr/bin/env python
"""
This module is querying PyPI to check if the current version set to package is already present on PyPI.
Used during PR checks, to ensure that package version is changed.
Finishes with an VersionExists exception and a non-zero exit code if the version exists on PyPI.
"""
from __future__ import annotations
__version__ = '1.0.0'
import json
from enum import IntFlag
from urllib.error import HTTPError
from urllib.request import urlopen
import os
import sys
from argparse import ArgumentParser
from importlib import invalidate_caches, import_module
from typing import List, Any, Optional, NamedTuple
from pkg_resources import safe_version
def check_unique(name: str, version: str, warehouse: str = 'https://pypi.org/pypi') -> None:
try:
response = urlopen(f'{warehouse}/{name}/json')
except HTTPError as e:
raise PypiError(name) from e
data = json.loads(response.read())
versions = set(data['releases'].keys())
if version in versions:
raise VersionExists(name, version)
def check_version_format(name: str, version: str) -> None:
if safe_version(version) != version:
raise InvalidVersionFormat(name, version)
def check_version_type(expected: VersionType, version: str) -> None:
actual = VersionType.parse(version)
if actual != expected:
raise VersionTypeMismatch(version, actual, expected)
class VersionType(IntFlag):
RELEASE = 0
ALPHA = 1
BETA = 2
RC = 4
DEV = 8
@classmethod
def parse(cls, version: str) -> VersionType:
version_type = cls.RELEASE
if 'a' in version:
version_type |= cls.ALPHA
if 'b' in version:
version_type |= cls.BETA
if 'rc' in version:
version_type |= cls.RC
if 'dev' in version:
version_type |= cls.DEV
return version_type
class InvalidRequirements(Exception):
...
class VersionTypeMismatch(Exception):
def __init__(self, version: str, actual: VersionType, expected: VersionType):
super().__init__(f'Package version {version} was specified to be {repr(expected)}, '
f'but actually it is {repr(actual)} ')
class InvalidVersionFormat(Exception):
def __init__(self, name: str, version: str):
super().__init__(f'Package "{name}" version "{version}" is not formatted according to PEP 440. '
f'Proper version may be "{safe_version(version)}. '
f'Read more: https://www.python.org/dev/peps/pep-0440/')
class VersionExists(Exception):
def __init__(self, name: str, version: str):
super().__init__(f'Package "{name}" with version "{version}" already exists on PyPI.{os.linesep}'
f'Change the "{name}.__version__" or "{name}.__init__.__version__" to fix this error.')
class PypiError(Exception):
def __init__(self, name: str):
super().__init__(f'Package "{name}" could not be fetched from PyPI. ')
parser = ArgumentParser(description='Check version of a Python package or module.')
parser.add_argument('module', type=str, help='the package/module with "__version__" defined')
parser.add_argument('-w', '--warehouse', type=str, default='https://pypi.org/pypi',
help='package index to use, default is "https://pypi.org/pypi"')
parser.add_argument('--alpha', action='store_true', default=False,
help='check that version is an alpha, e.g. 1.0.0a1')
parser.add_argument('--beta', action='store_true', default=False,
help='check that version is a beta, e.g. 1.0.0b2')
parser.add_argument('--rc', action='store_true', default=False,
help='check that version is a release candidate, e.g. 1.0.0rc')
parser.add_argument('--dev', action='store_true', default=False,
help='check that version is in development, e.g. 1.0.0.dev3')
parser.add_argument('--release', action='store_true', default=False,
help='check that version is a release without modifiers, e.g. 1.0.0')
parser.add_argument('--dry', action='store_true', default=False,
help='make no request to PyPI')
class Parameters(NamedTuple):
warehouse: str
package: str
version: str
expected_type: Optional[VersionType]
dry_run: bool
def _parse_args(args: List[str]) -> Parameters:
parameters = parser.parse_args(args)
module_name = parameters.module
module = _resolve_module(module_name)
version_type = _parse_version_type(parameters)
return Parameters(
parameters.warehouse,
module_name,
module.__version__,
version_type,
parameters.dry
)
def _parse_version_type(parameters):
if not any([parameters.release, parameters.alpha, parameters.beta, parameters.rc, parameters.dev]):
return None
if parameters.release:
if any([parameters.alpha, parameters.beta, parameters.rc, parameters.dev]):
raise InvalidRequirements('--release cannot be combined with --alpha, --beta, --rc, or --dev')
version_type = VersionType.RELEASE
elif parameters.alpha:
if any([parameters.beta, parameters.rc]):
raise InvalidRequirements('--alpha, --beta and --rc cannot be combined')
version_type = VersionType.ALPHA
elif parameters.beta:
if any([parameters.alpha, parameters.rc]):
raise InvalidRequirements('--alpha, --beta and --rc cannot be combined')
version_type = VersionType.BETA
elif parameters.rc:
version_type = VersionType.RC
else:
version_type = VersionType.RELEASE
if parameters.dev:
version_type |= VersionType.DEV
return version_type
def _resolve_module(module_name: str) -> Any:
"""Black magic. Prevents loading a package from cv dependencies."""
invalidate_caches()
old_module = sys.modules.pop(module_name, None)
module = import_module(module_name)
if old_module:
sys.modules[module_name] = old_module
return module
def main(args):
p = _parse_args(args)
check_version_format(p.package, p.version)
if p.expected_type is not None:
check_version_type(p.expected_type, p.version)
if not p.dry_run:
check_unique(p.package, version=p.version, warehouse=p.warehouse)
print(f'OK: {p.package} {p.version} is valid and not present on PyPI.')
if __name__ == '__main__':
sys.path.insert(0, os.getcwd())
main(sys.argv[1:])