├── .gitignore ├── .travis.yml ├── LICENSE ├── README.rst ├── bin ├── pip-diff └── pip-grep ├── requirements.txt ├── setup.py ├── tests ├── test-requirements.txt └── test-requirements2.txt └── tox.ini /.gitignore: -------------------------------------------------------------------------------- 1 | /.tox 2 | /*.egg-info 3 | *.pyc 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: python 3 | python: 4 | - "2.7" 5 | - "3.4" 6 | - "3.5" 7 | - "pypy" 8 | cache: pip 9 | install: 10 | - pip install -r requirements.txt 11 | script: 12 | - tox 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Kenneth Reitz. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | pip-pop 2 | ======= 3 | 4 | .. image:: https://travis-ci.org/kennethreitz/pip-pop.svg?branch=master 5 | :target: https://travis-ci.org/kennethreitz/pip-pop 6 | 7 | Working with lots of ``requirements.txt`` files can be a bit annoying. 8 | Have no fear, **pip-pop** is here! 9 | 10 | (work in progress) 11 | 12 | Planned Commands 13 | ---------------- 14 | 15 | ``$ pip-diff [--fresh | --stale] `` 16 | 17 | Generates a diff between two given requirements files. Lists either stale or fresh packages. 18 | 19 | ``$ pip-grep ...`` 20 | 21 | Takes a requirements file, and searches for the specified package (or packages) within it. 22 | Essential when working with included files. 23 | 24 | 25 | Possible Future Commands 26 | ------------------------ 27 | 28 | - Install with blacklisting support (wsgiref, distribute, setuptools). 29 | 30 | Development 31 | ----------- 32 | 33 | To run the tests: 34 | 35 | 1. ``pip install -r requirements.txt`` 36 | 2. ``tox`` 37 | -------------------------------------------------------------------------------- /bin/pip-diff: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """Usage: 5 | pip-diff (--fresh | --stale) [--exclude ...] 6 | pip-diff (-h | --help) 7 | 8 | Options: 9 | -h --help Show this screen. 10 | --fresh List newly added packages. 11 | --stale List removed packages. 12 | """ 13 | import os 14 | from docopt import docopt 15 | from pip.req import parse_requirements 16 | from pip.index import PackageFinder 17 | from pip._vendor.requests import session 18 | 19 | requests = session() 20 | 21 | 22 | class Requirements(object): 23 | def __init__(self, reqfile=None): 24 | super(Requirements, self).__init__() 25 | self.path = reqfile 26 | self.requirements = [] 27 | 28 | if reqfile: 29 | self.load(reqfile) 30 | 31 | def __repr__(self): 32 | return ''.format(self.path) 33 | 34 | def load(self, reqfile): 35 | if not os.path.exists(reqfile): 36 | raise ValueError('The given requirements file does not exist.') 37 | 38 | finder = PackageFinder([], [], session=requests) 39 | for requirement in parse_requirements(reqfile, finder=finder, session=requests): 40 | if requirement.req: 41 | if not getattr(requirement.req, 'name', None): 42 | # Prior to pip 8.1.2 the attribute `name` did not exist. 43 | requirement.req.name = requirement.req.project_name 44 | self.requirements.append(requirement.req) 45 | 46 | 47 | def diff(self, requirements, ignore_versions=False, excludes=None): 48 | r1 = self 49 | r2 = requirements 50 | results = {'fresh': [], 'stale': []} 51 | 52 | # Generate fresh packages. 53 | other_reqs = ( 54 | [r.name for r in r1.requirements] 55 | if ignore_versions else r1.requirements 56 | ) 57 | 58 | for req in r2.requirements: 59 | r = req.name if ignore_versions else req 60 | 61 | if r not in other_reqs and r not in excludes: 62 | results['fresh'].append(req) 63 | 64 | # Generate stale packages. 65 | other_reqs = ( 66 | [r.name for r in r2.requirements] 67 | if ignore_versions else r2.requirements 68 | ) 69 | 70 | for req in r1.requirements: 71 | r = req.name if ignore_versions else req 72 | 73 | if r not in other_reqs and r not in excludes: 74 | results['stale'].append(req) 75 | 76 | return results 77 | 78 | 79 | def diff(r1, r2, include_fresh=False, include_stale=False, excludes=None): 80 | include_versions = True if include_stale else False 81 | excludes = excludes if len(excludes) else [] 82 | 83 | try: 84 | r1 = Requirements(r1) 85 | r2 = Requirements(r2) 86 | except ValueError: 87 | print('There was a problem loading the given requirements files.') 88 | exit(os.EX_NOINPUT) 89 | 90 | results = r1.diff(r2, ignore_versions=True, excludes=excludes) 91 | 92 | if include_fresh: 93 | for line in results['fresh']: 94 | print(line.name if include_versions else line) 95 | 96 | if include_stale: 97 | for line in results['stale']: 98 | print(line.name if include_versions else line) 99 | 100 | 101 | def main(): 102 | args = docopt(__doc__, version='pip-diff') 103 | 104 | kwargs = { 105 | 'r1': args[''], 106 | 'r2': args[''], 107 | 'include_fresh': args['--fresh'], 108 | 'include_stale': args['--stale'], 109 | 'excludes': args[''] 110 | } 111 | 112 | diff(**kwargs) 113 | 114 | 115 | if __name__ == '__main__': 116 | main() 117 | -------------------------------------------------------------------------------- /bin/pip-grep: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """Usage: 5 | pip-grep [-s] ... 6 | 7 | Options: 8 | -h --help Show this screen. 9 | """ 10 | import os 11 | from docopt import docopt 12 | from pip.req import parse_requirements 13 | from pip.index import PackageFinder 14 | from pip._vendor.requests import session 15 | 16 | requests = session() 17 | 18 | 19 | class Requirements(object): 20 | def __init__(self, reqfile=None): 21 | super(Requirements, self).__init__() 22 | self.path = reqfile 23 | self.requirements = [] 24 | 25 | if reqfile: 26 | self.load(reqfile) 27 | 28 | def __repr__(self): 29 | return ''.format(self.path) 30 | 31 | def load(self, reqfile): 32 | if not os.path.exists(reqfile): 33 | raise ValueError('The given requirements file does not exist.') 34 | 35 | finder = PackageFinder([], [], session=requests) 36 | for requirement in parse_requirements(reqfile, finder=finder, session=requests): 37 | if requirement.req: 38 | if not getattr(requirement.req, 'name', None): 39 | # Prior to pip 8.1.2 the attribute `name` did not exist. 40 | requirement.req.name = requirement.req.project_name 41 | self.requirements.append(requirement.req) 42 | 43 | 44 | def grep(reqfile, packages, silent=False): 45 | try: 46 | r = Requirements(reqfile) 47 | except ValueError: 48 | if not silent: 49 | print('There was a problem loading the given requirement file.') 50 | exit(os.EX_NOINPUT) 51 | 52 | for req in r.requirements: 53 | if req.name in packages: 54 | if not silent: 55 | print('Package {} found!'.format(req.name)) 56 | exit(0) 57 | 58 | if not silent: 59 | print('Not found.') 60 | 61 | exit(1) 62 | 63 | 64 | def main(): 65 | args = docopt(__doc__, version='pip-grep') 66 | 67 | kwargs = {'reqfile': args[''], 'packages': args[''], 'silent': args['-s']} 68 | 69 | grep(**kwargs) 70 | 71 | 72 | if __name__ == '__main__': 73 | main() 74 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | docopt==0.6.2 2 | tox==2.3.1 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """ 2 | pip-pop manages your requirements files. 3 | """ 4 | import sys 5 | from setuptools import setup 6 | 7 | 8 | setup( 9 | name='pip-pop', 10 | version='0.1.0', 11 | url='https://github.com/kennethreitz/pip-pop', 12 | license='MIT', 13 | author='Kenneth Reitz', 14 | author_email='me@kennethreitz.org', 15 | description=__doc__.strip('\n'), 16 | #packages=[], 17 | scripts=['bin/pip-diff', 'bin/pip-grep'], 18 | #include_package_data=True, 19 | zip_safe=False, 20 | platforms='any', 21 | install_requires=['docopt', 'pip>=1.5.0'], 22 | tests_require=['tox'], 23 | classifiers=[ 24 | # As from https://pypi.python.org/pypi?%3Aaction=list_classifiers 25 | #'Development Status :: 1 - Planning', 26 | #'Development Status :: 2 - Pre-Alpha', 27 | #'Development Status :: 3 - Alpha', 28 | 'Development Status :: 4 - Beta', 29 | #'Development Status :: 5 - Production/Stable', 30 | #'Development Status :: 6 - Mature', 31 | #'Development Status :: 7 - Inactive', 32 | 'Programming Language :: Python', 33 | 'Programming Language :: Python :: 2', 34 | 'Programming Language :: Python :: 2.7', 35 | 'Programming Language :: Python :: 3', 36 | 'Programming Language :: Python :: 3.4', 37 | 'Programming Language :: Python :: 3.5', 38 | 'Programming Language :: Python :: Implementation :: CPython', 39 | 'Programming Language :: Python :: Implementation :: PyPy', 40 | 'Intended Audience :: Developers', 41 | 'Intended Audience :: System Administrators', 42 | 'License :: OSI Approved :: BSD License', 43 | 'Operating System :: OS Independent', 44 | 'Topic :: System :: Systems Administration', 45 | ] 46 | ) -------------------------------------------------------------------------------- /tests/test-requirements.txt: -------------------------------------------------------------------------------- 1 | cffi 2 | django 3 | requests[security] 4 | -------------------------------------------------------------------------------- /tests/test-requirements2.txt: -------------------------------------------------------------------------------- 1 | Django==1.9.6 2 | gunicorn 3 | requests[security] 4 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | # To reduce the size of the test matrix, tests the following pip versions: 3 | # * all of the latest major version releases (8.x) 4 | # * only the first and last point release of each older major version 5 | # * plus the latest distro version available via LTS Ubuntu package managers: 6 | # (1.5.4 for Trusty, 8.1.1 for Xenial). 7 | envlist = pip{154,156,600,611,700,712,800,801,802,803,810,811,812} 8 | 9 | [testenv] 10 | # Speeds up pip install and reduces log spam, for compatible versions of pip. 11 | setenv = PIP_DISABLE_PIP_VERSION_CHECK=1 12 | 13 | deps = 14 | pip154: pip==1.5.4 15 | pip156: pip==1.5.6 16 | pip600: pip==6.0.0 17 | pip611: pip==6.1.1 18 | pip700: pip==7.0.0 19 | pip712: pip==7.1.2 20 | pip800: pip==8.0.0 21 | pip801: pip==8.0.1 22 | pip802: pip==8.0.2 23 | pip803: pip==8.0.3 24 | pip810: pip==8.1.0 25 | pip811: pip==8.1.1 26 | pip812: pip==8.1.2 27 | 28 | # TODO: Replace with something like https://scripttest.readthedocs.io or else 29 | # rework the pip-grep and pip-diff scripts so they can more easily be unit-tested. 30 | commands = 31 | pip-grep tests/test-requirements.txt cffi 32 | pip-grep tests/test-requirements.txt requests 33 | pip-diff --fresh tests/test-requirements.txt tests/test-requirements2.txt 34 | pip-diff --stale tests/test-requirements.txt tests/test-requirements2.txt 35 | --------------------------------------------------------------------------------