Source code for requirements_builder.requirements_builder

# -*- coding: utf-8 -*-
# This file is part of Requirements-Builder
# Copyright (C) 2015, 2016, 2017, 2018 CERN.
# Requirements-Builder is free software; you can redistribute it and/or
# modify it under the terms of the Revised BSD License; see LICENSE
# file for more details.
"""Generate requirements from `` and `requirements-devel.txt`."""

from __future__ import absolute_import, print_function

import os
import re
import sys

import mock
import pkg_resources
import setuptools

[docs]def parse_set(string): """Parse set from comma separated string.""" string = string.strip() if string: return set(string.split(",")) else: return set()
[docs]def minver_error(pkg_name): """Report error about missing minimum version constraint and exit.""" print( 'ERROR: specify minimal version of "{0}" using ' '">=" or "=="'.format(pkg_name), file=sys.stderr ) sys.exit(1)
[docs]def build_pkg_name(pkg): """Build package name, including extras if present.""" if pkg.extras: return '{0}[{1}]'.format( pkg.project_name, ','.join(sorted(pkg.extras))) return pkg.project_name
[docs]def parse_pip_file(path): """Parse pip requirements file.""" # requirement lines sorted by importance # also collect other pip commands rdev = {} rnormal = [] stuff = [] try: with open(path) as f: for line in f: line = line.strip() # see if line.startswith('-e'): # devel requirement splitted = line.split('#egg=') rdev[splitted[1].lower()] = line elif line.startswith('-r'): # recursive file command splitted = re.split('-r\\s+', line) subrdev, subrnormal, substuff = parse_pip_file( os.path.join(os.path.dirname(path), splitted[1]) ) for k, v in subrdev.items(): if k not in rdev: rdev[k] = v rnormal.extend(subrnormal) elif line.startswith('-'): # another special command we don't recognize stuff.append(line) else: # ordinary requirement, similarly to them used in rnormal.append(line) except IOError: print( 'Warning: could not parse requirements file "{0}"!'.format(path), file=sys.stderr ) return rdev, rnormal, stuff
[docs]def iter_requirements(level, extras, pip_file, setup_fp): """Iterate over requirements.""" result = dict() requires = [] stuff = [] if level == 'dev' or setup_fp is None: result, requires, stuff = parse_pip_file(pip_file) install_requires = [] requires_extras = {} if setup_fp is not None: with mock.patch.object(setuptools, 'setup') as mock_setup: sys.path.append(os.path.dirname( g = {'__file__':, '__name__': '__main__'} exec(, g) sys.path.pop() assert g['setup'] # silence warning about unused imports # called arguments are in `mock_setup.call_args` mock_args, mock_kwargs = mock_setup.call_args install_requires = mock_kwargs.get( 'install_requires', install_requires ) requires_extras = mock_kwargs.get('extras_require', requires_extras) install_requires.extend(requires) for e, reqs in requires_extras.items(): # Handle conditions on extras. See pkginfo_to_metadata function # in Wheel for details. condition = '' if ':' in e: e, condition = e.split(':', 1) if not e or e in extras: if condition: reqs = ['{0}; {1}'.format(r, condition) for r in reqs] install_requires.extend(reqs) for pkg in pkg_resources.parse_requirements(install_requires): # skip things we already know # FIXME be smarter about merging things # Evaluate environment markers skip if not applicable if hasattr(pkg, 'marker') and pkg.marker is not None: if not pkg.marker.evaluate(): continue else: # Remove markers from the output pkg.marker = None if pkg.key in result: continue specs = dict(pkg.specs) if (('>=' in specs) and ('>' in specs)) \ or (('<=' in specs) and ('<' in specs)): print( 'ERROR: Do not specify such weird constraints! ' '("{0}")'.format(pkg), file=sys.stderr ) sys.exit(1) if '==' in specs: result[pkg.key] = '{0}=={1}'.format( build_pkg_name(pkg), specs['==']) elif '>=' in specs: if level == 'min': result[pkg.key] = '{0}=={1}'.format( build_pkg_name(pkg), specs['>='] ) else: result[pkg.key] = pkg elif '>' in specs: if level == 'min': minver_error(build_pkg_name(pkg)) else: result[pkg.key] = pkg elif '~=' in specs: if level == 'min': result[pkg.key] = '{0}=={1}'.format( build_pkg_name(pkg), specs['~=']) else: ver, _ = os.path.splitext(specs['~=']) result[pkg.key] = '{0}>={1},=={2}.*'.format( build_pkg_name(pkg), specs['~='], ver) else: if level == 'min': minver_error(build_pkg_name(pkg)) else: result[pkg.key] = build_pkg_name(pkg) for s in stuff: yield s for k in sorted(result.keys()): yield str(result[k])