Skip to content

Commit

Permalink
Merge branch 'release/1.1.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
jgonggrijp committed Mar 21, 2020
2 parents fb8bb19 + 6f18317 commit 68262a8
Show file tree
Hide file tree
Showing 8 changed files with 127 additions and 46 deletions.
4 changes: 2 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
language: python
python:
- "2.7"
- "3.3"
- "3.4"
- "3.6"
branches:
except:
- piptools-ignore-patch
install:
- "pip install -U pip setuptools wheel"
- "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"
- "pip install cram"
- "pip install ."
script:
Expand Down
7 changes: 4 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
# How to contribute to `pip-review`

First rule: please **do** contribute!
Please **do** contribute! In fact, please take over! See https://github.com/jgonggrijp/pip-review/issues/76.

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.

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.

## Suggestions

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

- 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.
- 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.


## The fine print

Expand Down
12 changes: 12 additions & 0 deletions LICENSE.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
Copyright 2012-2015 Vincent Driessen
Copyright 2015-2020 Julian Gonggrijp

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

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.

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.

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.
32 changes: 29 additions & 3 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
pip-review
==========

``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``.
*Looking for a new maintainer! See https://github.com/jgonggrijp/pip-review/issues/76.*

``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``.

Example, report-only:

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

Since version 0.5, you can also invoke pip-review as ``python -m pip_review``.
Note: If you want to pin specific packages to prevent them from automatically
being upgraded, you can use a constraint file (similar to ``requirements.txt``):

.. code:: console
$ export PIP_CONSTRAINT="${HOME}/constraints.txt
$ cat $PIP_CONSTRAINT
pyarrow==0.14.1
pandas<0.24.0
$ pip-review --auto
...
Set this variable in ``.bashrc`` or ``.zshenv`` to make it persistent.
Alternatively, this option can be specified in ``pip.conf``, e.g.:

.. code:: console
$ cat ~/.config/pip.conf
[global]
constraint = /home/username/constraints.txt
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.

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

Like ``pip``, ``pip-review`` updates **all** packages, including ``pip`` and ``pip-review``.


Installation
============
Expand Down Expand Up @@ -79,7 +105,7 @@ involves downloading packages, etc. So please be patient.
Origins
=======

``pip-review`` was originally part of pip-tools_ but
``pip-review`` was originally part of pip-tools_ but
has been discontinued_ as such. See `Pin Your Packages`_ by Vincent
Driessen for the original introduction. Since there are still use cases, the
tool now lives on as a separate package.
Expand Down
89 changes: 58 additions & 31 deletions pip_review/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,11 @@
import sys
import pip
import subprocess
try:
import urllib2 as urllib_request # Python2
except ImportError:
import urllib.request as urllib_request
from pkg_resources import parse_version
from packaging import version

try:
from subprocess import check_output
except ImportError:
PY3 = sys.version_info.major == 3
if PY3: # Python3 Imports
import urllib.request as urllib_request
import subprocess

def check_output(*args, **kwargs):
Expand All @@ -29,13 +25,12 @@ def check_output(*args, **kwargs):
raise error
return output

try:
else: # Python2 Imports
import urllib2 as urllib_request
from subprocess import check_output
import __builtin__
input = getattr(__builtin__, 'raw_input') # Python2
except (ImportError, AttributeError):
pass
input = getattr(__builtin__, 'raw_input')

from packaging import version

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

EPILOG = '''
Unrecognised arguments will be forwarded to pip list --outdated,
so you can pass things such as --user, --pre and --timeout and
they will do exactly what you expect. See pip list -h for a full
overview of the options.
Unrecognised arguments will be forwarded to pip list --outdated and
pip install, so you can pass things such as --user, --pre and --timeout
and they will do what you expect. See pip list -h and pip install -h
for a full overview of the options.
'''

DEPRECATED_NOTICE = '''
Expand All @@ -57,6 +52,12 @@ def check_output(*args, **kwargs):
Python>=3.3.
'''

# parameters that pip list supports but not pip install
LIST_ONLY = set('l local path format not-required exclude-editable include-editable'.split())

# parameters that pip install supports but not pip list
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())


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


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


def filter_forwards(args, exclude):
""" Return only the parts of `args` that do not appear in `exclude`. """
result = []
# Start with false, because an unknown argument not starting with a dash
# probably would just trip pip.
admitted = False
for arg in args:
if not arg.startswith('-'):
# assume this belongs with the previous argument.
if admitted:
result.append(arg)
elif arg.lstrip('-') in exclude:
admitted = False
else:
result.append(arg)
admitted = True
return result


def pip_cmd():
return [sys.executable, '-m', 'pip']

Expand Down Expand Up @@ -124,36 +144,41 @@ def setup_logging(verbose):
class InteractiveAsker(object):
def __init__(self):
self.cached_answer = None
self.last_answer= None

def ask(self, prompt):
if self.cached_answer is not None:
return self.cached_answer

answer = ''
while answer not in ['y', 'n', 'a', 'q']:
answer = input(
'{0} [Y]es, [N]o, [A]ll, [Q]uit '.format(prompt))
question_last='{0} [Y]es, [N]o, [A]ll, [Q]uit ({1}) '.format(prompt, self.last_answer)
question_default='{0} [Y]es, [N]o, [A]ll, [Q]uit '.format(prompt)
answer = input(question_last if self.last_answer else question_default)
answer = answer.strip().lower()
answer = self.last_answer if answer == '' else answer

if answer in ['q', 'a']:
self.cached_answer = answer
self.last_answer = answer

return answer


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


def update_packages(packages):
command = pip_cmd() + ['install'] + [
'{0}=={1}'.format(pkg['name'], pkg['latest_version']) for pkg in packages]

def update_packages(packages, forwarded):
command = pip_cmd() + ['install'] + forwarded + [
'{0}=={1}'.format(pkg['name'], pkg['latest_version']) for pkg in packages
]

subprocess.call(command, stdout=sys.stdout, stderr=sys.stderr)


def confirm(question):
answer = ''
while not answer in ['y', 'n']:
while answer not in ['y', 'n']:
answer = input(question)
answer = answer.strip().lower()
return answer == 'y'
Expand All @@ -177,10 +202,10 @@ def parse_legacy(pip_output):

def get_outdated_packages(forwarded):
command = pip_cmd() + ['list', '--outdated'] + forwarded
pip_version = parse_version(pip.__version__)
if pip_version >= parse_version('6.0'):
pip_version = version.parse(pip.__version__)
if pip_version >= version.parse('6.0'):
command.append('--disable-pip-version-check')
if pip_version > parse_version('9.0'):
if pip_version > version.parse('9.0'):
command.append('--format=json')
output = check_output(command).decode('utf-8')
packages = json.loads(output)
Expand All @@ -193,16 +218,18 @@ def get_outdated_packages(forwarded):

def main():
args, forwarded = parse_args()
list_args = filter_forwards(forwarded, INSTALL_ONLY)
install_args = filter_forwards(forwarded, LIST_ONLY)
logger = setup_logging(args.verbose)

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

outdated = get_outdated_packages(forwarded)
outdated = get_outdated_packages(list_args)
if not outdated and not args.raw:
logger.info('Everything up-to-date')
elif args.auto:
update_packages(outdated)
update_packages(outdated, install_args)
elif args.raw:
for pkg in outdated:
logger.info('{0}=={1}'.format(pkg['name'], pkg['latest_version']))
Expand All @@ -217,7 +244,7 @@ def main():
if answer in ['y', 'a']:
selected.append(pkg)
if selected:
update_packages(selected)
update_packages(selected, install_args)


if __name__ == '__main__':
Expand Down
6 changes: 5 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@

setup(
name='pip-review',
version='1.0',
version='1.1.0',
url='https://github.com/jgonggrijp/pip-review',
license='BSD',
author='Julian Gonggrijp, Vincent Driessen',
author_email='j.gonggrijp@gmail.com',
description=__doc__.strip('\n'),
long_description=open('README.rst').read(),
long_description_content_type='text/x-rst',
packages=[
'pip_review',
],
Expand Down Expand Up @@ -53,6 +55,8 @@
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Intended Audience :: Developers',
'Intended Audience :: System Administrators',
'License :: OSI Approved :: BSD License',
Expand Down
21 changes: 16 additions & 5 deletions tests/review.t
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Create a new playground first:
$ pip install packaging >/dev/null 2>&1
$ pip install -U --force-reinstall argparse >/dev/null 2>&1
$ pip install -U --force-reinstall wheel >/dev/null 2>&1
$ pip install -U --force-reinstall setuptools >/dev/null 2>&1
$ function pip-review { python -m pip_review.__main__ $* ; }

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

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

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

Or in raw mode:

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

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

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

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

$ pip-review --auto >/dev/null 2>&1
$ pip-review 2>&1
$ pip-review 2>&1 | egrep -v '^DEPRECATION:'
Everything up-to-date

It knows which arguments not to forward to pip list:

$ pip install python-dateutil==1.5 >/dev/null 2>&1
$ pip-review --auto --force-reinstall >/dev/null 2>&1

It knows which arguments not to forward to pip install:

$ pip install python-dateutil==1.5 >/dev/null 2>&1
$ pip-review --auto --not-required >/dev/null 2>&1

Next, let's test for regressions with older versions of pip:

$ pip install --force-reinstall --upgrade pip\<6.0 >/dev/null 2>&1
$ if python -c 'import sys; sys.exit(0 if sys.version_info < (3, 6) else 1)'; then
> rm -rf pip_review.egg-info # prevents spurious editable in pip freeze
> pip-review
> pip-review | egrep -v '^DEPRECATION:'
> else
> echo Skipped
> fi
Expand Down
2 changes: 1 addition & 1 deletion tox.ini
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[tox]
envlist = py27,py33,py36
envlist = py27,py34,py36

[testenv]
deps=cram
Expand Down

0 comments on commit 68262a8

Please sign in to comment.