pax_global_header00006660000000000000000000000064133250375020014512gustar00rootroot0000000000000052 comment=8cb9b77943e26165ce4539b311555b5aad51c48a requirements-detector-0.6/000077500000000000000000000000001332503750200157115ustar00rootroot00000000000000requirements-detector-0.6/.coveragerc000066400000000000000000000000441332503750200200300ustar00rootroot00000000000000 [run] source=requirements_detector requirements-detector-0.6/.gitignore000066400000000000000000000004571332503750200177070ustar00rootroot00000000000000*.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 requirements-detector-0.6/.landscape.yaml000066400000000000000000000000741332503750200206060ustar00rootroot00000000000000doc-warnings: no strictness: veryhigh max-line-length: 120 requirements-detector-0.6/.travis.yml000066400000000000000000000004621332503750200200240ustar00rootroot00000000000000sudo: false language: python python: - "2.6" - "2.7" - "3.4" - "3.5" - "3.6" - "3.7-dev" install: - "pip install nose coverage coveralls" - "pip install --editable ." script: nosetests -s --with-coverage --cover-package requirements_detector --cover-inclusive after_success: coveralls requirements-detector-0.6/LICENSE000066400000000000000000000020201332503750200167100ustar00rootroot00000000000000The MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. requirements-detector-0.6/MANIFEST.in000066400000000000000000000000601332503750200174430ustar00rootroot00000000000000include bin/detect-requirements include LICENSE requirements-detector-0.6/README.md000066400000000000000000000040611332503750200171710ustar00rootroot00000000000000# Requirements Detector ## Status [![Latest Version](https://img.shields.io/pypi/v/requirements-detector.svg?label=version&style=flat)](https://pypi.python.org/pypi/requirements-detector) [![Build Status](https://travis-ci.org/landscapeio/requirements-detector.png?branch=master)](https://travis-ci.org/landscapeio/requirements-detector) [![Health](https://landscape.io/github/landscapeio/requirements-detector/master/landscape.svg?style=flat)](https://landscape.io/github/landscapeio/requirements-detector/master) [![Coverage Status](https://img.shields.io/coveralls/landscapeio/requirements-detector.svg?style=flat)](https://coveralls.io/r/landscapeio/requirements-detector) [![Documentation](https://readthedocs.org/projects/requirements-detector/badge/?version=master)](https://readthedocs.org/projects/requirements-detector/) ## About `requirements-detector` is a simple Python tool which attempts to find and list the requirements of a Python project. When run from the root of a Python project, it will try to ascertain which libraries and the versions of those libraries that the project depends on. It uses the following methods in order, in the root of the project: 1. Parse `setup.py` (if this is successful, the remaining steps are skipped) 2. Parse `requirements.txt` or `requirements.pip` 3. Parse all `*.txt` and `*.pip` files inside a folder called `requirements` 4. Parse all files in the root folder matching `*requirements*.txt` or `reqs.txt` (so for example, `pip_requirements.txt` would match, as would `requirements_common.txt`) ### Usage ``` detect-requirements [path] ``` If `path` is not specified, the current working directory will be used. ### Output The output will be plaintext, and match that of a [pip requirements file](http://www.pip-installer.org/en/latest/logic.html), for example: ``` Django==1.5.2 South>=0.8 anyjson celery>=2.2,<3 ``` ### Usage From Python ``` >>> import os >>> from requirements_detector import find_requirements >>> find_requirements(os.getcwd()) [DetectedRequirement:Django==1.5.2, DetectedRequirement:South>=0.8, ...] ``` requirements-detector-0.6/bin/000077500000000000000000000000001332503750200164615ustar00rootroot00000000000000requirements-detector-0.6/bin/detect-requirements000077500000000000000000000001051332503750200223740ustar00rootroot00000000000000#!/usr/bin/env python from requirements_detector.run import run run()requirements-detector-0.6/requirements_detector/000077500000000000000000000000001332503750200223255ustar00rootroot00000000000000requirements-detector-0.6/requirements_detector/__compat__.py000066400000000000000000000006071332503750200247610ustar00rootroot00000000000000# Various shims to deal with node renames between astroid versions - astroid 2.0 renamed # some of the nodes used by this library so for backwards compatibility, old names are # translated to new. try: from astroid import Call except ImportError: from astroid import CallFunc as Call try: from astroid import AssignName except ImportError: from astroid import AssName as AssignName requirements-detector-0.6/requirements_detector/__init__.py000066400000000000000000000000721332503750200244350ustar00rootroot00000000000000from requirements_detector.detect import find_requirementsrequirements-detector-0.6/requirements_detector/detect.py000066400000000000000000000216251332503750200241550ustar00rootroot00000000000000import re import os import sys from astroid.builder import AstroidBuilder from astroid import MANAGER, Name, Assign, Keyword, List, Tuple, Const from requirements_detector.__compat__ import Call, AssignName from requirements_detector.requirement import DetectedRequirement __all__ = ['find_requirements', 'RequirementsNotFound', 'CouldNotParseRequirements'] # PEP263, see http://legacy.python.org/dev/peps/pep-0263/ _ENCODING_REGEXP = re.compile(r'coding[:=]\s*([-\w.]+)') _PY3K = sys.version_info >= (3, 0) _PIP_OPTIONS = ( '-i', '--index-url', '--extra-index-url', '--no-index', '-f', '--find-links', '-r' ) class RequirementsNotFound(Exception): pass class CouldNotParseRequirements(Exception): pass def _load_file_contents(filepath): # This function is a bit of a tedious workaround (AKA 'hack'). # Astroid calls 'compile' under the hood, which refuses to accept a Unicode # object which contains a PEP-263 encoding definition. However if we give # Astroid raw bytes, it'll assume ASCII. Therefore we need to detect the encoding # here, convert the file contents to a Unicode object, *and also strip the encoding # declaration* to avoid the compile step breaking. with open(filepath) as f: if _PY3K: return f.read() contents = f.readlines() result = [] encoding_lines = contents[0:2] encoding = 'utf-8' for line in encoding_lines: match = _ENCODING_REGEXP.search(line) if match is None: result.append(line.strip()) else: encoding = match.group(1) result += [line.rstrip() for line in contents[2:]] result = '\n'.join(result) return result.decode(encoding) def find_requirements(path): """ This method tries to determine the requirements of a particular project by inspecting the possible places that they could be defined. It will attempt, in order: 1) to parse setup.py in the root for an install_requires value 2) to read a requirements.txt file or a requirements.pip in the root 3) to read all .txt files in a folder called 'requirements' in the root 4) to read files matching "*requirements*.txt" and "*reqs*.txt" in the root, excluding any starting or ending with 'test' If one of these succeeds, then a list of pkg_resources.Requirement's will be returned. If none can be found, then a RequirementsNotFound will be raised """ requirements = [] setup_py = os.path.join(path, 'setup.py') if os.path.exists(setup_py) and os.path.isfile(setup_py): try: requirements = from_setup_py(setup_py) requirements.sort() return requirements except CouldNotParseRequirements: pass for reqfile_name in ('requirements.txt', 'requirements.pip'): reqfile_path = os.path.join(path, reqfile_name) if os.path.exists(reqfile_path) and os.path.isfile(reqfile_path): try: requirements += from_requirements_txt(reqfile_path) except CouldNotParseRequirements as e: pass requirements_dir = os.path.join(path, 'requirements') if os.path.exists(requirements_dir) and os.path.isdir(requirements_dir): from_dir = from_requirements_dir(requirements_dir) if from_dir is not None: requirements += from_dir from_blob = from_requirements_blob(path) if from_blob is not None: requirements += from_blob requirements = list(set(requirements)) if len(requirements) > 0: requirements.sort() return requirements raise RequirementsNotFound class SetupWalker(object): def __init__(self, ast): self._ast = ast self._setup_call = None self._top_level_assigns = {} self.walk() def walk(self, node=None): top = node is None node = node or self._ast # test to see if this is a call to setup() if isinstance(node, Call): for child_node in node.get_children(): if isinstance(child_node, Name) and child_node.name == 'setup': # TODO: what if this isn't actually the distutils setup? self._setup_call = node for child_node in node.get_children(): if top and isinstance(child_node, Assign): for target in child_node.targets: if isinstance(target, AssignName): self._top_level_assigns[target.name] = child_node.value self.walk(child_node) def _get_list_value(self, list_node): values = [] for child_node in list_node.get_children(): if not isinstance(child_node, Const): # we can't handle anything fancy, only constant values raise CouldNotParseRequirements values.append(child_node.value) return values def get_requires(self): # first, if we have a call to setup, then we can see what its "install_requires" argument is if not self._setup_call: raise CouldNotParseRequirements found_requirements = [] for child_node in self._setup_call.get_children(): if not isinstance(child_node, Keyword): # do we want to try to handle positional arguments? continue if child_node.arg not in ('install_requires', 'requires'): continue if isinstance(child_node.value, (List, Tuple)): # joy! this is a simple list or tuple of requirements # this is a Keyword -> List or Keyword -> Tuple found_requirements += self._get_list_value(child_node.value) continue if isinstance(child_node.value, Name): # otherwise, it's referencing a value defined elsewhere # this will be a Keyword -> Name try: reqs = self._top_level_assigns[child_node.value.name] except KeyError: raise CouldNotParseRequirements else: if isinstance(reqs, (List, Tuple)): found_requirements += self._get_list_value(reqs) continue # otherwise it's something funky and we can't handle it raise CouldNotParseRequirements # if we've fallen off the bottom with nothing in our list of requirements, # we simply didn't find anything useful if len(found_requirements) > 0: return found_requirements raise CouldNotParseRequirements def from_setup_py(setup_file): try: from astroid import AstroidBuildingException except ImportError: syntax_exceptions = (SyntaxError,) else: syntax_exceptions = (SyntaxError, AstroidBuildingException) try: contents = _load_file_contents(setup_file) ast = AstroidBuilder(MANAGER).string_build(contents) except syntax_exceptions: # if the setup file is broken, we can't do much about that... raise CouldNotParseRequirements walker = SetupWalker(ast) requirements = [] for req in walker.get_requires(): requirements.append(DetectedRequirement.parse(req, setup_file)) return [requirement for requirement in requirements if requirement is not None] def from_requirements_txt(requirements_file): # see http://www.pip-installer.org/en/latest/logic.html requirements = [] with open(requirements_file) as f: for req in f.readlines(): if req.strip() == '': # empty line continue if req.strip().startswith('#'): # this is a comment continue if req.strip().split()[0] in _PIP_OPTIONS: # this is a pip option continue detected = DetectedRequirement.parse(req, requirements_file) if detected is None: continue requirements.append(detected) return requirements def from_requirements_dir(path): requirements = [] for entry in os.listdir(path): filepath = os.path.join(path, entry) if not os.path.isfile(filepath): continue if entry.endswith('.txt') or entry.endswith('.pip'): # TODO: deal with duplicates requirements += from_requirements_txt(filepath) return requirements def from_requirements_blob(path): requirements = [] for entry in os.listdir(path): filepath = os.path.join(path, entry) if not os.path.isfile(filepath): continue m = re.match(r'^(\w*)req(uirement)?s(\w*)\.txt$', entry) if m is None: continue if m.group(1).startswith('test') or m.group(3).endswith('test'): continue requirements += from_requirements_txt(filepath) return requirements requirements-detector-0.6/requirements_detector/formatters.py000066400000000000000000000003621332503750200250660ustar00rootroot00000000000000 import sys def requirements_file(requirements_list): for requirement in requirements_list: sys.stdout.write(requirement.pip_format()) sys.stdout.write('\n') FORMATTERS = { 'requirements_file': requirements_file } requirements-detector-0.6/requirements_detector/requirement.py000066400000000000000000000114521332503750200252420ustar00rootroot00000000000000""" This module represents the various types of requirement that can be specified for a project. It is somewhat redundant to re-implement here as we could use `pip.req.InstallRequirement`, but that would require depending on pip which is not easy to do since it will usually be installed by the user at a specific version. Additionally, the pip implementation has a lot of extra features that we don't need - we don't expect relative file paths to exist, for example. Note that the parsing here is also intentionally more lenient - it is not our job to validate the requirements list. """ import os import re from pkg_resources import Requirement try: import urlparse except ImportError: # python3 from urllib import parse as urlparse def _is_filepath(req): # this is (probably) a file return os.path.sep in req or req.startswith('.') def _parse_egg_name(url_fragment): """ >>> _parse_egg_name('egg=fish&cake=lala') fish >>> _parse_egg_name('something_spurious') None """ if '=' not in url_fragment: return None parts = urlparse.parse_qs(url_fragment) if 'egg' not in parts: return None return parts['egg'][0] # taking the first value mimics pip's behaviour def _strip_fragment(urlparts): new_urlparts = ( urlparts.scheme, urlparts.netloc, urlparts.path, urlparts.params, urlparts.query, None ) return urlparse.urlunparse(new_urlparts) class DetectedRequirement(object): def __init__(self, name=None, url=None, requirement=None, location_defined=None): if requirement is not None: self.name = requirement.key self.requirement = requirement self.version_specs = requirement.specs self.url = None else: self.name = name self.version_specs = [] self.url = url self.requirement = None self.location_defined = location_defined def _format_specs(self): return ','.join(['%s%s' % (comp, version) for comp, version in self.version_specs]) def pip_format(self): if self.url: if self.name: return '%s#egg=%s' % (self.url, self.name) return self.url if self.name: if self.version_specs: return "%s%s" % (self.name, self._format_specs()) return self.name def __str__(self): rep = self.name or 'Unknown' if self.version_specs: specs = ','.join(['%s%s' % (comp, version) for comp, version in self.version_specs]) rep = '%s%s' % (rep, specs) if self.url: rep = '%s (%s)' % (rep, self.url) return rep def __hash__(self): return hash(str(self.name) + str(self.url) + str(self.version_specs)) def __repr__(self): return 'DetectedRequirement:%s' % str(self) def __eq__(self, other): return self.name == other.name and self.url == other.url and self.version_specs == other.version_specs def __gt__(self, other): return (self.name or "") > (other.name or "") @staticmethod def parse(line, location_defined=None): # the options for a Pip requirements file are: # # 1) # 2) # 3) (#egg=)? # 4) (#egg=)? # 5) # 6) (-e|--editable) (#egg=#egg= line = line.strip() # strip the editable flag line = re.sub('^(-e|--editable) ', '', line) url = urlparse.urlparse(line) # if it is a VCS URL, then we want to strip off the protocol as urlparse # might not handle it correctly vcs_scheme = None if '+' in url.scheme or url.scheme in ('git',): if url.scheme == 'git': vcs_scheme = 'git+git' else: vcs_scheme = url.scheme url = urlparse.urlparse(re.sub(r'^%s://' % re.escape(url.scheme), '', line)) if vcs_scheme is None and url.scheme == '' and not _is_filepath(line): # if we are here, it is a simple dependency try: req = Requirement.parse(line) except ValueError: # this happens if the line is invalid return None else: return DetectedRequirement(requirement=req, location_defined=location_defined) # otherwise, this is some kind of URL name = _parse_egg_name(url.fragment) url = _strip_fragment(url) if vcs_scheme: url = '%s://%s' % (vcs_scheme, url) return DetectedRequirement(name=name, url=url, location_defined=location_defined) requirements-detector-0.6/requirements_detector/run.py000066400000000000000000000015211332503750200235020ustar00rootroot00000000000000import os import sys from requirements_detector.detect import RequirementsNotFound from requirements_detector.formatters import FORMATTERS from requirements_detector import find_requirements def _die(message): sys.stderr.write("%s\n" % message) sys.exit(1) def run(): if len(sys.argv) > 1: path = sys.argv[1] else: path = os.getcwd() if not os.path.exists(path): _die("%s does not exist" % path) if not os.path.isdir(path): _die("%s is not a directory" % path) try: requirements = find_requirements(path) except RequirementsNotFound: _die("Unable to find requirements at %s" % path) format_name = 'requirements_file' # TODO: other output formats such as JSON FORMATTERS[format_name](requirements) sys.exit(0) if __name__ == '__main__': run() requirements-detector-0.6/setup.py000066400000000000000000000026771332503750200174370ustar00rootroot00000000000000# -*- coding: UTF-8 -*- from distutils.core import setup from setuptools import find_packages import sys _version = "0.6" _packages = find_packages(exclude=["*.tests", "*.tests.*", "tests.*", "tests"]) _short_description = "Python tool to find and list requirements of a Python project" _CLASSIFIERS = [ 'Development Status :: 5 - Production/Stable', 'Environment :: Console', 'Intended Audience :: Developers', 'Operating System :: Unix', 'Topic :: Software Development :: Quality Assurance', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'License :: OSI Approved :: MIT License', ] if sys.version_info < (2, 7): # pylint 1.4 dropped support for Python 2.6 _install_requires = [ 'astroid>=1.0,<1.3.0', ] else: _install_requires = [ 'astroid>=1.4', ] setup( name='requirements-detector', url='https://github.com/landscapeio/requirements-detector', author='landscape.io', author_email='code@landscape.io', description=_short_description, version=_version, scripts=['bin/detect-requirements'], install_requires=_install_requires, packages=_packages, license='MIT', keywords='python requirements detector', classifiers=_CLASSIFIERS, ) requirements-detector-0.6/tests/000077500000000000000000000000001332503750200170535ustar00rootroot00000000000000requirements-detector-0.6/tests/__init__.py000066400000000000000000000000001332503750200211520ustar00rootroot00000000000000requirements-detector-0.6/tests/detection/000077500000000000000000000000001332503750200210315ustar00rootroot00000000000000requirements-detector-0.6/tests/detection/syntax_error/000077500000000000000000000000001332503750200235705ustar00rootroot00000000000000requirements-detector-0.6/tests/detection/syntax_error/regular_indentation.py000066400000000000000000000004151332503750200301770ustar00rootroot00000000000000# -*- coding: UTF-8 -*- from distutils.core import setup if foo: # just for a test with indentation bar() setup( name=u'prospector-test-4-üéø', version='0.0.1', install_requires=[ 'Django==1.5.0', 'django-gubbins==1.1.2' ] )requirements-detector-0.6/tests/detection/syntax_error/setup.py000066400000000000000000000003021332503750200252750ustar00rootroot00000000000000from distutils.core import setup setup( name='prospector-test-1', version='0.0.1', install_requires=[ 'Django==1.5.0', 'django-gubbins==1.1.2' ] narm narm narm )requirements-detector-0.6/tests/detection/syntax_error/setup_multiline_string.py000066400000000000000000000003631332503750200307540ustar00rootroot00000000000000from distutils.core import setup comment = 'this is a long comment ' \ 'on two lines' setup( name='prospector-test-1', version='0.0.1', install_requires=[ 'Django==1.5.0', 'django-gubbins==1.1.2' ] )requirements-detector-0.6/tests/detection/test1/000077500000000000000000000000001332503750200220715ustar00rootroot00000000000000requirements-detector-0.6/tests/detection/test1/requirements.txt000066400000000000000000000001541332503750200253550ustar00rootroot00000000000000-i https://example.com/custom/pypi Django>=1.5.0 South==0.8.2 amqp!=1.0.13 # we want six too six<1.4,>=1.3.0requirements-detector-0.6/tests/detection/test2/000077500000000000000000000000001332503750200220725ustar00rootroot00000000000000requirements-detector-0.6/tests/detection/test2/requirements/000077500000000000000000000000001332503750200246155ustar00rootroot00000000000000requirements-detector-0.6/tests/detection/test2/requirements/base.txt000066400000000000000000000000341332503750200262650ustar00rootroot00000000000000amqp==1.0.13 anyjson==0.3.3 requirements-detector-0.6/tests/detection/test2/requirements/webui.pip000066400000000000000000000000321332503750200264350ustar00rootroot00000000000000Django==1.5.2 South==0.8.2requirements-detector-0.6/tests/detection/test3/000077500000000000000000000000001332503750200220735ustar00rootroot00000000000000requirements-detector-0.6/tests/detection/test3/pip_requirements.txt000066400000000000000000000000171332503750200262250ustar00rootroot00000000000000anyjson==0.3.3 requirements-detector-0.6/tests/detection/test3/reqs.txt000066400000000000000000000000251332503750200236030ustar00rootroot00000000000000django-gubbins==1.1.2requirements-detector-0.6/tests/detection/test3/requirements_base.txt000066400000000000000000000000151332503750200263450ustar00rootroot00000000000000amqp==1.0.13 requirements-detector-0.6/tests/detection/test3/requirements_test.txt000066400000000000000000000000141332503750200264110ustar00rootroot00000000000000South==0.8.2requirements-detector-0.6/tests/detection/test3/test_requirements.txt000066400000000000000000000000151332503750200264120ustar00rootroot00000000000000Django==1.5.2requirements-detector-0.6/tests/detection/test4/000077500000000000000000000000001332503750200220745ustar00rootroot00000000000000requirements-detector-0.6/tests/detection/test4/callable.py000066400000000000000000000003541332503750200242070ustar00rootroot00000000000000from distutils.core import setup def _install_requires(): return [ 'Django==1.5.0', 'django-gubbins==1.1.2' ] setup( name='prospector-test-2', version='0.0.1', install_requires=_install_requires() )requirements-detector-0.6/tests/detection/test4/in_file.py000066400000000000000000000003161332503750200240530ustar00rootroot00000000000000from distutils.core import setup _install_requires = [ 'Django==1.5.0', 'django-gubbins==1.1.2' ] setup( name='prospector-test-2', version='0.0.1', install_requires=_install_requires )requirements-detector-0.6/tests/detection/test4/simple.py000066400000000000000000000002631332503750200237400ustar00rootroot00000000000000from distutils.core import setup setup( name='prospector-test-1', version='0.0.1', install_requires=[ 'Django==1.5.0', 'django-gubbins==1.1.2' ] )requirements-detector-0.6/tests/detection/test4/subscript_assign.py000066400000000000000000000006411332503750200260310ustar00rootroot00000000000000""" This test is to verify that top level subscript assigns (x[y]) don't break the parser. For version <=0.1, a subscript assign would break the setup.py AST walker completely. """ from distutils.core import setup something = dict() something['fish'] = ['a', 'b', 'c'] setup( name='prospector-test-1', version='0.0.1', install_requires=( 'Django==1.5.0', 'django-gubbins==1.1.2' ) )requirements-detector-0.6/tests/detection/test4/tuple.py000066400000000000000000000002631332503750200236000ustar00rootroot00000000000000from distutils.core import setup setup( name='prospector-test-1', version='0.0.1', install_requires=( 'Django==1.5.0', 'django-gubbins==1.1.2' ) )requirements-detector-0.6/tests/detection/test4/uses_requires.py000066400000000000000000000002531332503750200253440ustar00rootroot00000000000000from distutils.core import setup setup( name='prospector-test-1', version='0.0.1', requires=[ 'Django==1.5.0', 'django-gubbins==1.1.2' ] )requirements-detector-0.6/tests/detection/test4/uses_requires_and_install_requires.py000066400000000000000000000003111332503750200316260ustar00rootroot00000000000000from distutils.core import setup setup( name='prospector-test-1', version='0.0.1', requires=[ 'Django==1.5.0', ], install_requires=[ 'django-gubbins==1.1.2' ] )requirements-detector-0.6/tests/detection/test4/utf8.py000066400000000000000000000003231332503750200233320ustar00rootroot00000000000000# -*- coding: UTF-8 -*- from distutils.core import setup setup( name=u'prospector-test-4-üéø', version='0.0.1', install_requires=[ 'Django==1.5.0', 'django-gubbins==1.1.2' ] )requirements-detector-0.6/tests/detection/test5/000077500000000000000000000000001332503750200220755ustar00rootroot00000000000000requirements-detector-0.6/tests/detection/test5/invalid_requirements.txt000066400000000000000000000000561332503750200270700ustar00rootroot00000000000000<<<<<<< HEAD django<1.6 ======= django >>>>>>>requirements-detector-0.6/tests/detection/test6/000077500000000000000000000000001332503750200220765ustar00rootroot00000000000000requirements-detector-0.6/tests/detection/test6/requirements.txt000066400000000000000000000022131332503750200253600ustar00rootroot00000000000000# core framework uwsgi flask # flask extensions flask-mail flask-migrate flask-oauthlib flask-script flask-scrypt flask-weasyprint flask-sqlalchemy #flask-testing git+git://github.com/jarus/flask-testing.git flask-jwt flask-bouncer flask-restful flask-marshmallow flask-assets flask-collect awesome-slugify # testing #fake-factory https://github.com/traumtopf/faker/tarball/master surrealism freezegun selenium testtools # maybe extensions #restless #flash-babel #flask-restless #flask-assets #flask-cache #flask-classy #flask-debugtoolbar #flask-googlemaps #flask-gravatar #flask-jinjahelpers #flask-lesscss #flask-markdown #flask-moment #flask-principal #flask-restdoc #flask-runner #flask-user # other extensions #wtforms-alchemy #sqlalchemy-defaults #wtforms-components #wtforms-json # testing #coverage # correctness #pyflakes #pep8 #flake8 #pylint prospector # other stuff paypalrestsdk #outputty pycountry #certifi psycopg2 #raven[flask] # client for sentry (a web application error logger) git+https://github.com/lazzrek/raven-python.git#egg=raven[flask] sadisplay==0.3.8dev isodate colour-runner requests pygeoip # webassets filters rjsmin cssminrequirements-detector-0.6/tests/test_detection.py000066400000000000000000000071601332503750200224460ustar00rootroot00000000000000import os from unittest import TestCase from requirements_detector.detect import from_requirements_txt, from_requirements_dir, \ from_requirements_blob, from_setup_py, CouldNotParseRequirements from requirements_detector.requirement import DetectedRequirement class DependencyDetectionTest(TestCase): def _expected(self, *requirements): return [DetectedRequirement.parse(req) for req in requirements] def test_requirements_txt_parsing(self): filepath = os.path.join(os.path.dirname(__file__), 'detection/test1/requirements.txt') dependencies = from_requirements_txt(filepath) expected = self._expected( 'amqp!=1.0.13', 'Django>=1.5.0', 'six<1.4,>=1.3.0', 'South==0.8.2', ) self.assertEqual(expected, sorted(dependencies)) def test_requirements_dir_parsing(self): filepath = os.path.join(os.path.dirname(__file__), 'detection/test2/requirements') dependencies = from_requirements_dir(filepath) expected = self._expected( 'amqp==1.0.13', 'anyjson==0.3.3', 'Django==1.5.2', 'South==0.8.2', ) self.assertEqual(expected, sorted(dependencies)) def test_requirements_blob_parsing(self): filepath = os.path.join(os.path.dirname(__file__), 'detection/test3') dependencies = from_requirements_blob(filepath) expected = self._expected( 'amqp==1.0.13', 'anyjson==0.3.3', 'django-gubbins==1.1.2', ) self.assertEqual(expected, sorted(dependencies)) def test_invalid_requirements_txt(self): filepath = os.path.join(os.path.dirname(__file__), 'detection/test5/invalid_requirements.txt') dependencies = from_requirements_txt(filepath) expected = self._expected('django<1.6', 'django') self.assertEqual(expected, sorted(dependencies)) def test_invalid_requirements_txt(self): filepath = os.path.join(os.path.dirname(__file__), 'detection/test6/requirements.txt') from_requirements_txt(filepath) def _test_setup_py(self, setup_py_file, *expected): filepath = os.path.join(os.path.dirname(__file__), 'detection/test4', setup_py_file) dependencies = from_setup_py(filepath) expected = self._expected(*expected) self.assertEqual(expected, sorted(dependencies)) def _test_setup_py_not_parseable(self, setup_py_file): filepath = os.path.join(os.path.dirname(__file__), 'detection/test4', setup_py_file) self.assertRaises(CouldNotParseRequirements, from_setup_py, filepath) def test_simple_setup_py_parsing(self): self._test_setup_py('simple.py', 'Django==1.5.0', 'django-gubbins==1.1.2') def test_setup_py_reqs_defined_in_file_parsing(self): self._test_setup_py('in_file.py', 'Django==1.5.0', 'django-gubbins==1.1.2') def test_setup_py_tuple(self): self._test_setup_py('tuple.py', 'Django==1.5.0', 'django-gubbins==1.1.2') def test_subscript_assign(self): self._test_setup_py('subscript_assign.py', 'Django==1.5.0', 'django-gubbins==1.1.2') def test_utf8_setup_py(self): self._test_setup_py('utf8.py', 'Django==1.5.0', 'django-gubbins==1.1.2') def test_requires_setup_py(self): self._test_setup_py('uses_requires.py', 'Django==1.5.0', 'django-gubbins==1.1.2') def test_requires_and_install_requires_setup_py(self): self._test_setup_py('uses_requires_and_install_requires.py', 'Django==1.5.0', 'django-gubbins==1.1.2') def test_callable_install_requires(self): self._test_setup_py_not_parseable('callable.py')requirements-detector-0.6/tests/test_failure_cases.py000066400000000000000000000013471332503750200232760ustar00rootroot00000000000000import os from unittest import TestCase from requirements_detector.detect import from_setup_py, CouldNotParseRequirements class SyntaxErrorTest(TestCase): def test_setup_py_syntax_error(self): filepath = os.path.join(os.path.dirname(__file__), 'detection/syntax_error/setup.py') self.assertRaises(CouldNotParseRequirements, from_setup_py, filepath) def test_setup_py_multiline_string(self): filepath = os.path.join(os.path.dirname(__file__), 'detection/syntax_error/setup_multiline_string.py') from_setup_py(filepath) def test_regular_indentation(self): filepath = os.path.join(os.path.dirname(__file__), 'detection/syntax_error/regular_indentation.py') from_setup_py(filepath) requirements-detector-0.6/tests/test_parsing.py000066400000000000000000000060231332503750200221300ustar00rootroot00000000000000 try: import urlparse except ImportError: # python3 from urllib import parse as urlparse from unittest import TestCase from requirements_detector.requirement import DetectedRequirement, _parse_egg_name, _strip_fragment class TestRequirementParsing(TestCase): def _test(self, requirement, name=None, version_specs=None, url=None): req = DetectedRequirement.parse(requirement) self.assertEqual(name, req.name) if version_specs is None: self.assertEqual([], req.version_specs) else: for spec in version_specs: self.assertTrue(spec in req.version_specs) self.assertEqual(url, req.url) def test_basic_requirement(self): self._test('Django', 'django') self._test('celery', 'celery') def test_requirement_with_versions(self): self._test('Django==1.5.2', 'django', [('==', '1.5.2')]) self._test('South>0.8', 'south', [('>', '0.8')]) self._test('django-gubbins!=1.1.1,>1.1', 'django-gubbins', [('!=', '1.1.1'), ('>', '1.1')]) def test_relative_file_path(self): self._test('../somelib', url='../somelib') def test_vcs_url(self): self._test('git+ssh://git@github.com/something/somelib.git', url='git+ssh://git@github.com/something/somelib.git') self._test('git+ssh://git@github.com/something/somelib.git#egg=somelib', name='somelib', url='git+ssh://git@github.com/something/somelib.git') self._test('git://github.com/peeb/django-mollie-ideal.git#egg=mollie', name='mollie', url='git+git://github.com/peeb/django-mollie-ideal.git') def test_archive_url(self): self._test('http://example.com/somelib.tar.gz', url='http://example.com/somelib.tar.gz') self._test('http://example.com/somelib.tar.gz#egg=somelib', name='somelib', url='http://example.com/somelib.tar.gz') def test_editable_relative_path(self): self._test('-e ../somelib', url='../somelib') def test_editable_vcs_url(self): self._test('--editable git+ssh://git@github.com/something/somelib.git#egg=somelib', name='somelib', url='git+ssh://git@github.com/something/somelib.git') class TestEggFragmentParsing(TestCase): def test_simple(self): self.assertEqual('somelib', _parse_egg_name('egg=somelib')) def test_no_egg_value(self): self.assertTrue(_parse_egg_name('a=b&c=2') is None) def test_no_pairs(self): self.assertTrue(_parse_egg_name('somelib') is None) def test_first_egg_val(self): self.assertEqual('somelib', _parse_egg_name('egg=somelib&egg=anotherlib')) def test_multiple_fragment_values(self): self.assertEqual('somelib', _parse_egg_name('a=1&egg=somelib&b=2')) class TestFragmentStripping(TestCase): def test_stripping(self): url = 'http://example.com/index.html?a=b&c=2#some_fragment' parts = urlparse.urlparse(url) self.assertEqual('http://example.com/index.html?a=b&c=2', _strip_fragment(parts)) requirements-detector-0.6/tox.ini000066400000000000000000000001241332503750200172210ustar00rootroot00000000000000[tox] envlist = py26,py27,py33,py34,py35 [testenv] deps=nose commands=nosetests -s