Skip to content

Commit 68262a8

Browse files
committed
Merge branch 'release/1.1.0'
2 parents fb8bb19 + 6f18317 commit 68262a8

File tree

8 files changed

+127
-46
lines changed

8 files changed

+127
-46
lines changed

.travis.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
language: python
22
python:
33
- "2.7"
4-
- "3.3"
4+
- "3.4"
55
- "3.6"
66
branches:
77
except:
88
- piptools-ignore-patch
99
install:
10-
- "pip install -U pip setuptools wheel"
10+
- "python -c 'import sys; sys.exit(0 if (3, 3) <= sys.version_info < (3, 4) else 1)' && pip install pip==10.0.1 setuptools==39.2 wheel==0.29.0 || pip install -U setuptools pip wheel"
1111
- "pip install cram"
1212
- "pip install ."
1313
script:

CONTRIBUTING.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
# How to contribute to `pip-review`
22

3-
First rule: please **do** contribute!
3+
Please **do** contribute! In fact, please take over! See https://github.com/jgonggrijp/pip-review/issues/76.
4+
5+
I (@jgonggrijp) will not just abondon `pip-review`, but I am not using it anymore myself and I cannot dedicate any time to actively maintaining it, other than accepting pull requests and maybe issueing a new release once in a while. So if you see a way to improve `pip-review`, whether by fixing a bug or by adding a feature, please go ahead and submit a pull request.
46

5-
I (@jgonggrijp) want to keep `pip-review` in the air, but I cannot dedicate much time to maintaining it. So if you see a way to improve `pip-review`, whether by fixing a bug or by adding a feature, please go ahead and submit a pull request.
67

78
## Suggestions
89

910
Any kind of contribution is welcome; nothing is "off limits". However, for those who would like some guidance:
1011

1112
- Look for issues with the [help wanted](https://github.com/jgonggrijp/pip-review/labels/help%20wanted), [question](https://github.com/jgonggrijp/pip-review/labels/question) or [poll](https://github.com/jgonggrijp/pip-review/labels/poll) label. In the latter case, if you have an opinion, vote by adding an emoticon of your choice to the opening post. Feel free to explain your vote in a response or to thumbs-up another response that explains your opinion.
12-
- Issues that are associated with a milestone are ordered by relative priority. To see the priority order, click on the milestone. Needless to say, the highest priority issue is at the top.
13+
1314

1415
## The fine print
1516

LICENSE.txt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
Copyright 2012-2015 Vincent Driessen
2+
Copyright 2015-2020 Julian Gonggrijp
3+
4+
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
5+
6+
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
7+
8+
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
9+
10+
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
11+
12+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

README.rst

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
pip-review
66
==========
77

8-
``pip-review`` is a convenience wrapper around ``pip``. It can list available updates by deferring to ``pip list --outdated``. It can also automatically or interactively install available updates for you by deferring to ``pip install``.
8+
*Looking for a new maintainer! See https://github.com/jgonggrijp/pip-review/issues/76.*
9+
10+
``pip-review`` is a convenience wrapper around ``pip``. It can list available updates by deferring to ``pip list --outdated``. It can also automatically or interactively install available updates for you by deferring to ``pip install``.
911

1012
Example, report-only:
1113

@@ -39,10 +41,34 @@ Example, run interactively, ask to upgrade for each package:
3941
4042
Run ``pip-review -h`` for a complete overview of the options.
4143

42-
Since version 0.5, you can also invoke pip-review as ``python -m pip_review``.
44+
Note: If you want to pin specific packages to prevent them from automatically
45+
being upgraded, you can use a constraint file (similar to ``requirements.txt``):
46+
47+
.. code:: console
48+
49+
$ export PIP_CONSTRAINT="${HOME}/constraints.txt
50+
$ cat $PIP_CONSTRAINT
51+
pyarrow==0.14.1
52+
pandas<0.24.0
53+
54+
$ pip-review --auto
55+
...
56+
57+
Set this variable in ``.bashrc`` or ``.zshenv`` to make it persistent.
58+
Alternatively, this option can be specified in ``pip.conf``, e.g.:
59+
60+
.. code:: console
61+
62+
$ cat ~/.config/pip.conf
63+
[global]
64+
constraint = /home/username/constraints.txt
65+
66+
Since version 0.5, you can also invoke pip-review as ``python -m pip_review``. This can be useful if you are using multiple versions of Python next to each other.
4367

4468
Before version 1.0, ``pip-review`` had its own logic for finding package updates instead of relying on ``pip list --outdated``.
4569

70+
Like ``pip``, ``pip-review`` updates **all** packages, including ``pip`` and ``pip-review``.
71+
4672

4773
Installation
4874
============
@@ -79,7 +105,7 @@ involves downloading packages, etc. So please be patient.
79105
Origins
80106
=======
81107

82-
``pip-review`` was originally part of pip-tools_ but
108+
``pip-review`` was originally part of pip-tools_ but
83109
has been discontinued_ as such. See `Pin Your Packages`_ by Vincent
84110
Driessen for the original introduction. Since there are still use cases, the
85111
tool now lives on as a separate package.

pip_review/__main__.py

Lines changed: 58 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,11 @@
88
import sys
99
import pip
1010
import subprocess
11-
try:
12-
import urllib2 as urllib_request # Python2
13-
except ImportError:
14-
import urllib.request as urllib_request
15-
from pkg_resources import parse_version
11+
from packaging import version
1612

17-
try:
18-
from subprocess import check_output
19-
except ImportError:
13+
PY3 = sys.version_info.major == 3
14+
if PY3: # Python3 Imports
15+
import urllib.request as urllib_request
2016
import subprocess
2117

2218
def check_output(*args, **kwargs):
@@ -29,13 +25,12 @@ def check_output(*args, **kwargs):
2925
raise error
3026
return output
3127

32-
try:
28+
else: # Python2 Imports
29+
import urllib2 as urllib_request
30+
from subprocess import check_output
3331
import __builtin__
34-
input = getattr(__builtin__, 'raw_input') # Python2
35-
except (ImportError, AttributeError):
36-
pass
32+
input = getattr(__builtin__, 'raw_input')
3733

38-
from packaging import version
3934

4035
VERSION_PATTERN = re.compile(
4136
version.VERSION_PATTERN,
@@ -45,10 +40,10 @@ def check_output(*args, **kwargs):
4540
NAME_PATTERN = re.compile(r'[a-z0-9_-]+', re.IGNORECASE)
4641

4742
EPILOG = '''
48-
Unrecognised arguments will be forwarded to pip list --outdated,
49-
so you can pass things such as --user, --pre and --timeout and
50-
they will do exactly what you expect. See pip list -h for a full
51-
overview of the options.
43+
Unrecognised arguments will be forwarded to pip list --outdated and
44+
pip install, so you can pass things such as --user, --pre and --timeout
45+
and they will do what you expect. See pip list -h and pip install -h
46+
for a full overview of the options.
5247
'''
5348

5449
DEPRECATED_NOTICE = '''
@@ -57,6 +52,12 @@ def check_output(*args, **kwargs):
5752
Python>=3.3.
5853
'''
5954

55+
# parameters that pip list supports but not pip install
56+
LIST_ONLY = set('l local path format not-required exclude-editable include-editable'.split())
57+
58+
# parameters that pip install supports but not pip list
59+
INSTALL_ONLY = set('c constraint no-deps t target platform python-version implementation abi root prefix b build src U upgrade upgrade-strategy force-reinstall I ignore-installed ignore-requires-python no-build-isolation use-pep517 install-option global-option compile no-compile no-warn-script-location no-warn-conflicts no-binary only-binary prefer-binary no-clean require-hashes progress-bar'.split())
60+
6061

6162
def version_epilog():
6263
"""Version-specific information to be add to the help page."""
@@ -67,7 +68,7 @@ def version_epilog():
6768

6869

6970
def parse_args():
70-
description = 'Keeps your Python packages fresh.'
71+
description = 'Keeps your Python packages fresh. Looking for a new maintainer! See https://github.com/jgonggrijp/pip-review/issues/76'
7172
parser = argparse.ArgumentParser(
7273
description=description,
7374
epilog=EPILOG+version_epilog(),
@@ -87,6 +88,25 @@ def parse_args():
8788
return parser.parse_known_args()
8889

8990

91+
def filter_forwards(args, exclude):
92+
""" Return only the parts of `args` that do not appear in `exclude`. """
93+
result = []
94+
# Start with false, because an unknown argument not starting with a dash
95+
# probably would just trip pip.
96+
admitted = False
97+
for arg in args:
98+
if not arg.startswith('-'):
99+
# assume this belongs with the previous argument.
100+
if admitted:
101+
result.append(arg)
102+
elif arg.lstrip('-') in exclude:
103+
admitted = False
104+
else:
105+
result.append(arg)
106+
admitted = True
107+
return result
108+
109+
90110
def pip_cmd():
91111
return [sys.executable, '-m', 'pip']
92112

@@ -124,36 +144,41 @@ def setup_logging(verbose):
124144
class InteractiveAsker(object):
125145
def __init__(self):
126146
self.cached_answer = None
147+
self.last_answer= None
127148

128149
def ask(self, prompt):
129150
if self.cached_answer is not None:
130151
return self.cached_answer
131152

132153
answer = ''
133154
while answer not in ['y', 'n', 'a', 'q']:
134-
answer = input(
135-
'{0} [Y]es, [N]o, [A]ll, [Q]uit '.format(prompt))
155+
question_last='{0} [Y]es, [N]o, [A]ll, [Q]uit ({1}) '.format(prompt, self.last_answer)
156+
question_default='{0} [Y]es, [N]o, [A]ll, [Q]uit '.format(prompt)
157+
answer = input(question_last if self.last_answer else question_default)
136158
answer = answer.strip().lower()
159+
answer = self.last_answer if answer == '' else answer
137160

138161
if answer in ['q', 'a']:
139162
self.cached_answer = answer
163+
self.last_answer = answer
140164

141165
return answer
142166

143167

144168
ask_to_install = partial(InteractiveAsker().ask, prompt='Upgrade now?')
145169

146170

147-
def update_packages(packages):
148-
command = pip_cmd() + ['install'] + [
149-
'{0}=={1}'.format(pkg['name'], pkg['latest_version']) for pkg in packages]
150-
171+
def update_packages(packages, forwarded):
172+
command = pip_cmd() + ['install'] + forwarded + [
173+
'{0}=={1}'.format(pkg['name'], pkg['latest_version']) for pkg in packages
174+
]
175+
151176
subprocess.call(command, stdout=sys.stdout, stderr=sys.stderr)
152177

153178

154179
def confirm(question):
155180
answer = ''
156-
while not answer in ['y', 'n']:
181+
while answer not in ['y', 'n']:
157182
answer = input(question)
158183
answer = answer.strip().lower()
159184
return answer == 'y'
@@ -177,10 +202,10 @@ def parse_legacy(pip_output):
177202

178203
def get_outdated_packages(forwarded):
179204
command = pip_cmd() + ['list', '--outdated'] + forwarded
180-
pip_version = parse_version(pip.__version__)
181-
if pip_version >= parse_version('6.0'):
205+
pip_version = version.parse(pip.__version__)
206+
if pip_version >= version.parse('6.0'):
182207
command.append('--disable-pip-version-check')
183-
if pip_version > parse_version('9.0'):
208+
if pip_version > version.parse('9.0'):
184209
command.append('--format=json')
185210
output = check_output(command).decode('utf-8')
186211
packages = json.loads(output)
@@ -193,16 +218,18 @@ def get_outdated_packages(forwarded):
193218

194219
def main():
195220
args, forwarded = parse_args()
221+
list_args = filter_forwards(forwarded, INSTALL_ONLY)
222+
install_args = filter_forwards(forwarded, LIST_ONLY)
196223
logger = setup_logging(args.verbose)
197224

198225
if args.raw and args.interactive:
199226
raise SystemExit('--raw and --interactive cannot be used together')
200227

201-
outdated = get_outdated_packages(forwarded)
228+
outdated = get_outdated_packages(list_args)
202229
if not outdated and not args.raw:
203230
logger.info('Everything up-to-date')
204231
elif args.auto:
205-
update_packages(outdated)
232+
update_packages(outdated, install_args)
206233
elif args.raw:
207234
for pkg in outdated:
208235
logger.info('{0}=={1}'.format(pkg['name'], pkg['latest_version']))
@@ -217,7 +244,7 @@ def main():
217244
if answer in ['y', 'a']:
218245
selected.append(pkg)
219246
if selected:
220-
update_packages(selected)
247+
update_packages(selected, install_args)
221248

222249

223250
if __name__ == '__main__':

setup.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,14 @@
77

88
setup(
99
name='pip-review',
10-
version='1.0',
10+
version='1.1.0',
1111
url='https://github.com/jgonggrijp/pip-review',
1212
license='BSD',
1313
author='Julian Gonggrijp, Vincent Driessen',
1414
author_email='[email protected]',
1515
description=__doc__.strip('\n'),
16+
long_description=open('README.rst').read(),
17+
long_description_content_type='text/x-rst',
1618
packages=[
1719
'pip_review',
1820
],
@@ -53,6 +55,8 @@
5355
'Programming Language :: Python :: 3.4',
5456
'Programming Language :: Python :: 3.5',
5557
'Programming Language :: Python :: 3.6',
58+
'Programming Language :: Python :: 3.7',
59+
'Programming Language :: Python :: 3.8',
5660
'Intended Audience :: Developers',
5761
'Intended Audience :: System Administrators',
5862
'License :: OSI Approved :: BSD License',

tests/review.t

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Create a new playground first:
99
$ pip install packaging >/dev/null 2>&1
1010
$ pip install -U --force-reinstall argparse >/dev/null 2>&1
1111
$ pip install -U --force-reinstall wheel >/dev/null 2>&1
12+
$ pip install -U --force-reinstall setuptools >/dev/null 2>&1
1213
$ function pip-review { python -m pip_review.__main__ $* ; }
1314

1415
Setup. Let's pretend we have some outdated package versions installed:
@@ -17,17 +18,17 @@ Setup. Let's pretend we have some outdated package versions installed:
1718

1819
Next, let's see what pip-review does:
1920

20-
$ pip-review 2>&1
21+
$ pip-review 2>&1 | egrep -v '^DEPRECATION:'
2122
python-dateutil==* is available (you have 1.5) (glob)
2223

2324
Or in raw mode:
2425

25-
$ pip-review --raw 2>&1
26+
$ pip-review --raw 2>&1 | egrep -v '^DEPRECATION:'
2627
python-dateutil==* (glob)
2728

2829
pip-review forwards arguments it doesn't recognize to pip:
2930

30-
$ pip-review --timeout 30 2>&1
31+
$ pip-review --timeout 30 2>&1 | egrep -v '^DEPRECATION:'
3132
python-dateutil==* is available (you have 1.5) (glob)
3233

3334
It only fails if pip doesn't recognize it either:
@@ -38,15 +39,25 @@ It only fails if pip doesn't recognize it either:
3839
We can also install these updates automatically:
3940

4041
$ pip-review --auto >/dev/null 2>&1
41-
$ pip-review 2>&1
42+
$ pip-review 2>&1 | egrep -v '^DEPRECATION:'
4243
Everything up-to-date
4344

45+
It knows which arguments not to forward to pip list:
46+
47+
$ pip install python-dateutil==1.5 >/dev/null 2>&1
48+
$ pip-review --auto --force-reinstall >/dev/null 2>&1
49+
50+
It knows which arguments not to forward to pip install:
51+
52+
$ pip install python-dateutil==1.5 >/dev/null 2>&1
53+
$ pip-review --auto --not-required >/dev/null 2>&1
54+
4455
Next, let's test for regressions with older versions of pip:
4556

4657
$ pip install --force-reinstall --upgrade pip\<6.0 >/dev/null 2>&1
4758
$ if python -c 'import sys; sys.exit(0 if sys.version_info < (3, 6) else 1)'; then
4859
> rm -rf pip_review.egg-info # prevents spurious editable in pip freeze
49-
> pip-review
60+
> pip-review | egrep -v '^DEPRECATION:'
5061
> else
5162
> echo Skipped
5263
> fi

tox.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[tox]
2-
envlist = py27,py33,py36
2+
envlist = py27,py34,py36
33

44
[testenv]
55
deps=cram

0 commit comments

Comments
 (0)