-
Notifications
You must be signed in to change notification settings - Fork 30
/
Copy pathupgrade.py
150 lines (116 loc) · 4.4 KB
/
upgrade.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
"""
Disable upgrades
================
When new dependencies are added it's tempting to keep everything else the same.
To recompile ``.txt`` keeping satisfying version use ``--no-upgrade``:
.. code-block:: text
--upgrade / --no-upgrade Upgrade package version (default true)
The option has no effect if there are no existing ``.txt`` files.
Upgrade only selected packages
==============================
To upgrade only one package and keep everything else untouched,
use following option:
.. code-block:: text
-P, --upgrade-package TEXT Only upgrade named package.
Can be supplied multiple times.
Under the hood it uses `the same option of pip-compile`_
and runs compilation only for files that have one of the passed packages.
This option implies ``--no-upgrade`` and takes precedence over ``--upgrade``.
Thanks to `Jonathan Rogers <https://github.com/JonathanRRogers>`_.
.. _`the same option of pip-compile`: \
https://github.com/jazzband/pip-tools#updating-requirements
"""
import re
from .base import BaseFeature, ClickOption
from .forward import ForwardOption
class UpgradeAll(ForwardOption):
"""Upgrade all packages in all environments."""
OPTION_NAME = 'upgrade'
CLICK_OPTION = ClickOption(
long_option='--upgrade/--no-upgrade',
default=True,
is_flag=True,
help_text='Upgrade package version (default true)',
)
enabled_pin_options = ['--upgrade']
def __init__(self, controller):
self._controller = controller
@property
def enabled(self):
"""Whether global upgrade is enabled."""
return self.value and not self._controller.upgrade_selected.active
class UpgradeSelected(BaseFeature):
"""Upgrade only specific packages in all environments."""
OPTION_NAME = 'upgrade_packages'
CLICK_OPTION = ClickOption(
long_option='--upgrade-package',
short_option='-P',
multiple=True,
help_text='Only upgrade named package. '
'Can be supplied multiple times.',
)
RE_PACKAGE_NAME = re.compile(
r'(?iu)(?P<package>[a-z0-9-_.]+)',
)
def __init__(self, controller):
self._controller = controller
self.reset()
def reset(self):
"""Clear cached packages."""
self._env_packages_cache = {}
@property
def package_specs(self):
"""List of package specs to upgrade."""
return self.value or []
@property
def package_names(self):
"""List of package names to upgrade."""
def name_from_spec(name):
match = self.RE_PACKAGE_NAME.match(name)
if match is None:
raise ValueError(
f"{name!r} does not appear to be a valid package spec",
)
return match.group(0)
return [name_from_spec(x) for x in self.package_specs]
@property
def active(self):
"""Whether selective upgrade is active."""
return bool(self.package_names)
def pin_options(self):
"""Pin command options for upgrading specific packages."""
return [
'--upgrade-package=' + package
for package in self.package_specs
]
def has_package(self, in_path, package_name):
"""Whether specified package name is already in the outfile."""
return package_name.lower() in self._get_packages(in_path)
def _get_packages(self, in_path):
if in_path not in self._env_packages_cache:
self._env_packages_cache[in_path] = self._read_packages(
self._compose_output_file_path(in_path)
)
return self._env_packages_cache[in_path]
@staticmethod
def _read_packages(outfile):
try:
with open(outfile, encoding="utf-8") as fp:
return set(
line.split('==', 1)[0].lower()
for line in fp
if '==' in line
)
except IOError:
# Act as if file is empty
return set()
def _compose_output_file_path(self, in_path):
return self._controller.compose_output_file_path(in_path)
def affected(self, in_path):
"""Whether environment was affected by upgraded packages."""
if not self.active:
return True
return any(
self.has_package(in_path, package_name)
for package_name in self.package_names
)