Skip to content

Commit 20433cd

Browse files
Michal Cyprianmcyprian
authored andcommitted
Use output of rpm --eval command, in case import of rpm module fails (#139)
* Use output of rpm --eval command, in case import of rpm module fails Redesign utils module to prevent circular dependency Resolves #117
1 parent d195c8e commit 20433cd

5 files changed

Lines changed: 124 additions & 104 deletions

File tree

pyp2rpm/metadata_extractors.py

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,50 @@ def cut_to_length(text, length, delim):
4040
return text
4141

4242

43+
def get_interpreter_path(version=None):
44+
"""Return the executable of a specified or current version."""
45+
if version and version != str(sys.version_info[0]):
46+
return settings.PYTHON_INTERPRETER + version
47+
else:
48+
return sys.executable
49+
50+
51+
def license_from_trove(trove):
52+
"""Finds out license from list of trove classifiers.
53+
Args:
54+
trove: list of trove classifiers
55+
Returns:
56+
Fedora name of the package license or empty string, if no licensing
57+
information is found in trove classifiers.
58+
"""
59+
license = []
60+
for classifier in trove:
61+
if 'License' in classifier:
62+
stripped = classifier.strip()
63+
# if taken from EGG-INFO, begins with Classifier:
64+
stripped = stripped[stripped.find('License'):]
65+
if stripped in settings.TROVE_LICENSES:
66+
license.append(settings.TROVE_LICENSES[stripped])
67+
return ' and '.join(license)
68+
69+
70+
def versions_from_trove(trove):
71+
"""Finds out python version from list of trove classifiers.
72+
Args:
73+
trove: list of trove classifiers
74+
Returns:
75+
python version string
76+
"""
77+
versions = set()
78+
for classifier in trove:
79+
if 'Programming Language :: Python ::' in classifier:
80+
ver = classifier.split('::')[-1]
81+
major = ver.split('.')[0].strip()
82+
if major:
83+
versions.add(major)
84+
return sorted([v for v in versions if v.replace('.', '', 1).isdigit()])
85+
86+
4387
def pypi_metadata_extension(extraction_fce):
4488
"""Extracts data from PyPI and merges them with data from extraction method."""
4589

@@ -65,7 +109,7 @@ def inner(self, client=None):
65109
data_dict[data_field] = release_data.get(data_field, '')
66110

67111
# we usually get better license representation from trove classifiers
68-
data_dict["license"] = utils.license_from_trove(release_data.get('classifiers', ''))
112+
data_dict["license"] = license_from_trove(release_data.get('classifiers', ''))
69113
data.set_from(data_dict, update=True)
70114
return data
71115
return inner
@@ -261,7 +305,7 @@ def _get_metadata(self, temp_dir):
261305
*settings.EXTRACT_DIST_COMMAND_ARGS + ['--stdout'])
262306

263307
current_version = self.base_python_version or str(sys.version_info[0])
264-
paths_to_attempt = (utils.get_interpreter_path(version=ver) for ver in (
308+
paths_to_attempt = (get_interpreter_path(version=ver) for ver in (
265309
current_version, # the version provided with `-b` option or default
266310
'2' if current_version == '3' else '3' # alternative Python version
267311
))
@@ -381,7 +425,7 @@ def versions_from_archive(self):
381425
Returns:
382426
(str, list) base_python_version, python_versions
383427
"""
384-
py_vers = utils.versions_from_trove(self.metadata['classifiers'])
428+
py_vers = versions_from_trove(self.metadata['classifiers'])
385429
if self.unsupported_version in py_vers:
386430
py_vers.remove(self.unsupported_version)
387431

@@ -528,7 +572,7 @@ def license(self):
528572

529573
@property
530574
def versions_from_archive(self):
531-
py_vers = utils.versions_from_trove(self.classifiers)
575+
py_vers = versions_from_trove(self.classifiers)
532576
return (py_vers[0] if py_vers else settings.DEFAULT_PYTHON_VERSION,
533577
py_vers[1:] if py_vers else [settings.DEFAULT_ADDITIONAL_VERSION])
534578

pyp2rpm/settings.py

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,12 @@
1-
try:
2-
import rpm
3-
DEFAULT_PKG_SAVE_PATH = rpm.expandMacro("%{_topdir}")
4-
except ImportError:
5-
import os
6-
DEFAULT_PKG_SAVE_PATH = os.path.expanduser('~/rpmbuild')
1+
from pyp2rpm import utils
72

83
DEFAULT_PYTHON_VERSION = '2'
94
DEFAULT_ADDITIONAL_VERSION = '3'
105
DEFAULT_PKG_SOURCE = 'pypi'
116
DEFAULT_METADATA_SOURCE = 'pypi'
127
DEFAULT_TEMPLATE = 'fedora'
138
DEFAULT_DISTRO = 'fedora'
9+
DEFAULT_PKG_SAVE_PATH = utils.get_default_save_path()
1410
KNOWN_DISTROS = ['fedora', 'mageia', 'pld']
1511
ARCHIVE_SUFFIXES = ['.tar', '.tgz', '.tar.gz', '.tar.bz2',
1612
'.gz', '.bz2', '.xz', '.zip', '.egg', '.whl']
@@ -23,7 +19,6 @@
2319
PYPI_USABLE_DATA = ['description', 'summary', 'license', 'home_page', 'requires']
2420
PYTHON_INTERPRETER = '/usr/bin/python'
2521
EXTRACT_DIST_COMMAND_ARGS = ['--quiet', '--command-packages', 'pyp2rpm.command', 'extract_dist']
26-
CONSOLE_LOGGING = True
2722

2823
TROVE_LICENSES = {'License :: OSI Approved :: Academic Free License (AFL)': 'AFL',
2924
'License :: OSI Approved :: Apache Software License': 'ASL %(TODO: version)s',

pyp2rpm/utils.py

Lines changed: 32 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@
99
import copy
1010
import itertools
1111

12-
from pyp2rpm import settings
13-
12+
try:
13+
import rpm
14+
except ImportError:
15+
rpm = None
1416

1517
logger = logging.getLogger(__name__)
1618

@@ -39,41 +41,6 @@ def __exit__(self, type, value, traceback): # TODO handle exception
3941
os.chdir(self.primary_path)
4042

4143

42-
class RedirectStdStreams(object):
43-
"""Temporarily redirect stdout/stderr"""
44-
45-
def __init__(self, stdout=None, stderr=None):
46-
if settings.CONSOLE_LOGGING:
47-
self.enabled = False
48-
else:
49-
self.enabled = True
50-
if isinstance(stdout, str):
51-
stdout = self.stdout_descriptor = open(stdout, "w")
52-
if isinstance(stderr, str):
53-
stderr = self.stdout_descriptor = open(stderr, "w")
54-
55-
self._stdout = stdout or sys.stdout
56-
self._stderr = stderr or sys.stderr
57-
58-
def __enter__(self):
59-
if self.enabled:
60-
self.old_stdout, self.old_stderr = sys.stdout, sys.stderr
61-
self.old_stdout.flush()
62-
self.old_stderr.flush()
63-
sys.stdout, sys.stderr = self._stdout, self._stderr
64-
65-
def __exit__(self, exc_type, exc_value, traceback):
66-
if self.enabled:
67-
self._stdout.flush()
68-
self._stderr.flush()
69-
sys.stdout = self.old_stdout
70-
sys.stderr = self.old_stderr
71-
if hasattr(self, 'stdout_descriptor'):
72-
self.stdout_descriptor.close()
73-
if hasattr(self, 'stderr_descriptor'):
74-
self.stderr_descriptor.close()
75-
76-
7744
def memoize_by_args(func):
7845
"""Memoizes return value of a func based on args."""
7946
memory = {}
@@ -89,42 +56,6 @@ def memoized(*args):
8956
return memoized
9057

9158

92-
def license_from_trove(trove):
93-
"""Finds out license from list of trove classifiers.
94-
Args:
95-
trove: list of trove classifiers
96-
Returns:
97-
Fedora name of the package license or empty string, if no licensing
98-
information is found in trove classifiers.
99-
"""
100-
license = []
101-
for classifier in trove:
102-
if 'License' in classifier != -1:
103-
stripped = classifier.strip()
104-
# if taken from EGG-INFO, begins with Classifier:
105-
stripped = stripped[stripped.find('License'):]
106-
if stripped in settings.TROVE_LICENSES:
107-
license.append(settings.TROVE_LICENSES[stripped])
108-
return ' and '.join(license)
109-
110-
111-
def versions_from_trove(trove):
112-
"""Finds out python version from list of trove classifiers.
113-
Args:
114-
trove: list of trove classifiers
115-
Returns:
116-
python version string
117-
"""
118-
versions = set()
119-
for classifier in trove:
120-
if 'Programming Language :: Python ::' in classifier:
121-
ver = classifier.split('::')[-1]
122-
major = ver.split('.')[0].strip()
123-
if major:
124-
versions.add(major)
125-
return sorted([v for v in versions if v.replace('.', '', 1).isdigit()])
126-
127-
12859
def build_srpm(specfile, save_dir):
12960
"""Builds a srpm from given specfile using rpmbuild.
13061
Generated srpm is stored in directory specified by save_dir.
@@ -134,7 +65,7 @@ def build_srpm(specfile, save_dir):
13465
save_dir: path to source and build tree
13566
"""
13667
logger.info('Starting rpmbuild to build: {0} SRPM.'.format(specfile))
137-
if save_dir != settings.DEFAULT_PKG_SAVE_PATH:
68+
if save_dir != get_default_save_path():
13869
try:
13970
msg = subprocess.Popen(['rpmbuild',
14071
'--define', '_sourcedir {0}'.format(save_dir),
@@ -185,14 +116,6 @@ def unique_deps(deps):
185116
return list(k for k, _ in itertools.groupby(deps))
186117

187118

188-
def get_interpreter_path(version=None):
189-
"""Return the executable of a specified or current version."""
190-
if version and version != str(sys.version_info[0]):
191-
return settings.PYTHON_INTERPRETER + version
192-
else:
193-
return sys.executable
194-
195-
196119
if PY3:
197120
def console_to_str(s):
198121
try:
@@ -211,3 +134,30 @@ def c_time_locale():
211134
locale.setlocale(locale.LC_TIME, 'C')
212135
yield
213136
locale.setlocale(locale.LC_TIME, old_time_locale)
137+
138+
139+
def rpm_eval(macro):
140+
"""Get value of given macro using rpm tool"""
141+
try:
142+
value = subprocess.Popen(
143+
['rpm', '--eval', macro],
144+
stdout=subprocess.PIPE).communicate()[0].strip()
145+
except OSError:
146+
logger.error('Failed to get value of {0} rpm macro'.format(
147+
macro), exc_info=True)
148+
value = b''
149+
return console_to_str(value)
150+
151+
152+
def get_default_save_path():
153+
"""Return default save path for the packages"""
154+
macro = '%{_topdir}'
155+
if rpm:
156+
save_path = rpm.expandMacro(macro)
157+
else:
158+
save_path = rpm_eval(macro)
159+
if not save_path:
160+
logger.warn("rpm tools are missing, using default save path "
161+
"~/rpmbuild/.")
162+
save_path = os.path.expanduser('~/rpmbuild')
163+
return save_path

tests/test_metadata_extractors.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,3 +379,15 @@ def setup_method(self, method):
379379
def test_extract(self, i, what, expected):
380380
data = self.e[i].extract_data()
381381
assert getattr(data, what) == expected
382+
383+
@pytest.mark.parametrize(("input", "expected"), [
384+
([], ""),
385+
(['License :: OSI Approved :: Python Software Foundation License'],
386+
'Python'),
387+
(['Classifier: License :: OSI Approved :: Python Software Foundation License'],
388+
'Python'),
389+
(['License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+)',
390+
'License :: OSI Approved :: MIT License'], 'GPLv2+ and MIT'),
391+
])
392+
def test_license_from_trove(self, input, expected):
393+
assert me.license_from_trove(input) == expected

tests/test_utils.py

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
import pytest
2+
import os
3+
from flexmock import flexmock
4+
try:
5+
import rpm
6+
except ImportError:
7+
rpm = None
28

39
from pyp2rpm import utils
4-
from pyp2rpm import settings
510

611

712
class TestUtils(object):
@@ -20,16 +25,6 @@ def memoized(self, num):
2025

2126
return num
2227

23-
@pytest.mark.parametrize(("input", "expected"), [
24-
([], ""),
25-
(['License :: OSI Approved :: Python Software Foundation License'], 'Python'),
26-
(['Classifier: License :: OSI Approved :: Python Software Foundation License'], 'Python'),
27-
(['License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+)',
28-
'License :: OSI Approved :: MIT License'], 'GPLv2+ and MIT'),
29-
])
30-
def test_license_from_trove(self, input, expected):
31-
assert utils.license_from_trove(input) == expected
32-
3328
@pytest.mark.parametrize(('input', 'expected'), [
3429
(['script', 'script2', 'script-0.1'], ['script', 'script2']),
3530
([], []),
@@ -63,3 +58,27 @@ def test_runtime_to_build(self, input, expected):
6358
])
6459
def test_unique_deps(self, input, expected):
6560
assert utils.unique_deps(input) == expected
61+
62+
def test_rpm_eval(self):
63+
if os.path.exists('/usr/bin/rpm'):
64+
assert utils.rpm_eval('macro') == 'macro'
65+
else:
66+
assert utils.rpm_eval('macro') == ''
67+
68+
def test_get_default_save_path_eval_success(self):
69+
if rpm:
70+
flexmock(rpm).should_receive(
71+
'expandMacro').once().and_return('foo')
72+
else:
73+
flexmock(utils).should_receive('rpm_eval').once().and_return('foo')
74+
assert utils.get_default_save_path() == 'foo'
75+
76+
def test_get_default_save_path_eval_fail(self):
77+
if rpm:
78+
flexmock(rpm).should_receive(
79+
'expandMacro').once().and_return('foo')
80+
else:
81+
flexmock(utils).should_receive('rpm_eval').once().and_return('')
82+
flexmock(os).should_receive('path.expanduser').once(
83+
).and_return('foo')
84+
assert utils.get_default_save_path() == 'foo'

0 commit comments

Comments
 (0)