Skip to content
Find file
097d52e
229 lines (201 sloc) 8.21 KB
import os
import re
import logging
import warnings
from pkg_resources import Requirement as Req
try:
from urllib.parse import urlparse
except ImportError:
from urlparse import urlparse
__version__ = '0.1.0'
logging.basicConfig(level=logging.WARNING)
VCS = ['git', 'hg', 'svn', 'bzr']
class Requirement(object):
"""
This class is inspired from
https://github.com/davidfischer/requirements-parser/blob/master/requirements/requirement.py#L30
License: BSD
"""
def __init__(self, line):
self.line = line
self.is_editable = False
self.is_local_file = False
self.is_specifier = False
self.vcs = None
self.name = None
self.uri = None
self.full_uri = None
self.path = None
self.revision = None
self.scheme = None
self.login = None
self.extras = []
self.specs = []
def __repr__(self):
return '<Requirement: "{0}">'.format(self.line)
@classmethod
def parse(cls, line, editable=False):
"""
Parses a Requirement from an "editable" requirement which is either
a local project path or a VCS project URI.
See: pip/req.py:from_editable()
:param line: an "editable" requirement
:returns: a Requirement instance for the given line
:raises: ValueError on an invalid requirement
"""
if editable:
req = cls('-e {0}'.format(line))
req.is_editable = True
else:
req = cls(line)
url = urlparse(line)
req.uri = None
if url.scheme:
req.scheme = url.scheme
req.uri = url.scheme + '://' + url.netloc + url.path
fragment = url.fragment.split(' ')[0].strip()
req.name = fragment.split('egg=')[-1] or None
req.path = url.path
if fragment:
req.uri += '#{}'.format(fragment)
if url.username or url.password:
username = url.username or ''
password = url.password or ''
req.login = username + ':' + password
if '@' in url.path:
req.revision = url.path.split('@')[-1]
for vcs in VCS:
if req.uri.startswith(vcs):
req.vcs = vcs
if req.scheme.startswith('file://'):
req.is_local_file = True
if not req.vcs and not req.is_local_file and 'egg=' not in line:
# This is a requirement specifier.
# Delegate to pkg_resources and hope for the best
req.is_specifier = True
pkg_req = Req.parse(line)
req.name = pkg_req.unsafe_name
req.extras = list(pkg_req.extras)
req.specs = pkg_req.specs
if req.specs:
req.specs = sorted(req.specs)
return req
class Requirements:
def __init__(
self,
requirements="requirements.txt",
tests_requirements="requirements/tests.txt"):
self.requirements_path = requirements
self.tests_requirements_path = tests_requirements
def format_specifiers(self, requirement):
return ', '.join(
['{} {}'.format(s[0], s[1]) for s in requirement.specs])
@property
def install_requires(self):
dependencies = []
for requirement in self.parse(self.requirements_path):
if not requirement.is_editable and not requirement.uri \
and not requirement.vcs:
full_name = requirement.name
specifiers = self.format_specifiers(requirement)
if specifiers:
full_name = "{} {}".format(full_name, specifiers)
dependencies.append(full_name)
for requirement in self.get_dependency_links():
print(":: (base:install_requires) {}".format(requirement.name))
dependencies.append(requirement.name)
return dependencies
@property
def tests_require(self):
dependencies = []
for requirement in self.parse(self.tests_requirements_path):
if not requirement.is_editable and not requirement.uri \
and not requirement.vcs:
full_name = requirement.name
specifiers = self.format_specifiers(requirement)
if specifiers:
full_name = "{} {}".format(full_name, specifiers)
print(":: (tests:tests_require) {}".format(full_name))
dependencies.append(full_name)
return dependencies
@property
def dependency_links(self):
dependencies = []
for requirement in self.parse(self.requirements_path):
if requirement.uri or requirement.vcs or requirement.path:
print(":: (base:dependency_links) {}".format(
requirement.uri))
dependencies.append(requirement.uri)
return dependencies
@property
def dependencies(self):
install_requires = self.install_requires
dependency_links = self.dependency_links
tests_require = self.tests_require
if dependency_links:
print(
"\n"
"!! Some dependencies are linked to repository or local path.")
print(
"!! You'll need to run pip with following option: "
"`--process-dependency-links`"
"\n")
return {
'install_requires': install_requires,
'dependency_links': dependency_links,
'tests_require': tests_require}
def get_dependency_links(self):
dependencies = []
for requirement in self.parse(self.requirements_path):
if requirement.uri or requirement.vcs or requirement.path:
dependencies.append(requirement)
return dependencies
def parse(self, path=None):
path = path or self.requirements_path
path = os.path.abspath(path)
base_directory = os.path.dirname(path)
if not os.path.exists(path):
warnings.warn(
'Requirements file: {} does not exists.'.format(path))
return
with open(path) as requirements:
for index, line in enumerate(requirements.readlines()):
index += 1
line = line.strip()
if not line:
logging.debug('Empty line (line {} from {})'.format(
index, path))
continue
elif line.startswith('#'):
logging.debug(
'Comments line (line {} from {})'.format(index, path))
elif line.startswith('-f') or \
line.startswith('--find-links') or \
line.startswith('-i') or \
line.startswith('--index-url') or \
line.startswith('--extra-index-url') or \
line.startswith('--no-index'):
warnings.warn('Private repos not supported. Skipping.')
continue
elif line.startswith('-Z') or line.startswith(
'--always-unzip'):
warnings.warn('Unused option --always-unzip. Skipping.')
continue
elif line.startswith('-r') or line.startswith('--requirement'):
logging.debug(
'Pining to another requirements file '
'(line {} from {})'.format(index, path))
for _line in self.parse(path=os.path.join(
base_directory, line.split()[1])):
yield _line
elif line.startswith('-e') or line.startswith('--editable'):
# Editable installs are either a local project path
# or a VCS project URI
yield Requirement.parse(
re.sub(r'^(-e|--editable=?)\s*', '', line),
editable=True)
else:
logging.debug('Found "{}" (line {} from {})'.format(
line, index, path))
yield Requirement.parse(line, editable=False)
r = Requirements()
Something went wrong with that request. Please try again.