Skip to content

Commit

Permalink
v1.0.0
Browse files Browse the repository at this point in the history
  • Loading branch information
alanhamlett committed May 9, 2016
0 parents commit c33975c
Show file tree
Hide file tree
Showing 18 changed files with 561 additions and 0 deletions.
39 changes: 39 additions & 0 deletions .gitignore
@@ -0,0 +1,39 @@
*.py[cod]

# C extensions
*.so

# Packages
*.egg
*.egg-info
dist
build
eggs
parts
bin
var
sdist
develop-eggs
.installed.cfg
lib
lib64

# Installer logs
pip-log.txt

# Unit test / coverage reports
.coverage
.tox
nosetests.xml

# Translations
*.mo

# Mr Developer
.mr.developer.cfg
.project
.pydevproject

virtualenv
venv
.DS_Store
19 changes: 19 additions & 0 deletions .travis.yml
@@ -0,0 +1,19 @@
language: python
python:
- "2.6"
- "2.7"
- "3.3"
- "3.4"
- "3.5"
# command to install dependencies
install:
- if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then travis_retry pip install unittest2; fi
- travis_retry pip install -r dev-requirements.txt
- travis_retry pip install coveralls
# use new travis-ci container-based infrastructure
sudo: false
# command to run tests
script: nosetests
# command to run after tests
after_success:
- coveralls
7 changes: 7 additions & 0 deletions AUTHORS
@@ -0,0 +1,7 @@
pip-update-requirements is written and maintained by Alan Hamlett:


Development Lead
----------------

- Alan Hamlett <alan.hamlett@gmail.com>
9 changes: 9 additions & 0 deletions HISTORY.rst
@@ -0,0 +1,9 @@

History
-------


1.0.0 (2016-05-09)
++++++++++++++++++

- Birth.
28 changes: 28 additions & 0 deletions LICENSE
@@ -0,0 +1,28 @@
BSD License
===========

Copyright (c) 2016 by the respective authors (see AUTHORS file).
All rights reserved.

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

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

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

THIS SOFTWARE AND DOCUMENTATION 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.
2 changes: 2 additions & 0 deletions MANIFEST.in
@@ -0,0 +1,2 @@
include README.rst LICENSE HISTORY.rst
recursive-include pur *.py
74 changes: 74 additions & 0 deletions README.rst
@@ -0,0 +1,74 @@
pip-update-requirements
=======================

.. image:: https://travis-ci.org/alanhamlett/pip-update-requirements.svg?branch=master
:target: https://travis-ci.org/alanhamlett/pip-update-requirements
:alt: Tests

.. image:: https://coveralls.io/repos/alanhamlett/pip-update-requirements/badge.svg?branch=master&service=github
:target: https://coveralls.io/github/alanhamlett/pip-update-requirements?branch=master
:alt: Coverage

.. image:: https://gemnasium.com/badges/github.com/alanhamlett/pip-update-requirements.svg
:target: https://gemnasium.com/github.com/alanhamlett/pip-update-requirements
:alt: Dependencies


Update the packages in a ``requirements.txt`` file.

.. image:: https://github.com/alanhamlett/pip-update-requirements/blob/master/pur.gif
:alt: Purring Cat


Installation
------------

::

pip install pur


Usage
-----

Give pur your ``requirements.txt`` file and it updates all your packages to
the latest versions.

For example, given a ``requirements.txt`` file::

flask==0.9
sqlalchemy==0.9.10
alembic==0.8.4

Running pur on that file updates the packages to current latest versions::

$ pur requirements.txt
Updated flask: 0.9 -> 0.10.1
Updated sqlalchemy: 0.9.10 -> 1.0.12
Updated alembic: 0.8.4 -> 0.8.6
All requirements up-to-date.


Pur never modifies your environment or installed packages, it only modifies
your ``requirements.txt`` file.


Options
-------

There is an optional ``--output`` argument to write the new
``requirements.txt`` file to a different location than the default. By
default, pur overwrites your ``requirements.txt`` file.


Contributing
------------

Before contributing a pull request, make sure tests pass::

virtualenv venv
. venv/bin/activate
pip install tox
tox

Many thanks to all `contributors <https://github.com/alanhamlett/pip-update-requirements/blob/master/AUTHORS>`_!
8 changes: 8 additions & 0 deletions dev-requirements.txt
@@ -0,0 +1,8 @@
-r requirements.txt

coverage==4.0.3
mock==2.0.0
nose==1.3.7
nose-capturestderr==1.2
testfixtures==4.9.1
-e git://github.com/alanhamlett/nose-exclude.git@f8ad6b1111e3927ecfaafd26f6952745c4b8df0c#egg=nose-exclude
Binary file added pur.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
152 changes: 152 additions & 0 deletions pur/__init__.py
@@ -0,0 +1,152 @@
# -*- coding: utf-8 -*-
"""
pip-update-requirements
~~~~~~~~~~~~~~~~~~~~~~~
Update packages in a requirements.txt file to latest versions.
:copyright: (c) 2016 Alan Hamlett.
:license: BSD, see LICENSE for more details.
"""


__title__ = 'pur'
__description__ = 'Update packages in a requirements.txt file to latest versions.'
__url__ = 'https://github.com/alanhamlett/pip-update-requirements'
__version_info__ = ('1', '0', '0')
__version__ = '.'.join(__version_info__)
__author__ = 'Alan Hamlett'
__author_email__ = 'alan.hamlett@gmail.com'
__license__ = 'BSD'
__copyright__ = 'Copyright 2016 Alan Hamlett'


import click
try:
from StringIO import StringIO
except ImportError: # pragma: nocover
from io import StringIO

from pip.download import PipSession, get_file_content
from pip.index import PackageFinder
from pip.models.index import PyPI
from pip.req import req_file
from pip.req.req_install import Version


@click.command()
@click.argument('requirements_file', type=click.Path())
@click.option('--output', type=click.Path(),
help='Output updated packages to this file; Defaults to ' +
'writing back to REQUIREMENTS_FILE.')
@click.version_option(__version__)
def pur(requirements_file, **options):
"""Command line entry point."""

if not options.get('output'):
options['output'] = requirements_file

# prevent processing nested requirements files
patch_pip()

requirements = get_requirements_and_latest(requirements_file)

buf = StringIO()
for line, req, spec_ver, latest_ver in requirements:
if req:
if spec_ver < latest_ver:
new_line = line.replace(str(spec_ver), str(latest_ver), 1)
buf.write(new_line)
click.echo('Updated {package}: {old} -> {new}'.format(
package=req.name,
old=spec_ver,
new=latest_ver,
))
else:
buf.write(line)
else:
buf.write(line)
buf.write("\n")

with open(options['output'], 'w') as output:
output.write(buf.getvalue())

buf.close()

click.echo('All requirements up-to-date.')
return 0


def get_requirements_and_latest(filename):
"""Parse a requirements file and get latest version for each requirement.
Yields a tuple of (original line, InstallRequirement instance,
spec_version, latest_version).
:param filename: Path to a requirements.txt file.
"""
session = PipSession()

url, content = get_file_content(filename, session=session)
lines = req_file.join_lines(enumerate(content.splitlines(), start=1))
for line_number, line in lines:
stripped_line = req_file.COMMENT_RE.sub('', line)
stripped_line = stripped_line.strip()
if stripped_line:
reqs = list(req_file.process_line(stripped_line, filename,
line_number, session=session))
if len(reqs) > 0:
req = reqs[0]
try:
spec_ver = Version(req.req.specs[0][1])
except IndexError:
spec_ver = None
if spec_ver:
latest_ver = latest_version(req, session)
yield (line, req, spec_ver, latest_ver)
else:
yield (line, None, None, None)
else:
yield (line, None, None, None)
else:
yield (line, None, None, None)


def latest_version(req, session, include_prereleases=False):
"""Returns a Version instance with the latest version for the package.
:param req: Instance of
pip.req.req_install.InstallRequirement.
:param session: Instance of pip.download.PipSession.
:param include_prereleases: Include prereleased beta versions.
"""
if not req: # pragma: nocover
return None

index_urls = [PyPI.simple_url]
finder = PackageFinder(session=session, find_links=[],
index_urls=index_urls)

all_candidates = finder.find_all_candidates(req.name)

if not include_prereleases:
all_candidates = [candidate for candidate in all_candidates
if not candidate.version.is_prerelease]

if not all_candidates:
return None

best_candidate = max(all_candidates,
key=finder._candidate_sort_key)
remote_version = best_candidate.version
return remote_version


def patch_pip():
old_fn = req_file.parse_requirements
def patched_parse_requirements(*args, **kwargs):
return []
req_file.parse_requirements = patched_parse_requirements
return old_fn


if __name__ == '__main__':
pur()
2 changes: 2 additions & 0 deletions requirements.txt
@@ -0,0 +1,2 @@
click==6.6
pip==8.1.1
6 changes: 6 additions & 0 deletions setup.cfg
@@ -0,0 +1,6 @@
[nosetests]
with-coverage = 1
cover-inclusive = 1
cover-package = pur
exclude-dir =
venv

0 comments on commit c33975c

Please sign in to comment.