diff --git a/Makefile b/Makefile index ba898b9e..8055e8ef 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,13 @@ -.PHONY: clean-pyc test upload-docs docs +.PHONY: clean-pyc ext-test test upload-docs docs all: clean-pyc test test: python setup.py test +ext-test: + python tests/flaskext_test.py --browse + release: python setup.py release sdist upload diff --git a/docs/extensiondev.rst b/docs/extensiondev.rst index e181fe3e..c2a6d2f7 100644 --- a/docs/extensiondev.rst +++ b/docs/extensiondev.rst @@ -314,6 +314,8 @@ extension to be approved you have to follow these guidelines: link to the documentation, website (if there is one) and there must be a link to automatically install the development version (``PackageName==dev``). +9. The ``zip_safe`` flag in the setup scrip must be set to ``False``, + even if the extension would be safe for zipping. .. _Flask Extension Wizard: diff --git a/extreview/approved.rst b/extreview/approved.rst new file mode 100644 index 00000000..a09aeb11 --- /dev/null +++ b/extreview/approved.rst @@ -0,0 +1,32 @@ +Approved Extensions +=================== + +This document contains a list of all extensions that were approved and the +date of approval as well as notes. This should make it possible to better +track the extension approval process. + + +Flask-Babel +----------- + +:First Approval: 2010-07-23 +:Last Review: 2010-07-23 +:Approved version: 0.6 +:Approved license: BSD + +Notes: Developed by the Flask development head + +How to improve: add a better long description to the next release + + +Flask-SQLAlchemy +---------------- + +:First Approval: 2010-07-25 +:Last Review: 2010-07-25 +:Approved version: 0.9 +:Approved license: BSD + +Notes: Developed by the Flask development head + +How to improve: add a better long description to the next release diff --git a/extreview/listed.rst b/extreview/listed.rst new file mode 100644 index 00000000..cd658e92 --- /dev/null +++ b/extreview/listed.rst @@ -0,0 +1,183 @@ +Listed Extensions +================= + +This list contains extensions that passed listing. This means the +extension is on the list of extensions on the website. It does not +contain extensions that are approved. + + +Flask-CouchDB +------------- + +:Last-Review: 2010-07-25 +:Reviewed version: 0.2 + +Would be fine for approval, but the test suite is not part of the sdist +package (missing entry in MANIFEST.in) and the test suite does not respond +to either "make test" or "python setup.py test". + + +Flask-CouchDBKit +---------------- + +:Last-Review: 2010-07-25 +:Reviewed Version: 0.2 + +Would be fine for approval, but the test suite is not part of the sdist +package (missing entry in MANIFEST.in) and the test suite does not respond +to either "make test" or "python setup.py test". + + +Flask-Creole +------------ + +:Last-Review: 2010-07-25 +:Reviewed Version: 0.2 + +Would be fine for approval, but the test suite is not part of the sdist +package (missing entry in MANIFEST.in) and the test suite does not respond +to either "make test" or "python setup.py test". Furthermore the README +file is empty. + + +flask-csrf +---------- + +:Last-Review: 2010-07-25 +:Reviewed Version: 0.2 + +Will not be approved because this is functionality that should be handled +in the form handling systems which is for Flask-WTF already the case. +Also, this implementation only supports one open tab with forms. + +Name is not following Flask extension naming rules. + +Considered for unlisting. + + +Flask-Genshi +------------ + +:Last-Review: 2010-07-25 +:Reviewed Version: 0.3 + +Would be fine for approval, but the test suite is not part of the sdist +package (missing entry in MANIFEST.in) and the test suite does not respond +to either "make test" or "python setup.py test". Furthermore the long +description is empty. The zip_safe flag is not set to False which is a +requirement for approved extensions. + + +flask-lesscss +------------- + +:Last-Review: 2010-07-25 +:Reviewed Version: 0.9.1 + +Broken package description, nonconforming package name, does not follow +standard API rules (init_lesscss instead of lesscss). + +Considered for unlisting, improved version should release as +"Flask-LessCSS" with a conforming API and fixed packages indices, as well +as a testsuite. + + +flask-mail +---------- + +:Last-Review: 2010-07-25 +:Reviewed Version: 0.3.1 + +Would be fine for approval, but the test suite is not part of the sdist +package (missing entry in MANIFEST.in) and the test suite does not respond +to either "make test" or "python setup.py test". Furthermore the long +description in the package index is a little bit too short. + +Package name should be changed to Flask-Mail with the approval to be +consistent, this might also be the change to improve the API if necessary, +but I don't see any big design problems there. + + +Flask-OAuth +----------- + +:Last-Review: 2010-07-25 +:Reviewed Version: 0.9 + +Short long description, missing tests. + + +Flask-OpenID +------------ + +:Last-Review: 2010-07-25 +:Reviewed Version: 1.0.1 + +Short long description, missing tests. + + +Flask-Script +------------ + +:Last-Review: 2010-07-25 +:Reviewed Version: 0.2 + +Would be fine for approval, but the test suite is not part of the sdist +package (missing entry in MANIFEST.in) and the test suite does not respond +to either "make test" or "python setup.py test". + +The upcoming 0.3 release looks promising, could need a longer "long +description" in the package index though. + + +Flask-Testing +------------- + +:Last-Review: 2010-07-25 +:Reviewed Version: 0.2 + +Would be fine for approval, but the test suite is not part of the sdist +package (missing entry in MANIFEST.in) and the test suite does not respond +to either "make test" or "python setup.py test". + + +Flask-Themes +------------ + +:Last-Review: 2010-07-25 +:Reviewed Version: 0.1 + +Would be fine for approval, but the test suite is not part of the sdist +package (missing entry in MANIFEST.in) and the test suite does not respond +to either "make test" or "python setup.py test". + + +Flask-Uploads +------------- + +:Last-Review: 2010-07-25 +:Reviewed Version: 0.1 + +Would be fine for approval, but the test suite is not part of the sdist +package (missing entry in MANIFEST.in) and the test suite does not respond +to either "make test" or "python setup.py test". + + +Flask-WTF +--------- + +:Last-Review: 2010-07-25 +:Reviewed Version: 0.2.1 + +Would be fine for approval, but the test suite is not part of the sdist +package (missing entry in MANIFEST.in) and the test suite does not respond +to either "make test" or "python setup.py test". + + +Flask-XML-RPC +------------- + +:Last-Review: 2010-07-25 +:Reviewed Version: 0.2.1 + +Missing tests, API wise it would be fine for approval. diff --git a/extreview/unlisted.rst b/extreview/unlisted.rst new file mode 100644 index 00000000..6168c912 --- /dev/null +++ b/extreview/unlisted.rst @@ -0,0 +1,55 @@ +Unlisted Extensions +=================== + +This is a list of extensions that is currently rejected from listing and +with that also not approved. If an extension ends up here it should +improved to be listed. + + +Flask-Actions +------------- + +:Last Review: 2010-07-25 +:Reviewed version: 0.2 + +Rejected because of missing description in PyPI, formatting issues with +the documentation (missing headlines, scrollbars etc.) and a general clash +of functionality with the Flask-Script package. Latter should not be a +problem, but the documentation should improve. For listing, the extension +developer should probably discuss the extension on the mailinglist with +others. + +Futhermore it also has an egg registered with an invalid filename. + + +Flask-Jinja2Extender +-------------------- + +:Last Review: 2010-07-25 +:Reviewed version: 0.1 + +Usecase not obvious, hacky implementation, does not solve a problem that +could not be solved with Flask itself. I suppose it is to aid other +extensions, but that should be discussed on the mailinglist. + + +Flask-Markdown +-------------- + +:Last Review: 2010-07-25 +:Reviewed version: 0.2 + +Would be great for enlisting but it should follow the API of Flask-Creole. +Besides that, the docstrings are not valid rst (run through rst2html to +see the issue) and it is missing tests. Otherwise fine :) + + +flask-urls +---------- + +:Last Review: 2010-07-25 +:Reviewed version: 0.9.2 + +Broken PyPI index and non-conforming extension name. Due to the small +featureset this was also delisted from the list. It was there previously +before the approval process was introduced. diff --git a/flask/app.py b/flask/app.py index 151525e6..9b6b7836 100644 --- a/flask/app.py +++ b/flask/app.py @@ -11,7 +11,6 @@ from __future__ import with_statement -import os from threading import Lock from datetime import timedelta, datetime from itertools import chain @@ -20,7 +19,7 @@ from jinja2 import Environment from werkzeug import ImmutableDict from werkzeug.routing import Map, Rule -from werkzeug.exceptions import HTTPException, InternalServerError, NotFound +from werkzeug.exceptions import HTTPException, InternalServerError from .helpers import _PackageBoundObject, url_for, get_flashed_messages, \ _tojson_filter, _endpoint_from_view_func diff --git a/flask/helpers.py b/flask/helpers.py index 39214a10..a2d951b4 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -15,7 +15,6 @@ import posixpath import mimetypes from time import time from zlib import adler32 -from functools import wraps # try to load the best simplejson implementation available. If JSON # is not installed, we add a failing class. diff --git a/flask/templating.py b/flask/templating.py index 41c0d39b..db78c3af 100644 --- a/flask/templating.py +++ b/flask/templating.py @@ -8,7 +8,7 @@ :copyright: (c) 2010 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ -from jinja2 import BaseLoader, FileSystemLoader, TemplateNotFound +from jinja2 import BaseLoader, TemplateNotFound from .globals import _request_ctx_stack from .signals import template_rendered diff --git a/tests/flaskext_test.py b/tests/flaskext_test.py index aa4b7461..3614ad58 100644 --- a/tests/flaskext_test.py +++ b/tests/flaskext_test.py @@ -11,7 +11,14 @@ from __future__ import with_statement -import tempfile, subprocess, urllib2, os +import os +import sys +import shutil +import urllib2 +import tempfile +import subprocess +import argparse +from cStringIO import StringIO from flask import json @@ -19,24 +26,136 @@ from setuptools.package_index import PackageIndex from setuptools.archive_util import unpack_archive flask_svc_url = 'http://flask.pocoo.org/extensions/' -tdir = tempfile.mkdtemp() + +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__), + '..')) -def run_tests(checkout_dir): - cmd = ['tox'] - return subprocess.call(cmd, cwd=checkout_dir, - stdout=open(os.path.join(tdir, 'tox.log'), 'w'), - stderr=subprocess.STDOUT) +RESULT_TEMPATE = u'''\ + +
+ This page contains the detailed test results for the test run of + all {{ 'approved' if approved }} Flask extensions. +
| Extension + | Version + | Author + | License + | Outcome + |
|---|---|---|---|---|
| {{ result.name }} + | {{ result.version }} + | {{ result.author }} + | {{ result.license }} + | {{ outcome }} + |
Detailed test logs for all tests on all platforms: +{%- for result in results %} + {%- for iptr, log in result.logs|dictsort %} +
{{ log }}
+ {%- 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):
- files = set(os.listdir(checkout_dir))
- if 'Makefile' in files:
+ if os.path.isfile(checkout_dir + '/Makefile'):
return 'make test'
- elif 'conftest.py' in files:
- return 'py.test'
- else:
- return 'nosetests'
+ return 'python setup.py test'
def fetch_extensions_list():
@@ -47,50 +166,111 @@ def fetch_extensions_list():
yield ext
-def checkout_extension(ext):
- name = ext['name']
+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(ext['name'], 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=py26
+envlist=%(env)s
[testenv]
-commands=
-%s
-downloadcache=
-%s
+deps=%(deps)s
+commands=bash flaskext-runtest.sh {envlogdir}/test.log
+downloadcache=%(cache)s
"""
-def create_tox_ini(checkout_path):
+
+def create_tox_ini(checkout_path, interpreters, flask_dep):
tox_path = os.path.join(checkout_path, 'tox.ini')
if not os.path.exists(tox_path):
with open(tox_path, 'w') as f:
- f.write(tox_template % (get_test_command(checkout_path), tdir))
+ f.write(tox_template % {
+ 'env': ','.join(interpreters),
+ 'cache': tdir,
+ 'deps': flask_dep
+ })
-# XXX command line
-only_approved = True
-def test_all_extensions(only_approved=only_approved):
+def iter_extensions(only_approved=True):
for ext in fetch_extensions_list():
if ext['approved'] or not only_approved:
- checkout_path = checkout_extension(ext)
- create_tox_ini(checkout_path)
- ret = run_tests(checkout_path)
- yield ext['name'], ret
+ 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
+ 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"\n')
+ f.close()
+
+ create_tox_ini(checkout_path, interpreters, flask_dep)
+ rv = subprocess.call(['tox'], cwd=checkout_path)
+ return TestResult(name, checkout_path, rv, interpreters)
+
+
+def run_tests(interpreters, only_approved=True):
+ results = {}
+ create_tdir()
+ log('Packaging Flask')
+ flask_dep = package_flask()
+ log('Running extension tests')
+ log('Temporary Environment: %s', tdir)
+ for name in iter_extensions(only_approved):
+ 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():
- for name, ret in test_all_extensions():
- print name, ret
+ 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')
+ args = parser.parse_args()
+
+ results = run_tests(['py26'], not args.all)
+ filename = render_results(results, not args.all)
+ if args.browse:
+ import webbrowser
+ webbrowser.open('file:///' + filename.lstrip('/'))
+ print 'Results written to', filename
if __name__ == '__main__':