Formatted plugins_index files to use a 80 char width

- Formatted code to follow pep-8 more closely;
- Got rid of header comments to better follow py.test coding style;
This commit is contained in:
Bruno Oliveira 2013-12-17 19:34:07 -02:00
parent 53a9ee21d4
commit 2058f11931
2 changed files with 118 additions and 114 deletions

View File

@ -1,9 +1,10 @@
''' """
Script to generate the file `plugins_index.txt` with information about pytest plugins taken directly Script to generate the file `plugins_index.txt` with information about
from a live PyPI server. pytest plugins taken directly from a live PyPI server.
This will evolve to include test compatibility (pythons and pytest versions) information also. This will evolve to include test compatibility (pythons and pytest versions)
''' information also.
"""
from collections import namedtuple from collections import namedtuple
import datetime import datetime
from distutils.version import LooseVersion from distutils.version import LooseVersion
@ -15,107 +16,108 @@ import xmlrpclib
import pytest import pytest
#===================================================================================================
# iter_plugins
#===================================================================================================
def iter_plugins(client, search='pytest-'): def iter_plugins(client, search='pytest-'):
''' """
Returns an iterator of (name, version) from PyPI. Returns an iterator of (name, version) from PyPI.
:param client: xmlrpclib.ServerProxy :param client: xmlrpclib.ServerProxy
:param search: package names to search for :param search: package names to search for
''' """
for plug_data in client.search({'name' : search}): for plug_data in client.search({'name': search}):
yield plug_data['name'], plug_data['version'] yield plug_data['name'], plug_data['version']
#===================================================================================================
# get_latest_versions
#===================================================================================================
def get_latest_versions(plugins): def get_latest_versions(plugins):
''' """
Returns an iterator of (name, version) from the given list of (name, version), but returning Returns an iterator of (name, version) from the given list of (name,
only the latest version of the package. Uses distutils.LooseVersion to ensure compatibility version), but returning only the latest version of the package. Uses
with PEP386. distutils.LooseVersion to ensure compatibility with PEP386.
''' """
plugins = [(name, LooseVersion(version)) for (name, version) in plugins] plugins = [(name, LooseVersion(version)) for (name, version) in plugins]
for name, grouped_plugins in itertools.groupby(plugins, key=lambda x: x[0]): for name, grouped_plugins in itertools.groupby(plugins, key=lambda x: x[0]):
name, loose_version = list(grouped_plugins)[-1] name, loose_version = list(grouped_plugins)[-1]
yield name, str(loose_version) yield name, str(loose_version)
#=================================================================================================== def obtain_plugins_table(plugins, client):
# obtain_plugins_table """
#=================================================================================================== Returns information to populate a table of plugins, their versions,
def obtain_plugins_table(plugins, client): authors, etc.
'''
Returns information to populate a table of plugins, their versions, authors, etc.
The returned information is a list of columns of `ColumnData` namedtuples(text, link). Link The returned information is a list of columns of `ColumnData`
can be None if the text for that column should not be linked to anything. namedtuples(text, link). Link can be None if the text for that column
should not be linked to anything.
:param plugins: list of (name, version) :param plugins: list of (name, version)
:param client: xmlrpclib.ServerProxy :param client: xmlrpclib.ServerProxy
''' """
rows = [] rows = []
ColumnData = namedtuple('ColumnData', 'text link') ColumnData = namedtuple('ColumnData', 'text link')
headers = ['Name', 'Author', 'Downloads', 'Python 2.7', 'Python 3.3', 'Summary'] headers = ['Name', 'Author', 'Downloads', 'Python 2.7', 'Python 3.3',
'Summary']
pytest_version = pytest.__version__ pytest_version = pytest.__version__
print '*** pytest-{0} ***'.format(pytest_version) print '*** pytest-{0} ***'.format(pytest_version)
plugins = list(plugins) plugins = list(plugins)
for index, (package_name, version) in enumerate(plugins): for index, (package_name, version) in enumerate(plugins):
print package_name, version, '...', print package_name, version, '...',
release_data = client.release_data(package_name, version) release_data = client.release_data(package_name, version)
download_count = release_data['downloads']['last_month'] download_count = release_data['downloads']['last_month']
image_url = '.. image:: http://pytest-plugs.herokuapp.com/status/{name}-{version}'.format(name=package_name, url = '.. image:: {site}/status/{name}-{version}'
version=version) image_url = url.format(
site='http://pytest-plugs.herokuapp.com',
name=package_name,
version=version)
image_url += '?py={py}&pytest={pytest}' image_url += '?py={py}&pytest={pytest}'
row = ( row = (
ColumnData(package_name + '-' + version, release_data['release_url']), ColumnData(package_name + '-' + version,
release_data['release_url']),
ColumnData(release_data['author'], release_data['author_email']), ColumnData(release_data['author'], release_data['author_email']),
ColumnData(str(download_count), None), ColumnData(str(download_count), None),
ColumnData(image_url.format(py='py27', pytest=pytest_version), None), ColumnData(image_url.format(py='py27', pytest=pytest_version),
ColumnData(image_url.format(py='py33', pytest=pytest_version), None), None),
ColumnData(image_url.format(py='py33', pytest=pytest_version),
None),
ColumnData(release_data['summary'], None), ColumnData(release_data['summary'], None),
) )
assert len(row) == len(headers) assert len(row) == len(headers)
rows.append(row) rows.append(row)
print 'OK (%d%%)' % ((index + 1) * 100 / len(plugins)) print 'OK (%d%%)' % ((index + 1) * 100 / len(plugins))
return headers, rows return headers, rows
#===================================================================================================
# generate_plugins_index_from_table
#===================================================================================================
def generate_plugins_index_from_table(filename, headers, rows): def generate_plugins_index_from_table(filename, headers, rows):
''' """
Generates a RST file with the table data given. Generates a RST file with the table data given.
:param filename: output filename :param filename: output filename
:param headers: see `obtain_plugins_table` :param headers: see `obtain_plugins_table`
:param rows: see `obtain_plugins_table` :param rows: see `obtain_plugins_table`
''' """
# creates a list of rows, each being a str containing appropriate column text and link # creates a list of rows, each being a str containing appropriate column
# text and link
table_texts = [] table_texts = []
for row in rows: for row in rows:
column_texts = [] column_texts = []
for i, col_data in enumerate(row): for i, col_data in enumerate(row):
text = '`%s <%s>`_' % (col_data.text, col_data.link) if col_data.link else col_data.text text = '`%s <%s>`_' % (
col_data.text,
col_data.link) if col_data.link else col_data.text
column_texts.append(text) column_texts.append(text)
table_texts.append(column_texts) table_texts.append(column_texts)
# compute max length of each column so we can build the rst table # compute max length of each column so we can build the rst table
column_lengths = [len(x) for x in headers] column_lengths = [len(x) for x in headers]
for column_texts in table_texts: for column_texts in table_texts:
for i, row_text in enumerate(column_texts): for i, row_text in enumerate(column_texts):
column_lengths[i] = max(column_lengths[i], len(row_text) + 2) column_lengths[i] = max(column_lengths[i], len(row_text) + 2)
def get_row_limiter(char): def get_row_limiter(char):
return ' '.join(char * length for length in column_lengths) return ' '.join(char * length for length in column_lengths)
with file(filename, 'w') as f: with file(filename, 'w') as f:
# write welcome # write welcome
print >> f, '.. _plugins_index:' print >> f, '.. _plugins_index:'
@ -123,17 +125,18 @@ def generate_plugins_index_from_table(filename, headers, rows):
print >> f, 'List of Third-Party Plugins' print >> f, 'List of Third-Party Plugins'
print >> f, '===========================' print >> f, '==========================='
print >> f print >> f
# table # table
print >> f, get_row_limiter('=') print >> f, get_row_limiter('=')
for i, header in enumerate(headers): for i, header in enumerate(headers):
print >> f, '{0:^{fill}}'.format(header, fill=column_lengths[i]), print >> f, '{0:^{fill}}'.format(header, fill=column_lengths[i]),
print >> f print >> f
print >> f, get_row_limiter('=') print >> f, get_row_limiter('=')
for column_texts in table_texts: for column_texts in table_texts:
for i, row_text in enumerate(column_texts): for i, row_text in enumerate(column_texts):
print >> f, '{0:^{fill}}'.format(row_text, fill=column_lengths[i]), print >> f, '{0:^{fill}}'.format(row_text,
fill=column_lengths[i]),
print >> f print >> f
print >> f print >> f
print >> f, get_row_limiter('=') print >> f, get_row_limiter('=')
@ -141,54 +144,50 @@ def generate_plugins_index_from_table(filename, headers, rows):
print >> f, '*(Downloads are given from last month only)*' print >> f, '*(Downloads are given from last month only)*'
print >> f print >> f
print >> f, '*(Updated on %s)*' % _get_today_as_str() print >> f, '*(Updated on %s)*' % _get_today_as_str()
#===================================================================================================
# _get_today_as_str
#===================================================================================================
def _get_today_as_str(): def _get_today_as_str():
''' """
internal. only exists so we can patch it in testing. internal. only exists so we can patch it in testing.
''' """
return datetime.date.today().strftime('%Y-%m-%d') return datetime.date.today().strftime('%Y-%m-%d')
#===================================================================================================
# generate_plugins_index
#===================================================================================================
def generate_plugins_index(client, filename): def generate_plugins_index(client, filename):
''' """
Generates an RST file with a table of the latest pytest plugins found in PyPI. Generates an RST file with a table of the latest pytest plugins found in
PyPI.
:param client: xmlrpclib.ServerProxy :param client: xmlrpclib.ServerProxy
:param filename: output filename :param filename: output filename
''' """
plugins = get_latest_versions(iter_plugins(client)) plugins = get_latest_versions(iter_plugins(client))
headers, rows = obtain_plugins_table(plugins, client) headers, rows = obtain_plugins_table(plugins, client)
generate_plugins_index_from_table(filename, headers, rows) generate_plugins_index_from_table(filename, headers, rows)
#===================================================================================================
# main
#===================================================================================================
def main(argv): def main(argv):
"""
Script entry point. Configures an option parser and calls the appropriate
internal function.
"""
filename = os.path.join(os.path.dirname(__file__), 'plugins_index.txt') filename = os.path.join(os.path.dirname(__file__), 'plugins_index.txt')
url = 'http://pypi.python.org/pypi' url = 'http://pypi.python.org/pypi'
parser = OptionParser(description='Generates a restructured document of pytest plugins from PyPI') parser = OptionParser(
parser.add_option('-f', '--filename', default=filename, help='output filename [default: %default]') description='Generates a restructured document of pytest plugins from PyPI')
parser.add_option('-u', '--url', default=url, help='url of PyPI server to obtain data from [default: %default]') parser.add_option('-f', '--filename', default=filename,
help='output filename [default: %default]')
parser.add_option('-u', '--url', default=url,
help='url of PyPI server to obtain data from [default: %default]')
(options, _) = parser.parse_args(argv[1:]) (options, _) = parser.parse_args(argv[1:])
client = xmlrpclib.ServerProxy(options.url) client = xmlrpclib.ServerProxy(options.url)
generate_plugins_index(client, options.filename) generate_plugins_index(client, options.filename)
print print
print '%s Updated.' % options.filename print '%s Updated.' % options.filename
return 0 return 0
#===================================================================================================
# main
#===================================================================================================
if __name__ == '__main__': if __name__ == '__main__':
sys.exit(main(sys.argv)) sys.exit(main(sys.argv))

View File

@ -4,64 +4,67 @@ import xmlrpclib
import pytest import pytest
#=================================================================================================== @pytest.mark.xfail(
# test_plugins_index reason="issue405 fails, not py33 ready, not a core pytest test")
#===================================================================================================
@pytest.mark.xfail(reason="issue405 fails, not py33 ready, not a core pytest test")
def test_plugins_index(tmpdir, monkeypatch): def test_plugins_index(tmpdir, monkeypatch):
''' """
Blackbox testing for plugins_index script. Calls main() generating a file and compares produced Blackbox testing for plugins_index script. Calls main() generating a file
output to expected. and compares produced output to expected.
.. note:: if the test fails, a file named `test_plugins_index.obtained.rst` will be generated in .. note:: if the test fails, a file named
the same directory as this test file. Ensure the contents are correct and overwrite `test_plugins_index.obtained.rst` will be generated in the same directory
as this test file. Ensure the contents are correct and overwrite
`test_plugins_index.expected.rst` with that file. `test_plugins_index.expected.rst` with that file.
''' """
import plugins_index import plugins_index
# dummy interface to xmlrpclib.ServerProxy # dummy interface to xmlrpclib.ServerProxy
class DummyProxy(object): class DummyProxy(object):
expected_url = 'http://dummy.pypi' expected_url = 'http://dummy.pypi'
def __init__(self, url): def __init__(self, url):
assert url == self.expected_url assert url == self.expected_url
def search(self, query): def search(self, query):
assert query == {'name' : 'pytest-'} assert query == {'name': 'pytest-'}
return [ return [
{'name': 'pytest-plugin1', 'version' : '0.8'}, {'name': 'pytest-plugin1', 'version': '0.8'},
{'name': 'pytest-plugin1', 'version' : '1.0'}, {'name': 'pytest-plugin1', 'version': '1.0'},
{'name': 'pytest-plugin2', 'version' : '1.2'}, {'name': 'pytest-plugin2', 'version': '1.2'},
] ]
def release_data(self, package_name, version): def release_data(self, package_name, version):
results = { results = {
('pytest-plugin1', '1.0') : { ('pytest-plugin1', '1.0'): {
'package_url' : 'http://plugin1', 'package_url': 'http://plugin1',
'release_url' : 'http://plugin1/1.0', 'release_url': 'http://plugin1/1.0',
'author' : 'someone', 'author': 'someone',
'author_email' : 'someone@py.com', 'author_email': 'someone@py.com',
'summary' : 'some plugin', 'summary': 'some plugin',
'downloads': {'last_day': 1, 'last_month': 4, 'last_week': 2}, 'downloads': {'last_day': 1, 'last_month': 4,
'last_week': 2},
}, },
('pytest-plugin2', '1.2') : { ('pytest-plugin2', '1.2'): {
'package_url' : 'http://plugin2', 'package_url': 'http://plugin2',
'release_url' : 'http://plugin2/1.2', 'release_url': 'http://plugin2/1.2',
'author' : 'other', 'author': 'other',
'author_email' : 'other@py.com', 'author_email': 'other@py.com',
'summary' : 'some other plugin', 'summary': 'some other plugin',
'downloads': {'last_day': 10, 'last_month': 40, 'last_week': 20}, 'downloads': {'last_day': 10, 'last_month': 40,
'last_week': 20},
}, },
} }
return results[(package_name, version)] return results[(package_name, version)]
monkeypatch.setattr(xmlrpclib, 'ServerProxy', DummyProxy, 'foo') monkeypatch.setattr(xmlrpclib, 'ServerProxy', DummyProxy, 'foo')
monkeypatch.setattr(plugins_index, '_get_today_as_str', lambda: '2013-10-20') monkeypatch.setattr(plugins_index, '_get_today_as_str',
lambda: '2013-10-20')
output_file = str(tmpdir.join('output.rst')) output_file = str(tmpdir.join('output.rst'))
assert plugins_index.main(['', '-f', output_file, '-u', DummyProxy.expected_url]) == 0 assert plugins_index.main(
['', '-f', output_file, '-u', DummyProxy.expected_url]) == 0
with file(output_file, 'rU') as f: with file(output_file, 'rU') as f:
obtained_output = f.read() obtained_output = f.read()
@ -79,13 +82,15 @@ def get_expected_output():
""" """
:return: string with expected rst output from the plugins_index.py script. :return: string with expected rst output from the plugins_index.py script.
""" """
expected_filename = os.path.join(os.path.dirname(__file__), 'test_plugins_index.expected.rst') expected_filename = os.path.join(os.path.dirname(__file__),
'test_plugins_index.expected.rst')
expected_output = open(expected_filename, 'rU').read() expected_output = open(expected_filename, 'rU').read()
return expected_output.replace('pytest=2.X.Y', 'pytest={0}'.format(pytest.__version__)) return expected_output.replace('pytest=2.X.Y',
'pytest={0}'.format(pytest.__version__))
#=================================================================================================== #===============================================================================
# main # main
#=================================================================================================== #===============================================================================
if __name__ == '__main__': if __name__ == '__main__':
pytest.main() pytest.main()