forked from orbit-oss/flask
update project metadata
new readme readme as setup.py long_description links in changes git in authors add travis osx env break out docs build in travis remove python_requires for now
This commit is contained in:
parent
f9c6f389ac
commit
9bf5c3b3a3
16 changed files with 309 additions and 652 deletions
|
|
@ -1,309 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Flask Extension Tests
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Tests the Flask extensions.
|
||||
|
||||
:copyright: (c) 2015 by Ali Afshar.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import shutil
|
||||
import urllib2
|
||||
import tempfile
|
||||
import subprocess
|
||||
import argparse
|
||||
|
||||
from flask import json
|
||||
|
||||
from setuptools.package_index import PackageIndex
|
||||
from setuptools.archive_util import unpack_archive
|
||||
|
||||
flask_svc_url = 'http://flask.pocoo.org/extensions/'
|
||||
|
||||
|
||||
# OS X has awful paths when using mkstemp or gettempdir(). I don't
|
||||
# care about security or clashes here, so pick something that is
|
||||
# actually memorable.
|
||||
if sys.platform == 'darwin':
|
||||
_tempdir = '/private/tmp'
|
||||
else:
|
||||
_tempdir = tempfile.gettempdir()
|
||||
tdir = _tempdir + '/flaskext-test'
|
||||
flaskdir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
|
||||
|
||||
|
||||
# virtualenv hack *cough*
|
||||
os.environ['PYTHONDONTWRITEBYTECODE'] = ''
|
||||
|
||||
|
||||
RESULT_TEMPATE = u'''\
|
||||
<!doctype html>
|
||||
<title>Flask-Extension Test Results</title>
|
||||
<style type=text/css>
|
||||
body { font-family: 'Georgia', serif; font-size: 17px; color: #000; }
|
||||
a { color: #004B6B; }
|
||||
a:hover { color: #6D4100; }
|
||||
h1, h2, h3 { font-family: 'Garamond', 'Georgia', serif; font-weight: normal; }
|
||||
h1 { font-size: 30px; margin: 15px 0 5px 0; }
|
||||
h2 { font-size: 24px; margin: 15px 0 5px 0; }
|
||||
h3 { font-size: 19px; margin: 15px 0 5px 0; }
|
||||
textarea, code,
|
||||
pre { font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono',
|
||||
'Bitstream Vera Sans Mono', monospace!important; font-size: 15px;
|
||||
background: #eee; }
|
||||
pre { padding: 7px 15px; line-height: 1.3; }
|
||||
p { line-height: 1.4; }
|
||||
table { border: 1px solid black; border-collapse: collapse;
|
||||
margin: 15px 0; }
|
||||
td, th { border: 1px solid black; padding: 4px 10px;
|
||||
text-align: left; }
|
||||
th { background: #eee; font-weight: normal; }
|
||||
tr.success { background: #D3F5CC; }
|
||||
tr.failed { background: #F5D2CB; }
|
||||
</style>
|
||||
<h1>Flask-Extension Test Results</h1>
|
||||
<p>
|
||||
This page contains the detailed test results for the test run of
|
||||
all {{ 'approved' if approved }} Flask extensions.
|
||||
<h2>Summary</h2>
|
||||
<table class=results>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Extension
|
||||
<th>Version
|
||||
<th>Author
|
||||
<th>License
|
||||
<th>Outcome
|
||||
{%- for iptr, _ in results[0].logs|dictsort %}
|
||||
<th>{{ iptr }}
|
||||
{%- endfor %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{%- for result in results %}
|
||||
{% set outcome = 'success' if result.success else 'failed' %}
|
||||
<tr class={{ outcome }}>
|
||||
<th>{{ result.name }}
|
||||
<td>{{ result.version }}
|
||||
<td>{{ result.author }}
|
||||
<td>{{ result.license }}
|
||||
<td>{{ outcome }}
|
||||
{%- for iptr, _ in result.logs|dictsort %}
|
||||
<td><a href="#{{ result.name }}-{{ iptr }}">see log</a>
|
||||
{%- endfor %}
|
||||
</tr>
|
||||
{%- endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<h2>Test Logs</h2>
|
||||
<p>Detailed test logs for all tests on all platforms:
|
||||
{%- for result in results %}
|
||||
{%- for iptr, log in result.logs|dictsort %}
|
||||
<h3 id="{{ result.name }}-{{ iptr }}">
|
||||
{{ result.name }} - {{ result.version }} [{{ iptr }}]</h3>
|
||||
<pre>{{ log }}</pre>
|
||||
{%- endfor %}
|
||||
{%- endfor %}
|
||||
'''
|
||||
|
||||
|
||||
def log(msg, *args):
|
||||
print('[EXTTEST] ' + (msg % args))
|
||||
|
||||
|
||||
class TestResult(object):
|
||||
|
||||
def __init__(self, name, folder, statuscode, interpreters):
|
||||
intrptr = os.path.join(folder, '.tox/%s/bin/python'
|
||||
% interpreters[0])
|
||||
self.statuscode = statuscode
|
||||
self.folder = folder
|
||||
self.success = statuscode == 0
|
||||
|
||||
def fetch(field):
|
||||
try:
|
||||
c = subprocess.Popen([intrptr, 'setup.py',
|
||||
'--' + field], cwd=folder,
|
||||
stdout=subprocess.PIPE)
|
||||
return c.communicate()[0].strip()
|
||||
except OSError:
|
||||
return '?'
|
||||
self.name = name
|
||||
self.license = fetch('license')
|
||||
self.author = fetch('author')
|
||||
self.version = fetch('version')
|
||||
|
||||
self.logs = {}
|
||||
for interpreter in interpreters:
|
||||
logfile = os.path.join(folder, '.tox/%s/log/test.log'
|
||||
% interpreter)
|
||||
if os.path.isfile(logfile):
|
||||
self.logs[interpreter] = open(logfile).read()
|
||||
else:
|
||||
self.logs[interpreter] = ''
|
||||
|
||||
|
||||
def create_tdir():
|
||||
try:
|
||||
shutil.rmtree(tdir)
|
||||
except Exception:
|
||||
pass
|
||||
os.mkdir(tdir)
|
||||
|
||||
|
||||
def package_flask():
|
||||
distfolder = tdir + '/.flask-dist'
|
||||
c = subprocess.Popen(['python', 'setup.py', 'sdist', '--formats=gztar',
|
||||
'--dist', distfolder], cwd=flaskdir)
|
||||
c.wait()
|
||||
return os.path.join(distfolder, os.listdir(distfolder)[0])
|
||||
|
||||
|
||||
def get_test_command(checkout_dir):
|
||||
if os.path.isfile(checkout_dir + '/Makefile'):
|
||||
return 'make test'
|
||||
return 'python setup.py test'
|
||||
|
||||
|
||||
def fetch_extensions_list():
|
||||
req = urllib2.Request(flask_svc_url, headers={'accept':'application/json'})
|
||||
d = urllib2.urlopen(req).read()
|
||||
data = json.loads(d)
|
||||
for ext in data['extensions']:
|
||||
yield ext
|
||||
|
||||
|
||||
def checkout_extension(name):
|
||||
log('Downloading extension %s to temporary folder', name)
|
||||
root = os.path.join(tdir, name)
|
||||
os.mkdir(root)
|
||||
checkout_path = PackageIndex().download(name, root)
|
||||
|
||||
unpack_archive(checkout_path, root)
|
||||
path = None
|
||||
for fn in os.listdir(root):
|
||||
path = os.path.join(root, fn)
|
||||
if os.path.isdir(path):
|
||||
break
|
||||
log('Downloaded to %s', path)
|
||||
return path
|
||||
|
||||
|
||||
tox_template = """[tox]
|
||||
envlist=%(env)s
|
||||
|
||||
[testenv]
|
||||
deps=
|
||||
%(deps)s
|
||||
distribute
|
||||
py
|
||||
commands=bash flaskext-runtest.sh {envlogdir}/test.log
|
||||
downloadcache=%(cache)s
|
||||
"""
|
||||
|
||||
|
||||
def create_tox_ini(checkout_path, interpreters, flask_dep):
|
||||
tox_path = os.path.join(checkout_path, 'tox-flask-test.ini')
|
||||
if not os.path.exists(tox_path):
|
||||
with open(tox_path, 'w') as f:
|
||||
f.write(tox_template % {
|
||||
'env': ','.join(interpreters),
|
||||
'cache': tdir,
|
||||
'deps': flask_dep
|
||||
})
|
||||
return tox_path
|
||||
|
||||
|
||||
def iter_extensions(only_approved=True):
|
||||
for ext in fetch_extensions_list():
|
||||
if ext['approved'] or not only_approved:
|
||||
yield ext['name']
|
||||
|
||||
|
||||
def test_extension(name, interpreters, flask_dep):
|
||||
checkout_path = checkout_extension(name)
|
||||
log('Running tests with tox in %s', checkout_path)
|
||||
|
||||
# figure out the test command and write a wrapper script. We
|
||||
# can't write that directly into the tox ini because tox does
|
||||
# not invoke the command from the shell so we have no chance
|
||||
# to pipe the output into a logfile. The /dev/null hack is
|
||||
# to trick py.test (if used) into not guessing widths from the
|
||||
# invoking terminal.
|
||||
test_command = get_test_command(checkout_path)
|
||||
log('Test command: %s', test_command)
|
||||
f = open(checkout_path + '/flaskext-runtest.sh', 'w')
|
||||
f.write(test_command + ' &> "$1" < /dev/null\n')
|
||||
f.close()
|
||||
|
||||
# if there is a tox.ini, remove it, it will cause troubles
|
||||
# for us. Remove it if present, we are running tox ourselves
|
||||
# afterall.
|
||||
|
||||
create_tox_ini(checkout_path, interpreters, flask_dep)
|
||||
rv = subprocess.call(['tox', '-c', 'tox-flask-test.ini'], cwd=checkout_path)
|
||||
return TestResult(name, checkout_path, rv, interpreters)
|
||||
|
||||
|
||||
def run_tests(extensions, interpreters):
|
||||
results = {}
|
||||
create_tdir()
|
||||
log('Packaging Flask')
|
||||
flask_dep = package_flask()
|
||||
log('Running extension tests')
|
||||
log('Temporary Environment: %s', tdir)
|
||||
for name in extensions:
|
||||
log('Testing %s', name)
|
||||
result = test_extension(name, interpreters, flask_dep)
|
||||
if result.success:
|
||||
log('Extension test succeeded')
|
||||
else:
|
||||
log('Extension test failed')
|
||||
results[name] = result
|
||||
return results
|
||||
|
||||
|
||||
def render_results(results, approved):
|
||||
from jinja2 import Template
|
||||
items = results.values()
|
||||
items.sort(key=lambda x: x.name.lower())
|
||||
rv = Template(RESULT_TEMPATE, autoescape=True).render(results=items,
|
||||
approved=approved)
|
||||
fd, filename = tempfile.mkstemp(suffix='.html')
|
||||
os.fdopen(fd, 'w').write(rv.encode('utf-8') + '\n')
|
||||
return filename
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='Runs Flask extension tests')
|
||||
parser.add_argument('--all', dest='all', action='store_true',
|
||||
help='run against all extensions, not just approved')
|
||||
parser.add_argument('--browse', dest='browse', action='store_true',
|
||||
help='show browser with the result summary')
|
||||
parser.add_argument('--env', dest='env', default='py25,py26,py27',
|
||||
help='the tox environments to run against')
|
||||
parser.add_argument('--extension=', dest='extension', default=None,
|
||||
help='tests a single extension')
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.extension is not None:
|
||||
only_approved = False
|
||||
extensions = [args.extension]
|
||||
else:
|
||||
only_approved = not args.all
|
||||
extensions = iter_extensions(only_approved)
|
||||
|
||||
results = run_tests(extensions, [x.strip() for x in args.env.split(',')])
|
||||
filename = render_results(results, only_approved)
|
||||
if args.browse:
|
||||
import webbrowser
|
||||
webbrowser.open('file:///' + filename.lstrip('/'))
|
||||
print('Results written to {}'.format(filename))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
82
scripts/make-release.py
Normal file → Executable file
82
scripts/make-release.py
Normal file → Executable file
|
|
@ -1,23 +1,13 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
make-release
|
||||
~~~~~~~~~~~~
|
||||
|
||||
Helper script that performs a release. Does pretty much everything
|
||||
automatically for us.
|
||||
|
||||
:copyright: (c) 2015 by Armin Ronacher.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
from __future__ import print_function
|
||||
import sys
|
||||
|
||||
import os
|
||||
import re
|
||||
from datetime import datetime, date
|
||||
from subprocess import Popen, PIPE
|
||||
import sys
|
||||
from datetime import date, datetime
|
||||
from subprocess import PIPE, Popen
|
||||
|
||||
_date_clean_re = re.compile(r'(\d+)(st|nd|rd|th)')
|
||||
_date_strip_re = re.compile(r'(?<=\d)(st|nd|rd|th)')
|
||||
|
||||
|
||||
def parse_changelog():
|
||||
|
|
@ -25,18 +15,27 @@ def parse_changelog():
|
|||
lineiter = iter(f)
|
||||
for line in lineiter:
|
||||
match = re.search('^Version\s+(.*)', line.strip())
|
||||
|
||||
if match is None:
|
||||
continue
|
||||
|
||||
version = match.group(1).strip()
|
||||
if lineiter.next().count('-') != len(match.group(0)):
|
||||
|
||||
if next(lineiter).count('-') != len(match.group(0)):
|
||||
continue
|
||||
|
||||
while 1:
|
||||
change_info = lineiter.next().strip()
|
||||
change_info = next(lineiter).strip()
|
||||
|
||||
if change_info:
|
||||
break
|
||||
|
||||
match = re.search(r'released on (\w+\s+\d+\w+\s+\d+)'
|
||||
r'(?:, codename (.*))?(?i)', change_info)
|
||||
match = re.search(
|
||||
r'released on (\w+\s+\d+\w+\s+\d+)(?:, codename (.*))?',
|
||||
change_info,
|
||||
flags=re.IGNORECASE
|
||||
)
|
||||
|
||||
if match is None:
|
||||
continue
|
||||
|
||||
|
|
@ -46,15 +45,16 @@ def parse_changelog():
|
|||
|
||||
def bump_version(version):
|
||||
try:
|
||||
parts = map(int, version.split('.'))
|
||||
parts = [int(i) for i in version.split('.')]
|
||||
except ValueError:
|
||||
fail('Current version is not numeric')
|
||||
|
||||
parts[-1] += 1
|
||||
return '.'.join(map(str, parts))
|
||||
|
||||
|
||||
def parse_date(string):
|
||||
string = _date_clean_re.sub(r'\1', string)
|
||||
string = _date_strip_re.sub('', string)
|
||||
return datetime.strptime(string, '%B %d %Y')
|
||||
|
||||
|
||||
|
|
@ -65,9 +65,13 @@ def set_filename_version(filename, version_number, pattern):
|
|||
before, old, after = match.groups()
|
||||
changed.append(True)
|
||||
return before + version_number + after
|
||||
|
||||
with open(filename) as f:
|
||||
contents = re.sub(r"^(\s*%s\s*=\s*')(.+?)(')(?sm)" % pattern,
|
||||
inject_version, f.read())
|
||||
contents = re.sub(
|
||||
r"^(\s*%s\s*=\s*')(.+?)(')" % pattern,
|
||||
inject_version, f.read(),
|
||||
flags=re.DOTALL | re.MULTILINE
|
||||
)
|
||||
|
||||
if not changed:
|
||||
fail('Could not find %s in %s', pattern, filename)
|
||||
|
|
@ -81,8 +85,9 @@ def set_init_version(version):
|
|||
set_filename_version('flask/__init__.py', version, '__version__')
|
||||
|
||||
|
||||
def build_and_upload():
|
||||
Popen([sys.executable, 'setup.py', 'release', 'sdist', 'bdist_wheel', 'upload']).wait()
|
||||
def build():
|
||||
cmd = [sys.executable, 'setup.py', 'sdist', 'bdist_wheel']
|
||||
Popen(cmd).wait()
|
||||
|
||||
|
||||
def fail(message, *args):
|
||||
|
|
@ -95,7 +100,9 @@ def info(message, *args):
|
|||
|
||||
|
||||
def get_git_tags():
|
||||
return set(Popen(['git', 'tag'], stdout=PIPE).communicate()[0].splitlines())
|
||||
return set(
|
||||
Popen(['git', 'tag'], stdout=PIPE).communicate()[0].splitlines()
|
||||
)
|
||||
|
||||
|
||||
def git_is_clean():
|
||||
|
|
@ -116,29 +123,40 @@ def main():
|
|||
os.chdir(os.path.join(os.path.dirname(__file__), '..'))
|
||||
|
||||
rv = parse_changelog()
|
||||
|
||||
if rv is None:
|
||||
fail('Could not parse changelog')
|
||||
|
||||
version, release_date, codename = rv
|
||||
dev_version = bump_version(version) + '-dev'
|
||||
dev_version = bump_version(version) + '.dev'
|
||||
|
||||
info('Releasing %s (codename %s, release date %s)',
|
||||
version, codename, release_date.strftime('%d/%m/%Y'))
|
||||
info(
|
||||
'Releasing %s (codename %s, release date %s)',
|
||||
version, codename, release_date.strftime('%d/%m/%Y')
|
||||
)
|
||||
tags = get_git_tags()
|
||||
|
||||
if version in tags:
|
||||
fail('Version "%s" is already tagged', version)
|
||||
|
||||
if release_date.date() != date.today():
|
||||
fail('Release date is not today (%s != %s)',
|
||||
release_date.date(), date.today())
|
||||
fail(
|
||||
'Release date is not today (%s != %s)',
|
||||
release_date.date(), date.today()
|
||||
)
|
||||
|
||||
if not git_is_clean():
|
||||
fail('You have uncommitted changes in git')
|
||||
|
||||
try:
|
||||
import wheel # noqa: F401
|
||||
except ImportError:
|
||||
fail('You need to install the wheel package.')
|
||||
|
||||
set_init_version(version)
|
||||
make_git_commit('Bump version number to %s', version)
|
||||
make_git_tag(version)
|
||||
build_and_upload()
|
||||
build()
|
||||
set_init_version(dev_version)
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue