ÿØÿà JFIF ` ` ÿþxØ
| Server IP : 109.234.164.53 / Your IP : 216.73.216.136 Web Server : Apache System : Linux cervelle.o2switch.net 4.18.0-553.32.1.lve.el8.x86_64 #1 SMP Thu Dec 19 13:14:03 UTC 2024 x86_64 User : computer3 ( 1098) PHP Version : 7.1.33 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : ON | Sudo : OFF | Pkexec : OFF Directory : /opt/alt/python37/lib/python3.7/site-packages/requirements_detector/ |
Upload File : |
import 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