diff --git a/db.sql.zip b/db.sql.zip new file mode 100644 index 0000000..c802c0b Binary files /dev/null and b/db.sql.zip differ diff --git a/db_api.py b/db_api.py index 5cc57d0..861ca31 100644 --- a/db_api.py +++ b/db_api.py @@ -643,18 +643,19 @@ def thread_listPosts(): query += 'createDate {} '.format(order) query += "limit {}".format(limit) if limit != '' else '' - elif sort == 'tree': - query = """select p2.id from posts as p join posts as p2 on p2.mpath - like CONCAT(p.mpath,'%') and p.parent is null and thread = {} and createDate between '{}' and '{}' - order by p.mpath {} {};""".format(thread, since_date, max_date, order, - "limit {}".format(limit) if limit != '' else '') - else: - query = """select p2.id from posts as p2 join - (select p.mpath, p.id from posts as p where parent is null {} ) as p - on p2.mpath like CONCAT(p.mpath,'%') and thread = {} and createDate between '{}' and '{}' - order by p.mpath {}""".format("limit {}".format(limit) if limit != '' else '', - thread, since_date, max_date, order) - + #elif sort == 'tree': + # query = """select p2.id from posts as p join posts as p2 on p2.mpath + # like CONCAT(p.mpath,'%') and p.parent is null and thread = {} and createDate between '{}' and '{}' + # order by p.mpath {} {};""".format(thread, since_date, max_date, order, + # "limit {}".format(limit) if limit != '' else '') + #else: + # query = """select p2.id from posts as p2 join + # (select p.mpath, p.id from posts as p where parent is null {} ) as p + # on p2.mpath like CONCAT(p.mpath,'%') and thread = {} and createDate between '{}' and '{}' + # order by p.mpath {}""".format("limit {}".format(limit) if limit != '' else '', + # thread, since_date, max_date, order) + else: + pass cursor.execute(query) a = [] for i in tuple(t[0] for t in cursor.fetchall()): diff --git a/db_api_3.py b/db_api_3.py new file mode 100644 index 0000000..bd04f74 --- /dev/null +++ b/db_api_3.py @@ -0,0 +1,1017 @@ +from flask import Flask, request, jsonify +from _mysql_exceptions import IntegrityError +from helper2_0 import get_forum_info, get_user_info, get_thread_info, get_post_info, DB, right_index, DoesNotExist +import MySQLdb +from datetime import datetime +from flask.ext.runner import Runner + + +app = Flask(__name__) +app.debug = True +runner = Runner(app) + + +db = DB() +class InvalidArg(Exception): + pass + + +""" FORUMS """ +@app.route('/db/api/forum/create/', methods=['POST']) +def forum_create(): + cursor = db.get_cursor() + try: + data = request.get_json() + name = data['name'] + short_name = data['short_name'] + user = data['user'] + cursor.execute("""insert into forums (name, shortname, user) + values (%s, %s, %s)""",(name, short_name, user)) + db.commit() + cursor.close() + return jsonify(code=0, response={ + 'id':cursor.lastrowid, + 'name':name, + 'short_name': short_name, + 'user': user}) + except KeyError: + return jsonify(code=2, response='invalid json') + except DoesNotExist: + return jsonify(code=1, response='user does not exist') + except IntegrityError, e: + if e[0] == 1062: + return jsonify(code=0, response=get_forum_info(short_name, db)) + elif e[0] == 1452: + return jsonify(code=1, response='user not found') + except: + return jsonify(code=4, response='opps') + +@app.route('/db/api/forum/details/', methods=['GET']) +def forum_details(): + try: + forum = request.args['forum'] + related = request.args.getlist('related') + return jsonify(code=0, response=get_forum_info(forum, db, related)) + + except KeyError: + return jsonify(code=2, response='invalid json') + + except DoesNotExist: + return jsonify(code=1, response="not found") + + except: + return jsonify(code=4, response='opps') + +@app.route('/db/api/forum/listPosts/', methods=['GET']) +def forum_listPosts(): + cursor = db.get_cursor(MySQLdb.cursors.DictCursor) + try: + forum = request.args['forum'] + limit = request.args.get('limit', '') + order = request.args.get('order', 'desc') + since_date = request.args.get('since', False) + + related = request.args.getlist('related') + + query = "" + query_params = () + if since_date: + query = "select * from posts where forum = %s and date >= %s order by date " + order + query_params += (forum, since_date, ) + else: + query = "select * from posts where forum = %s order by date " + order + query_params += (forum,) + + + if limit != '': + query += " limit " + limit + + + cursor.execute(query, query_params) + posts = cursor.fetchall() + + for post in posts: + if 'user' in related: + post.update({'user': get_user_info(post['user'], db)}) + if 'thread' in related: + post.update({'thread': get_thread_info(post['thread'], db)}) + if 'forum' in related: + post.update({'forum': get_forum_info(post['forum'], db)}) + post.update({"date": str(post["date"])}) + + + cursor.close() + return jsonify(code=0, response=posts) + except DoesNotExist: + return jsonify(code=1, response='does not exist') + except IntegrityError, e: + return jsonify(code=1, response='something not found') + except: + return jsonify(code=4, response='opps') + +@app.route('/db/api/forum/listThreads/', methods=['GET']) +def forum_listThreads(): + cursor = db.get_cursor(MySQLdb.cursors.DictCursor) + try: + forum = request.args['forum'] + limit = request.args.get('limit', '') + order = request.args.get('order', 'desc') + since_date = request.args.get('since', False) + + related = request.args.getlist('related') + query = "" + query_params = () + if since_date: + query = """select * from threads where forum = %s and date >= %s order by date """ + order + query_params = (forum, since_date, ) + else: + query = """select * from threads where forum = %s order by date """ + order + query_params += (forum,) + if limit != '': + query += " limit " + limit + + + cursor.execute(query, query_params) + threads = cursor.fetchall() + + for thread in threads: + if 'user' in related: + thread.update({'user': get_user_info(thread['user'], db)}) + if 'forum' in related: + thread.update({'forum': get_forum_info(thread['forum'], db)}) + thread.update({"date": str(thread["date"])}) + cursor.close() + return jsonify(code=0, response=threads) + except KeyError: + return jsonify(code=2, response='invalid json') + except DoesNotExist: + return jsonify(code=1, response='does not exist') + except IntegrityError, e: + return jsonify(code=1, response='something not found') + except: + return jsonify(code=4, response='opps') + +@app.route('/db/api/forum/listUsers/', methods=['GET']) +def forum_listUsers(): + cursor = db.get_cursor(MySQLdb.cursors.DictCursor) + try: + forum = request.args['forum'] + limit = request.args.get('limit', '') + order = request.args.get('order', 'desc') + since_id = request.args.get('since_id', False) + + query = "" + query_params = () + + + if since_id: + query = """select * from users where email in + (select distinct user from posts where forum = %s) and id >= %s order by name """ + order + query_params = (forum, since_id,) + else: + query = """select * from users where email in + (select distinct user from posts where forum = %s) order by name """ + order + query_params = (forum,) + + if limit != '': + query += " limit " + limit + + + cursor.execute(query, query_params) + + users = cursor.fetchall() + cursor = db.get_cursor() + for user in users: + cursor.execute("select followee from follows where follower = %s",(user['email'],)) + follower_followee = cursor.fetchall() + cursor.execute("select follower from follows where followee = %s",(user['email'],)) + followee_follower = cursor.fetchall() + cursor.execute("select thread from subscribes where user = %s",(user['email'],)) + subs = cursor.fetchall() + user.update({ + 'following': list(t[0] for t in follower_followee), + 'followers': list(t[0] for t in followee_follower), + 'subscriptions': list(t[0] for t in subs) + }) + cursor.close() + return jsonify(code=0, response=users) + except KeyError: + return jsonify(code=2, response='invalid json') + except DoesNotExist: + return jsonify(code=1, response='does not exist') + except IntegrityError, e: + return jsonify(code=1, response='something not found') + except: + return jsonify(code=4, response='opps') + +""" USERS """ + + +@app.route('/db/api/user/follow/', methods=["POST"]) +def user_follow(): + cursor = db.get_cursor() + try: + data = request.get_json() + follower = data['follower'] + followee = data['followee'] + cursor.execute("insert into follows (follower, followee) VALUES (%s, %s)",(follower,followee,)) + db.commit() + cursor.close() + return jsonify(code=0, + response=get_user_info(follower, db)) + except KeyError: + return jsonify(code=2, response='invalid json') + except IntegrityError, e: + if e[0] == 1062: + return jsonify(code=0, response=get_user_info(follower, db)) + elif e[0] == 1452: + return jsonify(code=1, response='user not found') + except: + return jsonify(code=4, response='opps') + +@app.route('/db/api/user/unfollow/', methods=['POST']) +def user_unfollow(): + cursor = db.get_cursor() + try: + data = request.get_json() + follower = data['follower'] + followee = data['followee'] + cursor.execute("delete from follows where followee = %s and follower = %s",(followee, follower,)) + db.commit() + cursor.close() + return jsonify(code=0, + response=get_user_info(follower, db)) + except KeyError: + return jsonify(code=2, response='invalid json') + except IntegrityError, e: + if e[0] == 1062: + return jsonify(code=0, response=get_user_info(follower, db)) + elif e[0] == 1452: + return jsonify(code=1, response='user not found') + except: + return jsonify(code=4, response='opps') + +@app.route('/db/api/user/create/', methods=['POST']) +def user_create(): + try: + cursor = db.get_cursor() + data = request.get_json() + name = data['name'] + username = data['username'] + email = data['email'] + about = data['about'] + isAnonymous = data.get('isAnonymous', False) + cursor.execute("""insert into users (name, username, email, about, isAnonymous) + VALUES (%s, %s, %s, %s, %s)""",(name, username, email, about, isAnonymous)) + db.commit() + cursor.close() + return jsonify(code=0, + response={"id": cursor.lastrowid, + "name":name, + "username":username, + "email":email, + "isAnonymous":bool(isAnonymous), + "about":about + }) + except KeyError: + return jsonify(code=2, response='invalid json') + except IntegrityError: + return jsonify(code=5,response='user alredy exist') + except: + return jsonify(code=4, response='opps') + +@app.route('/db/api/user/details/', methods=['GET']) +def user_details(): + try: + user = request.args['user'] + return jsonify(code=0, response=get_user_info(user, db)) + except KeyError: + return jsonify(code=2, response='invalid json') + except IntegrityError, e: + if e[0] == 1062: + return jsonify(code=0, response=get_user_info(user, db)) + elif e[0] == 1452: + return jsonify(code=1, response='user not found') + except: + return jsonify(code=4, response='opps') + +@app.route('/db/api/user/listFollowers/', methods=['GET']) +def user_listFollowers(): + try: + cursor = db.get_cursor(MySQLdb.cursors.DictCursor) + user = request.args['user'] + limit = request.args.get('limit', '') + order = request.args.get('order', 'desc') + since_id = request.args.get('since_id', False) + + query = "" + query_params = () + if since_id: + query = """select about, email, users.id, isAnonymous, name, username from follows + join users on follows.follower = users.email and follows.followee = %s and users.id >= %s order by name """ + order + query_params = (user, since_id, ) + else: + query = """select about, email, users.id, isAnonymous, name, username from follows + join users on follows.follower = users.email and follows.followee = %s order by name """ + order + query_params = (user,) + + if limit != '': + query += " limit " + limit + cursor.execute(query, query_params) + users = cursor.fetchall() + cursor = db.get_cursor() + for user in users: + cursor.execute("select followee from follows where follower = %s",(user['email'],)) + follower_followee = cursor.fetchall() + cursor.execute("select follower from follows where followee = %s",(user['email'],)) + followee_follower = cursor.fetchall() + cursor.execute("select thread from subscribes where user = %s",(user['email'],)) + subs = cursor.fetchall() + user.update({ + 'following': list(t[0] for t in follower_followee), + 'followers': list(t[0] for t in followee_follower), + 'subscriptions': list(t[0] for t in subs) + }) + + cursor.close() + return jsonify(code=0, response=users) + except KeyError: + return jsonify(code=2, response='invalid json') + except DoesNotExist: + return jsonify(code=1, response='does not exist') + except IntegrityError, e: + return jsonify(code=1, response='something not found') + except: + return jsonify(code=4, response='opps') + +@app.route('/db/api/user/listFollowing/', methods=['GET']) +def user_listFollowing(): + try: + cursor = db.get_cursor(MySQLdb.cursors.DictCursor) + user = request.args['user'] + limit = request.args.get('limit', '') + order = request.args.get('order', 'desc') + since_id = request.args.get('since_id', False) + + query = "" + query_params = () + if since_id: + query = """select about, email, users.id, isAnonymous, name, username from follows + join users on follows.followee = users.email and follows.follower = %s and users.id >= %s order by name """ + order + query_params = (user, since_id, ) + else: + query = """select about, email, users.id, isAnonymous, name, username from follows + join users on follows.followee = users.email and follows.follower = %s order by name """ + order + query_params = (user,) + + if limit != '': + query += " limit " + limit + + cursor.execute(query, query_params) + users = cursor.fetchall() + cursor = db.get_cursor() + for user in users: + cursor.execute("select followee from follows where follower = %s",(user['email'],)) + follower_followee = cursor.fetchall() + cursor.execute("select follower from follows where followee = %s",(user['email'],)) + followee_follower = cursor.fetchall() + cursor.execute("select thread from subscribes where user = %s",(user['email'],)) + subs = cursor.fetchall() + user.update({ + 'following': list(t[0] for t in follower_followee), + 'followers': list(t[0] for t in followee_follower), + 'subscriptions': list(t[0] for t in subs) + }) + + cursor.close() + return jsonify(code=0, response=users) + except KeyError: + return jsonify(code=2, response='invalid json') + except DoesNotExist: + return jsonify(code=1, response='does not exist') + except IntegrityError, e: + return jsonify(code=1, response='something not found') + except: + return jsonify(code=4, response='opps') + +@app.route('/db/api/user/updateProfile/', methods=['POST']) +def user_update(): + cursor = db.get_cursor() + try: + data = request.get_json() + name = data['name'] + user = data['user'] + about = data['about'] + cursor.execute("update users set name = %s, about = %s where email = %s",(name, about, user)) + db.commit() + cursor.close() + return jsonify(code=0, + response=get_user_info(user, db)) + except KeyError: + return jsonify(code=2, response='invalid json') + except IntegrityError: + return jsonify(code=1, response='user not found') + except: + return jsonify(code=4, response='opps') + +@app.route('/db/api/user/listPosts/', methods=['GET']) +def user_listPosts(): + cursor = db.get_cursor(MySQLdb.cursors.DictCursor) + try: + user = request.args['user'] + limit = request.args.get('limit', '') + order = request.args.get('order', 'desc') + since_date = request.args.get('since', False) + + query = "" + query_params = () + if since_date: + query = """select * from posts where user = %s and date >= %s order by date """ + order + query_params = (user, since_date, ) + else: + query = """select * from posts where user = %s order by date """ + order + query_params = (user,) + + if limit != '': + query += " limit " + limit + + cursor.execute(query, query_params) + posts = cursor.fetchall() + for post in posts: + post.update({"date": str(post["date"])}) + cursor.close() + return jsonify(code=0, response=posts) + except KeyError: + return jsonify(code=2, response='invalid json') + except DoesNotExist: + return jsonify(code=1, response='does not exist') + except IntegrityError, e: + return jsonify(code=1, response='something not found') + except: + return jsonify(code=4, response='opps') + +""" THREADS """ + +@app.route('/db/api/thread/create/', methods=['POST']) +def thread_create(): + cursor = db.get_cursor() + try: + data = request.get_json() + date = data['date'] + user = data['user'] + slug = data['slug'] + forum = data['forum'] + title = data['title'] + closed = int(data['isClosed']) + message = data['message'] + deleted = int(bool(data.get('isDeleted'))) + + cursor.execute("""insert into threads (date, forum, isClosed, isDeleted, + message, slug, title, user) + values (%s, %s, %s, %s, %s, + %s, %s, %s)""",(date, forum, closed, deleted, message, slug, title, user)) + db.commit() + cursor.close() + return jsonify(code=0, response={ + 'id':cursor.lastrowid, + 'date':date, + 'forum': forum, + 'isClosed': bool(closed), + 'isDeleted': bool(deleted), + 'message': message, + 'slug': slug, + 'title': title, + 'user': user}) + except KeyError: + return jsonify(code=2, response='invalid json') + except DoesNotExist: + return jsonify(code=1, response='does not exist') + except IntegrityError, e: + return jsonify(code=1, response='something not found') + except: + return jsonify(code=4, response='opps') + +@app.route("/db/api/thread/details/", methods=["GET"]) +def thread_details(): + try: + thread = request.args['thread'] + related = request.args.getlist('related') + if not all(i in ('user', 'forum') for i in related): raise InvalidArg + return jsonify(code=0, response=get_thread_info(thread, db, related)) + except KeyError: + return jsonify(code=2, response='invalid json') + except DoesNotExist: + return jsonify(code=1, response='does not exist') + except IntegrityError, e: + return jsonify(code=2, response='something not found') + except InvalidArg: + return jsonify(code=3, response='wrong args') + except: + return jsonify(code=4, response='opps') + +@app.route("/db/api/thread/subscribe/", methods=["POST"]) +def thread_subscribe(): + cursor = db.get_cursor() + try: + data = request.get_json() + user = data['user'] + thread = data['thread'] + cursor.execute("insert into subscribes (user, thread) VALUES (%s, %s)",(user, thread)) + db.commit() + cursor.close() + return jsonify(code=0, + response={"thread": thread, "user": user}) + except KeyError: + return jsonify(code=2, response='invalid json') + except IntegrityError, e: + if e[0] == 1062: + return jsonify(code=0, response={"thread": thread, "user": user}) + elif e[0] == 1452: + return jsonify(code=1, response='user or thread not found') + except: + return jsonify(code=4, response='opps') + +@app.route("/db/api/thread/unsubscribe/", methods=["POST"]) +def thread_unsubscribe(): + cursor = db.get_cursor() + try: + data = request.get_json() + user = data['user'] + thread = data['thread'] + cursor.execute("delete from subscribes where user = %s and thread = %s",(user, thread)) + db.commit() + cursor.close() + except KeyError: + return jsonify(code=2, response='invalid json') + except: + return jsonify(code=4, response='opps') + return jsonify(code=0, + response={"thread": thread, "user": user}) + +@app.route("/db/api/thread/update/", methods=['POST']) +def thread_update(): + cursor = db.get_cursor() + try: + data = request.get_json() + message = data['message'] + slug = data['slug'] + thread = data['thread'] + cursor.execute("update threads set message = %s, slug = %s where id = %s",(message, slug, thread)) + db.commit() + cursor.close() + return jsonify(code=0, + response=get_thread_info(thread, db)) + except KeyError: + return jsonify(code=2, response='invalid json') + except IntegrityError: + return jsonify(code=1, response='not found') + except: + return jsonify(code=4, response='opps') + +@app.route("/db/api/thread/close/", methods=['POST']) +def thread_close(): + cursor = db.get_cursor() + try: + data = request.get_json() + thread = data['thread'] + cursor.execute("update threads set isClosed = 1 where id = %s",(thread,)) + db.commit() + cursor.close() + return jsonify(code=0, + response={"thread": thread}) + except KeyError: + return jsonify(code=2, response='invalid json') + except IntegrityError: + return jsonify(code=1, response='thread not found') + except: + return jsonify(code=4, response='opps') + +@app.route("/db/api/thread/open/", methods=['POST']) +def thread_open(): + cursor = db.get_cursor() + try: + data = request.get_json() + thread = data['thread'] + cursor.execute("update threads set isClosed = 0 where id = %s",(thread,)) + db.commit() + cursor.close() + return jsonify(code=0, + response={"thread": thread}) + except KeyError: + return jsonify(code=2, response='invalid json') + except IntegrityError: + return jsonify(code=1, response='not found') + except: + return jsonify(code=4, response='opps') + +@app.route("/db/api/thread/remove/", methods=['POST']) +def thread_remove(): + cursor = db.get_cursor() + try: + data = request.get_json() + thread = data['thread'] + cursor.execute("""update threads set isDeleted = 1, posts = 0 where id = %s""",(thread,)) + cursor.execute("""update posts set isDeleted = 1 where thread = %s""", (thread,)) + db.commit() + cursor.close() + return jsonify(code=0, + response={"thread": thread}) + except KeyError: + return jsonify(code=2, response='invalid json') + except IntegrityError: + return jsonify(code=1, response='not found') + except: + return jsonify(code=4, response='opps') + +@app.route("/db/api/thread/restore/", methods=['POST']) +def thread_restore(): + cursor = db.get_cursor() + try: + data = request.get_json() + thread = data['thread'] + count_posts = cursor.execute("""update posts set isDeleted = 0 where thread = %s""", (thread,)) + cursor.execute("""update threads set isDeleted = 0, posts = %s where id = %s""",(count_posts, thread,)) + db.commit() + cursor.close() + return jsonify(code=0, + response={"thread": thread}) + except KeyError: + return jsonify(code=2, response='invalid json') + except IntegrityError: + return jsonify(code=1, response='not found') + except: + return jsonify(code=4, response='opps') + +@app.route("/db/api/thread/vote/", methods=['POST']) +def thread_vote(): + cursor = db.get_cursor() + try: + data = request.get_json() + vote = data['vote'] + thread = data['thread'] + if vote == 1: + cursor.execute("update threads set likes = likes + 1, points = points + 1 where id = %s",(thread,)) + else: + cursor.execute("update threads set dislikes = dislikes + 1, points = points - 1 where id = %s",(thread,)) + + db.commit() + cursor.close() + return jsonify(code=0, + response=get_thread_info(thread, db)) + except KeyError: + return jsonify(code=2, response='invalid json') + except IntegrityError: + return jsonify(code=1, response='not found') + except: + return jsonify(code=4, response='opps') + +@app.route("/db/api/thread/list/", methods=['GET']) +def thread_list(): + cursor = db.get_cursor(MySQLdb.cursors.DictCursor) + try: + user = request.args.get('user', False) + forum = request.args.get('forum', False) + + if ((user and forum) or not (user or forum)): raise Exception('only one') + + limit = request.args.get('limit', '') + order = request.args.get('order', 'desc') + since_date = request.args.get('since', False) + + + query = "" + query_params = () + if user: + query = """select * from threads where user = %s """ + query_params = (user,) + else: + query = """select * from threads where forum = %s """ + query_params = (forum,) + + if since_date: + query += "and date >= %s " + query_params += (since_date,) + query += "order by date " + order + + if limit != '': + query += " limit " + limit + + cursor.execute(query, query_params) + + threads = cursor.fetchall() + for thread in threads: + thread.update({"date": str(thread["date"])}) + cursor.close() + return jsonify(code=0, response=threads) + + except KeyError: + return jsonify(code=2, response='invalid json') + except DoesNotExist: + return jsonify(code=1, response='does not exist') + except IntegrityError, e: + return jsonify(code=1, response='something not found') + except: + return jsonify(code=4, response='opps') + +@app.route("/db/api/thread/listPosts/", methods=['GET']) +def thread_listPosts(): + cursor = db.get_cursor(MySQLdb.cursors.DictCursor) + try: + thread = request.args['thread'] + + limit = request.args.get('limit', '') + sort = request.args.get('sort', 'flat') + order = request.args.get('order', 'desc') + + since_date = request.args.get('since', False) + + + max_date = datetime.now() + + query = "" + query_params = () + if sort == 'flat': + query = """select * from posts where thread = %s """ + query_params += (thread,) + if since_date: + query += "and date >= %s " + query_params += (since_date, ) + query += "order by date " + order + + if limit != '': + query += " limit " + limit + + #elif sort == 'tree': + # query = """select p2.id from posts as p join posts as p2 on p2.mpath + # like CONCAT(p.mpath,'%') and p.parent is null and thread = {} and createDate between '{}' and '{}' + # order by p.mpath {} {};""".format(thread, since_date, max_date, order, + # "limit {}".format(limit) if limit != '' else '') + #else: + # query = """select p2.id from posts as p2 join + # (select p.mpath, p.id from posts as p where parent is null {} ) as p + # on p2.mpath like CONCAT(p.mpath,'%') and thread = {} and createDate between '{}' and '{}' + # order by p.mpath {}""".format("limit {}".format(limit) if limit != '' else '', + # thread, since_date, max_date, order) + + cursor.execute(query, query_params) + posts = cursor.fetchall() + for post in posts: + post.update({"date": str(post["date"])}) + cursor.close() + return jsonify(code=0, response=posts) + except KeyError: + return jsonify(code=2, response='invalid json') + except DoesNotExist: + return jsonify(code=1, response='does not exist') + except IntegrityError, e: + return jsonify(code=1, response='something not found') + except: + return jsonify(code=4, response='opps') + +""" POSTS """ + +@app.route('/db/api/post/create/', methods=['POST']) +def post_create(): + cursor = db.get_cursor() + try: + data = request.get_json() + + date = data["date"] + thread = data["thread"] + message = data["message"] + user = data["user"] + forum = data["forum"] + + parent = data.get("parent", None) + isApproved = data.get("isApproved", False) + isHighlighted = data.get("isHighlighted", False) + isEdited = data.get("isEdited", False) + isSpam = data.get("isSpam", False) + isDeleted = data.get("isDeleted", False) + cursor.execute("""insert into posts (date, forum, isHighlighted, isApproved, + isDeleted, isEdited, isSpam, message, parent, thread, user) + values (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s,%s)""", + (date, forum, isHighlighted, isApproved, isDeleted, isEdited, isSpam, + message, parent, thread, user)) + + lid = cursor.lastrowid + cursor.execute("""update threads set posts = posts + 1 where id = (select thread from posts where id = %s)""",(lid,)) + if parent: + cursor.execute("select mpath from posts where id = {}".format(parent)) + parent_mpath = cursor.fetchone()[0]+right_index(lid) + else: + parent_mpath = right_index(lid) + cursor.execute("update posts set mpath = '{}/' where id = {}".format(parent_mpath,lid)) + cursor.close() + + db.commit() + return jsonify(code=0, response={ + 'id':lid, + "date": date, + "forum": forum, + "isApproved": isApproved, + "isDeleted": isDeleted, + "isEdited": isEdited, + "isHighlighted": isHighlighted, + "isSpam": isSpam, + "message": message, + "parent": parent, + "thread": thread, + "user": user}) + except KeyError: + return jsonify(code=2, response='invalid json') + except DoesNotExist: + return jsonify(code=1, response='post does not exist') + except: + return jsonify(code=4, response='opps') + + #except IntegrityError, e: + # if e[0] == 1062: + # return jsonify(code=2, response=get_post_info(short_name, db)) + # elif e[0] == 1452: + # return jsonify(code=2, response='user not found') + #except: + # return jsonify(code=2, response='bad request') + +@app.route("/db/api/post/details/", methods=['GET']) +def post_details(): + try: + post = request.args['post'] + related = request.args.getlist('related') + return jsonify(code=0, response=get_post_info(post, db, related)) + except KeyError: + return jsonify(code=2, response='invalid json') + except DoesNotExist: + return jsonify(code=1, response='does not exist') + except IntegrityError, e: + return jsonify(code=1, response='something not found') + except: + return jsonify(code=4, response='opps') + +@app.route("/db/api/post/remove/", methods=['POST']) +def post_remove(): + cursor = db.get_cursor() + try: + data = request.get_json() + post = data['post'] + cursor.execute("update posts set isDeleted = 1 where id = %s",(post,)) + cursor.execute("update threads set posts = posts - 1 where id = (select thread from posts where id = %s)", (post,)) + db.commit() + cursor.close() + return jsonify(code=0, + response={"post": post}) + except KeyError: + return jsonify(code=2, response='invalid json') + except IntegrityError: + return jsonify(code=1, response='not found') + except: + return jsonify(code=4, response='opps') + +@app.route("/db/api/post/restore/", methods=['POST']) +def post_restore(): + cursor = db.get_cursor() + try: + data = request.get_json() + post = data['post'] + cursor.execute("update posts set isDeleted = 0 where id = %s",(post,)) + cursor.execute("update threads set posts = posts + 1 where id = (select thread from posts where id = %s)", (post,)) + + db.commit() + cursor.close() + return jsonify(code=0, + response={"post": post}) + except KeyError: + return jsonify(code=2, response='invalid json') + except IntegrityError: + return jsonify(code=1, response='not found') + except: + return jsonify(code=4, response='opps') + +@app.route("/db/api/post/vote/", methods=['POST']) +def post_vote(): + cursor = db.get_cursor() + try: + data = request.get_json() + vote = data['vote'] + post = data['post'] + if vote == 1: + cursor.execute("update posts set likes = likes + 1, points = points + 1 where id = %s",(post,)) + else: + cursor.execute("update posts set dislikes = dislikes + 1, points = points - 1 where id = %s",(post,)) + + db.commit() + cursor.close() + return jsonify(code=0, + response=get_post_info(post, db)) + except KeyError: + return jsonify(code=2, response='invalid json') + except IntegrityError: + return jsonify(code=1, response='not found') + except: + return jsonify(code=4, response='opps') + +@app.route("/db/api/post/update/", methods=['POST']) +def post_update(): + cursor = db.get_cursor() + try: + data = request.get_json() + post = data['post'] + message = data['message'] + cursor.execute("update posts set message = %s where id = %s",(message, post)) + db.commit() + cursor.close() + return jsonify(code=0, + response=get_post_info(post, db)) + except KeyError: + return jsonify(code=2, response='invalid json') + except IntegrityError: + return jsonify(code=1, response='not found') + except: + return jsonify(code=4, response='opps') + +@app.route("/db/api/post/list/", methods=['GET']) +def post_list(): + cursor = db.get_cursor(MySQLdb.cursors.DictCursor) + try: + thread = request.args.get('thread', False) + forum = request.args.get('forum', False) + + if ((thread and forum) or not (thread or forum)): raise Exception('only one') + + limit = request.args.get('limit', '') + order = request.args.get('order', 'desc') + since_date = request.args.get('since', False) + + query = "" + query_params = () + if thread: + query = """select * from posts where thread = %s """ + query_params = (thread,) + else: + query = """select * from posts where forum = %s """ + query_params = (forum,) + if since_date: + query += "and date >= %s " + query_params += (since_date, ) + query += " order by date " + order + + if limit != '': + query += " limit " + limit + + cursor.execute(query, query_params) + + posts = cursor.fetchall() + + for post in posts: + post.update({'date': str(post['date'])}) + + cursor.close() + return jsonify(code=0, response=posts) + + except KeyError: + return jsonify(code=2, response='invalid json') + except DoesNotExist: + return jsonify(code=1, response='does not exist') + except IntegrityError, e: + return jsonify(code=1, response='something not found') + except: + return jsonify(code=4, response='opps') + +@app.route('/db/api/clear/', methods=['POST']) +def clear(): + try: + cursor = db.get_cursor() + cursor.execute("""delete from users;"""); + db.commit() + cursor.close() + return jsonify(code=0, response="OK") + except: + return jsonify(code=4, response='opps') + +@app.route("/db/api/status/", methods=['GET']) +def status(): + try: + cursor = db.get_cursor() + cursor.execute("select count(*) from users") + count_users = cursor.fetchone()[0] + cursor.execute("select count(*) from forums") + count_forums = cursor.fetchone()[0] + cursor.execute("select count(*) from threads") + count_threads = cursor.fetchone()[0] + cursor.execute("select count(*) from posts") + count_posts = cursor.fetchone()[0] + return jsonify(code=0, response={"user":count_users, + "forum":count_forums, + "thread": count_threads, + "posts": count_posts}) + except: + return jsonify(code=4, response='opps') + + +@app.route("/") +def index(): + get_thread_info(63, db) + return "lol" + +if __name__ == '__main__': + runner.run() \ No newline at end of file diff --git a/db_api_3.pyc b/db_api_3.pyc new file mode 100644 index 0000000..af45598 Binary files /dev/null and b/db_api_3.pyc differ diff --git a/env/LICENSE b/env/LICENSE new file mode 100644 index 0000000..30a41f0 --- /dev/null +++ b/env/LICENSE @@ -0,0 +1,10 @@ +Copyright (c) 2013, Miguel Grinberg All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The views and conclusions contained in the software and documentation are those of the authors and should not be interpreted as representing official policies, either expressed or implied, of the Flask-Runner Project. + diff --git a/env/README.md b/env/README.md new file mode 100644 index 0000000..6907445 --- /dev/null +++ b/env/README.md @@ -0,0 +1,61 @@ +Flask-Runner +============ + +A set of standard command line arguments for Flask applications built on top of Flask-Script. + +Example code +------------ + +In its simplest usage, an application can create and initialize a `Runner` object as follows: + + from flask import Flask + from flask.ext.runner import Runner + app = Flask(__name__) + runner = Runner(app) + + @app.route('/') + def hello_world(): + return 'Hello World!' + + if __name__ == '__main__': + runner.run() + +This application now has command line options that expose many of the configuration options that can be sent as arguments to `app.run()`: + + $ python hello.py --help + usage: hello.py [-h] [-t HOST] [-p PORT] [--threaded] [--processes PROCESSES] + [--passthrough-errors] [-d] [-r] [--noeval] [--extra FILE] + [--profile] [--profile-count COUNT] + [--profile-percent PERCENT] [--profile-regex REGEX] + [--profile-dir DIR] [--lint] + + Runs the Flask development server i.e. app.run() + + optional arguments: + -h, --help show this help message and exit + -t HOST, --host HOST + -p PORT, --port PORT + --threaded + --processes PROCESSES + --passthrough-errors + -d, --no-debug + -r, --no-reload + --noeval disable exception evaluation in the debugger + --reload-extra FILE additional file for the reloader to watch for changes + --profile run the profiler for each request + --profile-count COUNT + restrict profiler output to the top COUNT lines + --profile-percent PERCENT + restrict profiler output to the top PERCENT lines + --profile-regex REGEX + filter profiler output with REGEX + --profile-dir DIR write profiler results one file per request in folder + DIR + --lint run the lint validation middleware + +Resources +--------- + +- [Documentation](http://pythonhosted.org/Flask-Runner) +- [pypi](https://pypi.python.org/pypi/Flask-Runner) + diff --git a/env/bin/gunicorn b/env/bin/gunicorn new file mode 100755 index 0000000..eff5f44 --- /dev/null +++ b/env/bin/gunicorn @@ -0,0 +1,11 @@ +#!/Users/Boris/db_api/env/bin/python + +# -*- coding: utf-8 -*- +import re +import sys + +from gunicorn.app.wsgiapp import run + +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(run()) diff --git a/env/bin/gunicorn_django b/env/bin/gunicorn_django new file mode 100755 index 0000000..b46bb63 --- /dev/null +++ b/env/bin/gunicorn_django @@ -0,0 +1,11 @@ +#!/Users/Boris/db_api/env/bin/python + +# -*- coding: utf-8 -*- +import re +import sys + +from gunicorn.app.djangoapp import run + +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(run()) diff --git a/env/bin/gunicorn_paster b/env/bin/gunicorn_paster new file mode 100755 index 0000000..9a183d9 --- /dev/null +++ b/env/bin/gunicorn_paster @@ -0,0 +1,11 @@ +#!/Users/Boris/db_api/env/bin/python + +# -*- coding: utf-8 -*- +import re +import sys + +from gunicorn.app.pasterapp import run + +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(run()) diff --git a/env/bin/nosetests b/env/bin/nosetests new file mode 100755 index 0000000..20a0df4 --- /dev/null +++ b/env/bin/nosetests @@ -0,0 +1,11 @@ +#!/Users/Boris/db_api/env/bin/python + +# -*- coding: utf-8 -*- +import re +import sys + +from nose import run_exit + +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(run_exit()) diff --git a/env/bin/nosetests-2.7 b/env/bin/nosetests-2.7 new file mode 100755 index 0000000..20a0df4 --- /dev/null +++ b/env/bin/nosetests-2.7 @@ -0,0 +1,11 @@ +#!/Users/Boris/db_api/env/bin/python + +# -*- coding: utf-8 -*- +import re +import sys + +from nose import run_exit + +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(run_exit()) diff --git a/env/lib/python2.7/site-packages/Flask_GAE_Mini_Profiler-0.1.2-py2.7-nspkg.pth b/env/lib/python2.7/site-packages/Flask_GAE_Mini_Profiler-0.1.2-py2.7-nspkg.pth new file mode 100644 index 0000000..1d5f762 --- /dev/null +++ b/env/lib/python2.7/site-packages/Flask_GAE_Mini_Profiler-0.1.2-py2.7-nspkg.pth @@ -0,0 +1 @@ +import sys, types, os;p = os.path.join(sys._getframe(1).f_locals['sitedir'], *('flaskext',));ie = os.path.exists(os.path.join(p,'__init__.py'));m = not ie and sys.modules.setdefault('flaskext', types.ModuleType('flaskext'));mp = (m or []) and m.__dict__.setdefault('__path__',[]);(p not in mp) and mp.append(p) diff --git a/env/lib/python2.7/site-packages/Flask_GAE_Mini_Profiler-0.1.2-py2.7.egg-info/PKG-INFO b/env/lib/python2.7/site-packages/Flask_GAE_Mini_Profiler-0.1.2-py2.7.egg-info/PKG-INFO new file mode 100644 index 0000000..b48eda8 --- /dev/null +++ b/env/lib/python2.7/site-packages/Flask_GAE_Mini_Profiler-0.1.2-py2.7.egg-info/PKG-INFO @@ -0,0 +1,32 @@ +Metadata-Version: 1.1 +Name: Flask-GAE-Mini-Profiler +Version: 0.1.2 +Summary: Flask integration of gae_mini_profiler +Home-page: http://packages.python.org/Flask-GAE-Mini-Profiler +Author: Pascal Hartig +Author-email: phartig@rdrei.net +License: MIT +Description: + Flask-GAE-Mini-Profiler + ----------------------- + + A drop-in, ubiquitous, production profiling tool for + `Flask `_ applications on Google App Engine using + `gae_mini_profiler `_. + + Links + ````` + + * `documentation `_ + * `development version + `_ + +Platform: any +Classifier: Development Status :: 4 - Beta +Classifier: Environment :: Web Environment +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content +Classifier: Topic :: Software Development :: Libraries :: Python Modules diff --git a/env/lib/python2.7/site-packages/Flask_GAE_Mini_Profiler-0.1.2-py2.7.egg-info/SOURCES.txt b/env/lib/python2.7/site-packages/Flask_GAE_Mini_Profiler-0.1.2-py2.7.egg-info/SOURCES.txt new file mode 100644 index 0000000..0a6c77b --- /dev/null +++ b/env/lib/python2.7/site-packages/Flask_GAE_Mini_Profiler-0.1.2-py2.7.egg-info/SOURCES.txt @@ -0,0 +1,33 @@ +LICENSE +MANIFEST.in +Makefile +setup.cfg +setup.py +Flask_GAE_Mini_Profiler.egg-info/PKG-INFO +Flask_GAE_Mini_Profiler.egg-info/SOURCES.txt +Flask_GAE_Mini_Profiler.egg-info/dependency_links.txt +Flask_GAE_Mini_Profiler.egg-info/namespace_packages.txt +Flask_GAE_Mini_Profiler.egg-info/not-zip-safe +Flask_GAE_Mini_Profiler.egg-info/requires.txt +Flask_GAE_Mini_Profiler.egg-info/top_level.txt +docs/.gitignore +docs/Makefile +docs/conf.py +docs/index.rst +docs/make.bat +docs/_static/profiler1.png +flaskext/__init__.py +flaskext/gae_mini_profiler/__init__.py +flaskext/gae_mini_profiler/profiler.py +flaskext/gae_mini_profiler/static/css/profiler.css +flaskext/gae_mini_profiler/static/js/profiler.js +flaskext/gae_mini_profiler/static/js/template.tmpl +flaskext/gae_mini_profiler/templates/includes.html +flaskext/gae_mini_profiler/templates/shared.html +tests/__init__.py +tests/extension.py +tests/gaemock/google/__init__.py +tests/gaemock/google/appengine/__init__.py +tests/gaemock/google/appengine/api.py +tests/gaemock/google/appengine/ext/__init__.py +tests/gaemock/google/appengine/ext/webapp.py \ No newline at end of file diff --git a/env/lib/python2.7/site-packages/Werkzeug-0.10.1-py2.7.egg-info/dependency_links.txt b/env/lib/python2.7/site-packages/Flask_GAE_Mini_Profiler-0.1.2-py2.7.egg-info/dependency_links.txt similarity index 100% rename from env/lib/python2.7/site-packages/Werkzeug-0.10.1-py2.7.egg-info/dependency_links.txt rename to env/lib/python2.7/site-packages/Flask_GAE_Mini_Profiler-0.1.2-py2.7.egg-info/dependency_links.txt diff --git a/env/lib/python2.7/site-packages/Flask_GAE_Mini_Profiler-0.1.2-py2.7.egg-info/installed-files.txt b/env/lib/python2.7/site-packages/Flask_GAE_Mini_Profiler-0.1.2-py2.7.egg-info/installed-files.txt new file mode 100644 index 0000000..f38288a --- /dev/null +++ b/env/lib/python2.7/site-packages/Flask_GAE_Mini_Profiler-0.1.2-py2.7.egg-info/installed-files.txt @@ -0,0 +1,18 @@ +../flaskext/gae_mini_profiler/__init__.py +../flaskext/gae_mini_profiler/profiler.py +../flaskext/gae_mini_profiler/templates/includes.html +../flaskext/gae_mini_profiler/templates/shared.html +../flaskext/gae_mini_profiler/static/js/profiler.js +../flaskext/gae_mini_profiler/static/js/template.tmpl +../flaskext/gae_mini_profiler/static/css/profiler.css +../flaskext/gae_mini_profiler/__init__.pyc +../flaskext/gae_mini_profiler/profiler.pyc +./ +dependency_links.txt +namespace_packages.txt +not-zip-safe +PKG-INFO +requires.txt +SOURCES.txt +top_level.txt +../Flask_GAE_Mini_Profiler-0.1.2-py2.7-nspkg.pth diff --git a/env/lib/python2.7/site-packages/Flask_GAE_Mini_Profiler-0.1.2-py2.7.egg-info/namespace_packages.txt b/env/lib/python2.7/site-packages/Flask_GAE_Mini_Profiler-0.1.2-py2.7.egg-info/namespace_packages.txt new file mode 100644 index 0000000..cbcdbd5 --- /dev/null +++ b/env/lib/python2.7/site-packages/Flask_GAE_Mini_Profiler-0.1.2-py2.7.egg-info/namespace_packages.txt @@ -0,0 +1 @@ +flaskext diff --git a/env/lib/python2.7/site-packages/Werkzeug-0.10.1-py2.7.egg-info/not-zip-safe b/env/lib/python2.7/site-packages/Flask_GAE_Mini_Profiler-0.1.2-py2.7.egg-info/not-zip-safe similarity index 100% rename from env/lib/python2.7/site-packages/Werkzeug-0.10.1-py2.7.egg-info/not-zip-safe rename to env/lib/python2.7/site-packages/Flask_GAE_Mini_Profiler-0.1.2-py2.7.egg-info/not-zip-safe diff --git a/env/lib/python2.7/site-packages/Flask_GAE_Mini_Profiler-0.1.2-py2.7.egg-info/requires.txt b/env/lib/python2.7/site-packages/Flask_GAE_Mini_Profiler-0.1.2-py2.7.egg-info/requires.txt new file mode 100644 index 0000000..e3e9a71 --- /dev/null +++ b/env/lib/python2.7/site-packages/Flask_GAE_Mini_Profiler-0.1.2-py2.7.egg-info/requires.txt @@ -0,0 +1 @@ +Flask diff --git a/env/lib/python2.7/site-packages/Flask_GAE_Mini_Profiler-0.1.2-py2.7.egg-info/top_level.txt b/env/lib/python2.7/site-packages/Flask_GAE_Mini_Profiler-0.1.2-py2.7.egg-info/top_level.txt new file mode 100644 index 0000000..cbcdbd5 --- /dev/null +++ b/env/lib/python2.7/site-packages/Flask_GAE_Mini_Profiler-0.1.2-py2.7.egg-info/top_level.txt @@ -0,0 +1 @@ +flaskext diff --git a/env/lib/python2.7/site-packages/Flask_Profile-0.1-py2.7.egg-info/PKG-INFO b/env/lib/python2.7/site-packages/Flask_Profile-0.1-py2.7.egg-info/PKG-INFO new file mode 100644 index 0000000..39f89e1 --- /dev/null +++ b/env/lib/python2.7/site-packages/Flask_Profile-0.1-py2.7.egg-info/PKG-INFO @@ -0,0 +1,30 @@ +Metadata-Version: 1.1 +Name: Flask-Profile +Version: 0.1 +Summary: Flask Application Profiler +Home-page: https://github.com/fengsp/flask-profile +Author: Shipeng Feng +Author-email: fsp261@gmail.com +License: BSD +Description: + Flask-Profile + ------------- + + A profiler extension for finding bottlenecks in Flask application. + + Links + ````` + + * `documentation `_ + * `development version + `_ + + +Platform: any +Classifier: Environment :: Web Environment +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: BSD License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content +Classifier: Topic :: Software Development :: Libraries :: Python Modules diff --git a/env/lib/python2.7/site-packages/Flask_Profile-0.1-py2.7.egg-info/SOURCES.txt b/env/lib/python2.7/site-packages/Flask_Profile-0.1-py2.7.egg-info/SOURCES.txt new file mode 100644 index 0000000..9cc9783 --- /dev/null +++ b/env/lib/python2.7/site-packages/Flask_Profile-0.1-py2.7.egg-info/SOURCES.txt @@ -0,0 +1,23 @@ +CHANGES +LICENSE +MANIFEST.in +README.rst +setup.cfg +setup.py +Flask_Profile.egg-info/PKG-INFO +Flask_Profile.egg-info/SOURCES.txt +Flask_Profile.egg-info/dependency_links.txt +Flask_Profile.egg-info/not-zip-safe +Flask_Profile.egg-info/requires.txt +Flask_Profile.egg-info/top_level.txt +flask_profile/__init__.py +flask_profile/static/.DS_Store +flask_profile/static/css/profiler.css +flask_profile/static/img/asc.gif +flask_profile/static/img/bg.gif +flask_profile/static/img/desc.gif +flask_profile/static/img/profile.png +flask_profile/static/js/jquery.js +flask_profile/static/js/jquery.tablesorter.js +flask_profile/static/js/profiler.js +flask_profile/templates/_profile/profiler.html \ No newline at end of file diff --git a/env/lib/python2.7/site-packages/Flask_Profile-0.1-py2.7.egg-info/dependency_links.txt b/env/lib/python2.7/site-packages/Flask_Profile-0.1-py2.7.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/env/lib/python2.7/site-packages/Flask_Profile-0.1-py2.7.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/env/lib/python2.7/site-packages/Flask_Profile-0.1-py2.7.egg-info/installed-files.txt b/env/lib/python2.7/site-packages/Flask_Profile-0.1-py2.7.egg-info/installed-files.txt new file mode 100644 index 0000000..5105cce --- /dev/null +++ b/env/lib/python2.7/site-packages/Flask_Profile-0.1-py2.7.egg-info/installed-files.txt @@ -0,0 +1,19 @@ +../flask_profile/__init__.py +../flask_profile/static/.DS_Store +../flask_profile/static/css/profiler.css +../flask_profile/static/img/asc.gif +../flask_profile/static/img/bg.gif +../flask_profile/static/img/desc.gif +../flask_profile/static/img/profile.png +../flask_profile/static/js/jquery.js +../flask_profile/static/js/jquery.tablesorter.js +../flask_profile/static/js/profiler.js +../flask_profile/templates/_profile/profiler.html +../flask_profile/__init__.pyc +./ +dependency_links.txt +not-zip-safe +PKG-INFO +requires.txt +SOURCES.txt +top_level.txt diff --git a/env/lib/python2.7/site-packages/Flask_Profile-0.1-py2.7.egg-info/not-zip-safe b/env/lib/python2.7/site-packages/Flask_Profile-0.1-py2.7.egg-info/not-zip-safe new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/env/lib/python2.7/site-packages/Flask_Profile-0.1-py2.7.egg-info/not-zip-safe @@ -0,0 +1 @@ + diff --git a/env/lib/python2.7/site-packages/Flask_Profile-0.1-py2.7.egg-info/requires.txt b/env/lib/python2.7/site-packages/Flask_Profile-0.1-py2.7.egg-info/requires.txt new file mode 100644 index 0000000..35f54a4 --- /dev/null +++ b/env/lib/python2.7/site-packages/Flask_Profile-0.1-py2.7.egg-info/requires.txt @@ -0,0 +1 @@ +Flask >= 0.7 diff --git a/env/lib/python2.7/site-packages/Flask_Profile-0.1-py2.7.egg-info/top_level.txt b/env/lib/python2.7/site-packages/Flask_Profile-0.1-py2.7.egg-info/top_level.txt new file mode 100644 index 0000000..015fe88 --- /dev/null +++ b/env/lib/python2.7/site-packages/Flask_Profile-0.1-py2.7.egg-info/top_level.txt @@ -0,0 +1 @@ +flask_profile diff --git a/env/lib/python2.7/site-packages/Flask_Runner-2.1.1-py2.7.egg-info/PKG-INFO b/env/lib/python2.7/site-packages/Flask_Runner-2.1.1-py2.7.egg-info/PKG-INFO new file mode 100644 index 0000000..f9ca442 --- /dev/null +++ b/env/lib/python2.7/site-packages/Flask_Runner-2.1.1-py2.7.egg-info/PKG-INFO @@ -0,0 +1,22 @@ +Metadata-Version: 1.1 +Name: Flask-Runner +Version: 2.1.1 +Summary: A set of standard command line arguments for Flask applications built on top of Flask-Script +Home-page: http://github.com/miguelgrinberg/flask-runner/ +Author: Miguel Grinberg +Author-email: miguelgrinberg50@gmail.com +License: BSD +Description: + Flask-Runner + ------------ + + A set of standard command line arguments for Flask applications + +Platform: any +Classifier: Environment :: Web Environment +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: BSD License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content +Classifier: Topic :: Software Development :: Libraries :: Python Modules diff --git a/env/lib/python2.7/site-packages/Flask_Runner-2.1.1-py2.7.egg-info/SOURCES.txt b/env/lib/python2.7/site-packages/Flask_Runner-2.1.1-py2.7.egg-info/SOURCES.txt new file mode 100644 index 0000000..142de40 --- /dev/null +++ b/env/lib/python2.7/site-packages/Flask_Runner-2.1.1-py2.7.egg-info/SOURCES.txt @@ -0,0 +1,12 @@ +LICENSE +MANIFEST.in +README.md +flask_runner.py +setup.cfg +setup.py +Flask_Runner.egg-info/PKG-INFO +Flask_Runner.egg-info/SOURCES.txt +Flask_Runner.egg-info/dependency_links.txt +Flask_Runner.egg-info/not-zip-safe +Flask_Runner.egg-info/requires.txt +Flask_Runner.egg-info/top_level.txt \ No newline at end of file diff --git a/env/lib/python2.7/site-packages/Flask_Runner-2.1.1-py2.7.egg-info/dependency_links.txt b/env/lib/python2.7/site-packages/Flask_Runner-2.1.1-py2.7.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/env/lib/python2.7/site-packages/Flask_Runner-2.1.1-py2.7.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/env/lib/python2.7/site-packages/Flask_Runner-2.1.1-py2.7.egg-info/installed-files.txt b/env/lib/python2.7/site-packages/Flask_Runner-2.1.1-py2.7.egg-info/installed-files.txt new file mode 100644 index 0000000..1c2bb7c --- /dev/null +++ b/env/lib/python2.7/site-packages/Flask_Runner-2.1.1-py2.7.egg-info/installed-files.txt @@ -0,0 +1,11 @@ +../flask_runner.py +../flask_runner.pyc +../../../../README.md +../../../../LICENSE +./ +dependency_links.txt +not-zip-safe +PKG-INFO +requires.txt +SOURCES.txt +top_level.txt diff --git a/env/lib/python2.7/site-packages/Flask_Runner-2.1.1-py2.7.egg-info/not-zip-safe b/env/lib/python2.7/site-packages/Flask_Runner-2.1.1-py2.7.egg-info/not-zip-safe new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/env/lib/python2.7/site-packages/Flask_Runner-2.1.1-py2.7.egg-info/not-zip-safe @@ -0,0 +1 @@ + diff --git a/env/lib/python2.7/site-packages/Flask_Runner-2.1.1-py2.7.egg-info/requires.txt b/env/lib/python2.7/site-packages/Flask_Runner-2.1.1-py2.7.egg-info/requires.txt new file mode 100644 index 0000000..72c8f92 --- /dev/null +++ b/env/lib/python2.7/site-packages/Flask_Runner-2.1.1-py2.7.egg-info/requires.txt @@ -0,0 +1,4 @@ +Flask>=0.9,<0.11 +Flask-Script>=0.6,<0.7 +argparse +nose diff --git a/env/lib/python2.7/site-packages/Flask_Runner-2.1.1-py2.7.egg-info/top_level.txt b/env/lib/python2.7/site-packages/Flask_Runner-2.1.1-py2.7.egg-info/top_level.txt new file mode 100644 index 0000000..f988a9a --- /dev/null +++ b/env/lib/python2.7/site-packages/Flask_Runner-2.1.1-py2.7.egg-info/top_level.txt @@ -0,0 +1 @@ +flask_runner diff --git a/env/lib/python2.7/site-packages/Flask_Script-0.6.7-py2.7.egg-info/PKG-INFO b/env/lib/python2.7/site-packages/Flask_Script-0.6.7-py2.7.egg-info/PKG-INFO new file mode 100644 index 0000000..ab82ad8 --- /dev/null +++ b/env/lib/python2.7/site-packages/Flask_Script-0.6.7-py2.7.egg-info/PKG-INFO @@ -0,0 +1,35 @@ +Metadata-Version: 1.1 +Name: Flask-Script +Version: 0.6.7 +Summary: Scripting support for Flask +Home-page: http://github.com/techniq/flask-script +Author: Sean Lynch +Author-email: techniq35@gmail.com +License: BSD +Description: + Flask-Script + -------------- + + Flask support for writing external scripts. + + Links + ````` + + * `documentation `_ + + + +Platform: any +Classifier: Development Status :: 4 - Beta +Classifier: Environment :: Web Environment +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: BSD License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 2.6 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.3 +Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content +Classifier: Topic :: Software Development :: Libraries :: Python Modules diff --git a/env/lib/python2.7/site-packages/Flask_Script-0.6.7-py2.7.egg-info/SOURCES.txt b/env/lib/python2.7/site-packages/Flask_Script-0.6.7-py2.7.egg-info/SOURCES.txt new file mode 100644 index 0000000..206ff39 --- /dev/null +++ b/env/lib/python2.7/site-packages/Flask_Script-0.6.7-py2.7.egg-info/SOURCES.txt @@ -0,0 +1,31 @@ +LICENSE +MANIFEST.in +README.rst +setup.cfg +setup.py +tests.py +Flask_Script.egg-info/PKG-INFO +Flask_Script.egg-info/SOURCES.txt +Flask_Script.egg-info/dependency_links.txt +Flask_Script.egg-info/not-zip-safe +Flask_Script.egg-info/requires.txt +Flask_Script.egg-info/top_level.txt +docs/.DS_Store +docs/Makefile +docs/conf.py +docs/index.rst +docs/make.bat +docs/_static/flask-script.png +docs/_static/index.html +docs/_static/index.zip +docs/_themes/README +docs/_themes/flask_theme_support.py +docs/_themes/flask/theme.conf +docs/_themes/flask/static/flasky.css_t +docs/_themes/flask_small/layout.html +docs/_themes/flask_small/theme.conf +docs/_themes/flask_small/static/flasky.css_t +flask_script/__init__.py +flask_script/_compat.py +flask_script/cli.py +flask_script/commands.py \ No newline at end of file diff --git a/env/lib/python2.7/site-packages/Flask_Script-0.6.7-py2.7.egg-info/dependency_links.txt b/env/lib/python2.7/site-packages/Flask_Script-0.6.7-py2.7.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/env/lib/python2.7/site-packages/Flask_Script-0.6.7-py2.7.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/env/lib/python2.7/site-packages/Flask_Script-0.6.7-py2.7.egg-info/installed-files.txt b/env/lib/python2.7/site-packages/Flask_Script-0.6.7-py2.7.egg-info/installed-files.txt new file mode 100644 index 0000000..2a196af --- /dev/null +++ b/env/lib/python2.7/site-packages/Flask_Script-0.6.7-py2.7.egg-info/installed-files.txt @@ -0,0 +1,15 @@ +../flask_script/__init__.py +../flask_script/_compat.py +../flask_script/cli.py +../flask_script/commands.py +../flask_script/__init__.pyc +../flask_script/_compat.pyc +../flask_script/cli.pyc +../flask_script/commands.pyc +./ +dependency_links.txt +not-zip-safe +PKG-INFO +requires.txt +SOURCES.txt +top_level.txt diff --git a/env/lib/python2.7/site-packages/Flask_Script-0.6.7-py2.7.egg-info/not-zip-safe b/env/lib/python2.7/site-packages/Flask_Script-0.6.7-py2.7.egg-info/not-zip-safe new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/env/lib/python2.7/site-packages/Flask_Script-0.6.7-py2.7.egg-info/not-zip-safe @@ -0,0 +1 @@ + diff --git a/env/lib/python2.7/site-packages/Flask_Script-0.6.7-py2.7.egg-info/requires.txt b/env/lib/python2.7/site-packages/Flask_Script-0.6.7-py2.7.egg-info/requires.txt new file mode 100644 index 0000000..e3e9a71 --- /dev/null +++ b/env/lib/python2.7/site-packages/Flask_Script-0.6.7-py2.7.egg-info/requires.txt @@ -0,0 +1 @@ +Flask diff --git a/env/lib/python2.7/site-packages/Flask_Script-0.6.7-py2.7.egg-info/top_level.txt b/env/lib/python2.7/site-packages/Flask_Script-0.6.7-py2.7.egg-info/top_level.txt new file mode 100644 index 0000000..efd6af0 --- /dev/null +++ b/env/lib/python2.7/site-packages/Flask_Script-0.6.7-py2.7.egg-info/top_level.txt @@ -0,0 +1 @@ +flask_script diff --git a/env/lib/python2.7/site-packages/Werkzeug-0.10.1-py2.7.egg-info/PKG-INFO b/env/lib/python2.7/site-packages/Werkzeug-0.10.1-py2.7.egg-info/PKG-INFO deleted file mode 100644 index ef968e6..0000000 --- a/env/lib/python2.7/site-packages/Werkzeug-0.10.1-py2.7.egg-info/PKG-INFO +++ /dev/null @@ -1,72 +0,0 @@ -Metadata-Version: 1.1 -Name: Werkzeug -Version: 0.10.1 -Summary: The Swiss Army knife of Python web development -Home-page: http://werkzeug.pocoo.org/ -Author: Armin Ronacher -Author-email: armin.ronacher@active-4.com -License: BSD -Description: - Werkzeug - ======== - - Werkzeug started as simple collection of various utilities for WSGI - applications and has become one of the most advanced WSGI utility - modules. It includes a powerful debugger, full featured request and - response objects, HTTP utilities to handle entity tags, cache control - headers, HTTP dates, cookie handling, file uploads, a powerful URL - routing system and a bunch of community contributed addon modules. - - Werkzeug is unicode aware and doesn't enforce a specific template - engine, database adapter or anything else. It doesn't even enforce - a specific way of handling requests and leaves all that up to the - developer. It's most useful for end user applications which should work - on as many server environments as possible (such as blogs, wikis, - bulletin boards, etc.). - - Details and example applications are available on the - `Werkzeug website `_. - - - Features - -------- - - - unicode awareness - - - request and response objects - - - various utility functions for dealing with HTTP headers such as - `Accept` and `Cache-Control` headers. - - - thread local objects with proper cleanup at request end - - - an interactive debugger - - - A simple WSGI server with support for threading and forking - with an automatic reloader. - - - a flexible URL routing system with REST support. - - - fully WSGI compatible - - - Development Version - ------------------- - - The Werkzeug development version can be installed by cloning the git - repository from `github`_:: - - git clone git@github.com:mitsuhiko/werkzeug.git - - .. _github: http://github.com/mitsuhiko/werkzeug - -Platform: any -Classifier: Development Status :: 5 - Production/Stable -Classifier: Environment :: Web Environment -Classifier: Intended Audience :: Developers -Classifier: License :: OSI Approved :: BSD License -Classifier: Operating System :: OS Independent -Classifier: Programming Language :: Python -Classifier: Programming Language :: Python :: 3 -Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content -Classifier: Topic :: Software Development :: Libraries :: Python Modules diff --git a/env/lib/python2.7/site-packages/Werkzeug-0.10.1-py2.7.egg-info/SOURCES.txt b/env/lib/python2.7/site-packages/Werkzeug-0.10.1-py2.7.egg-info/SOURCES.txt deleted file mode 100644 index 23565c7..0000000 --- a/env/lib/python2.7/site-packages/Werkzeug-0.10.1-py2.7.egg-info/SOURCES.txt +++ /dev/null @@ -1,288 +0,0 @@ -AUTHORS -CHANGES -LICENSE -MANIFEST.in -Makefile -README.rst -setup.cfg -setup.py -Werkzeug.egg-info/PKG-INFO -Werkzeug.egg-info/SOURCES.txt -Werkzeug.egg-info/dependency_links.txt -Werkzeug.egg-info/not-zip-safe -Werkzeug.egg-info/top_level.txt -artwork/.DS_Store -artwork/logo.png -artwork/logo.svg -docs/Makefile -docs/changes.rst -docs/conf.py -docs/contents.rst.inc -docs/datastructures.rst -docs/debug.rst -docs/exceptions.rst -docs/http.rst -docs/index.rst -docs/installation.rst -docs/latexindex.rst -docs/levels.rst -docs/local.rst -docs/logo.pdf -docs/make.bat -docs/makearchive.py -docs/middlewares.rst -docs/python3.rst -docs/quickstart.rst -docs/request_data.rst -docs/routing.rst -docs/serving.rst -docs/terms.rst -docs/test.rst -docs/transition.rst -docs/tutorial.rst -docs/unicode.rst -docs/urls.rst -docs/utils.rst -docs/werkzeugext.py -docs/werkzeugstyle.sty -docs/wrappers.rst -docs/wsgi.rst -docs/_static/background.png -docs/_static/codebackground.png -docs/_static/contents.png -docs/_static/debug-screenshot.png -docs/_static/favicon.ico -docs/_static/header.png -docs/_static/navigation.png -docs/_static/navigation_active.png -docs/_static/shortly.png -docs/_static/shorty-screenshot.png -docs/_static/style.css -docs/_static/werkzeug.js -docs/_static/werkzeug.png -docs/_templates/sidebarintro.html -docs/_templates/sidebarlogo.html -docs/contrib/atom.rst -docs/contrib/cache.rst -docs/contrib/fixers.rst -docs/contrib/index.rst -docs/contrib/iterio.rst -docs/contrib/lint.rst -docs/contrib/profiler.rst -docs/contrib/securecookie.rst -docs/contrib/sessions.rst -docs/contrib/wrappers.rst -docs/deployment/cgi.rst -docs/deployment/fastcgi.rst -docs/deployment/index.rst -docs/deployment/mod_wsgi.rst -docs/deployment/proxying.rst -examples/README -examples/cookieauth.py -examples/httpbasicauth.py -examples/manage-coolmagic.py -examples/manage-couchy.py -examples/manage-cupoftee.py -examples/manage-i18nurls.py -examples/manage-plnt.py -examples/manage-shorty.py -examples/manage-simplewiki.py -examples/manage-webpylike.py -examples/upload.py -examples/contrib/README -examples/contrib/securecookie.py -examples/contrib/sessions.py -examples/coolmagic/__init__.py -examples/coolmagic/application.py -examples/coolmagic/helpers.py -examples/coolmagic/utils.py -examples/coolmagic/public/style.css -examples/coolmagic/templates/layout.html -examples/coolmagic/templates/static/about.html -examples/coolmagic/templates/static/index.html -examples/coolmagic/templates/static/not_found.html -examples/coolmagic/views/__init__.py -examples/coolmagic/views/static.py -examples/couchy/README -examples/couchy/__init__.py -examples/couchy/application.py -examples/couchy/models.py -examples/couchy/utils.py -examples/couchy/views.py -examples/couchy/static/style.css -examples/couchy/templates/display.html -examples/couchy/templates/layout.html -examples/couchy/templates/list.html -examples/couchy/templates/new.html -examples/couchy/templates/not_found.html -examples/cupoftee/__init__.py -examples/cupoftee/application.py -examples/cupoftee/db.py -examples/cupoftee/network.py -examples/cupoftee/pages.py -examples/cupoftee/utils.py -examples/cupoftee/shared/content.png -examples/cupoftee/shared/down.png -examples/cupoftee/shared/favicon.ico -examples/cupoftee/shared/header.png -examples/cupoftee/shared/logo.png -examples/cupoftee/shared/style.css -examples/cupoftee/shared/up.png -examples/cupoftee/templates/layout.html -examples/cupoftee/templates/missingpage.html -examples/cupoftee/templates/search.html -examples/cupoftee/templates/server.html -examples/cupoftee/templates/serverlist.html -examples/i18nurls/__init__.py -examples/i18nurls/application.py -examples/i18nurls/urls.py -examples/i18nurls/views.py -examples/i18nurls/templates/about.html -examples/i18nurls/templates/blog.html -examples/i18nurls/templates/index.html -examples/i18nurls/templates/layout.html -examples/partial/README -examples/partial/complex_routing.py -examples/plnt/__init__.py -examples/plnt/database.py -examples/plnt/sync.py -examples/plnt/utils.py -examples/plnt/views.py -examples/plnt/webapp.py -examples/plnt/shared/style.css -examples/plnt/templates/about.html -examples/plnt/templates/index.html -examples/plnt/templates/layout.html -examples/shortly/shortly.py -examples/shortly/static/style.css -examples/shortly/templates/404.html -examples/shortly/templates/layout.html -examples/shortly/templates/new_url.html -examples/shortly/templates/short_link_details.html -examples/shorty/__init__.py -examples/shorty/application.py -examples/shorty/models.py -examples/shorty/utils.py -examples/shorty/views.py -examples/shorty/static/style.css -examples/shorty/templates/display.html -examples/shorty/templates/layout.html -examples/shorty/templates/list.html -examples/shorty/templates/new.html -examples/shorty/templates/not_found.html -examples/simplewiki/__init__.py -examples/simplewiki/actions.py -examples/simplewiki/application.py -examples/simplewiki/database.py -examples/simplewiki/specialpages.py -examples/simplewiki/utils.py -examples/simplewiki/shared/style.css -examples/simplewiki/templates/action_diff.html -examples/simplewiki/templates/action_edit.html -examples/simplewiki/templates/action_log.html -examples/simplewiki/templates/action_revert.html -examples/simplewiki/templates/action_show.html -examples/simplewiki/templates/layout.html -examples/simplewiki/templates/macros.xml -examples/simplewiki/templates/missing_action.html -examples/simplewiki/templates/page_index.html -examples/simplewiki/templates/page_missing.html -examples/simplewiki/templates/recent_changes.html -examples/webpylike/example.py -examples/webpylike/webpylike.py -tests/__init__.py -tests/conftest.py -tests/test_compat.py -tests/test_datastructures.py -tests/test_debug.py -tests/test_exceptions.py -tests/test_formparser.py -tests/test_http.py -tests/test_internal.py -tests/test_local.py -tests/test_routing.py -tests/test_security.py -tests/test_serving.py -tests/test_test.py -tests/test_urls.py -tests/test_utils.py -tests/test_wrappers.py -tests/test_wsgi.py -tests/contrib/__init__.py -tests/contrib/test_atom.py -tests/contrib/test_cache.py -tests/contrib/test_fixers.py -tests/contrib/test_iterio.py -tests/contrib/test_securecookie.py -tests/contrib/test_sessions.py -tests/contrib/test_wrappers.py -tests/multipart/ie7_full_path_request.txt -tests/multipart/test_collect.py -tests/multipart/firefox3-2png1txt/file1.png -tests/multipart/firefox3-2png1txt/file2.png -tests/multipart/firefox3-2png1txt/request.txt -tests/multipart/firefox3-2png1txt/text.txt -tests/multipart/firefox3-2pnglongtext/file1.png -tests/multipart/firefox3-2pnglongtext/file2.png -tests/multipart/firefox3-2pnglongtext/request.txt -tests/multipart/firefox3-2pnglongtext/text.txt -tests/multipart/ie6-2png1txt/file1.png -tests/multipart/ie6-2png1txt/file2.png -tests/multipart/ie6-2png1txt/request.txt -tests/multipart/ie6-2png1txt/text.txt -tests/multipart/opera8-2png1txt/file1.png -tests/multipart/opera8-2png1txt/file2.png -tests/multipart/opera8-2png1txt/request.txt -tests/multipart/opera8-2png1txt/text.txt -tests/multipart/webkit3-2png1txt/file1.png -tests/multipart/webkit3-2png1txt/file2.png -tests/multipart/webkit3-2png1txt/request.txt -tests/multipart/webkit3-2png1txt/text.txt -tests/res/test.txt -werkzeug/__init__.py -werkzeug/_compat.py -werkzeug/_internal.py -werkzeug/_reloader.py -werkzeug/datastructures.py -werkzeug/exceptions.py -werkzeug/formparser.py -werkzeug/http.py -werkzeug/local.py -werkzeug/posixemulation.py -werkzeug/routing.py -werkzeug/script.py -werkzeug/security.py -werkzeug/serving.py -werkzeug/test.py -werkzeug/testapp.py -werkzeug/urls.py -werkzeug/useragents.py -werkzeug/utils.py -werkzeug/wrappers.py -werkzeug/wsgi.py -werkzeug/contrib/__init__.py -werkzeug/contrib/atom.py -werkzeug/contrib/cache.py -werkzeug/contrib/fixers.py -werkzeug/contrib/iterio.py -werkzeug/contrib/jsrouting.py -werkzeug/contrib/limiter.py -werkzeug/contrib/lint.py -werkzeug/contrib/profiler.py -werkzeug/contrib/securecookie.py -werkzeug/contrib/sessions.py -werkzeug/contrib/testtools.py -werkzeug/contrib/wrappers.py -werkzeug/debug/__init__.py -werkzeug/debug/console.py -werkzeug/debug/repr.py -werkzeug/debug/tbtools.py -werkzeug/debug/shared/FONT_LICENSE -werkzeug/debug/shared/console.png -werkzeug/debug/shared/debugger.js -werkzeug/debug/shared/jquery.js -werkzeug/debug/shared/less.png -werkzeug/debug/shared/more.png -werkzeug/debug/shared/source.png -werkzeug/debug/shared/style.css -werkzeug/debug/shared/ubuntu.ttf \ No newline at end of file diff --git a/env/lib/python2.7/site-packages/Werkzeug-0.10.1-py2.7.egg-info/installed-files.txt b/env/lib/python2.7/site-packages/Werkzeug-0.10.1-py2.7.egg-info/installed-files.txt deleted file mode 100644 index 92e8cda..0000000 --- a/env/lib/python2.7/site-packages/Werkzeug-0.10.1-py2.7.egg-info/installed-files.txt +++ /dev/null @@ -1,91 +0,0 @@ -../werkzeug/__init__.py -../werkzeug/_compat.py -../werkzeug/_internal.py -../werkzeug/_reloader.py -../werkzeug/datastructures.py -../werkzeug/exceptions.py -../werkzeug/formparser.py -../werkzeug/http.py -../werkzeug/local.py -../werkzeug/posixemulation.py -../werkzeug/routing.py -../werkzeug/script.py -../werkzeug/security.py -../werkzeug/serving.py -../werkzeug/test.py -../werkzeug/testapp.py -../werkzeug/urls.py -../werkzeug/useragents.py -../werkzeug/utils.py -../werkzeug/wrappers.py -../werkzeug/wsgi.py -../werkzeug/debug/__init__.py -../werkzeug/debug/console.py -../werkzeug/debug/repr.py -../werkzeug/debug/tbtools.py -../werkzeug/contrib/__init__.py -../werkzeug/contrib/atom.py -../werkzeug/contrib/cache.py -../werkzeug/contrib/fixers.py -../werkzeug/contrib/iterio.py -../werkzeug/contrib/jsrouting.py -../werkzeug/contrib/limiter.py -../werkzeug/contrib/lint.py -../werkzeug/contrib/profiler.py -../werkzeug/contrib/securecookie.py -../werkzeug/contrib/sessions.py -../werkzeug/contrib/testtools.py -../werkzeug/contrib/wrappers.py -../werkzeug/debug/shared/FONT_LICENSE -../werkzeug/debug/shared/console.png -../werkzeug/debug/shared/debugger.js -../werkzeug/debug/shared/jquery.js -../werkzeug/debug/shared/less.png -../werkzeug/debug/shared/more.png -../werkzeug/debug/shared/source.png -../werkzeug/debug/shared/style.css -../werkzeug/debug/shared/ubuntu.ttf -../werkzeug/__init__.pyc -../werkzeug/_compat.pyc -../werkzeug/_internal.pyc -../werkzeug/_reloader.pyc -../werkzeug/datastructures.pyc -../werkzeug/exceptions.pyc -../werkzeug/formparser.pyc -../werkzeug/http.pyc -../werkzeug/local.pyc -../werkzeug/posixemulation.pyc -../werkzeug/routing.pyc -../werkzeug/script.pyc -../werkzeug/security.pyc -../werkzeug/serving.pyc -../werkzeug/test.pyc -../werkzeug/testapp.pyc -../werkzeug/urls.pyc -../werkzeug/useragents.pyc -../werkzeug/utils.pyc -../werkzeug/wrappers.pyc -../werkzeug/wsgi.pyc -../werkzeug/debug/__init__.pyc -../werkzeug/debug/console.pyc -../werkzeug/debug/repr.pyc -../werkzeug/debug/tbtools.pyc -../werkzeug/contrib/__init__.pyc -../werkzeug/contrib/atom.pyc -../werkzeug/contrib/cache.pyc -../werkzeug/contrib/fixers.pyc -../werkzeug/contrib/iterio.pyc -../werkzeug/contrib/jsrouting.pyc -../werkzeug/contrib/limiter.pyc -../werkzeug/contrib/lint.pyc -../werkzeug/contrib/profiler.pyc -../werkzeug/contrib/securecookie.pyc -../werkzeug/contrib/sessions.pyc -../werkzeug/contrib/testtools.pyc -../werkzeug/contrib/wrappers.pyc -./ -dependency_links.txt -not-zip-safe -PKG-INFO -SOURCES.txt -top_level.txt diff --git a/env/lib/python2.7/site-packages/Werkzeug-0.10.4.dist-info/DESCRIPTION.rst b/env/lib/python2.7/site-packages/Werkzeug-0.10.4.dist-info/DESCRIPTION.rst new file mode 100644 index 0000000..2a6e8bb --- /dev/null +++ b/env/lib/python2.7/site-packages/Werkzeug-0.10.4.dist-info/DESCRIPTION.rst @@ -0,0 +1,54 @@ +Werkzeug +======== + +Werkzeug started as simple collection of various utilities for WSGI +applications and has become one of the most advanced WSGI utility +modules. It includes a powerful debugger, full featured request and +response objects, HTTP utilities to handle entity tags, cache control +headers, HTTP dates, cookie handling, file uploads, a powerful URL +routing system and a bunch of community contributed addon modules. + +Werkzeug is unicode aware and doesn't enforce a specific template +engine, database adapter or anything else. It doesn't even enforce +a specific way of handling requests and leaves all that up to the +developer. It's most useful for end user applications which should work +on as many server environments as possible (such as blogs, wikis, +bulletin boards, etc.). + +Details and example applications are available on the +`Werkzeug website `_. + + +Features +-------- + +- unicode awareness + +- request and response objects + +- various utility functions for dealing with HTTP headers such as + `Accept` and `Cache-Control` headers. + +- thread local objects with proper cleanup at request end + +- an interactive debugger + +- A simple WSGI server with support for threading and forking + with an automatic reloader. + +- a flexible URL routing system with REST support. + +- fully WSGI compatible + + +Development Version +------------------- + +The Werkzeug development version can be installed by cloning the git +repository from `github`_:: + + git clone git@github.com:mitsuhiko/werkzeug.git + +.. _github: http://github.com/mitsuhiko/werkzeug + + diff --git a/env/lib/python2.7/site-packages/Werkzeug-0.10.4.dist-info/METADATA b/env/lib/python2.7/site-packages/Werkzeug-0.10.4.dist-info/METADATA new file mode 100644 index 0000000..ff9693c --- /dev/null +++ b/env/lib/python2.7/site-packages/Werkzeug-0.10.4.dist-info/METADATA @@ -0,0 +1,73 @@ +Metadata-Version: 2.0 +Name: Werkzeug +Version: 0.10.4 +Summary: The Swiss Army knife of Python web development +Home-page: http://werkzeug.pocoo.org/ +Author: Armin Ronacher +Author-email: armin.ronacher@active-4.com +License: BSD +Platform: any +Classifier: Development Status :: 5 - Production/Stable +Classifier: Environment :: Web Environment +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: BSD License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content +Classifier: Topic :: Software Development :: Libraries :: Python Modules + +Werkzeug +======== + +Werkzeug started as simple collection of various utilities for WSGI +applications and has become one of the most advanced WSGI utility +modules. It includes a powerful debugger, full featured request and +response objects, HTTP utilities to handle entity tags, cache control +headers, HTTP dates, cookie handling, file uploads, a powerful URL +routing system and a bunch of community contributed addon modules. + +Werkzeug is unicode aware and doesn't enforce a specific template +engine, database adapter or anything else. It doesn't even enforce +a specific way of handling requests and leaves all that up to the +developer. It's most useful for end user applications which should work +on as many server environments as possible (such as blogs, wikis, +bulletin boards, etc.). + +Details and example applications are available on the +`Werkzeug website `_. + + +Features +-------- + +- unicode awareness + +- request and response objects + +- various utility functions for dealing with HTTP headers such as + `Accept` and `Cache-Control` headers. + +- thread local objects with proper cleanup at request end + +- an interactive debugger + +- A simple WSGI server with support for threading and forking + with an automatic reloader. + +- a flexible URL routing system with REST support. + +- fully WSGI compatible + + +Development Version +------------------- + +The Werkzeug development version can be installed by cloning the git +repository from `github`_:: + + git clone git@github.com:mitsuhiko/werkzeug.git + +.. _github: http://github.com/mitsuhiko/werkzeug + + diff --git a/env/lib/python2.7/site-packages/Werkzeug-0.10.4.dist-info/RECORD b/env/lib/python2.7/site-packages/Werkzeug-0.10.4.dist-info/RECORD new file mode 100644 index 0000000..95aa8ab --- /dev/null +++ b/env/lib/python2.7/site-packages/Werkzeug-0.10.4.dist-info/RECORD @@ -0,0 +1,91 @@ +werkzeug/posixemulation.py,sha256=58IrWwAkpafaUgQMVvPfkPrjme73eIuY5PjKL2wT37Y,3483 +werkzeug/security.py,sha256=UxbSD8C-oFid9r5Oncofeuz-EALPpjE10fefDljbVfw,8971 +werkzeug/__init__.py,sha256=iYo261RIx6wL-YWtbovcn5skbouhaWdJJRGLh2kGAeM,7211 +werkzeug/testapp.py,sha256=txTIeAoq83FW6HVTBroW_0AAvnIUpZl0w8Vj2sluLDY,9398 +werkzeug/http.py,sha256=DtyRYXP06JzEdX6YS5mPFYTbtnrH7qpNvYqlqLMeoTs,34279 +werkzeug/routing.py,sha256=9fUMWiQePjbS_tugJgKTCOg9tyEnw9BaEfAj2VraAqY,64428 +werkzeug/utils.py,sha256=Ps0V2dT_HOXgXLxH4S6o_WyQG9IF8dpcAcvPsF_xgO4,23063 +werkzeug/exceptions.py,sha256=BAU0SzpOfZmU_18nyyIAzObKZhE_UrSnhymhrwGQg64,18577 +werkzeug/_reloader.py,sha256=v0WOEKO8b3q9VwKidJRQDGq4ycDZgu2znwphvgdOb_I,7938 +werkzeug/formparser.py,sha256=i0-ZzXhVxSNNMBDRHWzdbte9l5QRlycVHp0BENI2pCY,21205 +werkzeug/_compat.py,sha256=gsFXhixbO2A8rAcfm7oIDPLENSXKdeDkpieP__g8l1M,6190 +werkzeug/datastructures.py,sha256=zNlWB-h7ttq-Q-dvQqs-AAUDGH96EnYS1c60ffYanPw,86857 +werkzeug/wrappers.py,sha256=Fw1xlCuFSdq21pj6QvJkCXYkIykaSngF7w2NGPsGgdU,76919 +werkzeug/test.py,sha256=96zCaMfWMahIW2VeOdERQ66DlqslA5qrehIs6-orwrQ,34255 +werkzeug/urls.py,sha256=NObHlWIyrMA_PpOlRFCsiBn502kjM6giZ02LCgZzOfY,36596 +werkzeug/script.py,sha256=Fq9fTl2217_Chf4F2XRcpLU5xzQsNqK9tHz-CLZJKzw,11365 +werkzeug/useragents.py,sha256=DF0uXjvoKWcWgLHYOIRI98VlN0fezCNXWIJv0tmBE4A,5399 +werkzeug/local.py,sha256=r5qW9-Q2jNfZLBDoboae8A3ngw9olbVnpIs2byMDrLI,14095 +werkzeug/serving.py,sha256=WaJGURZx2uO1O95_J_zJ3eLLZq82qdxBGxFYWdWX5_0,25317 +werkzeug/wsgi.py,sha256=-lFqnoyiAV4pWf1C7gJISitddGTBKf2oqPc0Dt-XJZc,37837 +werkzeug/_internal.py,sha256=3ELUVz48eXOZeDfvgNaSSGdsg0keKw-Jvtl0Gu03WgY,13713 +werkzeug/contrib/fixers.py,sha256=8qM2G4yx1ZWm4wdHLzDVAAvFEVk03CZrY5fRnPFrUyk,10197 +werkzeug/contrib/limiter.py,sha256=wgprYdgFcwwxNy2sQlFrAyd2GxHvft0CObThiv6vPKI,1333 +werkzeug/contrib/__init__.py,sha256=f7PfttZhbrImqpr5Ezre8CXgwvcGUJK7zWNpO34WWrw,623 +werkzeug/contrib/testtools.py,sha256=MxyzW_79JppFjNOasq_KhcmYPLUY2H536pB638SwaSg,2449 +werkzeug/contrib/iterio.py,sha256=UZeSPsHEIE1p-fH0XShk7xsJKBEMbLaTXNEOCe4twug,10811 +werkzeug/contrib/cache.py,sha256=QgVRvCzRxXd3HhUh_fZ1L1d9jwK5BW4LRkou-XAhncQ,24912 +werkzeug/contrib/securecookie.py,sha256=DHXl5WnT3R_nyUnNEiDvyJBl37dZen711_JGeg3fusA,12204 +werkzeug/contrib/lint.py,sha256=TsxFaxqzizN3ClRnv7lEeOILEjd2fYzXDqs6rIZu04M,12282 +werkzeug/contrib/profiler.py,sha256=Xot8BuERy7sXtQowEnmCjkv6KaAAzLDZ3J7KLnyElpI,4921 +werkzeug/contrib/wrappers.py,sha256=7zZmAFuNV-UEqehlfCBuB-BcVr6kH2HQZ2LLcXtmC1I,10331 +werkzeug/contrib/atom.py,sha256=Y-Iv5Dn0iZ8kCi9izPyM0Jo1p2Gc19r-8-3mkfpZ0vs,15498 +werkzeug/contrib/jsrouting.py,sha256=QTmgeDoKXvNK02KzXgx9lr3cAH6fAzpwF5bBdPNvJPs,8564 +werkzeug/contrib/sessions.py,sha256=wXPc0K-bI8VRuaeeTLvfxMk62FlCBb7-i2ACR1v4Ik0,12450 +werkzeug/debug/console.py,sha256=pzd4NmCmEUQ0Oxa1l1FSujBQUIv8U02CtMVnQmaEX5E,5557 +werkzeug/debug/repr.py,sha256=PqLZIJbL6FlAmgjmUeZ3b0J3FmM8Ocd3tng3o1EwBDU,9350 +werkzeug/debug/__init__.py,sha256=x5Wp4s4hMQK-uTsvHwmSDPxnROGaJ2LiX6XzX75Lin8,7800 +werkzeug/debug/tbtools.py,sha256=NbHBRvqsllp89aCFP9RELm-_xNd6R0seiVHx0Q85t7Y,16913 +werkzeug/debug/shared/less.png,sha256=-4-kNRaXJSONVLahrQKUxMwXGm9R4OnZ9SxDGpHlIR4,191 +werkzeug/debug/shared/source.png,sha256=RoGcBTE4CyCB85GBuDGTFlAnUqxwTBiIfDqW15EpnUQ,818 +werkzeug/debug/shared/debugger.js,sha256=YoeX5PlYuX1vSeufjCuCHXaoHQy2gpIm7FC0O9pq9PA,6345 +werkzeug/debug/shared/style.css,sha256=Yeipe9D_NlbxztN0dEazATYBLwGtkrGdbXtVYkJJK0U,6075 +werkzeug/debug/shared/console.png,sha256=bxax6RXXlvOij_KeqvSNX0ojJf83YbnZ7my-3Gx9w2A,507 +werkzeug/debug/shared/FONT_LICENSE,sha256=LwAVEI1oYnvXiNMT9SnCH_TaLCxCpeHziDrMg0gPkAI,4673 +werkzeug/debug/shared/jquery.js,sha256=UXNk8tRRYvtQN0N7W2y5U9ANmys7ebqH2f5X6m7mBww,78601 +werkzeug/debug/shared/ubuntu.ttf,sha256=1eaHFyepmy4FyDvjLVzpITrGEBu_CZYY94jE0nED1c0,70220 +werkzeug/debug/shared/more.png,sha256=GngN7CioHQoV58rH6ojnkYi8c_qED2Aka5FO5UXrReY,200 +Werkzeug-0.10.4.dist-info/DESCRIPTION.rst,sha256=5sTwZ_Sj5aeEN8mlcOdNJ_ng40HiGazGmILLyTMX8o0,1595 +Werkzeug-0.10.4.dist-info/metadata.json,sha256=D5TEHaD4DmqnhCQ00QghGhJerVwqszsbpOOzFS3Bo44,851 +Werkzeug-0.10.4.dist-info/RECORD,, +Werkzeug-0.10.4.dist-info/top_level.txt,sha256=QRyj2VjwJoQkrwjwFIOlB8Xg3r9un0NtqVHQF-15xaw,9 +Werkzeug-0.10.4.dist-info/WHEEL,sha256=AvR0WeTpDaxT645bl5FQxUK6NPsTls2ttpcGJg3j1Xg,110 +Werkzeug-0.10.4.dist-info/METADATA,sha256=S_im0ZAXS1A-ReOQFPve62nnhH0aTnewuDvnNltATd4,2301 +werkzeug/_reloader.pyc,, +werkzeug/contrib/testtools.pyc,, +werkzeug/formparser.pyc,, +werkzeug/_compat.pyc,, +werkzeug/posixemulation.pyc,, +werkzeug/serving.pyc,, +werkzeug/contrib/__init__.pyc,, +werkzeug/contrib/iterio.pyc,, +werkzeug/datastructures.pyc,, +werkzeug/__init__.pyc,, +werkzeug/contrib/limiter.pyc,, +werkzeug/debug/tbtools.pyc,, +werkzeug/contrib/sessions.pyc,, +werkzeug/local.pyc,, +werkzeug/utils.pyc,, +werkzeug/contrib/lint.pyc,, +werkzeug/security.pyc,, +werkzeug/contrib/cache.pyc,, +werkzeug/contrib/securecookie.pyc,, +werkzeug/script.pyc,, +werkzeug/routing.pyc,, +werkzeug/wrappers.pyc,, +werkzeug/contrib/jsrouting.pyc,, +werkzeug/contrib/fixers.pyc,, +werkzeug/contrib/profiler.pyc,, +werkzeug/debug/console.pyc,, +werkzeug/debug/__init__.pyc,, +werkzeug/wsgi.pyc,, +werkzeug/test.pyc,, +werkzeug/http.pyc,, +werkzeug/urls.pyc,, +werkzeug/useragents.pyc,, +werkzeug/_internal.pyc,, +werkzeug/contrib/wrappers.pyc,, +werkzeug/exceptions.pyc,, +werkzeug/contrib/atom.pyc,, +werkzeug/testapp.pyc,, +werkzeug/debug/repr.pyc,, diff --git a/env/lib/python2.7/site-packages/Werkzeug-0.10.4.dist-info/WHEEL b/env/lib/python2.7/site-packages/Werkzeug-0.10.4.dist-info/WHEEL new file mode 100644 index 0000000..9dff69d --- /dev/null +++ b/env/lib/python2.7/site-packages/Werkzeug-0.10.4.dist-info/WHEEL @@ -0,0 +1,6 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.24.0) +Root-Is-Purelib: true +Tag: py2-none-any +Tag: py3-none-any + diff --git a/env/lib/python2.7/site-packages/Werkzeug-0.10.4.dist-info/metadata.json b/env/lib/python2.7/site-packages/Werkzeug-0.10.4.dist-info/metadata.json new file mode 100644 index 0000000..d8c519e --- /dev/null +++ b/env/lib/python2.7/site-packages/Werkzeug-0.10.4.dist-info/metadata.json @@ -0,0 +1 @@ +{"license": "BSD", "name": "Werkzeug", "metadata_version": "2.0", "generator": "bdist_wheel (0.24.0)", "summary": "The Swiss Army knife of Python web development", "platform": "any", "version": "0.10.4", "extensions": {"python.details": {"project_urls": {"Home": "http://werkzeug.pocoo.org/"}, "document_names": {"description": "DESCRIPTION.rst"}, "contacts": [{"role": "author", "email": "armin.ronacher@active-4.com", "name": "Armin Ronacher"}]}}, "classifiers": ["Development Status :: 5 - Production/Stable", "Environment :: Web Environment", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", "Topic :: Internet :: WWW/HTTP :: Dynamic Content", "Topic :: Software Development :: Libraries :: Python Modules"]} \ No newline at end of file diff --git a/env/lib/python2.7/site-packages/Werkzeug-0.10.1-py2.7.egg-info/top_level.txt b/env/lib/python2.7/site-packages/Werkzeug-0.10.4.dist-info/top_level.txt similarity index 100% rename from env/lib/python2.7/site-packages/Werkzeug-0.10.1-py2.7.egg-info/top_level.txt rename to env/lib/python2.7/site-packages/Werkzeug-0.10.4.dist-info/top_level.txt diff --git a/env/lib/python2.7/site-packages/argparse-1.3.0.dist-info/DESCRIPTION.rst b/env/lib/python2.7/site-packages/argparse-1.3.0.dist-info/DESCRIPTION.rst new file mode 100644 index 0000000..9535a3b --- /dev/null +++ b/env/lib/python2.7/site-packages/argparse-1.3.0.dist-info/DESCRIPTION.rst @@ -0,0 +1,50 @@ +The argparse module makes it easy to write user friendly command line +interfaces. + +The program defines what arguments it requires, and argparse will figure out +how to parse those out of sys.argv. The argparse module also automatically +generates help and usage messages and issues errors when users give the +program invalid arguments. + +As of Python >= 2.7 and >= 3.2, the argparse module is maintained within the +Python standard library. For users who still need to support Python < 2.7 or +< 3.2, it is also provided as a separate package, which tries to stay +compatible with the module in the standard library, but also supports older +Python versions. + +argparse is licensed under the Python license, for details see LICENSE.txt. + + +Compatibility +------------- + +argparse should work on Python >= 2.3, it was tested on: + +* 2.3, 2.4, 2.5, 2.6 and 2.7 +* 3.1, 3.2, 3.3, 3.4 + + +Installation +------------ + +Try one of these: + + python setup.py install + + easy_install argparse + + pip install argparse + + putting argparse.py in some directory listed in sys.path should also work + + +Bugs +---- + +If you find a bug, please try to reproduce it with python 2.7. + +If it happens there also, please file a bug in the python.org issue tracker. +If it does not happen in 2.7, file a bug in the argparse package issue tracker. + + + diff --git a/env/lib/python2.7/site-packages/argparse-1.3.0.dist-info/METADATA b/env/lib/python2.7/site-packages/argparse-1.3.0.dist-info/METADATA new file mode 100644 index 0000000..cc0cfa6 --- /dev/null +++ b/env/lib/python2.7/site-packages/argparse-1.3.0.dist-info/METADATA @@ -0,0 +1,80 @@ +Metadata-Version: 2.0 +Name: argparse +Version: 1.3.0 +Summary: Python command-line parsing library +Home-page: http://code.google.com/p/argparse/ +Author: Steven Bethard +Author-email: steven.bethard@gmail.com +License: Python Software Foundation License +Keywords: argparse command line parser parsing +Platform: any +Classifier: Development Status :: 5 - Production/Stable +Classifier: Environment :: Console +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: Python Software Foundation License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 2.3 +Classifier: Programming Language :: Python :: 2.4 +Classifier: Programming Language :: Python :: 2.5 +Classifier: Programming Language :: Python :: 2.6 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3.0 +Classifier: Programming Language :: Python :: 3.1 +Classifier: Programming Language :: Python :: 3.2 +Classifier: Programming Language :: Python :: 3.3 +Classifier: Programming Language :: Python :: 3.4 +Classifier: Topic :: Software Development + +The argparse module makes it easy to write user friendly command line +interfaces. + +The program defines what arguments it requires, and argparse will figure out +how to parse those out of sys.argv. The argparse module also automatically +generates help and usage messages and issues errors when users give the +program invalid arguments. + +As of Python >= 2.7 and >= 3.2, the argparse module is maintained within the +Python standard library. For users who still need to support Python < 2.7 or +< 3.2, it is also provided as a separate package, which tries to stay +compatible with the module in the standard library, but also supports older +Python versions. + +argparse is licensed under the Python license, for details see LICENSE.txt. + + +Compatibility +------------- + +argparse should work on Python >= 2.3, it was tested on: + +* 2.3, 2.4, 2.5, 2.6 and 2.7 +* 3.1, 3.2, 3.3, 3.4 + + +Installation +------------ + +Try one of these: + + python setup.py install + + easy_install argparse + + pip install argparse + + putting argparse.py in some directory listed in sys.path should also work + + +Bugs +---- + +If you find a bug, please try to reproduce it with python 2.7. + +If it happens there also, please file a bug in the python.org issue tracker. +If it does not happen in 2.7, file a bug in the argparse package issue tracker. + + + diff --git a/env/lib/python2.7/site-packages/argparse-1.3.0.dist-info/RECORD b/env/lib/python2.7/site-packages/argparse-1.3.0.dist-info/RECORD new file mode 100644 index 0000000..bd551f5 --- /dev/null +++ b/env/lib/python2.7/site-packages/argparse-1.3.0.dist-info/RECORD @@ -0,0 +1,8 @@ +argparse.py,sha256=xCHg6lWHfvz0PurQZwY9ptMAMpjuPN1ohGfgfFvGarc,88400 +argparse-1.3.0.dist-info/metadata.json,sha256=S1HDc9CFjPUkLuxV6roMM5ZbcCir8GPrYRox626T8YM,1322 +argparse-1.3.0.dist-info/WHEEL,sha256=AvR0WeTpDaxT645bl5FQxUK6NPsTls2ttpcGJg3j1Xg,110 +argparse-1.3.0.dist-info/top_level.txt,sha256=TgiWrQsF0mKWwqS2KHLORD0ZtqYHPRGdCAAzKwtVvJ4,9 +argparse-1.3.0.dist-info/METADATA,sha256=J80U_19qFS_NpsEYIoTb3KmIY7-HByzlKE4xbog8Cqk,2561 +argparse-1.3.0.dist-info/DESCRIPTION.rst,sha256=R6plu9_ZUt8XSH6Gz6rAH2FhFYmxIji7dqqtrrbjTlY,1313 +argparse-1.3.0.dist-info/RECORD,, +argparse.pyc,, diff --git a/env/lib/python2.7/site-packages/argparse-1.3.0.dist-info/WHEEL b/env/lib/python2.7/site-packages/argparse-1.3.0.dist-info/WHEEL new file mode 100644 index 0000000..9dff69d --- /dev/null +++ b/env/lib/python2.7/site-packages/argparse-1.3.0.dist-info/WHEEL @@ -0,0 +1,6 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.24.0) +Root-Is-Purelib: true +Tag: py2-none-any +Tag: py3-none-any + diff --git a/env/lib/python2.7/site-packages/argparse-1.3.0.dist-info/metadata.json b/env/lib/python2.7/site-packages/argparse-1.3.0.dist-info/metadata.json new file mode 100644 index 0000000..9686204 --- /dev/null +++ b/env/lib/python2.7/site-packages/argparse-1.3.0.dist-info/metadata.json @@ -0,0 +1 @@ +{"license": "Python Software Foundation License", "name": "argparse", "metadata_version": "2.0", "generator": "bdist_wheel (0.24.0)", "summary": "Python command-line parsing library", "platform": "any", "version": "1.3.0", "extensions": {"python.details": {"project_urls": {"Home": "http://code.google.com/p/argparse/"}, "document_names": {"description": "DESCRIPTION.rst"}, "contacts": [{"role": "author", "email": "steven.bethard@gmail.com", "name": "Steven Bethard"}]}}, "keywords": ["argparse", "command", "line", "parser", "parsing"], "classifiers": ["Development Status :: 5 - Production/Stable", "Environment :: Console", "Intended Audience :: Developers", "License :: OSI Approved :: Python Software Foundation License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 3", "Programming Language :: Python :: 2.3", "Programming Language :: Python :: 2.4", "Programming Language :: Python :: 2.5", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3.0", "Programming Language :: Python :: 3.1", "Programming Language :: Python :: 3.2", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Topic :: Software Development"]} \ No newline at end of file diff --git a/env/lib/python2.7/site-packages/argparse-1.3.0.dist-info/top_level.txt b/env/lib/python2.7/site-packages/argparse-1.3.0.dist-info/top_level.txt new file mode 100644 index 0000000..1352d5e --- /dev/null +++ b/env/lib/python2.7/site-packages/argparse-1.3.0.dist-info/top_level.txt @@ -0,0 +1 @@ +argparse diff --git a/env/lib/python2.7/site-packages/argparse.py b/env/lib/python2.7/site-packages/argparse.py new file mode 100644 index 0000000..5a68b70 --- /dev/null +++ b/env/lib/python2.7/site-packages/argparse.py @@ -0,0 +1,2378 @@ +# Author: Steven J. Bethard . + +"""Command-line parsing library + +This module is an optparse-inspired command-line parsing library that: + + - handles both optional and positional arguments + - produces highly informative usage messages + - supports parsers that dispatch to sub-parsers + +The following is a simple usage example that sums integers from the +command-line and writes the result to a file:: + + parser = argparse.ArgumentParser( + description='sum the integers at the command line') + parser.add_argument( + 'integers', metavar='int', nargs='+', type=int, + help='an integer to be summed') + parser.add_argument( + '--log', default=sys.stdout, type=argparse.FileType('w'), + help='the file where the sum should be written') + args = parser.parse_args() + args.log.write('%s' % sum(args.integers)) + args.log.close() + +The module contains the following public classes: + + - ArgumentParser -- The main entry point for command-line parsing. As the + example above shows, the add_argument() method is used to populate + the parser with actions for optional and positional arguments. Then + the parse_args() method is invoked to convert the args at the + command-line into an object with attributes. + + - ArgumentError -- The exception raised by ArgumentParser objects when + there are errors with the parser's actions. Errors raised while + parsing the command-line are caught by ArgumentParser and emitted + as command-line messages. + + - FileType -- A factory for defining types of files to be created. As the + example above shows, instances of FileType are typically passed as + the type= argument of add_argument() calls. + + - Action -- The base class for parser actions. Typically actions are + selected by passing strings like 'store_true' or 'append_const' to + the action= argument of add_argument(). However, for greater + customization of ArgumentParser actions, subclasses of Action may + be defined and passed as the action= argument. + + - HelpFormatter, RawDescriptionHelpFormatter, RawTextHelpFormatter, + ArgumentDefaultsHelpFormatter -- Formatter classes which + may be passed as the formatter_class= argument to the + ArgumentParser constructor. HelpFormatter is the default, + RawDescriptionHelpFormatter and RawTextHelpFormatter tell the parser + not to change the formatting for help text, and + ArgumentDefaultsHelpFormatter adds information about argument defaults + to the help. + +All other classes in this module are considered implementation details. +(Also note that HelpFormatter and RawDescriptionHelpFormatter are only +considered public as object names -- the API of the formatter objects is +still considered an implementation detail.) +""" + +__version__ = '1.3.0' # we use our own version number independant of the + # one in stdlib and we release this on pypi. + +__external_lib__ = True # to make sure the tests really test THIS lib, + # not the builtin one in Python stdlib + +__all__ = [ + 'ArgumentParser', + 'ArgumentError', + 'ArgumentTypeError', + 'FileType', + 'HelpFormatter', + 'ArgumentDefaultsHelpFormatter', + 'RawDescriptionHelpFormatter', + 'RawTextHelpFormatter', + 'Namespace', + 'Action', + 'ONE_OR_MORE', + 'OPTIONAL', + 'PARSER', + 'REMAINDER', + 'SUPPRESS', + 'ZERO_OR_MORE', +] + + +import copy as _copy +import os as _os +import re as _re +import sys as _sys +import textwrap as _textwrap + +from gettext import gettext as _ + +try: + set +except NameError: + # for python < 2.4 compatibility (sets module is there since 2.3): + from sets import Set as set + +try: + basestring +except NameError: + basestring = str + +try: + sorted +except NameError: + # for python < 2.4 compatibility: + def sorted(iterable, reverse=False): + result = list(iterable) + result.sort() + if reverse: + result.reverse() + return result + + +def _callable(obj): + return hasattr(obj, '__call__') or hasattr(obj, '__bases__') + + +SUPPRESS = '==SUPPRESS==' + +OPTIONAL = '?' +ZERO_OR_MORE = '*' +ONE_OR_MORE = '+' +PARSER = 'A...' +REMAINDER = '...' +_UNRECOGNIZED_ARGS_ATTR = '_unrecognized_args' + +# ============================= +# Utility functions and classes +# ============================= + +class _AttributeHolder(object): + """Abstract base class that provides __repr__. + + The __repr__ method returns a string in the format:: + ClassName(attr=name, attr=name, ...) + The attributes are determined either by a class-level attribute, + '_kwarg_names', or by inspecting the instance __dict__. + """ + + def __repr__(self): + type_name = type(self).__name__ + arg_strings = [] + for arg in self._get_args(): + arg_strings.append(repr(arg)) + for name, value in self._get_kwargs(): + arg_strings.append('%s=%r' % (name, value)) + return '%s(%s)' % (type_name, ', '.join(arg_strings)) + + def _get_kwargs(self): + return sorted(self.__dict__.items()) + + def _get_args(self): + return [] + + +def _ensure_value(namespace, name, value): + if getattr(namespace, name, None) is None: + setattr(namespace, name, value) + return getattr(namespace, name) + + +# =============== +# Formatting Help +# =============== + +class HelpFormatter(object): + """Formatter for generating usage messages and argument help strings. + + Only the name of this class is considered a public API. All the methods + provided by the class are considered an implementation detail. + """ + + def __init__(self, + prog, + indent_increment=2, + max_help_position=24, + width=None): + + # default setting for width + if width is None: + try: + width = int(_os.environ['COLUMNS']) + except (KeyError, ValueError): + width = 80 + width -= 2 + + self._prog = prog + self._indent_increment = indent_increment + self._max_help_position = max_help_position + self._width = width + + self._current_indent = 0 + self._level = 0 + self._action_max_length = 0 + + self._root_section = self._Section(self, None) + self._current_section = self._root_section + + self._whitespace_matcher = _re.compile(r'\s+') + self._long_break_matcher = _re.compile(r'\n\n\n+') + + # =============================== + # Section and indentation methods + # =============================== + def _indent(self): + self._current_indent += self._indent_increment + self._level += 1 + + def _dedent(self): + self._current_indent -= self._indent_increment + assert self._current_indent >= 0, 'Indent decreased below 0.' + self._level -= 1 + + class _Section(object): + + def __init__(self, formatter, parent, heading=None): + self.formatter = formatter + self.parent = parent + self.heading = heading + self.items = [] + + def format_help(self): + # format the indented section + if self.parent is not None: + self.formatter._indent() + join = self.formatter._join_parts + for func, args in self.items: + func(*args) + item_help = join([func(*args) for func, args in self.items]) + if self.parent is not None: + self.formatter._dedent() + + # return nothing if the section was empty + if not item_help: + return '' + + # add the heading if the section was non-empty + if self.heading is not SUPPRESS and self.heading is not None: + current_indent = self.formatter._current_indent + heading = '%*s%s:\n' % (current_indent, '', self.heading) + else: + heading = '' + + # join the section-initial newline, the heading and the help + return join(['\n', heading, item_help, '\n']) + + def _add_item(self, func, args): + self._current_section.items.append((func, args)) + + # ======================== + # Message building methods + # ======================== + def start_section(self, heading): + self._indent() + section = self._Section(self, self._current_section, heading) + self._add_item(section.format_help, []) + self._current_section = section + + def end_section(self): + self._current_section = self._current_section.parent + self._dedent() + + def add_text(self, text): + if text is not SUPPRESS and text is not None: + self._add_item(self._format_text, [text]) + + def add_usage(self, usage, actions, groups, prefix=None): + if usage is not SUPPRESS: + args = usage, actions, groups, prefix + self._add_item(self._format_usage, args) + + def add_argument(self, action): + if action.help is not SUPPRESS: + + # find all invocations + get_invocation = self._format_action_invocation + invocations = [get_invocation(action)] + for subaction in self._iter_indented_subactions(action): + invocations.append(get_invocation(subaction)) + + # update the maximum item length + invocation_length = max([len(s) for s in invocations]) + action_length = invocation_length + self._current_indent + self._action_max_length = max(self._action_max_length, + action_length) + + # add the item to the list + self._add_item(self._format_action, [action]) + + def add_arguments(self, actions): + for action in actions: + self.add_argument(action) + + # ======================= + # Help-formatting methods + # ======================= + def format_help(self): + help = self._root_section.format_help() + if help: + help = self._long_break_matcher.sub('\n\n', help) + help = help.strip('\n') + '\n' + return help + + def _join_parts(self, part_strings): + return ''.join([part + for part in part_strings + if part and part is not SUPPRESS]) + + def _format_usage(self, usage, actions, groups, prefix): + if prefix is None: + prefix = _('usage: ') + + # if usage is specified, use that + if usage is not None: + usage = usage % dict(prog=self._prog) + + # if no optionals or positionals are available, usage is just prog + elif usage is None and not actions: + usage = '%(prog)s' % dict(prog=self._prog) + + # if optionals and positionals are available, calculate usage + elif usage is None: + prog = '%(prog)s' % dict(prog=self._prog) + + # split optionals from positionals + optionals = [] + positionals = [] + for action in actions: + if action.option_strings: + optionals.append(action) + else: + positionals.append(action) + + # build full usage string + format = self._format_actions_usage + action_usage = format(optionals + positionals, groups) + usage = ' '.join([s for s in [prog, action_usage] if s]) + + # wrap the usage parts if it's too long + text_width = self._width - self._current_indent + if len(prefix) + len(usage) > text_width: + + # break usage into wrappable parts + part_regexp = r'\(.*?\)+|\[.*?\]+|\S+' + opt_usage = format(optionals, groups) + pos_usage = format(positionals, groups) + opt_parts = _re.findall(part_regexp, opt_usage) + pos_parts = _re.findall(part_regexp, pos_usage) + assert ' '.join(opt_parts) == opt_usage + assert ' '.join(pos_parts) == pos_usage + + # helper for wrapping lines + def get_lines(parts, indent, prefix=None): + lines = [] + line = [] + if prefix is not None: + line_len = len(prefix) - 1 + else: + line_len = len(indent) - 1 + for part in parts: + if line_len + 1 + len(part) > text_width: + lines.append(indent + ' '.join(line)) + line = [] + line_len = len(indent) - 1 + line.append(part) + line_len += len(part) + 1 + if line: + lines.append(indent + ' '.join(line)) + if prefix is not None: + lines[0] = lines[0][len(indent):] + return lines + + # if prog is short, follow it with optionals or positionals + if len(prefix) + len(prog) <= 0.75 * text_width: + indent = ' ' * (len(prefix) + len(prog) + 1) + if opt_parts: + lines = get_lines([prog] + opt_parts, indent, prefix) + lines.extend(get_lines(pos_parts, indent)) + elif pos_parts: + lines = get_lines([prog] + pos_parts, indent, prefix) + else: + lines = [prog] + + # if prog is long, put it on its own line + else: + indent = ' ' * len(prefix) + parts = opt_parts + pos_parts + lines = get_lines(parts, indent) + if len(lines) > 1: + lines = [] + lines.extend(get_lines(opt_parts, indent)) + lines.extend(get_lines(pos_parts, indent)) + lines = [prog] + lines + + # join lines into usage + usage = '\n'.join(lines) + + # prefix with 'usage:' + return '%s%s\n\n' % (prefix, usage) + + def _format_actions_usage(self, actions, groups): + # find group indices and identify actions in groups + group_actions = set() + inserts = {} + for group in groups: + try: + start = actions.index(group._group_actions[0]) + except ValueError: + continue + else: + end = start + len(group._group_actions) + if actions[start:end] == group._group_actions: + for action in group._group_actions: + group_actions.add(action) + if not group.required: + if start in inserts: + inserts[start] += ' [' + else: + inserts[start] = '[' + inserts[end] = ']' + else: + if start in inserts: + inserts[start] += ' (' + else: + inserts[start] = '(' + inserts[end] = ')' + for i in range(start + 1, end): + inserts[i] = '|' + + # collect all actions format strings + parts = [] + for i, action in enumerate(actions): + + # suppressed arguments are marked with None + # remove | separators for suppressed arguments + if action.help is SUPPRESS: + parts.append(None) + if inserts.get(i) == '|': + inserts.pop(i) + elif inserts.get(i + 1) == '|': + inserts.pop(i + 1) + + # produce all arg strings + elif not action.option_strings: + part = self._format_args(action, action.dest) + + # if it's in a group, strip the outer [] + if action in group_actions: + if part[0] == '[' and part[-1] == ']': + part = part[1:-1] + + # add the action string to the list + parts.append(part) + + # produce the first way to invoke the option in brackets + else: + option_string = action.option_strings[0] + + # if the Optional doesn't take a value, format is: + # -s or --long + if action.nargs == 0: + part = '%s' % option_string + + # if the Optional takes a value, format is: + # -s ARGS or --long ARGS + else: + default = action.dest.upper() + args_string = self._format_args(action, default) + part = '%s %s' % (option_string, args_string) + + # make it look optional if it's not required or in a group + if not action.required and action not in group_actions: + part = '[%s]' % part + + # add the action string to the list + parts.append(part) + + # insert things at the necessary indices + for i in sorted(inserts, reverse=True): + parts[i:i] = [inserts[i]] + + # join all the action items with spaces + text = ' '.join([item for item in parts if item is not None]) + + # clean up separators for mutually exclusive groups + open = r'[\[(]' + close = r'[\])]' + text = _re.sub(r'(%s) ' % open, r'\1', text) + text = _re.sub(r' (%s)' % close, r'\1', text) + text = _re.sub(r'%s *%s' % (open, close), r'', text) + text = _re.sub(r'\(([^|]*)\)', r'\1', text) + text = text.strip() + + # return the text + return text + + def _format_text(self, text): + if '%(prog)' in text: + text = text % dict(prog=self._prog) + text_width = self._width - self._current_indent + indent = ' ' * self._current_indent + return self._fill_text(text, text_width, indent) + '\n\n' + + def _format_action(self, action): + # determine the required width and the entry label + help_position = min(self._action_max_length + 2, + self._max_help_position) + help_width = self._width - help_position + action_width = help_position - self._current_indent - 2 + action_header = self._format_action_invocation(action) + + # ho nelp; start on same line and add a final newline + if not action.help: + tup = self._current_indent, '', action_header + action_header = '%*s%s\n' % tup + + # short action name; start on the same line and pad two spaces + elif len(action_header) <= action_width: + tup = self._current_indent, '', action_width, action_header + action_header = '%*s%-*s ' % tup + indent_first = 0 + + # long action name; start on the next line + else: + tup = self._current_indent, '', action_header + action_header = '%*s%s\n' % tup + indent_first = help_position + + # collect the pieces of the action help + parts = [action_header] + + # if there was help for the action, add lines of help text + if action.help: + help_text = self._expand_help(action) + help_lines = self._split_lines(help_text, help_width) + parts.append('%*s%s\n' % (indent_first, '', help_lines[0])) + for line in help_lines[1:]: + parts.append('%*s%s\n' % (help_position, '', line)) + + # or add a newline if the description doesn't end with one + elif not action_header.endswith('\n'): + parts.append('\n') + + # if there are any sub-actions, add their help as well + for subaction in self._iter_indented_subactions(action): + parts.append(self._format_action(subaction)) + + # return a single string + return self._join_parts(parts) + + def _format_action_invocation(self, action): + if not action.option_strings: + metavar, = self._metavar_formatter(action, action.dest)(1) + return metavar + + else: + parts = [] + + # if the Optional doesn't take a value, format is: + # -s, --long + if action.nargs == 0: + parts.extend(action.option_strings) + + # if the Optional takes a value, format is: + # -s ARGS, --long ARGS + else: + default = action.dest.upper() + args_string = self._format_args(action, default) + for option_string in action.option_strings: + parts.append('%s %s' % (option_string, args_string)) + + return ', '.join(parts) + + def _metavar_formatter(self, action, default_metavar): + if action.metavar is not None: + result = action.metavar + elif action.choices is not None: + choice_strs = [str(choice) for choice in action.choices] + result = '{%s}' % ','.join(choice_strs) + else: + result = default_metavar + + def format(tuple_size): + if isinstance(result, tuple): + return result + else: + return (result, ) * tuple_size + return format + + def _format_args(self, action, default_metavar): + get_metavar = self._metavar_formatter(action, default_metavar) + if action.nargs is None: + result = '%s' % get_metavar(1) + elif action.nargs == OPTIONAL: + result = '[%s]' % get_metavar(1) + elif action.nargs == ZERO_OR_MORE: + result = '[%s [%s ...]]' % get_metavar(2) + elif action.nargs == ONE_OR_MORE: + result = '%s [%s ...]' % get_metavar(2) + elif action.nargs == REMAINDER: + result = '...' + elif action.nargs == PARSER: + result = '%s ...' % get_metavar(1) + else: + formats = ['%s' for _ in range(action.nargs)] + result = ' '.join(formats) % get_metavar(action.nargs) + return result + + def _expand_help(self, action): + params = dict(vars(action), prog=self._prog) + for name in list(params): + if params[name] is SUPPRESS: + del params[name] + for name in list(params): + if hasattr(params[name], '__name__'): + params[name] = params[name].__name__ + if params.get('choices') is not None: + choices_str = ', '.join([str(c) for c in params['choices']]) + params['choices'] = choices_str + return self._get_help_string(action) % params + + def _iter_indented_subactions(self, action): + try: + get_subactions = action._get_subactions + except AttributeError: + pass + else: + self._indent() + for subaction in get_subactions(): + yield subaction + self._dedent() + + def _split_lines(self, text, width): + text = self._whitespace_matcher.sub(' ', text).strip() + return _textwrap.wrap(text, width) + + def _fill_text(self, text, width, indent): + text = self._whitespace_matcher.sub(' ', text).strip() + return _textwrap.fill(text, width, initial_indent=indent, + subsequent_indent=indent) + + def _get_help_string(self, action): + return action.help + + +class RawDescriptionHelpFormatter(HelpFormatter): + """Help message formatter which retains any formatting in descriptions. + + Only the name of this class is considered a public API. All the methods + provided by the class are considered an implementation detail. + """ + + def _fill_text(self, text, width, indent): + return ''.join([indent + line for line in text.splitlines(True)]) + + +class RawTextHelpFormatter(RawDescriptionHelpFormatter): + """Help message formatter which retains formatting of all help text. + + Only the name of this class is considered a public API. All the methods + provided by the class are considered an implementation detail. + """ + + def _split_lines(self, text, width): + return text.splitlines() + + +class ArgumentDefaultsHelpFormatter(HelpFormatter): + """Help message formatter which adds default values to argument help. + + Only the name of this class is considered a public API. All the methods + provided by the class are considered an implementation detail. + """ + + def _get_help_string(self, action): + help = action.help + if '%(default)' not in action.help: + if action.default is not SUPPRESS: + defaulting_nargs = [OPTIONAL, ZERO_OR_MORE] + if action.option_strings or action.nargs in defaulting_nargs: + help += ' (default: %(default)s)' + return help + + +# ===================== +# Options and Arguments +# ===================== + +def _get_action_name(argument): + if argument is None: + return None + elif argument.option_strings: + return '/'.join(argument.option_strings) + elif argument.metavar not in (None, SUPPRESS): + return argument.metavar + elif argument.dest not in (None, SUPPRESS): + return argument.dest + else: + return None + + +class ArgumentError(Exception): + """An error from creating or using an argument (optional or positional). + + The string value of this exception is the message, augmented with + information about the argument that caused it. + """ + + def __init__(self, argument, message): + self.argument_name = _get_action_name(argument) + self.message = message + + def __str__(self): + if self.argument_name is None: + format = '%(message)s' + else: + format = 'argument %(argument_name)s: %(message)s' + return format % dict(message=self.message, + argument_name=self.argument_name) + + +class ArgumentTypeError(Exception): + """An error from trying to convert a command line string to a type.""" + pass + + +# ============== +# Action classes +# ============== + +class Action(_AttributeHolder): + """Information about how to convert command line strings to Python objects. + + Action objects are used by an ArgumentParser to represent the information + needed to parse a single argument from one or more strings from the + command line. The keyword arguments to the Action constructor are also + all attributes of Action instances. + + Keyword Arguments: + + - option_strings -- A list of command-line option strings which + should be associated with this action. + + - dest -- The name of the attribute to hold the created object(s) + + - nargs -- The number of command-line arguments that should be + consumed. By default, one argument will be consumed and a single + value will be produced. Other values include: + - N (an integer) consumes N arguments (and produces a list) + - '?' consumes zero or one arguments + - '*' consumes zero or more arguments (and produces a list) + - '+' consumes one or more arguments (and produces a list) + Note that the difference between the default and nargs=1 is that + with the default, a single value will be produced, while with + nargs=1, a list containing a single value will be produced. + + - const -- The value to be produced if the option is specified and the + option uses an action that takes no values. + + - default -- The value to be produced if the option is not specified. + + - type -- The type which the command-line arguments should be converted + to, should be one of 'string', 'int', 'float', 'complex' or a + callable object that accepts a single string argument. If None, + 'string' is assumed. + + - choices -- A container of values that should be allowed. If not None, + after a command-line argument has been converted to the appropriate + type, an exception will be raised if it is not a member of this + collection. + + - required -- True if the action must always be specified at the + command line. This is only meaningful for optional command-line + arguments. + + - help -- The help string describing the argument. + + - metavar -- The name to be used for the option's argument with the + help string. If None, the 'dest' value will be used as the name. + """ + + def __init__(self, + option_strings, + dest, + nargs=None, + const=None, + default=None, + type=None, + choices=None, + required=False, + help=None, + metavar=None): + self.option_strings = option_strings + self.dest = dest + self.nargs = nargs + self.const = const + self.default = default + self.type = type + self.choices = choices + self.required = required + self.help = help + self.metavar = metavar + + def _get_kwargs(self): + names = [ + 'option_strings', + 'dest', + 'nargs', + 'const', + 'default', + 'type', + 'choices', + 'help', + 'metavar', + ] + return [(name, getattr(self, name)) for name in names] + + def __call__(self, parser, namespace, values, option_string=None): + raise NotImplementedError(_('.__call__() not defined')) + + +class _StoreAction(Action): + + def __init__(self, + option_strings, + dest, + nargs=None, + const=None, + default=None, + type=None, + choices=None, + required=False, + help=None, + metavar=None): + if nargs == 0: + raise ValueError('nargs for store actions must be > 0; if you ' + 'have nothing to store, actions such as store ' + 'true or store const may be more appropriate') + if const is not None and nargs != OPTIONAL: + raise ValueError('nargs must be %r to supply const' % OPTIONAL) + super(_StoreAction, self).__init__( + option_strings=option_strings, + dest=dest, + nargs=nargs, + const=const, + default=default, + type=type, + choices=choices, + required=required, + help=help, + metavar=metavar) + + def __call__(self, parser, namespace, values, option_string=None): + setattr(namespace, self.dest, values) + + +class _StoreConstAction(Action): + + def __init__(self, + option_strings, + dest, + const, + default=None, + required=False, + help=None, + metavar=None): + super(_StoreConstAction, self).__init__( + option_strings=option_strings, + dest=dest, + nargs=0, + const=const, + default=default, + required=required, + help=help) + + def __call__(self, parser, namespace, values, option_string=None): + setattr(namespace, self.dest, self.const) + + +class _StoreTrueAction(_StoreConstAction): + + def __init__(self, + option_strings, + dest, + default=False, + required=False, + help=None): + super(_StoreTrueAction, self).__init__( + option_strings=option_strings, + dest=dest, + const=True, + default=default, + required=required, + help=help) + + +class _StoreFalseAction(_StoreConstAction): + + def __init__(self, + option_strings, + dest, + default=True, + required=False, + help=None): + super(_StoreFalseAction, self).__init__( + option_strings=option_strings, + dest=dest, + const=False, + default=default, + required=required, + help=help) + + +class _AppendAction(Action): + + def __init__(self, + option_strings, + dest, + nargs=None, + const=None, + default=None, + type=None, + choices=None, + required=False, + help=None, + metavar=None): + if nargs == 0: + raise ValueError('nargs for append actions must be > 0; if arg ' + 'strings are not supplying the value to append, ' + 'the append const action may be more appropriate') + if const is not None and nargs != OPTIONAL: + raise ValueError('nargs must be %r to supply const' % OPTIONAL) + super(_AppendAction, self).__init__( + option_strings=option_strings, + dest=dest, + nargs=nargs, + const=const, + default=default, + type=type, + choices=choices, + required=required, + help=help, + metavar=metavar) + + def __call__(self, parser, namespace, values, option_string=None): + items = _copy.copy(_ensure_value(namespace, self.dest, [])) + items.append(values) + setattr(namespace, self.dest, items) + + +class _AppendConstAction(Action): + + def __init__(self, + option_strings, + dest, + const, + default=None, + required=False, + help=None, + metavar=None): + super(_AppendConstAction, self).__init__( + option_strings=option_strings, + dest=dest, + nargs=0, + const=const, + default=default, + required=required, + help=help, + metavar=metavar) + + def __call__(self, parser, namespace, values, option_string=None): + items = _copy.copy(_ensure_value(namespace, self.dest, [])) + items.append(self.const) + setattr(namespace, self.dest, items) + + +class _CountAction(Action): + + def __init__(self, + option_strings, + dest, + default=None, + required=False, + help=None): + super(_CountAction, self).__init__( + option_strings=option_strings, + dest=dest, + nargs=0, + default=default, + required=required, + help=help) + + def __call__(self, parser, namespace, values, option_string=None): + new_count = _ensure_value(namespace, self.dest, 0) + 1 + setattr(namespace, self.dest, new_count) + + +class _HelpAction(Action): + + def __init__(self, + option_strings, + dest=SUPPRESS, + default=SUPPRESS, + help=None): + super(_HelpAction, self).__init__( + option_strings=option_strings, + dest=dest, + default=default, + nargs=0, + help=help) + + def __call__(self, parser, namespace, values, option_string=None): + parser.print_help() + parser.exit() + + +class _VersionAction(Action): + + def __init__(self, + option_strings, + version=None, + dest=SUPPRESS, + default=SUPPRESS, + help="show program's version number and exit"): + super(_VersionAction, self).__init__( + option_strings=option_strings, + dest=dest, + default=default, + nargs=0, + help=help) + self.version = version + + def __call__(self, parser, namespace, values, option_string=None): + version = self.version + if version is None: + version = parser.version + formatter = parser._get_formatter() + formatter.add_text(version) + parser.exit(message=formatter.format_help()) + + +class _SubParsersAction(Action): + + class _ChoicesPseudoAction(Action): + + def __init__(self, name, aliases, help): + metavar = dest = name + if aliases: + metavar += ' (%s)' % ', '.join(aliases) + sup = super(_SubParsersAction._ChoicesPseudoAction, self) + sup.__init__(option_strings=[], dest=dest, help=help, + metavar=metavar) + + def __init__(self, + option_strings, + prog, + parser_class, + dest=SUPPRESS, + help=None, + metavar=None): + + self._prog_prefix = prog + self._parser_class = parser_class + self._name_parser_map = {} + self._choices_actions = [] + + super(_SubParsersAction, self).__init__( + option_strings=option_strings, + dest=dest, + nargs=PARSER, + choices=self._name_parser_map, + help=help, + metavar=metavar) + + def add_parser(self, name, **kwargs): + # set prog from the existing prefix + if kwargs.get('prog') is None: + kwargs['prog'] = '%s %s' % (self._prog_prefix, name) + + aliases = kwargs.pop('aliases', ()) + + # create a pseudo-action to hold the choice help + if 'help' in kwargs: + help = kwargs.pop('help') + choice_action = self._ChoicesPseudoAction(name, aliases, help) + self._choices_actions.append(choice_action) + + # create the parser and add it to the map + parser = self._parser_class(**kwargs) + self._name_parser_map[name] = parser + + # make parser available under aliases also + for alias in aliases: + self._name_parser_map[alias] = parser + + return parser + + def _get_subactions(self): + return self._choices_actions + + def __call__(self, parser, namespace, values, option_string=None): + parser_name = values[0] + arg_strings = values[1:] + + # set the parser name if requested + if self.dest is not SUPPRESS: + setattr(namespace, self.dest, parser_name) + + # select the parser + try: + parser = self._name_parser_map[parser_name] + except KeyError: + tup = parser_name, ', '.join(self._name_parser_map) + msg = _('unknown parser %r (choices: %s)' % tup) + raise ArgumentError(self, msg) + + # parse all the remaining options into the namespace + # store any unrecognized options on the object, so that the top + # level parser can decide what to do with them + namespace, arg_strings = parser.parse_known_args(arg_strings, namespace) + if arg_strings: + vars(namespace).setdefault(_UNRECOGNIZED_ARGS_ATTR, []) + getattr(namespace, _UNRECOGNIZED_ARGS_ATTR).extend(arg_strings) + + +# ============== +# Type classes +# ============== + +class FileType(object): + """Factory for creating file object types + + Instances of FileType are typically passed as type= arguments to the + ArgumentParser add_argument() method. + + Keyword Arguments: + - mode -- A string indicating how the file is to be opened. Accepts the + same values as the builtin open() function. + - bufsize -- The file's desired buffer size. Accepts the same values as + the builtin open() function. + """ + + def __init__(self, mode='r', bufsize=None): + self._mode = mode + self._bufsize = bufsize + + def __call__(self, string): + # the special argument "-" means sys.std{in,out} + if string == '-': + if 'r' in self._mode: + return _sys.stdin + elif 'w' in self._mode: + return _sys.stdout + else: + msg = _('argument "-" with mode %r' % self._mode) + raise ValueError(msg) + + # all other arguments are used as file names + if self._bufsize: + return open(string, self._mode, self._bufsize) + else: + return open(string, self._mode) + + def __repr__(self): + args = [self._mode, self._bufsize] + args_str = ', '.join([repr(arg) for arg in args if arg is not None]) + return '%s(%s)' % (type(self).__name__, args_str) + +# =========================== +# Optional and Positional Parsing +# =========================== + +class Namespace(_AttributeHolder): + """Simple object for storing attributes. + + Implements equality by attribute names and values, and provides a simple + string representation. + """ + + def __init__(self, **kwargs): + for name in kwargs: + setattr(self, name, kwargs[name]) + + __hash__ = None + + def __eq__(self, other): + return vars(self) == vars(other) + + def __ne__(self, other): + return not (self == other) + + def __contains__(self, key): + return key in self.__dict__ + + +class _ActionsContainer(object): + + def __init__(self, + description, + prefix_chars, + argument_default, + conflict_handler): + super(_ActionsContainer, self).__init__() + + self.description = description + self.argument_default = argument_default + self.prefix_chars = prefix_chars + self.conflict_handler = conflict_handler + + # set up registries + self._registries = {} + + # register actions + self.register('action', None, _StoreAction) + self.register('action', 'store', _StoreAction) + self.register('action', 'store_const', _StoreConstAction) + self.register('action', 'store_true', _StoreTrueAction) + self.register('action', 'store_false', _StoreFalseAction) + self.register('action', 'append', _AppendAction) + self.register('action', 'append_const', _AppendConstAction) + self.register('action', 'count', _CountAction) + self.register('action', 'help', _HelpAction) + self.register('action', 'version', _VersionAction) + self.register('action', 'parsers', _SubParsersAction) + + # raise an exception if the conflict handler is invalid + self._get_handler() + + # action storage + self._actions = [] + self._option_string_actions = {} + + # groups + self._action_groups = [] + self._mutually_exclusive_groups = [] + + # defaults storage + self._defaults = {} + + # determines whether an "option" looks like a negative number + self._negative_number_matcher = _re.compile(r'^-\d+$|^-\d*\.\d+$') + + # whether or not there are any optionals that look like negative + # numbers -- uses a list so it can be shared and edited + self._has_negative_number_optionals = [] + + # ==================== + # Registration methods + # ==================== + def register(self, registry_name, value, object): + registry = self._registries.setdefault(registry_name, {}) + registry[value] = object + + def _registry_get(self, registry_name, value, default=None): + return self._registries[registry_name].get(value, default) + + # ================================== + # Namespace default accessor methods + # ================================== + def set_defaults(self, **kwargs): + self._defaults.update(kwargs) + + # if these defaults match any existing arguments, replace + # the previous default on the object with the new one + for action in self._actions: + if action.dest in kwargs: + action.default = kwargs[action.dest] + + def get_default(self, dest): + for action in self._actions: + if action.dest == dest and action.default is not None: + return action.default + return self._defaults.get(dest, None) + + + # ======================= + # Adding argument actions + # ======================= + def add_argument(self, *args, **kwargs): + """ + add_argument(dest, ..., name=value, ...) + add_argument(option_string, option_string, ..., name=value, ...) + """ + + # if no positional args are supplied or only one is supplied and + # it doesn't look like an option string, parse a positional + # argument + chars = self.prefix_chars + if not args or len(args) == 1 and args[0][0] not in chars: + if args and 'dest' in kwargs: + raise ValueError('dest supplied twice for positional argument') + kwargs = self._get_positional_kwargs(*args, **kwargs) + + # otherwise, we're adding an optional argument + else: + kwargs = self._get_optional_kwargs(*args, **kwargs) + + # if no default was supplied, use the parser-level default + if 'default' not in kwargs: + dest = kwargs['dest'] + if dest in self._defaults: + kwargs['default'] = self._defaults[dest] + elif self.argument_default is not None: + kwargs['default'] = self.argument_default + + # create the action object, and add it to the parser + action_class = self._pop_action_class(kwargs) + if not _callable(action_class): + raise ValueError('unknown action "%s"' % action_class) + action = action_class(**kwargs) + + # raise an error if the action type is not callable + type_func = self._registry_get('type', action.type, action.type) + if not _callable(type_func): + raise ValueError('%r is not callable' % type_func) + + return self._add_action(action) + + def add_argument_group(self, *args, **kwargs): + group = _ArgumentGroup(self, *args, **kwargs) + self._action_groups.append(group) + return group + + def add_mutually_exclusive_group(self, **kwargs): + group = _MutuallyExclusiveGroup(self, **kwargs) + self._mutually_exclusive_groups.append(group) + return group + + def _add_action(self, action): + # resolve any conflicts + self._check_conflict(action) + + # add to actions list + self._actions.append(action) + action.container = self + + # index the action by any option strings it has + for option_string in action.option_strings: + self._option_string_actions[option_string] = action + + # set the flag if any option strings look like negative numbers + for option_string in action.option_strings: + if self._negative_number_matcher.match(option_string): + if not self._has_negative_number_optionals: + self._has_negative_number_optionals.append(True) + + # return the created action + return action + + def _remove_action(self, action): + self._actions.remove(action) + + def _add_container_actions(self, container): + # collect groups by titles + title_group_map = {} + for group in self._action_groups: + if group.title in title_group_map: + msg = _('cannot merge actions - two groups are named %r') + raise ValueError(msg % (group.title)) + title_group_map[group.title] = group + + # map each action to its group + group_map = {} + for group in container._action_groups: + + # if a group with the title exists, use that, otherwise + # create a new group matching the container's group + if group.title not in title_group_map: + title_group_map[group.title] = self.add_argument_group( + title=group.title, + description=group.description, + conflict_handler=group.conflict_handler) + + # map the actions to their new group + for action in group._group_actions: + group_map[action] = title_group_map[group.title] + + # add container's mutually exclusive groups + # NOTE: if add_mutually_exclusive_group ever gains title= and + # description= then this code will need to be expanded as above + for group in container._mutually_exclusive_groups: + mutex_group = self.add_mutually_exclusive_group( + required=group.required) + + # map the actions to their new mutex group + for action in group._group_actions: + group_map[action] = mutex_group + + # add all actions to this container or their group + for action in container._actions: + group_map.get(action, self)._add_action(action) + + def _get_positional_kwargs(self, dest, **kwargs): + # make sure required is not specified + if 'required' in kwargs: + msg = _("'required' is an invalid argument for positionals") + raise TypeError(msg) + + # mark positional arguments as required if at least one is + # always required + if kwargs.get('nargs') not in [OPTIONAL, ZERO_OR_MORE]: + kwargs['required'] = True + if kwargs.get('nargs') == ZERO_OR_MORE and 'default' not in kwargs: + kwargs['required'] = True + + # return the keyword arguments with no option strings + return dict(kwargs, dest=dest, option_strings=[]) + + def _get_optional_kwargs(self, *args, **kwargs): + # determine short and long option strings + option_strings = [] + long_option_strings = [] + for option_string in args: + # error on strings that don't start with an appropriate prefix + if not option_string[0] in self.prefix_chars: + msg = _('invalid option string %r: ' + 'must start with a character %r') + tup = option_string, self.prefix_chars + raise ValueError(msg % tup) + + # strings starting with two prefix characters are long options + option_strings.append(option_string) + if option_string[0] in self.prefix_chars: + if len(option_string) > 1: + if option_string[1] in self.prefix_chars: + long_option_strings.append(option_string) + + # infer destination, '--foo-bar' -> 'foo_bar' and '-x' -> 'x' + dest = kwargs.pop('dest', None) + if dest is None: + if long_option_strings: + dest_option_string = long_option_strings[0] + else: + dest_option_string = option_strings[0] + dest = dest_option_string.lstrip(self.prefix_chars) + if not dest: + msg = _('dest= is required for options like %r') + raise ValueError(msg % option_string) + dest = dest.replace('-', '_') + + # return the updated keyword arguments + return dict(kwargs, dest=dest, option_strings=option_strings) + + def _pop_action_class(self, kwargs, default=None): + action = kwargs.pop('action', default) + return self._registry_get('action', action, action) + + def _get_handler(self): + # determine function from conflict handler string + handler_func_name = '_handle_conflict_%s' % self.conflict_handler + try: + return getattr(self, handler_func_name) + except AttributeError: + msg = _('invalid conflict_resolution value: %r') + raise ValueError(msg % self.conflict_handler) + + def _check_conflict(self, action): + + # find all options that conflict with this option + confl_optionals = [] + for option_string in action.option_strings: + if option_string in self._option_string_actions: + confl_optional = self._option_string_actions[option_string] + confl_optionals.append((option_string, confl_optional)) + + # resolve any conflicts + if confl_optionals: + conflict_handler = self._get_handler() + conflict_handler(action, confl_optionals) + + def _handle_conflict_error(self, action, conflicting_actions): + message = _('conflicting option string(s): %s') + conflict_string = ', '.join([option_string + for option_string, action + in conflicting_actions]) + raise ArgumentError(action, message % conflict_string) + + def _handle_conflict_resolve(self, action, conflicting_actions): + + # remove all conflicting options + for option_string, action in conflicting_actions: + + # remove the conflicting option + action.option_strings.remove(option_string) + self._option_string_actions.pop(option_string, None) + + # if the option now has no option string, remove it from the + # container holding it + if not action.option_strings: + action.container._remove_action(action) + + +class _ArgumentGroup(_ActionsContainer): + + def __init__(self, container, title=None, description=None, **kwargs): + # add any missing keyword arguments by checking the container + update = kwargs.setdefault + update('conflict_handler', container.conflict_handler) + update('prefix_chars', container.prefix_chars) + update('argument_default', container.argument_default) + super_init = super(_ArgumentGroup, self).__init__ + super_init(description=description, **kwargs) + + # group attributes + self.title = title + self._group_actions = [] + + # share most attributes with the container + self._registries = container._registries + self._actions = container._actions + self._option_string_actions = container._option_string_actions + self._defaults = container._defaults + self._has_negative_number_optionals = \ + container._has_negative_number_optionals + + def _add_action(self, action): + action = super(_ArgumentGroup, self)._add_action(action) + self._group_actions.append(action) + return action + + def _remove_action(self, action): + super(_ArgumentGroup, self)._remove_action(action) + self._group_actions.remove(action) + + +class _MutuallyExclusiveGroup(_ArgumentGroup): + + def __init__(self, container, required=False): + super(_MutuallyExclusiveGroup, self).__init__(container) + self.required = required + self._container = container + + def _add_action(self, action): + if action.required: + msg = _('mutually exclusive arguments must be optional') + raise ValueError(msg) + action = self._container._add_action(action) + self._group_actions.append(action) + return action + + def _remove_action(self, action): + self._container._remove_action(action) + self._group_actions.remove(action) + + +class ArgumentParser(_AttributeHolder, _ActionsContainer): + """Object for parsing command line strings into Python objects. + + Keyword Arguments: + - prog -- The name of the program (default: sys.argv[0]) + - usage -- A usage message (default: auto-generated from arguments) + - description -- A description of what the program does + - epilog -- Text following the argument descriptions + - parents -- Parsers whose arguments should be copied into this one + - formatter_class -- HelpFormatter class for printing help messages + - prefix_chars -- Characters that prefix optional arguments + - fromfile_prefix_chars -- Characters that prefix files containing + additional arguments + - argument_default -- The default value for all arguments + - conflict_handler -- String indicating how to handle conflicts + - add_help -- Add a -h/-help option + """ + + def __init__(self, + prog=None, + usage=None, + description=None, + epilog=None, + version=None, + parents=[], + formatter_class=HelpFormatter, + prefix_chars='-', + fromfile_prefix_chars=None, + argument_default=None, + conflict_handler='error', + add_help=True): + + if version is not None: + import warnings + warnings.warn( + """The "version" argument to ArgumentParser is deprecated. """ + """Please use """ + """"add_argument(..., action='version', version="N", ...)" """ + """instead""", DeprecationWarning) + + superinit = super(ArgumentParser, self).__init__ + superinit(description=description, + prefix_chars=prefix_chars, + argument_default=argument_default, + conflict_handler=conflict_handler) + + # default setting for prog + if prog is None: + prog = _os.path.basename(_sys.argv[0]) + + self.prog = prog + self.usage = usage + self.epilog = epilog + self.version = version + self.formatter_class = formatter_class + self.fromfile_prefix_chars = fromfile_prefix_chars + self.add_help = add_help + + add_group = self.add_argument_group + self._positionals = add_group(_('positional arguments')) + self._optionals = add_group(_('optional arguments')) + self._subparsers = None + + # register types + def identity(string): + return string + self.register('type', None, identity) + + # add help and version arguments if necessary + # (using explicit default to override global argument_default) + if '-' in prefix_chars: + default_prefix = '-' + else: + default_prefix = prefix_chars[0] + if self.add_help: + self.add_argument( + default_prefix+'h', default_prefix*2+'help', + action='help', default=SUPPRESS, + help=_('show this help message and exit')) + if self.version: + self.add_argument( + default_prefix+'v', default_prefix*2+'version', + action='version', default=SUPPRESS, + version=self.version, + help=_("show program's version number and exit")) + + # add parent arguments and defaults + for parent in parents: + self._add_container_actions(parent) + try: + defaults = parent._defaults + except AttributeError: + pass + else: + self._defaults.update(defaults) + + # ======================= + # Pretty __repr__ methods + # ======================= + def _get_kwargs(self): + names = [ + 'prog', + 'usage', + 'description', + 'version', + 'formatter_class', + 'conflict_handler', + 'add_help', + ] + return [(name, getattr(self, name)) for name in names] + + # ================================== + # Optional/Positional adding methods + # ================================== + def add_subparsers(self, **kwargs): + if self._subparsers is not None: + self.error(_('cannot have multiple subparser arguments')) + + # add the parser class to the arguments if it's not present + kwargs.setdefault('parser_class', type(self)) + + if 'title' in kwargs or 'description' in kwargs: + title = _(kwargs.pop('title', 'subcommands')) + description = _(kwargs.pop('description', None)) + self._subparsers = self.add_argument_group(title, description) + else: + self._subparsers = self._positionals + + # prog defaults to the usage message of this parser, skipping + # optional arguments and with no "usage:" prefix + if kwargs.get('prog') is None: + formatter = self._get_formatter() + positionals = self._get_positional_actions() + groups = self._mutually_exclusive_groups + formatter.add_usage(self.usage, positionals, groups, '') + kwargs['prog'] = formatter.format_help().strip() + + # create the parsers action and add it to the positionals list + parsers_class = self._pop_action_class(kwargs, 'parsers') + action = parsers_class(option_strings=[], **kwargs) + self._subparsers._add_action(action) + + # return the created parsers action + return action + + def _add_action(self, action): + if action.option_strings: + self._optionals._add_action(action) + else: + self._positionals._add_action(action) + return action + + def _get_optional_actions(self): + return [action + for action in self._actions + if action.option_strings] + + def _get_positional_actions(self): + return [action + for action in self._actions + if not action.option_strings] + + # ===================================== + # Command line argument parsing methods + # ===================================== + def parse_args(self, args=None, namespace=None): + args, argv = self.parse_known_args(args, namespace) + if argv: + msg = _('unrecognized arguments: %s') + self.error(msg % ' '.join(argv)) + return args + + def parse_known_args(self, args=None, namespace=None): + # args default to the system args + if args is None: + args = _sys.argv[1:] + + # default Namespace built from parser defaults + if namespace is None: + namespace = Namespace() + + # add any action defaults that aren't present + for action in self._actions: + if action.dest is not SUPPRESS: + if not hasattr(namespace, action.dest): + if action.default is not SUPPRESS: + default = action.default + if isinstance(action.default, basestring): + default = self._get_value(action, default) + setattr(namespace, action.dest, default) + + # add any parser defaults that aren't present + for dest in self._defaults: + if not hasattr(namespace, dest): + setattr(namespace, dest, self._defaults[dest]) + + # parse the arguments and exit if there are any errors + try: + namespace, args = self._parse_known_args(args, namespace) + if hasattr(namespace, _UNRECOGNIZED_ARGS_ATTR): + args.extend(getattr(namespace, _UNRECOGNIZED_ARGS_ATTR)) + delattr(namespace, _UNRECOGNIZED_ARGS_ATTR) + return namespace, args + except ArgumentError: + err = _sys.exc_info()[1] + self.error(str(err)) + + def _parse_known_args(self, arg_strings, namespace): + # replace arg strings that are file references + if self.fromfile_prefix_chars is not None: + arg_strings = self._read_args_from_files(arg_strings) + + # map all mutually exclusive arguments to the other arguments + # they can't occur with + action_conflicts = {} + for mutex_group in self._mutually_exclusive_groups: + group_actions = mutex_group._group_actions + for i, mutex_action in enumerate(mutex_group._group_actions): + conflicts = action_conflicts.setdefault(mutex_action, []) + conflicts.extend(group_actions[:i]) + conflicts.extend(group_actions[i + 1:]) + + # find all option indices, and determine the arg_string_pattern + # which has an 'O' if there is an option at an index, + # an 'A' if there is an argument, or a '-' if there is a '--' + option_string_indices = {} + arg_string_pattern_parts = [] + arg_strings_iter = iter(arg_strings) + for i, arg_string in enumerate(arg_strings_iter): + + # all args after -- are non-options + if arg_string == '--': + arg_string_pattern_parts.append('-') + for arg_string in arg_strings_iter: + arg_string_pattern_parts.append('A') + + # otherwise, add the arg to the arg strings + # and note the index if it was an option + else: + option_tuple = self._parse_optional(arg_string) + if option_tuple is None: + pattern = 'A' + else: + option_string_indices[i] = option_tuple + pattern = 'O' + arg_string_pattern_parts.append(pattern) + + # join the pieces together to form the pattern + arg_strings_pattern = ''.join(arg_string_pattern_parts) + + # converts arg strings to the appropriate and then takes the action + seen_actions = set() + seen_non_default_actions = set() + + def take_action(action, argument_strings, option_string=None): + seen_actions.add(action) + argument_values = self._get_values(action, argument_strings) + + # error if this argument is not allowed with other previously + # seen arguments, assuming that actions that use the default + # value don't really count as "present" + if argument_values is not action.default: + seen_non_default_actions.add(action) + for conflict_action in action_conflicts.get(action, []): + if conflict_action in seen_non_default_actions: + msg = _('not allowed with argument %s') + action_name = _get_action_name(conflict_action) + raise ArgumentError(action, msg % action_name) + + # take the action if we didn't receive a SUPPRESS value + # (e.g. from a default) + if argument_values is not SUPPRESS: + action(self, namespace, argument_values, option_string) + + # function to convert arg_strings into an optional action + def consume_optional(start_index): + + # get the optional identified at this index + option_tuple = option_string_indices[start_index] + action, option_string, explicit_arg = option_tuple + + # identify additional optionals in the same arg string + # (e.g. -xyz is the same as -x -y -z if no args are required) + match_argument = self._match_argument + action_tuples = [] + while True: + + # if we found no optional action, skip it + if action is None: + extras.append(arg_strings[start_index]) + return start_index + 1 + + # if there is an explicit argument, try to match the + # optional's string arguments to only this + if explicit_arg is not None: + arg_count = match_argument(action, 'A') + + # if the action is a single-dash option and takes no + # arguments, try to parse more single-dash options out + # of the tail of the option string + chars = self.prefix_chars + if arg_count == 0 and option_string[1] not in chars: + action_tuples.append((action, [], option_string)) + char = option_string[0] + option_string = char + explicit_arg[0] + new_explicit_arg = explicit_arg[1:] or None + optionals_map = self._option_string_actions + if option_string in optionals_map: + action = optionals_map[option_string] + explicit_arg = new_explicit_arg + else: + msg = _('ignored explicit argument %r') + raise ArgumentError(action, msg % explicit_arg) + + # if the action expect exactly one argument, we've + # successfully matched the option; exit the loop + elif arg_count == 1: + stop = start_index + 1 + args = [explicit_arg] + action_tuples.append((action, args, option_string)) + break + + # error if a double-dash option did not use the + # explicit argument + else: + msg = _('ignored explicit argument %r') + raise ArgumentError(action, msg % explicit_arg) + + # if there is no explicit argument, try to match the + # optional's string arguments with the following strings + # if successful, exit the loop + else: + start = start_index + 1 + selected_patterns = arg_strings_pattern[start:] + arg_count = match_argument(action, selected_patterns) + stop = start + arg_count + args = arg_strings[start:stop] + action_tuples.append((action, args, option_string)) + break + + # add the Optional to the list and return the index at which + # the Optional's string args stopped + assert action_tuples + for action, args, option_string in action_tuples: + take_action(action, args, option_string) + return stop + + # the list of Positionals left to be parsed; this is modified + # by consume_positionals() + positionals = self._get_positional_actions() + + # function to convert arg_strings into positional actions + def consume_positionals(start_index): + # match as many Positionals as possible + match_partial = self._match_arguments_partial + selected_pattern = arg_strings_pattern[start_index:] + arg_counts = match_partial(positionals, selected_pattern) + + # slice off the appropriate arg strings for each Positional + # and add the Positional and its args to the list + for action, arg_count in zip(positionals, arg_counts): + args = arg_strings[start_index: start_index + arg_count] + start_index += arg_count + take_action(action, args) + + # slice off the Positionals that we just parsed and return the + # index at which the Positionals' string args stopped + positionals[:] = positionals[len(arg_counts):] + return start_index + + # consume Positionals and Optionals alternately, until we have + # passed the last option string + extras = [] + start_index = 0 + if option_string_indices: + max_option_string_index = max(option_string_indices) + else: + max_option_string_index = -1 + while start_index <= max_option_string_index: + + # consume any Positionals preceding the next option + next_option_string_index = min([ + index + for index in option_string_indices + if index >= start_index]) + if start_index != next_option_string_index: + positionals_end_index = consume_positionals(start_index) + + # only try to parse the next optional if we didn't consume + # the option string during the positionals parsing + if positionals_end_index > start_index: + start_index = positionals_end_index + continue + else: + start_index = positionals_end_index + + # if we consumed all the positionals we could and we're not + # at the index of an option string, there were extra arguments + if start_index not in option_string_indices: + strings = arg_strings[start_index:next_option_string_index] + extras.extend(strings) + start_index = next_option_string_index + + # consume the next optional and any arguments for it + start_index = consume_optional(start_index) + + # consume any positionals following the last Optional + stop_index = consume_positionals(start_index) + + # if we didn't consume all the argument strings, there were extras + extras.extend(arg_strings[stop_index:]) + + # if we didn't use all the Positional objects, there were too few + # arg strings supplied. + if positionals: + self.error(_('too few arguments')) + + # make sure all required actions were present + for action in self._actions: + if action.required: + if action not in seen_actions: + name = _get_action_name(action) + self.error(_('argument %s is required') % name) + + # make sure all required groups had one option present + for group in self._mutually_exclusive_groups: + if group.required: + for action in group._group_actions: + if action in seen_non_default_actions: + break + + # if no actions were used, report the error + else: + names = [_get_action_name(action) + for action in group._group_actions + if action.help is not SUPPRESS] + msg = _('one of the arguments %s is required') + self.error(msg % ' '.join(names)) + + # return the updated namespace and the extra arguments + return namespace, extras + + def _read_args_from_files(self, arg_strings): + # expand arguments referencing files + new_arg_strings = [] + for arg_string in arg_strings: + + # for regular arguments, just add them back into the list + if arg_string[0] not in self.fromfile_prefix_chars: + new_arg_strings.append(arg_string) + + # replace arguments referencing files with the file content + else: + try: + args_file = open(arg_string[1:]) + try: + arg_strings = [] + for arg_line in args_file.read().splitlines(): + for arg in self.convert_arg_line_to_args(arg_line): + arg_strings.append(arg) + arg_strings = self._read_args_from_files(arg_strings) + new_arg_strings.extend(arg_strings) + finally: + args_file.close() + except IOError: + err = _sys.exc_info()[1] + self.error(str(err)) + + # return the modified argument list + return new_arg_strings + + def convert_arg_line_to_args(self, arg_line): + return [arg_line] + + def _match_argument(self, action, arg_strings_pattern): + # match the pattern for this action to the arg strings + nargs_pattern = self._get_nargs_pattern(action) + match = _re.match(nargs_pattern, arg_strings_pattern) + + # raise an exception if we weren't able to find a match + if match is None: + nargs_errors = { + None: _('expected one argument'), + OPTIONAL: _('expected at most one argument'), + ONE_OR_MORE: _('expected at least one argument'), + } + default = _('expected %s argument(s)') % action.nargs + msg = nargs_errors.get(action.nargs, default) + raise ArgumentError(action, msg) + + # return the number of arguments matched + return len(match.group(1)) + + def _match_arguments_partial(self, actions, arg_strings_pattern): + # progressively shorten the actions list by slicing off the + # final actions until we find a match + result = [] + for i in range(len(actions), 0, -1): + actions_slice = actions[:i] + pattern = ''.join([self._get_nargs_pattern(action) + for action in actions_slice]) + match = _re.match(pattern, arg_strings_pattern) + if match is not None: + result.extend([len(string) for string in match.groups()]) + break + + # return the list of arg string counts + return result + + def _parse_optional(self, arg_string): + # if it's an empty string, it was meant to be a positional + if not arg_string: + return None + + # if it doesn't start with a prefix, it was meant to be positional + if not arg_string[0] in self.prefix_chars: + return None + + # if the option string is present in the parser, return the action + if arg_string in self._option_string_actions: + action = self._option_string_actions[arg_string] + return action, arg_string, None + + # if it's just a single character, it was meant to be positional + if len(arg_string) == 1: + return None + + # if the option string before the "=" is present, return the action + if '=' in arg_string: + option_string, explicit_arg = arg_string.split('=', 1) + if option_string in self._option_string_actions: + action = self._option_string_actions[option_string] + return action, option_string, explicit_arg + + # search through all possible prefixes of the option string + # and all actions in the parser for possible interpretations + option_tuples = self._get_option_tuples(arg_string) + + # if multiple actions match, the option string was ambiguous + if len(option_tuples) > 1: + options = ', '.join([option_string + for action, option_string, explicit_arg in option_tuples]) + tup = arg_string, options + self.error(_('ambiguous option: %s could match %s') % tup) + + # if exactly one action matched, this segmentation is good, + # so return the parsed action + elif len(option_tuples) == 1: + option_tuple, = option_tuples + return option_tuple + + # if it was not found as an option, but it looks like a negative + # number, it was meant to be positional + # unless there are negative-number-like options + if self._negative_number_matcher.match(arg_string): + if not self._has_negative_number_optionals: + return None + + # if it contains a space, it was meant to be a positional + if ' ' in arg_string: + return None + + # it was meant to be an optional but there is no such option + # in this parser (though it might be a valid option in a subparser) + return None, arg_string, None + + def _get_option_tuples(self, option_string): + result = [] + + # option strings starting with two prefix characters are only + # split at the '=' + chars = self.prefix_chars + if option_string[0] in chars and option_string[1] in chars: + if '=' in option_string: + option_prefix, explicit_arg = option_string.split('=', 1) + else: + option_prefix = option_string + explicit_arg = None + for option_string in self._option_string_actions: + if option_string.startswith(option_prefix): + action = self._option_string_actions[option_string] + tup = action, option_string, explicit_arg + result.append(tup) + + # single character options can be concatenated with their arguments + # but multiple character options always have to have their argument + # separate + elif option_string[0] in chars and option_string[1] not in chars: + option_prefix = option_string + explicit_arg = None + short_option_prefix = option_string[:2] + short_explicit_arg = option_string[2:] + + for option_string in self._option_string_actions: + if option_string == short_option_prefix: + action = self._option_string_actions[option_string] + tup = action, option_string, short_explicit_arg + result.append(tup) + elif option_string.startswith(option_prefix): + action = self._option_string_actions[option_string] + tup = action, option_string, explicit_arg + result.append(tup) + + # shouldn't ever get here + else: + self.error(_('unexpected option string: %s') % option_string) + + # return the collected option tuples + return result + + def _get_nargs_pattern(self, action): + # in all examples below, we have to allow for '--' args + # which are represented as '-' in the pattern + nargs = action.nargs + + # the default (None) is assumed to be a single argument + if nargs is None: + nargs_pattern = '(-*A-*)' + + # allow zero or one arguments + elif nargs == OPTIONAL: + nargs_pattern = '(-*A?-*)' + + # allow zero or more arguments + elif nargs == ZERO_OR_MORE: + nargs_pattern = '(-*[A-]*)' + + # allow one or more arguments + elif nargs == ONE_OR_MORE: + nargs_pattern = '(-*A[A-]*)' + + # allow any number of options or arguments + elif nargs == REMAINDER: + nargs_pattern = '([-AO]*)' + + # allow one argument followed by any number of options or arguments + elif nargs == PARSER: + nargs_pattern = '(-*A[-AO]*)' + + # all others should be integers + else: + nargs_pattern = '(-*%s-*)' % '-*'.join('A' * nargs) + + # if this is an optional action, -- is not allowed + if action.option_strings: + nargs_pattern = nargs_pattern.replace('-*', '') + nargs_pattern = nargs_pattern.replace('-', '') + + # return the pattern + return nargs_pattern + + # ======================== + # Value conversion methods + # ======================== + def _get_values(self, action, arg_strings): + # for everything but PARSER args, strip out '--' + if action.nargs not in [PARSER, REMAINDER]: + arg_strings = [s for s in arg_strings if s != '--'] + + # optional argument produces a default when not present + if not arg_strings and action.nargs == OPTIONAL: + if action.option_strings: + value = action.const + else: + value = action.default + if isinstance(value, basestring): + value = self._get_value(action, value) + self._check_value(action, value) + + # when nargs='*' on a positional, if there were no command-line + # args, use the default if it is anything other than None + elif (not arg_strings and action.nargs == ZERO_OR_MORE and + not action.option_strings): + if action.default is not None: + value = action.default + else: + value = arg_strings + self._check_value(action, value) + + # single argument or optional argument produces a single value + elif len(arg_strings) == 1 and action.nargs in [None, OPTIONAL]: + arg_string, = arg_strings + value = self._get_value(action, arg_string) + self._check_value(action, value) + + # REMAINDER arguments convert all values, checking none + elif action.nargs == REMAINDER: + value = [self._get_value(action, v) for v in arg_strings] + + # PARSER arguments convert all values, but check only the first + elif action.nargs == PARSER: + value = [self._get_value(action, v) for v in arg_strings] + self._check_value(action, value[0]) + + # all other types of nargs produce a list + else: + value = [self._get_value(action, v) for v in arg_strings] + for v in value: + self._check_value(action, v) + + # return the converted value + return value + + def _get_value(self, action, arg_string): + type_func = self._registry_get('type', action.type, action.type) + if not _callable(type_func): + msg = _('%r is not callable') + raise ArgumentError(action, msg % type_func) + + # convert the value to the appropriate type + try: + result = type_func(arg_string) + + # ArgumentTypeErrors indicate errors + except ArgumentTypeError: + name = getattr(action.type, '__name__', repr(action.type)) + msg = str(_sys.exc_info()[1]) + raise ArgumentError(action, msg) + + # TypeErrors or ValueErrors also indicate errors + except (TypeError, ValueError): + name = getattr(action.type, '__name__', repr(action.type)) + msg = _('invalid %s value: %r') + raise ArgumentError(action, msg % (name, arg_string)) + + # return the converted value + return result + + def _check_value(self, action, value): + # converted value must be one of the choices (if specified) + if action.choices is not None and value not in action.choices: + tup = value, ', '.join(map(repr, action.choices)) + msg = _('invalid choice: %r (choose from %s)') % tup + raise ArgumentError(action, msg) + + # ======================= + # Help-formatting methods + # ======================= + def format_usage(self): + formatter = self._get_formatter() + formatter.add_usage(self.usage, self._actions, + self._mutually_exclusive_groups) + return formatter.format_help() + + def format_help(self): + formatter = self._get_formatter() + + # usage + formatter.add_usage(self.usage, self._actions, + self._mutually_exclusive_groups) + + # description + formatter.add_text(self.description) + + # positionals, optionals and user-defined groups + for action_group in self._action_groups: + formatter.start_section(action_group.title) + formatter.add_text(action_group.description) + formatter.add_arguments(action_group._group_actions) + formatter.end_section() + + # epilog + formatter.add_text(self.epilog) + + # determine help from format above + return formatter.format_help() + + def format_version(self): + import warnings + warnings.warn( + 'The format_version method is deprecated -- the "version" ' + 'argument to ArgumentParser is no longer supported.', + DeprecationWarning) + formatter = self._get_formatter() + formatter.add_text(self.version) + return formatter.format_help() + + def _get_formatter(self): + return self.formatter_class(prog=self.prog) + + # ===================== + # Help-printing methods + # ===================== + def print_usage(self, file=None): + if file is None: + file = _sys.stdout + self._print_message(self.format_usage(), file) + + def print_help(self, file=None): + if file is None: + file = _sys.stdout + self._print_message(self.format_help(), file) + + def print_version(self, file=None): + import warnings + warnings.warn( + 'The print_version method is deprecated -- the "version" ' + 'argument to ArgumentParser is no longer supported.', + DeprecationWarning) + self._print_message(self.format_version(), file) + + def _print_message(self, message, file=None): + if message: + if file is None: + file = _sys.stderr + file.write(message) + + # =============== + # Exiting methods + # =============== + def exit(self, status=0, message=None): + if message: + self._print_message(message, _sys.stderr) + _sys.exit(status) + + def error(self, message): + """error(message: string) + + Prints a usage message incorporating the message to stderr and + exits. + + If you override this in a subclass, it should not return -- it + should either exit or raise an exception. + """ + self.print_usage(_sys.stderr) + self.exit(2, _('%s: error: %s\n') % (self.prog, message)) diff --git a/env/lib/python2.7/site-packages/argparse.pyc b/env/lib/python2.7/site-packages/argparse.pyc new file mode 100644 index 0000000..1cf9908 Binary files /dev/null and b/env/lib/python2.7/site-packages/argparse.pyc differ diff --git a/env/lib/python2.7/site-packages/flask_profile/__init__.py b/env/lib/python2.7/site-packages/flask_profile/__init__.py new file mode 100644 index 0000000..5bd77cc --- /dev/null +++ b/env/lib/python2.7/site-packages/flask_profile/__init__.py @@ -0,0 +1,168 @@ +# -*- coding: utf-8 -*- +""" + flaskext.profile + ~~~~~~~~~~~~~~~~ + + Adds profiling to Flask. + + :copyright: (c) 2014 by Shipeng Feng. + :license: BSD, see LICENSE for more details. +""" + +import pstats +import os +import sys + +try: + import cProfile as profile +except ImportError: + import profile + +from flask import Blueprint, request, render_template, current_app + + +# Profile blueprint used to render template and serving static files +blueprint = Blueprint('_profile', __name__, static_folder='static', + static_url_path='/static', template_folder='templates', + url_prefix='/_profile') + + +def insensitive_replace(string, target, replacement): + """string.replace() that is case insensitive. + """ + no_case = string.lower() + index = no_case.rfind(target.lower()) + if index >= 0: + return string[:index] + replacement + string[index + len(target):] + else: + return string + + +def filename_format(filename): + """Format the profiler filename. + """ + if not os.path.isabs(filename): + if filename.startswith(('{', '<')) or \ + filename.startswith('.' + os.path.sep): + return filename + return os.path.join('.', filename) + + if filename.startswith(current_app.root_path): + return os.path.join('', filename[len(current_app.root_path):]) + + prefix = '' + prefix_len = 0 + for path in sys.path: + current_prefix = os.path.commonprefix([path, filename]) + if len(current_prefix) > prefix_len: + prefix = current_prefix + prefix_len = len(prefix) + filename = filename[prefix_len:] + return os.path.join('', filename) + + +class Profiler(object): + """Profiler Extension. + """ + + def __init__(self, app=None): + self.app = app + if app is not None: + self.init_app(app) + + def init_app(self, app): + """This is used to set up profiler for your flask app object. + + :param app: the Flask app object. + """ + if app.debug: + app.register_blueprint(blueprint) + app.before_request(ProfilerTool.before_request) + app.after_request(ProfilerTool.after_request) + + +class ProfilerTool(object): + """Profiler. + """ + + def __init__(self): + self.profiler = profile.Profile() + self.stats = None + self.profiler.enable() + + def disable(self): + self.profiler.disable() + self.stats = pstats.Stats(self.profiler) + + @property + def func_calls(self): + """Get collected profiling data. + """ + func_calls = [] + if not self.stats: + return func_calls + + for func in self.stats.sort_stats(1).fcn_list: + info = self.stats.stats[func] + stat = {} + + # Number of calls + if info[0] != info[1]: + stat['ncalls'] = "%d/%d" % (info[1], info[0]) + else: + stat['ncalls'] = info[1] + + # Total time + stat['tottime'] = info[2] * 1000 + + # Time per call + if info[1]: + stat['percall'] = info[2] * 1000 / info[1] + else: + stat['percall'] = 0 + + # Cumulative time spent in this and all subfunctions + stat['cumtime'] = info[3] * 1000 + + # Cumulative time per primitive call + if info[0]: + stat['percall_cum'] = info[3] * 1000 / info[0] + else: + stat['percall_cum'] = 0 + + # Filename + filename = pstats.func_std_string(func) + stat['filename'] = filename_format(filename) + + func_calls.append(stat) + + return func_calls + + @property + def total_time(self): + if self.stats: + return float(self.stats.total_tt) * 1000 + + @property + def content(self): + """HTML content for your profile stats. + """ + return render_template('_profile/profiler.html', + total_time=self.total_time, func_calls=self.func_calls) + + @classmethod + def before_request(cls): + request._profiler_tool = ProfilerTool() + + @classmethod + def after_request(cls, response): + request._profiler_tool.disable() + if response.status_code == 200 and \ + response.headers['content-type'].startswith('text/html'): + html = response.data.decode(response.charset) + html = insensitive_replace(html, '', + request._profiler_tool.content + '') + html = html.encode(response.charset) + response.response = [html] + response.content_length = len(html) + return response diff --git a/env/lib/python2.7/site-packages/flask_profile/__init__.pyc b/env/lib/python2.7/site-packages/flask_profile/__init__.pyc new file mode 100644 index 0000000..a6c3f2b Binary files /dev/null and b/env/lib/python2.7/site-packages/flask_profile/__init__.pyc differ diff --git a/env/lib/python2.7/site-packages/flask_profile/static/css/profiler.css b/env/lib/python2.7/site-packages/flask_profile/static/css/profiler.css new file mode 100644 index 0000000..919b6ef --- /dev/null +++ b/env/lib/python2.7/site-packages/flask_profile/static/css/profiler.css @@ -0,0 +1,109 @@ +#_profileContent { + font-family: 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva', 'Verdana', sans-serif; + color: #000; + font-size: 15px; +} +#_profileContentWrapper { + position: fixed; + background-color: #fff; + margin: 0px; + top: 0px; + left: 0px; + right: 0px; + bottom: 0px; + padding: 28px 20px 20px 20px; + overflow: auto; + z-index: 99999999; +} +#_profileContent h1 { + font-size: 32px; + margin: 0 0 0.3em 0; +} +#_profileContent h2 { + font-size: 16px; + margin: 1.3em 8px 0 8px; + padding: 9px; + background-color: #11557C; + color: white; +} +#_profileContent h1, h2 { + font-weight: normal; +} +#_profileContent em { + font-style: normal; + color: #A5D6D9; + font-weight: normal; +} +#_profileContent .profileContentDetail { + margin: 0 8px 0 8px; + font-size: 14px; +} +#_profileContent .profileContentFooter { + font-size: 13px; + text-align: right; + margin: 30px 8px 0 0; + color: #86989B; +} + +/* tablesorted */ +#_profileContent table.tablesorter { + width: 100%; +} +#_profileContent table.tablesorter thead th { + background: url(../img/bg.gif) center right no-repeat; + padding-right: 20px; + cursor: pointer; +} +#_profileContent table.tablesorter tr.profileContentTrOdd { + background-color: #fff; +} +#_profileContent table.tablesorter tr.profileContentTrEven { + background-color: #E8EFF0; +} +#_profileContent table.tablesorter thead .headerSortUp { + background-image: url(../img/asc.gif); +} +#_profileContent table.tablesorter thead .headerSortDown { + background-image: url(../img/desc.gif); +} +#_profileContent table.tablesorter thead .headerSortDown, #_profileContent table.tablesorter thead .headerSortUp { + background-color: #8dbdd8; +} + +/* handle */ +#_profileContent #flaskProfileHandle { + position: fixed; + background: #fff; + border: 1px solid #11557C; + top: 30px; + right: 0; + z-index: 100000000; + opacity: 0.75; +} + +#_profileContent a#flaskShowProfileButton { + display: block; + height: 75px; + width: 30px; + border-right: none; + border-bottom: 4px solid #fff; + border-top: 4px solid #fff; + border-left: 4px solid #fff; + color: #fff; + font-size: 10px; + font-weight: bold; + text-decoration: none; + text-align: center; + text-indent: -999999px; + background: #11557C url(../img/profile.png) no-repeat left center; + opacity: 0.5; +} + +#_profileContent a#flaskShowProfileButton:hover { + background-color: #11557C; + padding-right: 6px; + border-top-color: #FFFF00; + border-left-color: #FFFF00; + border-bottom-color: #FFFF00; + opacity: 1.0; +} diff --git a/env/lib/python2.7/site-packages/flask_profile/static/img/asc.gif b/env/lib/python2.7/site-packages/flask_profile/static/img/asc.gif new file mode 100644 index 0000000..7415786 Binary files /dev/null and b/env/lib/python2.7/site-packages/flask_profile/static/img/asc.gif differ diff --git a/env/lib/python2.7/site-packages/flask_profile/static/img/bg.gif b/env/lib/python2.7/site-packages/flask_profile/static/img/bg.gif new file mode 100644 index 0000000..fac668f Binary files /dev/null and b/env/lib/python2.7/site-packages/flask_profile/static/img/bg.gif differ diff --git a/env/lib/python2.7/site-packages/flask_profile/static/img/desc.gif b/env/lib/python2.7/site-packages/flask_profile/static/img/desc.gif new file mode 100644 index 0000000..3b30b3c Binary files /dev/null and b/env/lib/python2.7/site-packages/flask_profile/static/img/desc.gif differ diff --git a/env/lib/python2.7/site-packages/flask_profile/static/img/profile.png b/env/lib/python2.7/site-packages/flask_profile/static/img/profile.png new file mode 100644 index 0000000..3131419 Binary files /dev/null and b/env/lib/python2.7/site-packages/flask_profile/static/img/profile.png differ diff --git a/env/lib/python2.7/site-packages/flask_profile/static/js/jquery.js b/env/lib/python2.7/site-packages/flask_profile/static/js/jquery.js new file mode 100644 index 0000000..9144b8a --- /dev/null +++ b/env/lib/python2.7/site-packages/flask_profile/static/js/jquery.js @@ -0,0 +1,16 @@ +/*! + * jQuery JavaScript Library v1.5 + * http://jquery.com/ + * + * Copyright 2011, John Resig + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * Includes Sizzle.js + * http://sizzlejs.com/ + * Copyright 2011, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * + * Date: Mon Jan 31 08:31:29 2011 -0500 + */ +(function(a,b){function b$(a){return d.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function bX(a){if(!bR[a]){var b=d("<"+a+">").appendTo("body"),c=b.css("display");b.remove();if(c==="none"||c==="")c="block";bR[a]=c}return bR[a]}function bW(a,b){var c={};d.each(bV.concat.apply([],bV.slice(0,b)),function(){c[this]=a});return c}function bJ(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var e=a.dataTypes,f=a.converters,g,h=e.length,i,j=e[0],k,l,m,n,o;for(g=1;g=0===c})}function N(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function F(a,b){return(a&&a!=="*"?a+".":"")+b.replace(q,"`").replace(r,"&")}function E(a){var b,c,e,f,g,h,i,j,k,l,m,n,p,q=[],r=[],s=d._data(this,u);typeof s==="function"&&(s=s.events);if(a.liveFired!==this&&s&&s.live&&!a.target.disabled&&(!a.button||a.type!=="click")){a.namespace&&(n=new RegExp("(^|\\.)"+a.namespace.split(".").join("\\.(?:.*\\.)?")+"(\\.|$)")),a.liveFired=this;var t=s.live.slice(0);for(i=0;ic)break;a.currentTarget=f.elem,a.data=f.handleObj.data,a.handleObj=f.handleObj,p=f.handleObj.origHandler.apply(f.elem,arguments);if(p===!1||a.isPropagationStopped()){c=f.level,p===!1&&(b=!1);if(a.isImmediatePropagationStopped())break}}return b}}function C(a,b,c){c[0].type=a;return d.event.handle.apply(b,c)}function w(){return!0}function v(){return!1}function f(a,c,f){if(f===b&&a.nodeType===1){f=a.getAttribute("data-"+c);if(typeof f==="string"){try{f=f==="true"?!0:f==="false"?!1:f==="null"?null:d.isNaN(f)?e.test(f)?d.parseJSON(f):f:parseFloat(f)}catch(g){}d.data(a,c,f)}else f=b}return f}var c=a.document,d=function(){function I(){if(!d.isReady){try{c.documentElement.doScroll("left")}catch(a){setTimeout(I,1);return}d.ready()}}var d=function(a,b){return new d.fn.init(a,b,g)},e=a.jQuery,f=a.$,g,h=/^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]+)$)/,i=/\S/,j=/^\s+/,k=/\s+$/,l=/\d/,m=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,n=/^[\],:{}\s]*$/,o=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,p=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,q=/(?:^|:|,)(?:\s*\[)+/g,r=/(webkit)[ \/]([\w.]+)/,s=/(opera)(?:.*version)?[ \/]([\w.]+)/,t=/(msie) ([\w.]+)/,u=/(mozilla)(?:.*? rv:([\w.]+))?/,v=navigator.userAgent,w,x=!1,y,z="then done fail isResolved isRejected promise".split(" "),A,B=Object.prototype.toString,C=Object.prototype.hasOwnProperty,D=Array.prototype.push,E=Array.prototype.slice,F=String.prototype.trim,G=Array.prototype.indexOf,H={};d.fn=d.prototype={constructor:d,init:function(a,e,f){var g,i,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!e&&c.body){this.context=c,this[0]=c.body,this.selector="body",this.length=1;return this}if(typeof a==="string"){g=h.exec(a);if(!g||!g[1]&&e)return!e||e.jquery?(e||f).find(a):this.constructor(e).find(a);if(g[1]){e=e instanceof d?e[0]:e,k=e?e.ownerDocument||e:c,j=m.exec(a),j?d.isPlainObject(e)?(a=[c.createElement(j[1])],d.fn.attr.call(a,e,!0)):a=[k.createElement(j[1])]:(j=d.buildFragment([g[1]],[k]),a=(j.cacheable?d.clone(j.fragment):j.fragment).childNodes);return d.merge(this,a)}i=c.getElementById(g[2]);if(i&&i.parentNode){if(i.id!==g[2])return f.find(a);this.length=1,this[0]=i}this.context=c,this.selector=a;return this}if(d.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return d.makeArray(a,this)},selector:"",jquery:"1.5",length:0,size:function(){return this.length},toArray:function(){return E.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var e=this.constructor();d.isArray(a)?D.apply(e,a):d.merge(e,a),e.prevObject=this,e.context=this.context,b==="find"?e.selector=this.selector+(this.selector?" ":"")+c:b&&(e.selector=this.selector+"."+b+"("+c+")");return e},each:function(a,b){return d.each(this,a,b)},ready:function(a){d.bindReady(),y.done(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(E.apply(this,arguments),"slice",E.call(arguments).join(","))},map:function(a){return this.pushStack(d.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:D,sort:[].sort,splice:[].splice},d.fn.init.prototype=d.fn,d.extend=d.fn.extend=function(){var a,c,e,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i==="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!=="object"&&!d.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j0)return;y.resolveWith(c,[d]),d.fn.trigger&&d(c).trigger("ready").unbind("ready")}},bindReady:function(){if(!x){x=!0;if(c.readyState==="complete")return setTimeout(d.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",A,!1),a.addEventListener("load",d.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",A),a.attachEvent("onload",d.ready);var b=!1;try{b=a.frameElement==null}catch(e){}c.documentElement.doScroll&&b&&I()}}},isFunction:function(a){return d.type(a)==="function"},isArray:Array.isArray||function(a){return d.type(a)==="array"},isWindow:function(a){return a&&typeof a==="object"&&"setInterval"in a},isNaN:function(a){return a==null||!l.test(a)||isNaN(a)},type:function(a){return a==null?String(a):H[B.call(a)]||"object"},isPlainObject:function(a){if(!a||d.type(a)!=="object"||a.nodeType||d.isWindow(a))return!1;if(a.constructor&&!C.call(a,"constructor")&&!C.call(a.constructor.prototype,"isPrototypeOf"))return!1;var c;for(c in a){}return c===b||C.call(a,c)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw a},parseJSON:function(b){if(typeof b!=="string"||!b)return null;b=d.trim(b);if(n.test(b.replace(o,"@").replace(p,"]").replace(q,"")))return a.JSON&&a.JSON.parse?a.JSON.parse(b):(new Function("return "+b))();d.error("Invalid JSON: "+b)},parseXML:function(b,c,e){a.DOMParser?(e=new DOMParser,c=e.parseFromString(b,"text/xml")):(c=new ActiveXObject("Microsoft.XMLDOM"),c.async="false",c.loadXML(b)),e=c.documentElement,(!e||!e.nodeName||e.nodeName==="parsererror")&&d.error("Invalid XML: "+b);return c},noop:function(){},globalEval:function(a){if(a&&i.test(a)){var b=c.getElementsByTagName("head")[0]||c.documentElement,e=c.createElement("script");e.type="text/javascript",d.support.scriptEval()?e.appendChild(c.createTextNode(a)):e.text=a,b.insertBefore(e,b.firstChild),b.removeChild(e)}},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,e){var f,g=0,h=a.length,i=h===b||d.isFunction(a);if(e){if(i){for(f in a)if(c.apply(a[f],e)===!1)break}else for(;g1?(g=Array(c),d.each(b,function(a,b){d.when(b).then(function(b){g[a]=arguments.length>1?E.call(arguments,0):b,--c||e.resolveWith(f,g)},e.reject)})):e!==a&&e.resolve(a);return f},uaMatch:function(a){a=a.toLowerCase();var b=r.exec(a)||s.exec(a)||t.exec(a)||a.indexOf("compatible")<0&&u.exec(a)||[];return{browser:b[1]||"",version:b[2]||"0"}},sub:function(){function a(b,c){return new a.fn.init(b,c)}d.extend(!0,a,this),a.superclass=this,a.fn=a.prototype=this(),a.fn.constructor=a,a.subclass=this.subclass,a.fn.init=function b(b,c){c&&c instanceof d&&!(c instanceof a)&&(c=a(c));return d.fn.init.call(this,b,c,e)},a.fn.init.prototype=a.fn;var e=a(c);return a},browser:{}}),y=d._Deferred(),d.each("Boolean Number String Function Array Date RegExp Object".split(" "),function(a,b){H["[object "+b+"]"]=b.toLowerCase()}),w=d.uaMatch(v),w.browser&&(d.browser[w.browser]=!0,d.browser.version=w.version),d.browser.webkit&&(d.browser.safari=!0),G&&(d.inArray=function(a,b){return G.call(b,a)}),i.test(" ")&&(j=/^[\s\xA0]+/,k=/[\s\xA0]+$/),g=d(c),c.addEventListener?A=function(){c.removeEventListener("DOMContentLoaded",A,!1),d.ready()}:c.attachEvent&&(A=function(){c.readyState==="complete"&&(c.detachEvent("onreadystatechange",A),d.ready())});return a.jQuery=a.$=d}();(function(){d.support={};var b=c.createElement("div");b.style.display="none",b.innerHTML="
a";var e=b.getElementsByTagName("*"),f=b.getElementsByTagName("a")[0],g=c.createElement("select"),h=g.appendChild(c.createElement("option"));if(e&&e.length&&f){d.support={leadingWhitespace:b.firstChild.nodeType===3,tbody:!b.getElementsByTagName("tbody").length,htmlSerialize:!!b.getElementsByTagName("link").length,style:/red/.test(f.getAttribute("style")),hrefNormalized:f.getAttribute("href")==="/a",opacity:/^0.55$/.test(f.style.opacity),cssFloat:!!f.style.cssFloat,checkOn:b.getElementsByTagName("input")[0].value==="on",optSelected:h.selected,deleteExpando:!0,optDisabled:!1,checkClone:!1,_scriptEval:null,noCloneEvent:!0,boxModel:null,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableHiddenOffsets:!0},g.disabled=!0,d.support.optDisabled=!h.disabled,d.support.scriptEval=function(){if(d.support._scriptEval===null){var b=c.documentElement,e=c.createElement("script"),f="script"+d.now();e.type="text/javascript";try{e.appendChild(c.createTextNode("window."+f+"=1;"))}catch(g){}b.insertBefore(e,b.firstChild),a[f]?(d.support._scriptEval=!0,delete a[f]):d.support._scriptEval=!1,b.removeChild(e),b=e=f=null}return d.support._scriptEval};try{delete b.test}catch(i){d.support.deleteExpando=!1}b.attachEvent&&b.fireEvent&&(b.attachEvent("onclick",function j(){d.support.noCloneEvent=!1,b.detachEvent("onclick",j)}),b.cloneNode(!0).fireEvent("onclick")),b=c.createElement("div"),b.innerHTML="";var k=c.createDocumentFragment();k.appendChild(b.firstChild),d.support.checkClone=k.cloneNode(!0).cloneNode(!0).lastChild.checked,d(function(){var a=c.createElement("div"),b=c.getElementsByTagName("body")[0];if(b){a.style.width=a.style.paddingLeft="1px",b.appendChild(a),d.boxModel=d.support.boxModel=a.offsetWidth===2,"zoom"in a.style&&(a.style.display="inline",a.style.zoom=1,d.support.inlineBlockNeedsLayout=a.offsetWidth===2,a.style.display="",a.innerHTML="
",d.support.shrinkWrapBlocks=a.offsetWidth!==2),a.innerHTML="
t
";var e=a.getElementsByTagName("td");d.support.reliableHiddenOffsets=e[0].offsetHeight===0,e[0].style.display="",e[1].style.display="none",d.support.reliableHiddenOffsets=d.support.reliableHiddenOffsets&&e[0].offsetHeight===0,a.innerHTML="",b.removeChild(a).style.display="none",a=e=null}});var l=function(a){var b=c.createElement("div");a="on"+a;if(!b.attachEvent)return!0;var d=a in b;d||(b.setAttribute(a,"return;"),d=typeof b[a]==="function"),b=null;return d};d.support.submitBubbles=l("submit"),d.support.changeBubbles=l("change"),b=e=f=null}})();var e=/^(?:\{.*\}|\[.*\])$/;d.extend({cache:{},uuid:0,expando:"jQuery"+(d.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?d.cache[a[d.expando]]:a[d.expando];return!!a&&!d.isEmptyObject(a)},data:function(a,c,e,f){if(d.acceptData(a)){var g=d.expando,h=typeof c==="string",i,j=a.nodeType,k=j?d.cache:a,l=j?a[d.expando]:a[d.expando]&&d.expando;if((!l||f&&l&&!k[l][g])&&h&&e===b)return;l||(j?a[d.expando]=l=++d.uuid:l=d.expando),k[l]||(k[l]={}),typeof c==="object"&&(f?k[l][g]=d.extend(k[l][g],c):k[l]=d.extend(k[l],c)),i=k[l],f&&(i[g]||(i[g]={}),i=i[g]),e!==b&&(i[c]=e);if(c==="events"&&!i[c])return i[g]&&i[g].events;return h?i[c]:i}},removeData:function(b,c,e){if(d.acceptData(b)){var f=d.expando,g=b.nodeType,h=g?d.cache:b,i=g?b[d.expando]:d.expando;if(!h[i])return;if(c){var j=e?h[i][f]:h[i];if(j){delete j[c];if(!d.isEmptyObject(j))return}}if(e){delete h[i][f];if(!d.isEmptyObject(h[i]))return}var k=h[i][f];d.support.deleteExpando||h!=a?delete h[i]:h[i]=null,k?(h[i]={},h[i][f]=k):g&&(d.support.deleteExpando?delete b[d.expando]:b.removeAttribute?b.removeAttribute(d.expando):b[d.expando]=null)}},_data:function(a,b,c){return d.data(a,b,c,!0)},acceptData:function(a){if(a.nodeName){var b=d.noData[a.nodeName.toLowerCase()];if(b)return b!==!0&&a.getAttribute("classid")===b}return!0}}),d.fn.extend({data:function(a,c){var e=null;if(typeof a==="undefined"){if(this.length){e=d.data(this[0]);if(this[0].nodeType===1){var g=this[0].attributes,h;for(var i=0,j=g.length;i-1)return!0;return!1},val:function(a){if(!arguments.length){var c=this[0];if(c){if(d.nodeName(c,"option")){var e=c.attributes.value;return!e||e.specified?c.value:c.text}if(d.nodeName(c,"select")){var f=c.selectedIndex,g=[],h=c.options,j=c.type==="select-one";if(f<0)return null;for(var k=j?f:0,l=j?f+1:h.length;k=0;else if(d.nodeName(this,"select")){var f=d.makeArray(e);d("option",this).each(function(){this.selected=d.inArray(d(this).val(),f)>=0}),f.length||(this.selectedIndex=-1)}else this.value=e}})}}),d.extend({attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attr:function(a,c,e,f){if(!a||a.nodeType===3||a.nodeType===8||a.nodeType===2)return b;if(f&&c in d.attrFn)return d(a)[c](e);var g=a.nodeType!==1||!d.isXMLDoc(a),h=e!==b;c=g&&d.props[c]||c;if(a.nodeType===1){var i=j.test(c);if(c==="selected"&&!d.support.optSelected){var n=a.parentNode;n&&(n.selectedIndex,n.parentNode&&n.parentNode.selectedIndex)}if((c in a||a[c]!==b)&&g&&!i){h&&(c==="type"&&k.test(a.nodeName)&&a.parentNode&&d.error("type property can't be changed"),e===null?a.nodeType===1&&a.removeAttribute(c):a[c]=e);if(d.nodeName(a,"form")&&a.getAttributeNode(c))return a.getAttributeNode(c).nodeValue;if(c==="tabIndex"){var o=a.getAttributeNode("tabIndex");return o&&o.specified?o.value:l.test(a.nodeName)||m.test(a.nodeName)&&a.href?0:b}return a[c]}if(!d.support.style&&g&&c==="style"){h&&(a.style.cssText=""+e);return a.style.cssText}h&&a.setAttribute(c,""+e);if(!a.attributes[c]&&(a.hasAttribute&&!a.hasAttribute(c)))return b;var p=!d.support.hrefNormalized&&g&&i?a.getAttribute(c,2):a.getAttribute(c);return p===null?b:p}h&&(a[c]=e);return a[c]}});var o=/\.(.*)$/,p=/^(?:textarea|input|select)$/i,q=/\./g,r=/ /g,s=/[^\w\s.|`]/g,t=function(a){return a.replace(s,"\\$&")},u="events";d.event={add:function(c,e,f,g){if(c.nodeType!==3&&c.nodeType!==8){d.isWindow(c)&&(c!==a&&!c.frameElement)&&(c=a);if(f===!1)f=v;else if(!f)return;var h,i;f.handler&&(h=f,f=h.handler),f.guid||(f.guid=d.guid++);var j=d._data(c);if(!j)return;var k=j[u],l=j.handle;typeof k==="function"?(l=k.handle,k=k.events):k||(c.nodeType||(j[u]=j=function(){}),j.events=k={}),l||(j.handle=l=function(){return typeof d!=="undefined"&&!d.event.triggered?d.event.handle.apply(l.elem,arguments):b}),l.elem=c,e=e.split(" ");var m,n=0,o;while(m=e[n++]){i=h?d.extend({},h):{handler:f,data:g},m.indexOf(".")>-1?(o=m.split("."),m=o.shift(),i.namespace=o.slice(0).sort().join(".")):(o=[],i.namespace=""),i.type=m,i.guid||(i.guid=f.guid);var p=k[m],q=d.event.special[m]||{};if(!p){p=k[m]=[];if(!q.setup||q.setup.call(c,g,o,l)===!1)c.addEventListener?c.addEventListener(m,l,!1):c.attachEvent&&c.attachEvent("on"+m,l)}q.add&&(q.add.call(c,i),i.handler.guid||(i.handler.guid=f.guid)),p.push(i),d.event.global[m]=!0}c=null}},global:{},remove:function(a,c,e,f){if(a.nodeType!==3&&a.nodeType!==8){e===!1&&(e=v);var g,h,i,j,k=0,l,m,n,o,p,q,r,s=d.hasData(a)&&d._data(a),w=s&&s[u];if(!s||!w)return;typeof w==="function"&&(s=w,w=w.events),c&&c.type&&(e=c.handler,c=c.type);if(!c||typeof c==="string"&&c.charAt(0)==="."){c=c||"";for(h in w)d.event.remove(a,h+c);return}c=c.split(" ");while(h=c[k++]){r=h,q=null,l=h.indexOf(".")<0,m=[],l||(m=h.split("."),h=m.shift(),n=new RegExp("(^|\\.)"+d.map(m.slice(0).sort(),t).join("\\.(?:.*\\.)?")+"(\\.|$)")),p=w[h];if(!p)continue;if(!e){for(j=0;j=0&&(a.type=f=f.slice(0,-1),a.exclusive=!0),e||(a.stopPropagation(),d.event.global[f]&&d.each(d.cache,function(){var b=d.expando,e=this[b];e&&e.events&&e.events[f]&&d.event.trigger(a,c,e.handle.elem)}));if(!e||e.nodeType===3||e.nodeType===8)return b;a.result=b,a.target=e,c=d.makeArray(c),c.unshift(a)}a.currentTarget=e;var h=e.nodeType?d._data(e,"handle"):(d._data(e,u)||{}).handle;h&&h.apply(e,c);var i=e.parentNode||e.ownerDocument;try{e&&e.nodeName&&d.noData[e.nodeName.toLowerCase()]||e["on"+f]&&e["on"+f].apply(e,c)===!1&&(a.result=!1,a.preventDefault())}catch(j){}if(!a.isPropagationStopped()&&i)d.event.trigger(a,c,i,!0);else if(!a.isDefaultPrevented()){var k,l=a.target,m=f.replace(o,""),n=d.nodeName(l,"a")&&m==="click",p=d.event.special[m]||{};if((!p._default||p._default.call(e,a)===!1)&&!n&&!(l&&l.nodeName&&d.noData[l.nodeName.toLowerCase()])){try{l[m]&&(k=l["on"+m],k&&(l["on"+m]=null),d.event.triggered=!0,l[m]())}catch(q){}k&&(l["on"+m]=k),d.event.triggered=!1}}},handle:function(c){var e,f,g,h,i,j=[],k=d.makeArray(arguments);c=k[0]=d.event.fix(c||a.event),c.currentTarget=this,e=c.type.indexOf(".")<0&&!c.exclusive,e||(g=c.type.split("."),c.type=g.shift(),j=g.slice(0).sort(),h=new RegExp("(^|\\.)"+j.join("\\.(?:.*\\.)?")+"(\\.|$)")),c.namespace=c.namespace||j.join("."),i=d._data(this,u),typeof i==="function"&&(i=i.events),f=(i||{})[c.type];if(i&&f){f=f.slice(0);for(var l=0,m=f.length;l-1?d.map(a.options,function(a){return a.selected}).join("-"):"":a.nodeName.toLowerCase()==="select"&&(c=a.selectedIndex);return c},B=function B(a){var c=a.target,e,f;if(p.test(c.nodeName)&&!c.readOnly){e=d._data(c,"_change_data"),f=A(c),(a.type!=="focusout"||c.type!=="radio")&&d._data(c,"_change_data",f);if(e===b||f===e)return;if(e!=null||f){a.type="change",a.liveFired=b;return d.event.trigger(a,arguments[1],c)}}};d.event.special.change={filters:{focusout:B,beforedeactivate:B,click:function(a){var b=a.target,c=b.type;if(c==="radio"||c==="checkbox"||b.nodeName.toLowerCase()==="select")return B.call(this,a)},keydown:function(a){var b=a.target,c=b.type;if(a.keyCode===13&&b.nodeName.toLowerCase()!=="textarea"||a.keyCode===32&&(c==="checkbox"||c==="radio")||c==="select-multiple")return B.call(this,a)},beforeactivate:function(a){var b=a.target;d._data(b,"_change_data",A(b))}},setup:function(a,b){if(this.type==="file")return!1;for(var c in z)d.event.add(this,c+".specialChange",z[c]);return p.test(this.nodeName)},teardown:function(a){d.event.remove(this,".specialChange");return p.test(this.nodeName)}},z=d.event.special.change.filters,z.focus=z.beforeactivate}c.addEventListener&&d.each({focus:"focusin",blur:"focusout"},function(a,b){function c(a){a=d.event.fix(a),a.type=b;return d.event.handle.call(this,a)}d.event.special[b]={setup:function(){this.addEventListener(a,c,!0)},teardown:function(){this.removeEventListener(a,c,!0)}}}),d.each(["bind","one"],function(a,c){d.fn[c]=function(a,e,f){if(typeof a==="object"){for(var g in a)this[c](g,e,a[g],f);return this}if(d.isFunction(e)||e===!1)f=e,e=b;var h=c==="one"?d.proxy(f,function(a){d(this).unbind(a,h);return f.apply(this,arguments)}):f;if(a==="unload"&&c!=="one")this.one(a,e,f);else for(var i=0,j=this.length;i0?this.bind(b,a,c):this.trigger(b)},d.attrFn&&(d.attrFn[b]=!0)}),function(){function s(a,b,c,d,e,f){for(var g=0,h=d.length;g0){k=j;break}}j=j[a]}d[g]=k}}}function r(a,b,c,d,e,f){for(var g=0,h=d.length;g+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,e=0,f=Object.prototype.toString,g=!1,h=!0;[0,0].sort(function(){h=!1;return 0});var i=function(b,d,e,g){e=e||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!=="string")return e;var l,m,o,p,q,r,s,u,v=!0,w=i.isXML(d),x=[],y=b;do{a.exec(""),l=a.exec(y);if(l){y=l[3],x.push(l[1]);if(l[2]){p=l[3];break}}}while(l);if(x.length>1&&k.exec(b))if(x.length===2&&j.relative[x[0]])m=t(x[0]+x[1],d);else{m=j.relative[x[0]]?[d]:i(x.shift(),d);while(x.length)b=x.shift(),j.relative[b]&&(b+=x.shift()),m=t(b,m)}else{!g&&x.length>1&&d.nodeType===9&&!w&&j.match.ID.test(x[0])&&!j.match.ID.test(x[x.length-1])&&(q=i.find(x.shift(),d,w),d=q.expr?i.filter(q.expr,q.set)[0]:q.set[0]);if(d){q=g?{expr:x.pop(),set:n(g)}:i.find(x.pop(),x.length===1&&(x[0]==="~"||x[0]==="+")&&d.parentNode?d.parentNode:d,w),m=q.expr?i.filter(q.expr,q.set):q.set,x.length>0?o=n(m):v=!1;while(x.length)r=x.pop(),s=r,j.relative[r]?s=x.pop():r="",s==null&&(s=d),j.relative[r](o,s,w)}else o=x=[]}o||(o=m),o||i.error(r||b);if(f.call(o)==="[object Array]")if(v)if(d&&d.nodeType===1)for(u=0;o[u]!=null;u++)o[u]&&(o[u]===!0||o[u].nodeType===1&&i.contains(d,o[u]))&&e.push(m[u]);else for(u=0;o[u]!=null;u++)o[u]&&o[u].nodeType===1&&e.push(m[u]);else e.push.apply(e,o);else n(o,e);p&&(i(p,h,e,g),i.uniqueSort(e));return e};i.uniqueSort=function(a){if(p){g=h,a.sort(p);if(g)for(var b=1;b0},i.find=function(a,b,c){var d;if(!a)return[];for(var e=0,f=j.order.length;e":function(a,b){var c,d=typeof b==="string",e=0,f=a.length;if(d&&!/\W/.test(b)){b=b.toLowerCase();for(;e=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(/\\/g,"")},TAG:function(a,b){return a[1].toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||i.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&i.error(a[0]);a[0]=e++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(/\\/g,"");!f&&j.attrMap[g]&&(a[1]=j.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(/\\/g,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=i(b[3],null,null,c);else{var g=i.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(j.match.POS.test(b[0])||j.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!i(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){return"text"===a.type},radio:function(a){return"radio"===a.type},checkbox:function(a){return"checkbox"===a.type},file:function(a){return"file"===a.type},password:function(a){return"password"===a.type},submit:function(a){return"submit"===a.type},image:function(a){return"image"===a.type},reset:function(a){return"reset"===a.type},button:function(a){return"button"===a.type||a.nodeName.toLowerCase()==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return bc[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=j.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||i.getText([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,k=g.length;h=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=j.attrHandle[c]?j.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=j.setFilters[e];if(f)return f(a,c,b,d)}}},k=j.match.POS,l=function(a,b){return"\\"+(b-0+1)};for(var m in j.match)j.match[m]=new RegExp(j.match[m].source+/(?![^\[]*\])(?![^\(]*\))/.source),j.leftMatch[m]=new RegExp(/(^(?:.|\r|\n)*?)/.source+j.match[m].source.replace(/\\(\d+)/g,l));var n=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(o){n=function(a,b){var c=0,d=b||[];if(f.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length==="number")for(var e=a.length;c",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(j.find.ID=function(a,c,d){if(typeof c.getElementById!=="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!=="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},j.filter.ID=function(a,b){var c=typeof a.getAttributeNode!=="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(j.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="",a.firstChild&&typeof a.firstChild.getAttribute!=="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(j.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=i,b=c.createElement("div"),d="__sizzle__";b.innerHTML="

";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){i=function(b,e,f,g){e=e||c;if(!g&&!i.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return n(e.getElementsByTagName(b),f);if(h[2]&&j.find.CLASS&&e.getElementsByClassName)return n(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return n([e.body],f);if(h&&h[3]){var k=e.getElementById(h[3]);if(!k||!k.parentNode)return n([],f);if(k.id===h[3])return n([k],f)}try{return n(e.querySelectorAll(b),f)}catch(l){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var m=e.getAttribute("id"),o=m||d,p=e.parentNode,q=/^\s*[+~]/.test(b);m?o=o.replace(/'/g,"\\$&"):e.setAttribute("id",o),q&&p&&(e=e.parentNode);try{if(!q||p)return n(e.querySelectorAll("[id='"+o+"'] "+b),f)}catch(r){}finally{m||e.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)i[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector,d=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(e){d=!0}b&&(i.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!i.isXML(a))try{if(d||!j.match.PSEUDO.test(c)&&!/!=/.test(c))return b.call(a,c)}catch(e){}return i(c,null,null,[a]).length>0})}(),function(){var a=c.createElement("div");a.innerHTML="
";if(a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;j.order.splice(1,0,"CLASS"),j.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!=="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?i.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?i.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:i.contains=function(){return!1},i.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var t=function(a,b){var c,d=[],e="",f=b.nodeType?[b]:b;while(c=j.match.PSEUDO.exec(a))e+=c[0],a=a.replace(j.match.PSEUDO,"");a=j.relative[a]?a+"*":a;for(var g=0,h=f.length;g0)for(var g=c;g0},closest:function(a,b){var c=[],e,f,g=this[0];if(d.isArray(a)){var h,i,j={},k=1;if(g&&a.length){for(e=0,f=a.length;e-1:d(g).is(h))&&c.push({selector:i,elem:g,level:k});g=g.parentNode,k++}}return c}var l=L.test(a)?d(a,b||this.context):null;for(e=0,f=this.length;e-1:d.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b)break}}c=c.length>1?d.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a||typeof a==="string")return d.inArray(this[0],a?d(a):this.parent().children());return d.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a==="string"?d(a,b):d.makeArray(a),e=d.merge(this.get(),c);return this.pushStack(N(c[0])||N(e[0])?e:d.unique(e))},andSelf:function(){return this.add(this.prevObject)}}),d.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return d.dir(a,"parentNode")},parentsUntil:function(a,b,c){return d.dir(a,"parentNode",c)},next:function(a){return d.nth(a,2,"nextSibling")},prev:function(a){return d.nth(a,2,"previousSibling")},nextAll:function(a){return d.dir(a,"nextSibling")},prevAll:function(a){return d.dir(a,"previousSibling")},nextUntil:function(a,b,c){return d.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return d.dir(a,"previousSibling",c)},siblings:function(a){return d.sibling(a.parentNode.firstChild,a)},children:function(a){return d.sibling(a.firstChild)},contents:function(a){return d.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:d.makeArray(a.childNodes)}},function(a,b){d.fn[a]=function(c,e){var f=d.map(this,b,c),g=K.call(arguments);G.test(a)||(e=c),e&&typeof e==="string"&&(f=d.filter(e,f)),f=this.length>1&&!M[a]?d.unique(f):f,(this.length>1||I.test(e))&&H.test(a)&&(f=f.reverse());return this.pushStack(f,a,g.join(","))}}),d.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?d.find.matchesSelector(b[0],a)?[b[0]]:[]:d.find.matches(a,b)},dir:function(a,c,e){var f=[],g=a[c];while(g&&g.nodeType!==9&&(e===b||g.nodeType!==1||!d(g).is(e)))g.nodeType===1&&f.push(g),g=g[c];return f},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var P=/ jQuery\d+="(?:\d+|null)"/g,Q=/^\s+/,R=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,S=/<([\w:]+)/,T=/",""],legend:[1,"
","
"],thead:[1,"","
"],tr:[2,"","
"],td:[3,"","
"],col:[2,"","
"],area:[1,"",""],_default:[0,"",""]};X.optgroup=X.option,X.tbody=X.tfoot=X.colgroup=X.caption=X.thead,X.th=X.td,d.support.htmlSerialize||(X._default=[1,"div
","
"]),d.fn.extend({text:function(a){if(d.isFunction(a))return this.each(function(b){var c=d(this);c.text(a.call(this,b,c.text()))});if(typeof a!=="object"&&a!==b)return this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a));return d.text(this)},wrapAll:function(a){if(d.isFunction(a))return this.each(function(b){d(this).wrapAll(a.call(this,b))});if(this[0]){var b=d(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(d.isFunction(a))return this.each(function(b){d(this).wrapInner(a.call(this,b))});return this.each(function(){var b=d(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){d(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){d.nodeName(this,"body")||d(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=d(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,d(arguments[0]).toArray());return a}},remove:function(a,b){for(var c=0,e;(e=this[c])!=null;c++)if(!a||d.filter(a,[e]).length)!b&&e.nodeType===1&&(d.cleanData(e.getElementsByTagName("*")),d.cleanData([e])),e.parentNode&&e.parentNode.removeChild(e);return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&d.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!0:a,b=b==null?a:b;return this.map(function(){return d.clone(this,a,b)})},html:function(a){if(a===b)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(P,""):null;if(typeof a!=="string"||V.test(a)||!d.support.leadingWhitespace&&Q.test(a)||X[(S.exec(a)||["",""])[1].toLowerCase()])d.isFunction(a)?this.each(function(b){var c=d(this);c.html(a.call(this,b,c.html()))}):this.empty().append(a);else{a=a.replace(R,"<$1>");try{for(var c=0,e=this.length;c1&&l0?this.clone(!0):this).get();d(f[h])[b](j),e=e.concat(j)}return this.pushStack(e,a,f.selector)}}),d.extend({clone:function(a,b,c){var e=a.cloneNode(!0),f,g,h;if(!d.support.noCloneEvent&&(a.nodeType===1||a.nodeType===11)&&!d.isXMLDoc(a)){f=a.getElementsByTagName("*"),g=e.getElementsByTagName("*");for(h=0;f[h];++h)$(f[h],g[h]);$(a,e)}if(b){Z(a,e);if(c&&"getElementsByTagName"in a){f=a.getElementsByTagName("*"),g=e.getElementsByTagName("*");if(f.length)for(h=0;f[h];++h)Z(f[h],g[h])}}return e},clean:function(a,b,e,f){b=b||c,typeof b.createElement==="undefined"&&(b=b.ownerDocument||b[0]&&b[0].ownerDocument||c);var g=[];for(var h=0,i;(i=a[h])!=null;h++){typeof i==="number"&&(i+="");if(!i)continue;if(typeof i!=="string"||U.test(i)){if(typeof i==="string"){i=i.replace(R,"<$1>");var j=(S.exec(i)||["",""])[1].toLowerCase(),k=X[j]||X._default,l=k[0],m=b.createElement("div");m.innerHTML=k[1]+i+k[2];while(l--)m=m.lastChild;if(!d.support.tbody){var n=T.test(i),o=j==="table"&&!n?m.firstChild&&m.firstChild.childNodes:k[1]===""&&!n?m.childNodes:[];for(var p=o.length-1;p>=0;--p)d.nodeName(o[p],"tbody")&&!o[p].childNodes.length&&o[p].parentNode.removeChild(o[p])}!d.support.leadingWhitespace&&Q.test(i)&&m.insertBefore(b.createTextNode(Q.exec(i)[0]),m.firstChild),i=m.childNodes}}else i=b.createTextNode(i);i.nodeType?g.push(i):g=d.merge(g,i)}if(e)for(h=0;g[h];h++)!f||!d.nodeName(g[h],"script")||g[h].type&&g[h].type.toLowerCase()!=="text/javascript"?(g[h].nodeType===1&&g.splice.apply(g,[h+1,0].concat(d.makeArray(g[h].getElementsByTagName("script")))),e.appendChild(g[h])):f.push(g[h].parentNode?g[h].parentNode.removeChild(g[h]):g[h]);return g},cleanData:function(a){var b,c,e=d.cache,f=d.expando,g=d.event.special,h=d.support.deleteExpando;for(var i=0,j;(j=a[i])!=null;i++){if(j.nodeName&&d.noData[j.nodeName.toLowerCase()])continue;c=j[d.expando];if(c){b=e[c]&&e[c][f];if(b&&b.events){for(var k in b.events)g[k]?d.event.remove(j,k):d.removeEvent(j,k,b.handle);b.handle&&(b.handle.elem=null)}h?delete j[d.expando]:j.removeAttribute&&j.removeAttribute(d.expando),delete e[c]}}}});var ba=/alpha\([^)]*\)/i,bb=/opacity=([^)]*)/,bc=/-([a-z])/ig,bd=/([A-Z])/g,be=/^-?\d+(?:px)?$/i,bf=/^-?\d/,bg={position:"absolute",visibility:"hidden",display:"block"},bh=["Left","Right"],bi=["Top","Bottom"],bj,bk,bl,bm=function(a,b){return b.toUpperCase()};d.fn.css=function(a,c){if(arguments.length===2&&c===b)return this;return d.access(this,a,c,!0,function(a,c,e){return e!==b?d.style(a,c,e):d.css(a,c)})},d.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=bj(a,"opacity","opacity");return c===""?"1":c}return a.style.opacity}}},cssNumber:{zIndex:!0,fontWeight:!0,opacity:!0,zoom:!0,lineHeight:!0},cssProps:{"float":d.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,e,f){if(a&&a.nodeType!==3&&a.nodeType!==8&&a.style){var g,h=d.camelCase(c),i=a.style,j=d.cssHooks[h];c=d.cssProps[h]||h;if(e===b){if(j&&"get"in j&&(g=j.get(a,!1,f))!==b)return g;return i[c]}if(typeof e==="number"&&isNaN(e)||e==null)return;typeof e==="number"&&!d.cssNumber[h]&&(e+="px");if(!j||!("set"in j)||(e=j.set(a,e))!==b)try{i[c]=e}catch(k){}}},css:function(a,c,e){var f,g=d.camelCase(c),h=d.cssHooks[g];c=d.cssProps[g]||g;if(h&&"get"in h&&(f=h.get(a,!0,e))!==b)return f;if(bj)return bj(a,c,g)},swap:function(a,b,c){var d={};for(var e in b)d[e]=a.style[e],a.style[e]=b[e];c.call(a);for(e in b)a.style[e]=d[e]},camelCase:function(a){return a.replace(bc,bm)}}),d.curCSS=d.css,d.each(["height","width"],function(a,b){d.cssHooks[b]={get:function(a,c,e){var f;if(c){a.offsetWidth!==0?f=bn(a,b,e):d.swap(a,bg,function(){f=bn(a,b,e)});if(f<=0){f=bj(a,b,b),f==="0px"&&bl&&(f=bl(a,b,b));if(f!=null)return f===""||f==="auto"?"0px":f}if(f<0||f==null){f=a.style[b];return f===""||f==="auto"?"0px":f}return typeof f==="string"?f:f+"px"}},set:function(a,b){if(!be.test(b))return b;b=parseFloat(b);if(b>=0)return b+"px"}}}),d.support.opacity||(d.cssHooks.opacity={get:function(a,b){return bb.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style;c.zoom=1;var e=d.isNaN(b)?"":"alpha(opacity="+b*100+")",f=c.filter||"";c.filter=ba.test(f)?f.replace(ba,e):c.filter+" "+e}}),c.defaultView&&c.defaultView.getComputedStyle&&(bk=function(a,c,e){var f,g,h;e=e.replace(bd,"-$1").toLowerCase();if(!(g=a.ownerDocument.defaultView))return b;if(h=g.getComputedStyle(a,null))f=h.getPropertyValue(e),f===""&&!d.contains(a.ownerDocument.documentElement,a)&&(f=d.style(a,e));return f}),c.documentElement.currentStyle&&(bl=function(a,b){var c,d=a.currentStyle&&a.currentStyle[b],e=a.runtimeStyle&&a.runtimeStyle[b],f=a.style;!be.test(d)&&bf.test(d)&&(c=f.left,e&&(a.runtimeStyle.left=a.currentStyle.left),f.left=b==="fontSize"?"1em":d||0,d=f.pixelLeft+"px",f.left=c,e&&(a.runtimeStyle.left=e));return d===""?"auto":d}),bj=bk||bl,d.expr&&d.expr.filters&&(d.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!d.support.reliableHiddenOffsets&&(a.style.display||d.css(a,"display"))==="none"},d.expr.filters.visible=function(a){return!d.expr.filters.hidden(a)});var bo=/%20/g,bp=/\[\]$/,bq=/\r?\n/g,br=/#.*$/,bs=/^(.*?):\s*(.*?)\r?$/mg,bt=/^(?:color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bu=/^(?:GET|HEAD)$/,bv=/^\/\//,bw=/\?/,bx=/)<[^<]*)*<\/script>/gi,by=/^(?:select|textarea)/i,bz=/\s+/,bA=/([?&])_=[^&]*/,bB=/^(\w+:)\/\/([^\/?#:]+)(?::(\d+))?/,bC=d.fn.load,bD={},bE={};d.fn.extend({load:function(a,b,c){if(typeof a!=="string"&&bC)return bC.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var f=a.slice(e,a.length);a=a.slice(0,e)}var g="GET";b&&(d.isFunction(b)?(c=b,b=null):typeof b==="object"&&(b=d.param(b,d.ajaxSettings.traditional),g="POST"));var h=this;d.ajax({url:a,type:g,dataType:"html",data:b,complete:function(a,b,e){e=a.responseText,a.isResolved()&&(a.done(function(a){e=a}),h.html(f?d("
").append(e.replace(bx,"")).find(f):e)),c&&h.each(c,[e,b,a])}});return this},serialize:function(){return d.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?d.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||by.test(this.nodeName)||bt.test(this.type))}).map(function(a,b){var c=d(this).val();return c==null?null:d.isArray(c)?d.map(c,function(a,c){return{name:b.name,value:a.replace(bq,"\r\n")}}):{name:b.name,value:c.replace(bq,"\r\n")}}).get()}}),d.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){d.fn[b]=function(a){return this.bind(b,a)}}),d.each(["get","post"],function(a,b){d[b]=function(a,c,e,f){d.isFunction(c)&&(f=f||e,e=c,c=null);return d.ajax({type:b,url:a,data:c,success:e,dataType:f})}}),d.extend({getScript:function(a,b){return d.get(a,null,b,"script")},getJSON:function(a,b,c){return d.get(a,b,c,"json")},ajaxSetup:function(a){d.extend(!0,d.ajaxSettings,a),a.context&&(d.ajaxSettings.context=a.context)},ajaxSettings:{url:location.href,global:!0,type:"GET",contentType:"application/x-www-form-urlencoded",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":"*/*"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":d.parseJSON,"text xml":d.parseXML}},ajaxPrefilter:bF(bD),ajaxTransport:bF(bE),ajax:function(a,e){function w(a,c,e,l){if(t!==2){t=2,p&&clearTimeout(p),o=b,m=l||"",v.readyState=a?4:0;var n,q,r,s=e?bI(f,v,e):b,u,w;if(a>=200&&a<300||a===304){if(f.ifModified){if(u=v.getResponseHeader("Last-Modified"))d.lastModified[f.url]=u;if(w=v.getResponseHeader("Etag"))d.etag[f.url]=w}if(a===304)c="notmodified",n=!0;else try{q=bJ(f,s),c="success",n=!0}catch(x){c="parsererror",r=x}}else r=c,a&&(c="error",a<0&&(a=0));v.status=a,v.statusText=c,n?i.resolveWith(g,[q,c,v]):i.rejectWith(g,[v,c,r]),v.statusCode(k),k=b,f.global&&h.trigger("ajax"+(n?"Success":"Error"),[v,f,n?q:r]),j.resolveWith(g,[v,c]),f.global&&(h.trigger("ajaxComplete",[v,f]),--d.active||d.event.trigger("ajaxStop"))}}typeof e!=="object"&&(e=a,a=b),e=e||{};var f=d.extend(!0,{},d.ajaxSettings,e),g=(f.context=("context"in e?e:d.ajaxSettings).context)||f,h=g===f?d.event:d(g),i=d.Deferred(),j=d._Deferred(),k=f.statusCode||{},l={},m,n,o,p,q=c.location,r=q.protocol||"http:",s,t=0,u,v={readyState:0,setRequestHeader:function(a,b){t===0&&(l[a.toLowerCase()]=b);return this},getAllResponseHeaders:function(){return t===2?m:null},getResponseHeader:function(a){var b;if(t===2){if(!n){n={};while(b=bs.exec(m))n[b[1].toLowerCase()]=b[2]}b=n[a.toLowerCase()]}return b||null},abort:function(a){a=a||"abort",o&&o.abort(a),w(0,a);return this}};i.promise(v),v.success=v.done,v.error=v.fail,v.complete=j.done,v.statusCode=function(a){if(a){var b;if(t<2)for(b in a)k[b]=[k[b],a[b]];else b=a[v.status],v.then(b,b)}return this},f.url=(""+(a||f.url)).replace(br,"").replace(bv,r+"//"),f.dataTypes=d.trim(f.dataType||"*").toLowerCase().split(bz),f.crossDomain||(s=bB.exec(f.url.toLowerCase()),f.crossDomain=s&&(s[1]!=r||s[2]!=q.hostname||(s[3]||(s[1]==="http:"?80:443))!=(q.port||(r==="http:"?80:443)))),f.data&&f.processData&&typeof f.data!=="string"&&(f.data=d.param(f.data,f.traditional)),bG(bD,f,e,v),f.type=f.type.toUpperCase(),f.hasContent=!bu.test(f.type),f.global&&d.active++===0&&d.event.trigger("ajaxStart");if(!f.hasContent){f.data&&(f.url+=(bw.test(f.url)?"&":"?")+f.data);if(f.cache===!1){var x=d.now(),y=f.url.replace(bA,"$1_="+x);f.url=y+(y===f.url?(bw.test(f.url)?"&":"?")+"_="+x:"")}}if(f.data&&f.hasContent&&f.contentType!==!1||e.contentType)l["content-type"]=f.contentType;f.ifModified&&(d.lastModified[f.url]&&(l["if-modified-since"]=d.lastModified[f.url]),d.etag[f.url]&&(l["if-none-match"]=d.etag[f.url])),l.accept=f.dataTypes[0]&&f.accepts[f.dataTypes[0]]?f.accepts[f.dataTypes[0]]+(f.dataTypes[0]!=="*"?", */*; q=0.01":""):f.accepts["*"];for(u in f.headers)l[u.toLowerCase()]=f.headers[u];if(!f.beforeSend||f.beforeSend.call(g,v,f)!==!1&&t!==2){for(u in {success:1,error:1,complete:1})v[u](f[u]);o=bG(bE,f,e,v);if(o){t=v.readyState=1,f.global&&h.trigger("ajaxSend",[v,f]),f.async&&f.timeout>0&&(p=setTimeout(function(){v.abort("timeout")},f.timeout));try{o.send(l,w)}catch(z){status<2?w(-1,z):d.error(z)}}else w(-1,"No Transport")}else w(0,"abort"),v=!1;return v},param:function(a,c){var e=[],f=function(a,b){b=d.isFunction(b)?b():b,e[e.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=d.ajaxSettings.traditional);if(d.isArray(a)||a.jquery)d.each(a,function(){f(this.name,this.value)});else for(var g in a)bH(g,a[g],c,f);return e.join("&").replace(bo,"+")}}),d.extend({active:0,lastModified:{},etag:{}});var bK=d.now(),bL=/(\=)\?(&|$)|()\?\?()/i;d.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return d.expando+"_"+bK++}}),d.ajaxPrefilter("json jsonp",function(b,c,e){e=typeof b.data==="string";if(b.dataTypes[0]==="jsonp"||c.jsonpCallback||c.jsonp!=null||b.jsonp!==!1&&(bL.test(b.url)||e&&bL.test(b.data))){var f,g=b.jsonpCallback=d.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,h=a[g],i=b.url,j=b.data,k="$1"+g+"$2";b.jsonp!==!1&&(i=i.replace(bL,k),b.url===i&&(e&&(j=j.replace(bL,k)),b.data===j&&(i+=(/\?/.test(i)?"&":"?")+b.jsonp+"="+g))),b.url=i,b.data=j,a[g]=function(a){f=[a]},b.complete=[function(){a[g]=h;if(h)f&&d.isFunction(h)&&a[g](f[0]);else try{delete a[g]}catch(b){}},b.complete],b.converters["script json"]=function(){f||d.error(g+" was not called");return f[0]},b.dataTypes[0]="json";return"script"}}),d.ajaxSetup({accepts:{script:"text/javascript, application/javascript"},contents:{script:/javascript/},converters:{"text script":function(a){d.globalEval(a);return a}}}),d.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),d.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var bM=d.now(),bN={},bO,bP;d.ajaxSettings.xhr=a.ActiveXObject?function(){if(a.location.protocol!=="file:")try{return new a.XMLHttpRequest}catch(b){}try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(c){}}:function(){return new a.XMLHttpRequest};try{bP=d.ajaxSettings.xhr()}catch(bQ){}d.support.ajax=!!bP,d.support.cors=bP&&"withCredentials"in bP,bP=b,d.support.ajax&&d.ajaxTransport(function(b){if(!b.crossDomain||d.support.cors){var c;return{send:function(e,f){bO||(bO=1,d(a).bind("unload",function(){d.each(bN,function(a,b){b.onreadystatechange&&b.onreadystatechange(1)})}));var g=b.xhr(),h;b.username?g.open(b.type,b.url,b.async,b.username,b.password):g.open(b.type,b.url,b.async),(!b.crossDomain||b.hasContent)&&!e["x-requested-with"]&&(e["x-requested-with"]="XMLHttpRequest");try{d.each(e,function(a,b){g.setRequestHeader(a,b)})}catch(i){}g.send(b.hasContent&&b.data||null),c=function(a,e){if(c&&(e||g.readyState===4)){c=0,h&&(g.onreadystatechange=d.noop,delete bN[h]);if(e)g.readyState!==4&&g.abort();else{var i=g.status,j,k=g.getAllResponseHeaders(),l={},m=g.responseXML;m&&m.documentElement&&(l.xml=m),l.text=g.responseText;try{j=g.statusText}catch(n){j=""}i=i===0?!b.crossDomain||j?k?304:0:302:i==1223?204:i,f(i,j,l,k)}}},b.async&&g.readyState!==4?(h=bM++,bN[h]=g,g.onreadystatechange=c):c()},abort:function(){c&&c(0,1)}}}});var bR={},bS=/^(?:toggle|show|hide)$/,bT=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,bU,bV=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];d.fn.extend({show:function(a,b,c){var e,f;if(a||a===0)return this.animate(bW("show",3),a,b,c);for(var g=0,h=this.length;g=0;a--)c[a].elem===this&&(b&&c[a](!0),c.splice(a,1))}),b||this.dequeue();return this}}),d.each({slideDown:bW("show",1),slideUp:bW("hide",1),slideToggle:bW("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){d.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),d.extend({speed:function(a,b,c){var e=a&&typeof a==="object"?d.extend({},a):{complete:c||!c&&b||d.isFunction(a)&&a,duration:a,easing:c&&b||b&&!d.isFunction(b)&&b};e.duration=d.fx.off?0:typeof e.duration==="number"?e.duration:e.duration in d.fx.speeds?d.fx.speeds[e.duration]:d.fx.speeds._default,e.old=e.complete,e.complete=function(){e.queue!==!1&&d(this).dequeue(),d.isFunction(e.old)&&e.old.call(this)};return e},easing:{linear:function(a,b,c,d){return c+d*a},swing:function(a,b,c,d){return(-Math.cos(a*Math.PI)/2+.5)*d+c}},timers:[],fx:function(a,b,c){this.options=b,this.elem=a,this.prop=c,b.orig||(b.orig={})}}),d.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this),(d.fx.step[this.prop]||d.fx.step._default)(this)},cur:function(){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];var a=parseFloat(d.css(this.elem,this.prop));return a||0},custom:function(a,b,c){function g(a){return e.step(a)}var e=this,f=d.fx;this.startTime=d.now(),this.start=a,this.end=b,this.unit=c||this.unit||"px",this.now=this.start,this.pos=this.state=0,g.elem=this.elem,g()&&d.timers.push(g)&&!bU&&(bU=setInterval(f.tick,f.interval))},show:function(){this.options.orig[this.prop]=d.style(this.elem,this.prop),this.options.show=!0,this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur()),d(this.elem).show()},hide:function(){this.options.orig[this.prop]=d.style(this.elem,this.prop),this.options.hide=!0,this.custom(this.cur(),0)},step:function(a){var b=d.now(),c=!0;if(a||b>=this.options.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),this.options.curAnim[this.prop]=!0;for(var e in this.options.curAnim)this.options.curAnim[e]!==!0&&(c=!1);if(c){if(this.options.overflow!=null&&!d.support.shrinkWrapBlocks){var f=this.elem,g=this.options;d.each(["","X","Y"],function(a,b){f.style["overflow"+b]=g.overflow[a]})}this.options.hide&&d(this.elem).hide();if(this.options.hide||this.options.show)for(var h in this.options.curAnim)d.style(this.elem,h,this.options.orig[h]);this.options.complete.call(this.elem)}return!1}var i=b-this.startTime;this.state=i/this.options.duration;var j=this.options.specialEasing&&this.options.specialEasing[this.prop],k=this.options.easing||(d.easing.swing?"swing":"linear");this.pos=d.easing[j||k](this.state,i,0,1,this.options.duration),this.now=this.start+(this.end-this.start)*this.pos,this.update();return!0}},d.extend(d.fx,{tick:function(){var a=d.timers;for(var b=0;b
";d.extend(b.style,{position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"}),b.innerHTML=j,a.insertBefore(b,a.firstChild),e=b.firstChild,f=e.firstChild,h=e.nextSibling.firstChild.firstChild,this.doesNotAddBorder=f.offsetTop!==5,this.doesAddBorderForTableAndCells=h.offsetTop===5,f.style.position="fixed",f.style.top="20px",this.supportsFixedPosition=f.offsetTop===20||f.offsetTop===15,f.style.position=f.style.top="",e.style.overflow="hidden",e.style.position="relative",this.subtractsBorderForOverflowNotVisible=f.offsetTop===-5,this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==i,a.removeChild(b),a=b=e=f=g=h=null,d.offset.initialize=d.noop},bodyOffset:function(a){var b=a.offsetTop,c=a.offsetLeft;d.offset.initialize(),d.offset.doesNotIncludeMarginInBodyOffset&&(b+=parseFloat(d.css(a,"marginTop"))||0,c+=parseFloat(d.css(a,"marginLeft"))||0);return{top:b,left:c}},setOffset:function(a,b,c){var e=d.css(a,"position");e==="static"&&(a.style.position="relative");var f=d(a),g=f.offset(),h=d.css(a,"top"),i=d.css(a,"left"),j=e==="absolute"&&d.inArray("auto",[h,i])>-1,k={},l={},m,n;j&&(l=f.position()),m=j?l.top:parseInt(h,10)||0,n=j?l.left:parseInt(i,10)||0,d.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):f.css(k)}},d.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),e=bZ.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(d.css(a,"marginTop"))||0,c.left-=parseFloat(d.css(a,"marginLeft"))||0,e.top+=parseFloat(d.css(b[0],"borderTopWidth"))||0,e.left+=parseFloat(d.css(b[0],"borderLeftWidth"))||0;return{top:c.top-e.top,left:c.left-e.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&(!bZ.test(a.nodeName)&&d.css(a,"position")==="static"))a=a.offsetParent;return a})}}),d.each(["Left","Top"],function(a,c){var e="scroll"+c;d.fn[e]=function(c){var f=this[0],g;if(!f)return null;if(c!==b)return this.each(function(){g=b$(this),g?g.scrollTo(a?d(g).scrollLeft():c,a?c:d(g).scrollTop()):this[e]=c});g=b$(f);return g?"pageXOffset"in g?g[a?"pageYOffset":"pageXOffset"]:d.support.boxModel&&g.document.documentElement[e]||g.document.body[e]:f[e]}}),d.each(["Height","Width"],function(a,c){var e=c.toLowerCase();d.fn["inner"+c]=function(){return this[0]?parseFloat(d.css(this[0],e,"padding")):null},d.fn["outer"+c]=function(a){return this[0]?parseFloat(d.css(this[0],e,a?"margin":"border")):null},d.fn[e]=function(a){var f=this[0];if(!f)return a==null?null:this;if(d.isFunction(a))return this.each(function(b){var c=d(this);c[e](a.call(this,b,c[e]()))});if(d.isWindow(f)){var g=f.document.documentElement["client"+c];return f.document.compatMode==="CSS1Compat"&&g||f.document.body["client"+c]||g}if(f.nodeType===9)return Math.max(f.documentElement["client"+c],f.body["scroll"+c],f.documentElement["scroll"+c],f.body["offset"+c],f.documentElement["offset"+c]);if(a===b){var h=d.css(f,e),i=parseFloat(h);return d.isNaN(i)?h:i}return this.css(e,typeof a==="string"?a:a+"px")}})})(window); diff --git a/env/lib/python2.7/site-packages/flask_profile/static/js/jquery.tablesorter.js b/env/lib/python2.7/site-packages/flask_profile/static/js/jquery.tablesorter.js new file mode 100644 index 0000000..9b58731 --- /dev/null +++ b/env/lib/python2.7/site-packages/flask_profile/static/js/jquery.tablesorter.js @@ -0,0 +1,1031 @@ +/* + * + * TableSorter 2.0 - Client-side table sorting with ease! + * Version 2.0.5b + * @requires jQuery v1.2.3 + * + * Copyright (c) 2007 Christian Bach + * Examples and docs at: http://tablesorter.com + * Dual licensed under the MIT and GPL licenses: + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl.html + * + */ +/** + * + * @description Create a sortable table with multi-column sorting capabilitys + * + * @example $('table').tablesorter(); + * @desc Create a simple tablesorter interface. + * + * @example $('table').tablesorter({ sortList:[[0,0],[1,0]] }); + * @desc Create a tablesorter interface and sort on the first and secound column column headers. + * + * @example $('table').tablesorter({ headers: { 0: { sorter: false}, 1: {sorter: false} } }); + * + * @desc Create a tablesorter interface and disableing the first and second column headers. + * + * + * @example $('table').tablesorter({ headers: { 0: {sorter:"integer"}, 1: {sorter:"currency"} } }); + * + * @desc Create a tablesorter interface and set a column parser for the first + * and second column. + * + * + * @param Object + * settings An object literal containing key/value pairs to provide + * optional settings. + * + * + * @option String cssHeader (optional) A string of the class name to be appended + * to sortable tr elements in the thead of the table. Default value: + * "header" + * + * @option String cssAsc (optional) A string of the class name to be appended to + * sortable tr elements in the thead on a ascending sort. Default value: + * "headerSortUp" + * + * @option String cssDesc (optional) A string of the class name to be appended + * to sortable tr elements in the thead on a descending sort. Default + * value: "headerSortDown" + * + * @option String sortInitialOrder (optional) A string of the inital sorting + * order can be asc or desc. Default value: "asc" + * + * @option String sortMultisortKey (optional) A string of the multi-column sort + * key. Default value: "shiftKey" + * + * @option String textExtraction (optional) A string of the text-extraction + * method to use. For complex html structures inside td cell set this + * option to "complex", on large tables the complex option can be slow. + * Default value: "simple" + * + * @option Object headers (optional) An array containing the forces sorting + * rules. This option let's you specify a default sorting rule. Default + * value: null + * + * @option Array sortList (optional) An array containing the forces sorting + * rules. This option let's you specify a default sorting rule. Default + * value: null + * + * @option Array sortForce (optional) An array containing forced sorting rules. + * This option let's you specify a default sorting rule, which is + * prepended to user-selected rules. Default value: null + * + * @option Boolean sortLocaleCompare (optional) Boolean flag indicating whatever + * to use String.localeCampare method or not. Default set to true. + * + * + * @option Array sortAppend (optional) An array containing forced sorting rules. + * This option let's you specify a default sorting rule, which is + * appended to user-selected rules. Default value: null + * + * @option Boolean widthFixed (optional) Boolean flag indicating if tablesorter + * should apply fixed widths to the table columns. This is usefull when + * using the pager companion plugin. This options requires the dimension + * jquery plugin. Default value: false + * + * @option Boolean cancelSelection (optional) Boolean flag indicating if + * tablesorter should cancel selection of the table headers text. + * Default value: true + * + * @option Boolean debug (optional) Boolean flag indicating if tablesorter + * should display debuging information usefull for development. + * + * @type jQuery + * + * @name tablesorter + * + * @cat Plugins/Tablesorter + * + * @author Christian Bach/christian.bach@polyester.se + */ + +(function ($) { + $.extend({ + tablesorter: new + function () { + + var parsers = [], + widgets = []; + + this.defaults = { + cssHeader: "header", + cssAsc: "headerSortUp", + cssDesc: "headerSortDown", + cssChildRow: "expand-child", + sortInitialOrder: "asc", + sortMultiSortKey: "shiftKey", + sortForce: null, + sortAppend: null, + sortLocaleCompare: true, + textExtraction: "simple", + parsers: {}, widgets: [], + widgetZebra: { + css: ["even", "odd"] + }, headers: {}, widthFixed: false, + cancelSelection: true, + sortList: [], + headerList: [], + dateFormat: "us", + decimal: '/\.|\,/g', + onRenderHeader: null, + selectorHeaders: 'thead th', + debug: false + }; + + /* debuging utils */ + + function benchmark(s, d) { + log(s + "," + (new Date().getTime() - d.getTime()) + "ms"); + } + + this.benchmark = benchmark; + + function log(s) { + if (typeof console != "undefined" && typeof console.debug != "undefined") { + console.log(s); + } else { + alert(s); + } + } + + /* parsers utils */ + + function buildParserCache(table, $headers) { + + if (table.config.debug) { + var parsersDebug = ""; + } + + if (table.tBodies.length == 0) return; // In the case of empty tables + var rows = table.tBodies[0].rows; + + if (rows[0]) { + + var list = [], + cells = rows[0].cells, + l = cells.length; + + for (var i = 0; i < l; i++) { + + var p = false; + + if ($.metadata && ($($headers[i]).metadata() && $($headers[i]).metadata().sorter)) { + + p = getParserById($($headers[i]).metadata().sorter); + + } else if ((table.config.headers[i] && table.config.headers[i].sorter)) { + + p = getParserById(table.config.headers[i].sorter); + } + if (!p) { + + p = detectParserForColumn(table, rows, -1, i); + } + + if (table.config.debug) { + parsersDebug += "column:" + i + " parser:" + p.id + "\n"; + } + + list.push(p); + } + } + + if (table.config.debug) { + log(parsersDebug); + } + + return list; + }; + + function detectParserForColumn(table, rows, rowIndex, cellIndex) { + var l = parsers.length, + node = false, + nodeValue = false, + keepLooking = true; + while (nodeValue == '' && keepLooking) { + rowIndex++; + if (rows[rowIndex]) { + node = getNodeFromRowAndCellIndex(rows, rowIndex, cellIndex); + nodeValue = trimAndGetNodeText(table.config, node); + if (table.config.debug) { + log('Checking if value was empty on row:' + rowIndex); + } + } else { + keepLooking = false; + } + } + for (var i = 1; i < l; i++) { + if (parsers[i].is(nodeValue, table, node)) { + return parsers[i]; + } + } + // 0 is always the generic parser (text) + return parsers[0]; + } + + function getNodeFromRowAndCellIndex(rows, rowIndex, cellIndex) { + return rows[rowIndex].cells[cellIndex]; + } + + function trimAndGetNodeText(config, node) { + return $.trim(getElementText(config, node)); + } + + function getParserById(name) { + var l = parsers.length; + for (var i = 0; i < l; i++) { + if (parsers[i].id.toLowerCase() == name.toLowerCase()) { + return parsers[i]; + } + } + return false; + } + + /* utils */ + + function buildCache(table) { + + if (table.config.debug) { + var cacheTime = new Date(); + } + + var totalRows = (table.tBodies[0] && table.tBodies[0].rows.length) || 0, + totalCells = (table.tBodies[0].rows[0] && table.tBodies[0].rows[0].cells.length) || 0, + parsers = table.config.parsers, + cache = { + row: [], + normalized: [] + }; + + for (var i = 0; i < totalRows; ++i) { + + /** Add the table data to main data array */ + var c = $(table.tBodies[0].rows[i]), + cols = []; + + // if this is a child row, add it to the last row's children and + // continue to the next row + if (c.hasClass(table.config.cssChildRow)) { + cache.row[cache.row.length - 1] = cache.row[cache.row.length - 1].add(c); + // go to the next for loop + continue; + } + + cache.row.push(c); + + for (var j = 0; j < totalCells; ++j) { + cols.push(parsers[j].format(getElementText(table.config, c[0].cells[j]), table, c[0].cells[j])); + } + + cols.push(cache.normalized.length); // add position for rowCache + cache.normalized.push(cols); + cols = null; + }; + + if (table.config.debug) { + benchmark("Building cache for " + totalRows + " rows:", cacheTime); + } + + return cache; + }; + + function getElementText(config, node) { + + var text = ""; + + if (!node) return ""; + + if (!config.supportsTextContent) config.supportsTextContent = node.textContent || false; + + if (config.textExtraction == "simple") { + if (config.supportsTextContent) { + text = node.textContent; + } else { + if (node.childNodes[0] && node.childNodes[0].hasChildNodes()) { + text = node.childNodes[0].innerHTML; + } else { + text = node.innerHTML; + } + } + } else { + if (typeof(config.textExtraction) == "function") { + text = config.textExtraction(node); + } else { + text = $(node).text(); + } + } + return text; + } + + function appendToTable(table, cache) { + + if (table.config.debug) { + var appendTime = new Date() + } + + var c = cache, + r = c.row, + n = c.normalized, + totalRows = n.length, + checkCell = (n[0].length - 1), + tableBody = $(table.tBodies[0]), + rows = []; + + + for (var i = 0; i < totalRows; i++) { + var pos = n[i][checkCell]; + + rows.push(r[pos]); + + if (!table.config.appender) { + + //var o = ; + var l = r[pos].length; + for (var j = 0; j < l; j++) { + tableBody[0].appendChild(r[pos][j]); + } + + // + } + } + + + + if (table.config.appender) { + + table.config.appender(table, rows); + } + + rows = null; + + if (table.config.debug) { + benchmark("Rebuilt table:", appendTime); + } + + // apply table widgets + applyWidget(table); + + // trigger sortend + setTimeout(function () { + $(table).trigger("sortEnd"); + }, 0); + + }; + + function buildHeaders(table) { + + if (table.config.debug) { + var time = new Date(); + } + + var meta = ($.metadata) ? true : false; + + var header_index = computeTableHeaderCellIndexes(table); + + $tableHeaders = $(table.config.selectorHeaders, table).each(function (index) { + + this.column = header_index[this.parentNode.rowIndex + "-" + this.cellIndex]; + // this.column = index; + this.order = formatSortingOrder(table.config.sortInitialOrder); + + + this.count = this.order; + + if (checkHeaderMetadata(this) || checkHeaderOptions(table, index)) this.sortDisabled = true; + if (checkHeaderOptionsSortingLocked(table, index)) this.order = this.lockedOrder = checkHeaderOptionsSortingLocked(table, index); + + if (!this.sortDisabled) { + var $th = $(this).addClass(table.config.cssHeader); + if (table.config.onRenderHeader) table.config.onRenderHeader.apply($th); + } + + // add cell to headerList + table.config.headerList[index] = this; + }); + + if (table.config.debug) { + benchmark("Built headers:", time); + log($tableHeaders); + } + + return $tableHeaders; + + }; + + // from: + // http://www.javascripttoolbox.com/lib/table/examples.php + // http://www.javascripttoolbox.com/temp/table_cellindex.html + + + function computeTableHeaderCellIndexes(t) { + var matrix = []; + var lookup = {}; + var thead = t.getElementsByTagName('THEAD')[0]; + var trs = thead.getElementsByTagName('TR'); + + for (var i = 0; i < trs.length; i++) { + var cells = trs[i].cells; + for (var j = 0; j < cells.length; j++) { + var c = cells[j]; + + var rowIndex = c.parentNode.rowIndex; + var cellId = rowIndex + "-" + c.cellIndex; + var rowSpan = c.rowSpan || 1; + var colSpan = c.colSpan || 1 + var firstAvailCol; + if (typeof(matrix[rowIndex]) == "undefined") { + matrix[rowIndex] = []; + } + // Find first available column in the first row + for (var k = 0; k < matrix[rowIndex].length + 1; k++) { + if (typeof(matrix[rowIndex][k]) == "undefined") { + firstAvailCol = k; + break; + } + } + lookup[cellId] = firstAvailCol; + for (var k = rowIndex; k < rowIndex + rowSpan; k++) { + if (typeof(matrix[k]) == "undefined") { + matrix[k] = []; + } + var matrixrow = matrix[k]; + for (var l = firstAvailCol; l < firstAvailCol + colSpan; l++) { + matrixrow[l] = "x"; + } + } + } + } + return lookup; + } + + function checkCellColSpan(table, rows, row) { + var arr = [], + r = table.tHead.rows, + c = r[row].cells; + + for (var i = 0; i < c.length; i++) { + var cell = c[i]; + + if (cell.colSpan > 1) { + arr = arr.concat(checkCellColSpan(table, headerArr, row++)); + } else { + if (table.tHead.length == 1 || (cell.rowSpan > 1 || !r[row + 1])) { + arr.push(cell); + } + // headerArr[row] = (i+row); + } + } + return arr; + }; + + function checkHeaderMetadata(cell) { + if (($.metadata) && ($(cell).metadata().sorter === false)) { + return true; + }; + return false; + } + + function checkHeaderOptions(table, i) { + if ((table.config.headers[i]) && (table.config.headers[i].sorter === false)) { + return true; + }; + return false; + } + + function checkHeaderOptionsSortingLocked(table, i) { + if ((table.config.headers[i]) && (table.config.headers[i].lockedOrder)) return table.config.headers[i].lockedOrder; + return false; + } + + function applyWidget(table) { + var c = table.config.widgets; + var l = c.length; + for (var i = 0; i < l; i++) { + + getWidgetById(c[i]).format(table); + } + + } + + function getWidgetById(name) { + var l = widgets.length; + for (var i = 0; i < l; i++) { + if (widgets[i].id.toLowerCase() == name.toLowerCase()) { + return widgets[i]; + } + } + }; + + function formatSortingOrder(v) { + if (typeof(v) != "Number") { + return (v.toLowerCase() == "desc") ? 1 : 0; + } else { + return (v == 1) ? 1 : 0; + } + } + + function isValueInArray(v, a) { + var l = a.length; + for (var i = 0; i < l; i++) { + if (a[i][0] == v) { + return true; + } + } + return false; + } + + function setHeadersCss(table, $headers, list, css) { + // remove all header information + $headers.removeClass(css[0]).removeClass(css[1]); + + var h = []; + $headers.each(function (offset) { + if (!this.sortDisabled) { + h[this.column] = $(this); + } + }); + + var l = list.length; + for (var i = 0; i < l; i++) { + h[list[i][0]].addClass(css[list[i][1]]); + } + } + + function fixColumnWidth(table, $headers) { + var c = table.config; + if (c.widthFixed) { + var colgroup = $(''); + $("tr:first td", table.tBodies[0]).each(function () { + colgroup.append($('').css('width', $(this).width())); + }); + $(table).prepend(colgroup); + }; + } + + function updateHeaderSortCount(table, sortList) { + var c = table.config, + l = sortList.length; + for (var i = 0; i < l; i++) { + var s = sortList[i], + o = c.headerList[s[0]]; + o.count = s[1]; + o.count++; + } + } + + /* sorting methods */ + + function multisort(table, sortList, cache) { + + if (table.config.debug) { + var sortTime = new Date(); + } + + var dynamicExp = "var sortWrapper = function(a,b) {", + l = sortList.length; + + // TODO: inline functions. + for (var i = 0; i < l; i++) { + + var c = sortList[i][0]; + var order = sortList[i][1]; + // var s = (getCachedSortType(table.config.parsers,c) == "text") ? + // ((order == 0) ? "sortText" : "sortTextDesc") : ((order == 0) ? + // "sortNumeric" : "sortNumericDesc"); + // var s = (table.config.parsers[c].type == "text") ? ((order == 0) + // ? makeSortText(c) : makeSortTextDesc(c)) : ((order == 0) ? + // makeSortNumeric(c) : makeSortNumericDesc(c)); + var s = (table.config.parsers[c].type == "text") ? ((order == 0) ? makeSortFunction("text", "asc", c) : makeSortFunction("text", "desc", c)) : ((order == 0) ? makeSortFunction("numeric", "asc", c) : makeSortFunction("numeric", "desc", c)); + var e = "e" + i; + + dynamicExp += "var " + e + " = " + s; // + "(a[" + c + "],b[" + c + // + "]); "; + dynamicExp += "if(" + e + ") { return " + e + "; } "; + dynamicExp += "else { "; + + } + + // if value is the same keep orignal order + var orgOrderCol = cache.normalized[0].length - 1; + dynamicExp += "return a[" + orgOrderCol + "]-b[" + orgOrderCol + "];"; + + for (var i = 0; i < l; i++) { + dynamicExp += "}; "; + } + + dynamicExp += "return 0; "; + dynamicExp += "}; "; + + if (table.config.debug) { + benchmark("Evaling expression:" + dynamicExp, new Date()); + } + + eval(dynamicExp); + + cache.normalized.sort(sortWrapper); + + if (table.config.debug) { + benchmark("Sorting on " + sortList.toString() + " and dir " + order + " time:", sortTime); + } + + return cache; + }; + + function makeSortFunction(type, direction, index) { + var a = "a[" + index + "]", + b = "b[" + index + "]"; + if (type == 'text' && direction == 'asc') { + return "(" + a + " == " + b + " ? 0 : (" + a + " === null ? Number.POSITIVE_INFINITY : (" + b + " === null ? Number.NEGATIVE_INFINITY : (" + a + " < " + b + ") ? -1 : 1 )));"; + } else if (type == 'text' && direction == 'desc') { + return "(" + a + " == " + b + " ? 0 : (" + a + " === null ? Number.POSITIVE_INFINITY : (" + b + " === null ? Number.NEGATIVE_INFINITY : (" + b + " < " + a + ") ? -1 : 1 )));"; + } else if (type == 'numeric' && direction == 'asc') { + return "(" + a + " === null && " + b + " === null) ? 0 :(" + a + " === null ? Number.POSITIVE_INFINITY : (" + b + " === null ? Number.NEGATIVE_INFINITY : " + a + " - " + b + "));"; + } else if (type == 'numeric' && direction == 'desc') { + return "(" + a + " === null && " + b + " === null) ? 0 :(" + a + " === null ? Number.POSITIVE_INFINITY : (" + b + " === null ? Number.NEGATIVE_INFINITY : " + b + " - " + a + "));"; + } + }; + + function makeSortText(i) { + return "((a[" + i + "] < b[" + i + "]) ? -1 : ((a[" + i + "] > b[" + i + "]) ? 1 : 0));"; + }; + + function makeSortTextDesc(i) { + return "((b[" + i + "] < a[" + i + "]) ? -1 : ((b[" + i + "] > a[" + i + "]) ? 1 : 0));"; + }; + + function makeSortNumeric(i) { + return "a[" + i + "]-b[" + i + "];"; + }; + + function makeSortNumericDesc(i) { + return "b[" + i + "]-a[" + i + "];"; + }; + + function sortText(a, b) { + if (table.config.sortLocaleCompare) return a.localeCompare(b); + return ((a < b) ? -1 : ((a > b) ? 1 : 0)); + }; + + function sortTextDesc(a, b) { + if (table.config.sortLocaleCompare) return b.localeCompare(a); + return ((b < a) ? -1 : ((b > a) ? 1 : 0)); + }; + + function sortNumeric(a, b) { + return a - b; + }; + + function sortNumericDesc(a, b) { + return b - a; + }; + + function getCachedSortType(parsers, i) { + return parsers[i].type; + }; /* public methods */ + this.construct = function (settings) { + return this.each(function () { + // if no thead or tbody quit. + if (!this.tHead || !this.tBodies) return; + // declare + var $this, $document, $headers, cache, config, shiftDown = 0, + sortOrder; + // new blank config object + this.config = {}; + // merge and extend. + config = $.extend(this.config, $.tablesorter.defaults, settings); + // store common expression for speed + $this = $(this); + // save the settings where they read + $.data(this, "tablesorter", config); + // build headers + $headers = buildHeaders(this); + // try to auto detect column type, and store in tables config + this.config.parsers = buildParserCache(this, $headers); + // build the cache for the tbody cells + cache = buildCache(this); + // get the css class names, could be done else where. + var sortCSS = [config.cssDesc, config.cssAsc]; + // fixate columns if the users supplies the fixedWidth option + fixColumnWidth(this); + // apply event handling to headers + // this is to big, perhaps break it out? + $headers.click( + + function (e) { + var totalRows = ($this[0].tBodies[0] && $this[0].tBodies[0].rows.length) || 0; + if (!this.sortDisabled && totalRows > 0) { + // Only call sortStart if sorting is + // enabled. + $this.trigger("sortStart"); + // store exp, for speed + var $cell = $(this); + // get current column index + var i = this.column; + // get current column sort order + this.order = this.count++ % 2; + // always sort on the locked order. + if(this.lockedOrder) this.order = this.lockedOrder; + + // user only whants to sort on one + // column + if (!e[config.sortMultiSortKey]) { + // flush the sort list + config.sortList = []; + if (config.sortForce != null) { + var a = config.sortForce; + for (var j = 0; j < a.length; j++) { + if (a[j][0] != i) { + config.sortList.push(a[j]); + } + } + } + // add column to sort list + config.sortList.push([i, this.order]); + // multi column sorting + } else { + // the user has clicked on an all + // ready sortet column. + if (isValueInArray(i, config.sortList)) { + // revers the sorting direction + // for all tables. + for (var j = 0; j < config.sortList.length; j++) { + var s = config.sortList[j], + o = config.headerList[s[0]]; + if (s[0] == i) { + o.count = s[1]; + o.count++; + s[1] = o.count % 2; + } + } + } else { + // add column to sort list array + config.sortList.push([i, this.order]); + } + }; + setTimeout(function () { + // set css for headers + setHeadersCss($this[0], $headers, config.sortList, sortCSS); + appendToTable( + $this[0], multisort( + $this[0], config.sortList, cache) + ); + }, 1); + // stop normal event by returning false + return false; + } + // cancel selection + }).mousedown(function () { + if (config.cancelSelection) { + this.onselectstart = function () { + return false + }; + return false; + } + }); + // apply easy methods that trigger binded events + $this.bind("update", function () { + var me = this; + setTimeout(function () { + // rebuild parsers. + me.config.parsers = buildParserCache( + me, $headers); + // rebuild the cache map + cache = buildCache(me); + }, 1); + }).bind("updateCell", function (e, cell) { + var config = this.config; + // get position from the dom. + var pos = [(cell.parentNode.rowIndex - 1), cell.cellIndex]; + // update cache + cache.normalized[pos[0]][pos[1]] = config.parsers[pos[1]].format( + getElementText(config, cell), cell); + }).bind("sorton", function (e, list) { + $(this).trigger("sortStart"); + config.sortList = list; + // update and store the sortlist + var sortList = config.sortList; + // update header count index + updateHeaderSortCount(this, sortList); + // set css for headers + setHeadersCss(this, $headers, sortList, sortCSS); + // sort the table and append it to the dom + appendToTable(this, multisort(this, sortList, cache)); + }).bind("appendCache", function () { + appendToTable(this, cache); + }).bind("applyWidgetId", function (e, id) { + getWidgetById(id).format(this); + }).bind("applyWidgets", function () { + // apply widgets + applyWidget(this); + }); + if ($.metadata && ($(this).metadata() && $(this).metadata().sortlist)) { + config.sortList = $(this).metadata().sortlist; + } + // if user has supplied a sort list to constructor. + if (config.sortList.length > 0) { + $this.trigger("sorton", [config.sortList]); + } + // apply widgets + applyWidget(this); + }); + }; + this.addParser = function (parser) { + var l = parsers.length, + a = true; + for (var i = 0; i < l; i++) { + if (parsers[i].id.toLowerCase() == parser.id.toLowerCase()) { + a = false; + } + } + if (a) { + parsers.push(parser); + }; + }; + this.addWidget = function (widget) { + widgets.push(widget); + }; + this.formatFloat = function (s) { + var i = parseFloat(s); + return (isNaN(i)) ? 0 : i; + }; + this.formatInt = function (s) { + var i = parseInt(s); + return (isNaN(i)) ? 0 : i; + }; + this.isDigit = function (s, config) { + // replace all an wanted chars and match. + return /^[-+]?\d*$/.test($.trim(s.replace(/[,.']/g, ''))); + }; + this.clearTableBody = function (table) { + if ($.browser.msie) { + function empty() { + while (this.firstChild) + this.removeChild(this.firstChild); + } + empty.apply(table.tBodies[0]); + } else { + table.tBodies[0].innerHTML = ""; + } + }; + } + }); + + // extend plugin scope + $.fn.extend({ + tablesorter: $.tablesorter.construct + }); + + // make shortcut + var ts = $.tablesorter; + + // add default parsers + ts.addParser({ + id: "text", + is: function (s) { + return true; + }, format: function (s) { + return $.trim(s.toLocaleLowerCase()); + }, type: "text" + }); + + ts.addParser({ + id: "digit", + is: function (s, table) { + var c = table.config; + return $.tablesorter.isDigit(s, c); + }, format: function (s) { + return $.tablesorter.formatFloat(s); + }, type: "numeric" + }); + + ts.addParser({ + id: "currency", + is: function (s) { + return /^[£$€?.]/.test(s); + }, format: function (s) { + return $.tablesorter.formatFloat(s.replace(new RegExp(/[£$€]/g), "")); + }, type: "numeric" + }); + + ts.addParser({ + id: "ipAddress", + is: function (s) { + return /^\d{2,3}[\.]\d{2,3}[\.]\d{2,3}[\.]\d{2,3}$/.test(s); + }, format: function (s) { + var a = s.split("."), + r = "", + l = a.length; + for (var i = 0; i < l; i++) { + var item = a[i]; + if (item.length == 2) { + r += "0" + item; + } else { + r += item; + } + } + return $.tablesorter.formatFloat(r); + }, type: "numeric" + }); + + ts.addParser({ + id: "url", + is: function (s) { + return /^(https?|ftp|file):\/\/$/.test(s); + }, format: function (s) { + return jQuery.trim(s.replace(new RegExp(/(https?|ftp|file):\/\//), '')); + }, type: "text" + }); + + ts.addParser({ + id: "isoDate", + is: function (s) { + return /^\d{4}[\/-]\d{1,2}[\/-]\d{1,2}$/.test(s); + }, format: function (s) { + return $.tablesorter.formatFloat((s != "") ? new Date(s.replace( + new RegExp(/-/g), "/")).getTime() : "0"); + }, type: "numeric" + }); + + ts.addParser({ + id: "percent", + is: function (s) { + return /\%$/.test($.trim(s)); + }, format: function (s) { + return $.tablesorter.formatFloat(s.replace(new RegExp(/%/g), "")); + }, type: "numeric" + }); + + ts.addParser({ + id: "usLongDate", + is: function (s) { + return s.match(new RegExp(/^[A-Za-z]{3,10}\.? [0-9]{1,2}, ([0-9]{4}|'?[0-9]{2}) (([0-2]?[0-9]:[0-5][0-9])|([0-1]?[0-9]:[0-5][0-9]\s(AM|PM)))$/)); + }, format: function (s) { + return $.tablesorter.formatFloat(new Date(s).getTime()); + }, type: "numeric" + }); + + ts.addParser({ + id: "shortDate", + is: function (s) { + return /\d{1,2}[\/\-]\d{1,2}[\/\-]\d{2,4}/.test(s); + }, format: function (s, table) { + var c = table.config; + s = s.replace(/\-/g, "/"); + if (c.dateFormat == "us") { + // reformat the string in ISO format + s = s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{4})/, "$3/$1/$2"); + } else if (c.dateFormat == "uk") { + // reformat the string in ISO format + s = s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{4})/, "$3/$2/$1"); + } else if (c.dateFormat == "dd/mm/yy" || c.dateFormat == "dd-mm-yy") { + s = s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{2})/, "$1/$2/$3"); + } + return $.tablesorter.formatFloat(new Date(s).getTime()); + }, type: "numeric" + }); + ts.addParser({ + id: "time", + is: function (s) { + return /^(([0-2]?[0-9]:[0-5][0-9])|([0-1]?[0-9]:[0-5][0-9]\s(am|pm)))$/.test(s); + }, format: function (s) { + return $.tablesorter.formatFloat(new Date("2000/01/01 " + s).getTime()); + }, type: "numeric" + }); + ts.addParser({ + id: "metadata", + is: function (s) { + return false; + }, format: function (s, table, cell) { + var c = table.config, + p = (!c.parserMetadataName) ? 'sortValue' : c.parserMetadataName; + return $(cell).metadata()[p]; + }, type: "numeric" + }); + // add default widgets + ts.addWidget({ + id: "zebra", + format: function (table) { + if (table.config.debug) { + var time = new Date(); + } + var $tr, row = -1, + odd; + // loop through the visible rows + $("tr:visible", table.tBodies[0]).each(function (i) { + $tr = $(this); + // style children rows the same way the parent + // row was styled + if (!$tr.hasClass(table.config.cssChildRow)) row++; + odd = (row % 2 == 0); + $tr.removeClass( + table.config.widgetZebra.css[odd ? 0 : 1]).addClass( + table.config.widgetZebra.css[odd ? 1 : 0]) + }); + if (table.config.debug) { + $.tablesorter.benchmark("Applying Zebra widget", time); + } + } + }); +})(jQuery); \ No newline at end of file diff --git a/env/lib/python2.7/site-packages/flask_profile/static/js/profiler.js b/env/lib/python2.7/site-packages/flask_profile/static/js/profiler.js new file mode 100644 index 0000000..926a7cd --- /dev/null +++ b/env/lib/python2.7/site-packages/flask_profile/static/js/profiler.js @@ -0,0 +1,24 @@ +var _flaskProfileBackupOriginalHtml; + +$(document).ready(function() { + $('#_profileContent table.tablesorter') + .tablesorter() + .bind('sortStart', function() { + $(this).find('tbody tr') + .removeClass('profileContentTrEven') + .removeClass('profileContentTrOdd'); + }) + .bind('sortEnd', function() { + $(this).find('tbody tr').each(function(idx, elem) { + var even = idx % 2 == 0; + $(elem) + .toggleClass('profileContentTrEven', even) + .toggleClass('profileContentTrOdd', !even); + }); + }); + + $('#flaskShowProfileButton').click(function() { + $('#_profileContentWrapper').toggle(); + return false; + }); +}); diff --git a/env/lib/python2.7/site-packages/flask_profile/templates/_profile/profiler.html b/env/lib/python2.7/site-packages/flask_profile/templates/_profile/profiler.html new file mode 100644 index 0000000..683bb4c --- /dev/null +++ b/env/lib/python2.7/site-packages/flask_profile/templates/_profile/profiler.html @@ -0,0 +1,45 @@ +
+ + + + +
+ « +
+ + + + +
diff --git a/env/lib/python2.7/site-packages/flask_runner.py b/env/lib/python2.7/site-packages/flask_runner.py new file mode 100644 index 0000000..ee64bae --- /dev/null +++ b/env/lib/python2.7/site-packages/flask_runner.py @@ -0,0 +1,137 @@ +import sys +import os +import argparse +from flask.ext.script import Manager as BaseManager, Server as BaseServer, Shell, Command, Option + +class Server(BaseServer): + def get_options(self): + options = super(Server, self).get_options() + options += ( + Option('--noeval', + dest = 'use_evalex', + action = 'store_false', + default = True, + help = 'disable exception evaluation in the debugger'), + Option('--extra', + metavar = 'FILE', + type = str, + dest = 'extra_files', + action = 'append', + help = 'additional file for the reloader to watch for changes'), + Option('--profile', + action = 'store_true', + default = False, + help = 'run the profiler for each request'), + Option('--profile-count', + metavar = 'COUNT', + type = int, + dest = 'profile_restrictions', + action = 'append', + help = 'restrict profiler output to the top COUNT lines'), + Option('--profile-percent', + metavar = 'PERCENT', + type = float, + dest = 'profile_restrictions', + action = 'append', + help = 'restrict profiler output to the top PERCENT lines'), + Option('--profile-regex', + metavar = 'REGEX', + type = str, + dest = 'profile_restrictions', + action = 'append', + help = 'filter profiler output with REGEX'), + Option('--profile-dir', + metavar = 'DIR', + default = None, + help = 'write profiler results one file per request in folder DIR'), + Option('--lint', + action = 'store_true', + default = False, + help = 'run the lint validation middleware'), + ) + return options + + def handle(self, app, *args, **kwargs): + #host, port, use_debugger, use_reloader, + #threaded, processes, passthrough_errors, use_evalex, + #extra_files, profile, profile_restrictions, profile_dir, lint): + # we don't need to run the server in request context + # so just run it directly + + profile = kwargs['profile'] + profile_restrictions = kwargs['profile_restrictions'] or () + profile_dir = kwargs['profile_dir'] + lint = kwargs['lint'] + + if profile: + from werkzeug.contrib.profiler import ProfilerMiddleware + app.wsgi_app = ProfilerMiddleware(app.wsgi_app, + restrictions = profile_restrictions, profile_dir = profile_dir) + + if lint: + from werkzeug.contrib.lint import LintMiddleware + app.wsgi_app = LintMiddleware(app.wsgi_app) + + app.run(host = kwargs['host'], + port = kwargs['port'], + use_debugger = kwargs['use_debugger'], + use_reloader = kwargs['use_reloader'], + threaded = kwargs['threaded'], + processes = kwargs['processes'], + passthrough_errors = kwargs['passthrough_errors'], + use_evalex = kwargs['use_evalex'], + extra_files = kwargs['extra_files']) + +class Test(Command): + description = 'Runs unit tests.' + + def get_options(self): + return (Option('-c', '--with-coverage', + dest = 'coverage', + action = 'store_true', + help = 'Include coverage report'),) + + def run(self, coverage): + options = "" + if coverage: + options += ' --with-coverage --cover-package=app' + os.system('nosetests' + options) + +class Manager(BaseManager): + def __init__(self, app=None, with_default_commands=None, usage=None): + super(Manager, self).__init__(app, with_default_commands = False, usage = usage) + if with_default_commands or (app and with_default_commands is None): + self.add_default_commands() + + def make_shell_context(self): + d = dict(app = self.app) + try: + from app import db + d['db'] = db + except: + pass + try: + from app import models + d['models'] = models + except: + pass + return d + + def add_default_commands(self): + self.add_command("runserver", Server()) + self.add_command("shell", Shell(make_context = self.make_shell_context)) + self.add_command("test", Test()) + +class Runner(object): + def __init__(self, app): + self.app = app + + def handle(self, prog, args = None): + server = Server() + arg_parser = server.create_parser(prog) + + args = arg_parser.parse_args(args) + server.handle(self.app, **args.__dict__) + + def run(self): + self.handle(sys.argv[0], sys.argv[1:]) diff --git a/env/lib/python2.7/site-packages/flask_runner.pyc b/env/lib/python2.7/site-packages/flask_runner.pyc new file mode 100644 index 0000000..5129752 Binary files /dev/null and b/env/lib/python2.7/site-packages/flask_runner.pyc differ diff --git a/env/lib/python2.7/site-packages/flask_script/__init__.py b/env/lib/python2.7/site-packages/flask_script/__init__.py new file mode 100644 index 0000000..5a08aea --- /dev/null +++ b/env/lib/python2.7/site-packages/flask_script/__init__.py @@ -0,0 +1,427 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import + +import os +import re +import sys +import types +import inspect +import warnings + +import argparse + +from flask import Flask + +from ._compat import text_type, iteritems, izip +from .commands import Group, Option, Command, Server, Shell +from .cli import prompt, prompt_pass, prompt_bool, prompt_choices + +__all__ = ["Command", "Shell", "Server", "Manager", "Group", "Option", + "prompt", "prompt_pass", "prompt_bool", "prompt_choices"] + +safe_actions = (argparse._StoreAction, + argparse._StoreConstAction, + argparse._StoreTrueAction, + argparse._StoreFalseAction, + argparse._AppendAction, + argparse._AppendConstAction, + argparse._CountAction) + + +try: + import argcomplete + ARGCOMPLETE_IMPORTED = True +except ImportError: + ARGCOMPLETE_IMPORTED = False + + +class Manager(object): + """ + Controller class for handling a set of commands. + + Typical usage:: + + class Print(Command): + + def run(self): + print "hello" + + app = Flask(__name__) + + manager = Manager(app) + manager.add_command("print", Print()) + + if __name__ == "__main__": + manager.run() + + On command line:: + + python manage.py print + > hello + + :param app: Flask instance or callable returning a Flask instance. + :param with_default_commands: load commands **runserver** and **shell** + by default. + :param disable_argcomplete: disable automatic loading of argcomplete. + + """ + + def __init__(self, app=None, with_default_commands=None, usage=None, + help=None, description=None, disable_argcomplete=False): + + self.app = app + + self._commands = dict() + self._options = list() + + # Primary/root Manager instance adds default commands by default, + # Sub-Managers do not + if with_default_commands or (app and with_default_commands is None): + self.add_default_commands() + + self.usage = usage + self.help = help if help is not None else usage + self.description = description if description is not None else usage + self.disable_argcomplete = disable_argcomplete + + self.parent = None + + def add_default_commands(self): + """ + Adds the shell and runserver default commands. To override these + simply add your own equivalents using add_command or decorators. + """ + + self.add_command("shell", Shell()) + self.add_command("runserver", Server()) + + def add_option(self, *args, **kwargs): + """ + Adds an application-wide option. This is useful if you want to set + variables applying to the application setup, rather than individual + commands. + + For this to work, the manager must be initialized with a factory + function rather than an instance. Otherwise any options you set will + be ignored. + + The arguments are then passed to your function, e.g.:: + + def create_app(config=None): + app = Flask(__name__) + if config: + app.config.from_pyfile(config) + + return app + + manager = Manager(create_app) + manager.add_option("-c", "--config", dest="config", required=False) + + and are evoked like this:: + + > python manage.py -c dev.cfg mycommand + + Any manager options passed in the command line will not be passed to + the command. + + Arguments for this function are the same as for the Option class. + """ + + self._options.append(Option(*args, **kwargs)) + + def create_app(self, **kwargs): + if self.parent: + # Sub-manager, defer to parent Manager + return self.parent.create_app(**kwargs) + + if isinstance(self.app, Flask): + return self.app + + return self.app(**kwargs) + + def create_parser(self, prog, parents=None): + """ + Creates an ArgumentParser instance from options returned + by get_options(), and a subparser for the given command. + """ + prog = os.path.basename(prog) + + options_parser = argparse.ArgumentParser(add_help=False) + for option in self.get_options(): + options_parser.add_argument(*option.args, **option.kwargs) + + # parser_parents = parents if parents else [option_parser] + # parser_parents = [options_parser] + + parser = argparse.ArgumentParser(prog=prog, usage=self.usage, + description=self.description, + parents=[options_parser]) + + self._patch_argparser(parser) + + subparsers = parser.add_subparsers() + + for name, command in self._commands.items(): + usage = getattr(command, 'usage', None) + help = getattr(command, 'help', command.__doc__) + description = getattr(command, 'description', command.__doc__) + + # Only pass `parents` argument for commands that support it + try: + command_parser = command.create_parser(name, parents=[options_parser]) + except TypeError: + warnings.warn("create_parser for {0} command should accept a `parents` argument".format(name), DeprecationWarning) + command_parser = command.create_parser(name) + + subparser = subparsers.add_parser(name, usage=usage, help=help, + description=description, + parents=[command_parser], add_help=False) + + if isinstance(command, Manager): + self._patch_argparser(subparser) + + ## enable autocomplete only for parent parser when argcomplete is + ## imported and it is NOT disabled in constructor + if parents is None and ARGCOMPLETE_IMPORTED \ + and not self.disable_argcomplete: + argcomplete.autocomplete(parser, always_complete_options=True) + + return parser + + # def foo(self, app, *args, **kwargs): + # print(args) + + def _patch_argparser(self, parser): + """ + Patches the parser to print the full help if no arguments are supplied + """ + + def _parse_known_args(self, arg_strings, *args, **kw): + if not arg_strings: + self.print_help() + self.exit(2) + + return self._parse_known_args2(arg_strings, *args, **kw) + + parser._parse_known_args2 = parser._parse_known_args + parser._parse_known_args = types.MethodType(_parse_known_args, parser) + + def get_options(self): + if self.parent: + return self.parent._options + + return self._options + + def add_command(self, *args, **kwargs): + """ + Adds command to registry. + + :param command: Command instance + :param name: Name of the command (optional) + :param namespace: Namespace of the command (optional; pass as kwarg) + """ + + if len(args) == 1: + command = args[0] + name = None + + else: + name, command = args + + if name is None: + if hasattr(command, 'name'): + name = command.name + + else: + name = type(command).__name__.lower() + name = re.sub(r'command$', '', name) + + if isinstance(command, Manager): + command.parent = self + + if isinstance(command, type): + command = command() + + namespace = kwargs.get('namespace') + if not namespace: + namespace = getattr(command, 'namespace', None) + + if namespace: + if namespace not in self._commands: + self.add_command(namespace, Manager()) + + self._commands[namespace]._commands[name] = command + + else: + self._commands[name] = command + + def command(self, func): + """ + Decorator to add a command function to the registry. + + :param func: command function.Arguments depend on the + options. + + """ + + args, varargs, keywords, defaults = inspect.getargspec(func) + + options = [] + + # first arg is always "app" : ignore + + defaults = defaults or [] + kwargs = dict(izip(*[reversed(l) for l in (args, defaults)])) + + for arg in args: + + if arg in kwargs: + + default = kwargs[arg] + + if isinstance(default, bool): + options.append(Option('-%s' % arg[0], + '--%s' % arg, + action="store_true", + dest=arg, + required=False, + default=default)) + else: + options.append(Option('-%s' % arg[0], + '--%s' % arg, + dest=arg, + type=text_type, + required=False, + default=default)) + + else: + options.append(Option(arg, type=text_type)) + + command = Command() + command.run = func + command.__doc__ = func.__doc__ + command.option_list = options + + self.add_command(func.__name__, command) + + return func + + def option(self, *args, **kwargs): + """ + Decorator to add an option to a function. Automatically registers the + function - do not use together with ``@command``. You can add as many + ``@option`` calls as you like, for example:: + + @option('-n', '--name', dest='name') + @option('-u', '--url', dest='url') + def hello(name, url): + print "hello", name, url + + Takes the same arguments as the ``Option`` constructor. + """ + + option = Option(*args, **kwargs) + + def decorate(func): + name = func.__name__ + + if name not in self._commands: + + command = Command() + command.run = func + command.__doc__ = func.__doc__ + command.option_list = [] + + self.add_command(name, command) + + self._commands[name].option_list.append(option) + return func + return decorate + + def shell(self, func): + """ + Decorator that wraps function in shell command. This is equivalent to:: + + def _make_context(app): + return dict(app=app) + + manager.add_command("shell", Shell(make_context=_make_context)) + + The decorated function should take a single "app" argument, and return + a dict. + + For more sophisticated usage use the Shell class. + """ + + self.add_command('shell', Shell(make_context=func)) + + return func + + def handle(self, prog, args=None): + + app_parser = self.create_parser(prog) + + args = list(args or []) + app_namespace, remaining_args = app_parser.parse_known_args(args) + + # get the handle function and remove it from parsed options + kwargs = app_namespace.__dict__ + handle = kwargs.pop('func_handle', None) + if not handle: + app_parser.error('too few arguments') + + # get only safe config options + app_config_keys = [action.dest for action in app_parser._actions + if action.__class__ in safe_actions] + + # pass only safe app config keys + app_config = dict((k, v) for k, v in iteritems(kwargs) + if k in app_config_keys) + + # remove application config keys from handle kwargs + kwargs = dict((k, v) for k, v in iteritems(kwargs) + if k not in app_config_keys) + + # get command from bound handle function (py2.7+) + command = handle.__self__ + if getattr(command, 'capture_all_args', False): + positional_args = [remaining_args] + else: + if len(remaining_args): + # raise correct exception + # FIXME maybe change capture_all_args flag + app_parser.parse_args(args) + # sys.exit(2) + pass + positional_args = [] + + app = self.create_app(**app_config) + # for convience usage in a command + self.app = app + + return handle(app, *positional_args, **kwargs) + + def run(self, commands=None, default_command=None): + """ + Prepares manager to receive command line input. Usually run + inside "if __name__ == "__main__" block in a Python script. + + :param commands: optional dict of commands. Appended to any commands + added using add_command(). + + :param default_command: name of default command to run if no + arguments passed. + """ + + if commands: + self._commands.update(commands) + + if default_command is not None and len(sys.argv) == 1: + sys.argv.append(default_command) + + try: + result = self.handle(sys.argv[0], sys.argv[1:]) + except SystemExit as e: + result = e.code + + sys.exit(result or 0) diff --git a/env/lib/python2.7/site-packages/flask_script/__init__.pyc b/env/lib/python2.7/site-packages/flask_script/__init__.pyc new file mode 100644 index 0000000..7fa08a9 Binary files /dev/null and b/env/lib/python2.7/site-packages/flask_script/__init__.pyc differ diff --git a/env/lib/python2.7/site-packages/flask_script/_compat.py b/env/lib/python2.7/site-packages/flask_script/_compat.py new file mode 100644 index 0000000..4d2c19e --- /dev/null +++ b/env/lib/python2.7/site-packages/flask_script/_compat.py @@ -0,0 +1,117 @@ +# -*- coding: utf-8 -*- +""" + flask_script._compat + ~~~~~~~~~~~~~~~~~~~~ + + Some py2/py3 compatibility support based on a stripped down + version of six so we don't have to depend on a specific version + of it. + + :copyright: (c) 2013 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" +import sys + +PY2 = sys.version_info[0] == 2 +PYPY = hasattr(sys, 'pypy_translation_info') +_identity = lambda x: x + + +if not PY2: + unichr = chr + range_type = range + text_type = str + string_types = (str, ) + integer_types = (int, ) + + iterkeys = lambda d: iter(d.keys()) + itervalues = lambda d: iter(d.values()) + iteritems = lambda d: iter(d.items()) + + import pickle + from io import BytesIO, StringIO + NativeStringIO = StringIO + + def reraise(tp, value, tb=None): + if value.__traceback__ is not tb: + raise value.with_traceback(tb) + raise value + + ifilter = filter + imap = map + izip = zip + intern = sys.intern + + implements_iterator = _identity + implements_to_string = _identity + encode_filename = _identity + get_next = lambda x: x.__next__ + + input = input + from string import ascii_lowercase + +else: + unichr = unichr + text_type = unicode + range_type = xrange + string_types = (str, unicode) + integer_types = (int, long) + + iterkeys = lambda d: d.iterkeys() + itervalues = lambda d: d.itervalues() + iteritems = lambda d: d.iteritems() + + import cPickle as pickle + from cStringIO import StringIO as BytesIO, StringIO + NativeStringIO = BytesIO + + exec('def reraise(tp, value, tb=None):\n raise tp, value, tb') + + from itertools import imap, izip, ifilter + intern = intern + + def implements_iterator(cls): + cls.next = cls.__next__ + del cls.__next__ + return cls + + def implements_to_string(cls): + cls.__unicode__ = cls.__str__ + cls.__str__ = lambda x: x.__unicode__().encode('utf-8') + return cls + + get_next = lambda x: x.next + + def encode_filename(filename): + if isinstance(filename, unicode): + return filename.encode('utf-8') + return filename + + input = raw_input + from string import lower as ascii_lowercase + + +def with_metaclass(meta, *bases): + # This requires a bit of explanation: the basic idea is to make a + # dummy metaclass for one level of class instantiation that replaces + # itself with the actual metaclass. Because of internal type checks + # we also need to make sure that we downgrade the custom metaclass + # for one level to something closer to type (that's why __call__ and + # __init__ comes back from type etc.). + # + # This has the advantage over six.with_metaclass in that it does not + # introduce dummy classes into the final MRO. + class metaclass(meta): + __call__ = type.__call__ + __init__ = type.__init__ + def __new__(cls, name, this_bases, d): + if this_bases is None: + return type.__new__(cls, name, (), d) + return meta(name, bases, d) + return metaclass('temporary_class', None, {}) + + +try: + from urllib.parse import quote_from_bytes as url_quote +except ImportError: + from urllib import quote as url_quote diff --git a/env/lib/python2.7/site-packages/flask_script/_compat.pyc b/env/lib/python2.7/site-packages/flask_script/_compat.pyc new file mode 100644 index 0000000..71a2c40 Binary files /dev/null and b/env/lib/python2.7/site-packages/flask_script/_compat.pyc differ diff --git a/env/lib/python2.7/site-packages/flask_script/cli.py b/env/lib/python2.7/site-packages/flask_script/cli.py new file mode 100644 index 0000000..150dc92 --- /dev/null +++ b/env/lib/python2.7/site-packages/flask_script/cli.py @@ -0,0 +1,97 @@ +# -*- coding: utf-8 -*- + +import getpass +from ._compat import string_types, ascii_lowercase, input + +def prompt(name, default=None): + """ + Grab user input from command line. + + :param name: prompt text + :param default: default value if no input provided. + """ + + prompt = name + (default and ' [%s]' % default or '') + prompt += name.endswith('?') and ' ' or ': ' + while True: + rv = input(prompt) + if rv: + return rv + if default is not None: + return default + + +def prompt_pass(name, default=None): + """ + Grabs hidden (password) input from command line. + + :param name: prompt text + :param default: default value if no input provided. + """ + + prompt = name + (default and ' [%s]' % default or '') + prompt += name.endswith('?') and ' ' or ': ' + while True: + rv = getpass.getpass(prompt) + if rv: + return rv + if default is not None: + return default + + +def prompt_bool(name, default=False, yes_choices=None, no_choices=None): + """ + Grabs user input from command line and converts to boolean + value. + + :param name: prompt text + :param default: default value if no input provided. + :param yes_choices: default 'y', 'yes', '1', 'on', 'true', 't' + :param no_choices: default 'n', 'no', '0', 'off', 'false', 'f' + """ + + yes_choices = yes_choices or ('y', 'yes', '1', 'on', 'true', 't') + no_choices = no_choices or ('n', 'no', '0', 'off', 'false', 'f') + + while True: + rv = prompt(name, default and yes_choices[0] or no_choices[0]) + if not rv: + return default + if rv.lower() in yes_choices: + return True + elif rv.lower() in no_choices: + return False + + +def prompt_choices(name, choices, default=None, resolve=ascii_lowercase, + no_choice=('none',)): + """ + Grabs user input from command line from set of provided choices. + + :param name: prompt text + :param choices: list or tuple of available choices. Choices may be + single strings or (key, value) tuples. + :param default: default value if no input provided. + :param no_choice: acceptable list of strings for "null choice" + """ + + _choices = [] + options = [] + + for choice in choices: + if isinstance(choice, string_types): + options.append(choice) + else: + options.append("%s [%s]" % (choice[1], choice[0])) + choice = choice[0] + _choices.append(choice) + + while True: + rv = prompt(name + ' - (%s)' % ', '.join(options), default) + if not rv: + return default + rv = resolve(rv) + if rv in no_choice: + return None + if rv in _choices: + return rv diff --git a/env/lib/python2.7/site-packages/flask_script/cli.pyc b/env/lib/python2.7/site-packages/flask_script/cli.pyc new file mode 100644 index 0000000..fe1e4dd Binary files /dev/null and b/env/lib/python2.7/site-packages/flask_script/cli.pyc differ diff --git a/env/lib/python2.7/site-packages/flask_script/commands.py b/env/lib/python2.7/site-packages/flask_script/commands.py new file mode 100644 index 0000000..8cdc538 --- /dev/null +++ b/env/lib/python2.7/site-packages/flask_script/commands.py @@ -0,0 +1,455 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import + +import os +import code +import warnings +import string + +import argparse + +from flask import _request_ctx_stack + +from .cli import prompt, prompt_pass, prompt_bool, prompt_choices + + +class InvalidCommand(Exception): + pass + + +class Group(object): + """ + Stores argument groups and mutually exclusive groups for + `ArgumentParser.add_argument_group ` + or `ArgumentParser.add_mutually_exclusive_group `. + + Note: The title and description params cannot be used with the exclusive + or required params. + + :param options: A list of Option classes to add to this group + :param title: A string to use as the title of the argument group + :param description: A string to use as the description of the argument + group + :param exclusive: A boolean indicating if this is an argument group or a + mutually exclusive group + :param required: A boolean indicating if this mutually exclusive group + must have an option selected + """ + + def __init__(self, *options, **kwargs): + self.option_list = options + + self.title = kwargs.pop("title", None) + self.description = kwargs.pop("description", None) + self.exclusive = kwargs.pop("exclusive", None) + self.required = kwargs.pop("required", None) + + if ((self.title or self.description) and + (self.required or self.exclusive)): + raise TypeError("title and/or description cannot be used with " + "required and/or exclusive.") + + super(Group, self).__init__(**kwargs) + + def get_options(self): + """ + By default, returns self.option_list. Override if you + need to do instance-specific configuration. + """ + return self.option_list + + +class Option(object): + """ + Stores positional and optional arguments for `ArgumentParser.add_argument + `_. + + :param name_or_flags: Either a name or a list of option strings, + e.g. foo or -f, --foo + :param action: The basic type of action to be taken when this argument + is encountered at the command-line. + :param nargs: The number of command-line arguments that should be consumed. + :param const: A constant value required by some action and nargs selections. + :param default: The value produced if the argument is absent from + the command-line. + :param type: The type to which the command-line arg should be converted. + :param choices: A container of the allowable values for the argument. + :param required: Whether or not the command-line option may be omitted + (optionals only). + :param help: A brief description of what the argument does. + :param metavar: A name for the argument in usage messages. + :param dest: The name of the attribute to be added to the object + returned by parse_args(). + """ + + def __init__(self, *args, **kwargs): + self.args = args + self.kwargs = kwargs + + +class Command(object): + """ + Base class for creating commands. + """ + + option_list = [] + + @property + def description(self): + description = self.__doc__ or '' + return description.strip() + + def add_option(self, option): + """ + Adds Option to option list. + """ + self.option_list.append(option) + + def get_options(self): + """ + By default, returns self.option_list. Override if you + need to do instance-specific configuration. + """ + return self.option_list + + def create_parser(self, *args, **kwargs): + + parser = argparse.ArgumentParser(*args, **kwargs) + + for option in self.get_options(): + if isinstance(option, Group): + if option.exclusive: + group = parser.add_mutually_exclusive_group( + required=option.required, + ) + else: + group = parser.add_argument_group( + title=option.title, + description=option.description, + ) + for opt in option.get_options(): + group.add_argument(*opt.args, **opt.kwargs) + else: + parser.add_argument(*option.args, **option.kwargs) + + parser.set_defaults(func_handle=self.handle) + + return parser + + def handle(self, app, *args, **kwargs): + """ + Handles the command with given app. Default behaviour is to call within + a test request context. + """ + with app.test_request_context(): + return self.run(*args, **kwargs) + + def run(self): + """ + Runs a command. This must be implemented by the subclass. Should take + arguments as configured by the Command options. + """ + raise NotImplementedError + + def prompt(self, name, default=None): + warnings.warn_explicit( + "Command.prompt is deprecated, use prompt() function instead") + + prompt(name, default) + + def prompt_pass(self, name, default=None): + warnings.warn_explicit( + "Command.prompt_pass is deprecated, use prompt_pass() function " + "instead") + + prompt_pass(name, default) + + def prompt_bool(self, name, default=False): + warnings.warn_explicit( + "Command.prompt_bool is deprecated, use prompt_bool() function " + "instead") + + prompt_bool(name, default) + + def prompt_choices(self, name, choices, default=None): + warnings.warn_explicit( + "Command.choices is deprecated, use prompt_choices() function " + "instead") + + prompt_choices(name, choices, default) + + +class Shell(Command): + """ + Runs a Python shell inside Flask application context. + + :param banner: banner appearing at top of shell when started + :param make_context: a callable returning a dict of variables + used in the shell namespace. By default + returns a dict consisting of just the app. + :param use_bpython: use BPython shell if available, ignore if not. + The BPython shell can be turned off in command + line by passing the **--no-bpython** flag. + :param use_ipython: use IPython shell if available, ignore if not. + The IPython shell can be turned off in command + line by passing the **--no-ipython** flag. + """ + + banner = '' + + help = description = 'Runs a Python shell inside Flask application context.' + + def __init__(self, banner=None, make_context=None, use_ipython=True, + use_bpython=True): + + self.banner = banner or self.banner + self.use_ipython = use_ipython + self.use_bpython = use_bpython + + if make_context is None: + make_context = lambda: dict(app=_request_ctx_stack.top.app) + + self.make_context = make_context + + def get_options(self): + return ( + Option('--no-ipython', + action="store_true", + dest='no_ipython', + default=not(self.use_ipython)), + Option('--no-bpython', + action="store_true", + dest='no_bpython', + default=not(self.use_bpython)) + ) + + def get_context(self): + """ + Returns a dict of context variables added to the shell namespace. + """ + return self.make_context() + + def run(self, no_ipython, no_bpython): + """ + Runs the shell. If no_bpython is False or use_bpython is True, then + a BPython shell is run (if installed). Else, if no_ipython is False or + use_python is True then a IPython shell is run (if installed). + """ + + context = self.get_context() + + if not no_bpython: + # Try BPython + try: + from bpython import embed + embed(banner=self.banner, locals_=context) + return + except ImportError: + pass + + if not no_ipython: + # Try IPython + try: + try: + # 0.10.x + from IPython.Shell import IPShellEmbed + ipshell = IPShellEmbed(banner=self.banner) + ipshell(global_ns=dict(), local_ns=context) + except ImportError: + # 0.12+ + from IPython import embed + embed(banner1=self.banner, user_ns=context) + return + except ImportError: + pass + + # Use basic python shell + code.interact(self.banner, local=context) + + +class Server(Command): + """ + Runs the Flask development server i.e. app.run() + + :param host: server host + :param port: server port + :param use_debugger: if False, will no longer use Werkzeug debugger. + This can be overriden in the command line + by passing the **-d** flag. + :param use_reloader: if False, will no longer use auto-reloader. + This can be overriden in the command line by + passing the **-r** flag. + :param threaded: should the process handle each request in a separate + thread? + :param processes: number of processes to spawn + :param passthrough_errors: disable the error catching. This means that the server will die on errors but it can be useful to hook debuggers in (pdb etc.) + :param options: :func:`werkzeug.run_simple` options. + """ + + help = description = 'Runs the Flask development server i.e. app.run()' + + def __init__(self, host='127.0.0.1', port=5000, use_debugger=True, + use_reloader=True, threaded=False, processes=1, + passthrough_errors=False, **options): + + self.port = port + self.host = host + self.use_debugger = use_debugger + self.use_reloader = use_reloader + self.server_options = options + self.threaded = threaded + self.processes = processes + self.passthrough_errors = passthrough_errors + + def get_options(self): + + options = ( + Option('-t', '--host', + dest='host', + default=self.host), + + Option('-p', '--port', + dest='port', + type=int, + default=self.port), + + Option('--threaded', + dest='threaded', + action='store_true', + default=self.threaded), + + Option('--processes', + dest='processes', + type=int, + default=self.processes), + + Option('--passthrough-errors', + action='store_true', + dest='passthrough_errors', + default=self.passthrough_errors), + ) + + if self.use_debugger: + options += (Option('-d', '--no-debug', + action='store_false', + dest='use_debugger', + default=self.use_debugger),) + + else: + options += (Option('-d', '--debug', + action='store_true', + dest='use_debugger', + default=self.use_debugger),) + + if self.use_reloader: + options += (Option('-r', '--no-reload', + action='store_false', + dest='use_reloader', + default=self.use_reloader),) + + else: + options += (Option('-r', '--reload', + action='store_true', + dest='use_reloader', + default=self.use_reloader),) + + return options + + def handle(self, app, host, port, use_debugger, use_reloader, + threaded, processes, passthrough_errors): + # we don't need to run the server in request context + # so just run it directly + + app.run(host=host, + port=port, + debug=use_debugger, + use_debugger=use_debugger, + use_reloader=use_reloader, + threaded=threaded, + processes=processes, + passthrough_errors=passthrough_errors, + **self.server_options) + + +class Clean(Command): + "Remove *.pyc and *.pyo files recursively starting at current directory" + def run(self): + for dirpath, dirnames, filenames in os.walk('.'): + for filename in filenames: + if filename.endswith('.pyc') or filename.endswith('.pyo'): + full_pathname = os.path.join(dirpath, filename) + print('Removing %s' % full_pathname) + os.remove(full_pathname) + + +class ShowUrls(Command): + """ + Displays all of the url matching routes for the project + """ + def __init__(self, order='rule'): + self.order = order + + def get_options(self): + return ( + Option('url', + nargs='?', + help='Url to test (ex. /static/image.png)'), + Option('--order', + dest='order', + default=self.order, + help='Property on Rule to order by (default: %s)' % self.order) + ) + + return options + + def run(self, url, order): + from flask import current_app + from werkzeug.exceptions import NotFound, MethodNotAllowed + + rows = [] + column_length = 0 + column_headers = ('Rule', 'Endpoint', 'Arguments') + + if url: + try: + rule, arguments = current_app.url_map \ + .bind('localhost') \ + .match(url, return_rule=True) + rows.append((rule.rule, rule.endpoint, arguments)) + column_length = 3 + except (NotFound, MethodNotAllowed) as e: + rows.append(("<%s>" % e, None, None)) + column_length = 1 + else: + rules = sorted(current_app.url_map.iter_rules(), key=lambda rule: getattr(rule, order)) + for rule in rules: + rows.append((rule.rule, rule.endpoint, None)) + column_length = 2 + + str_template = '' + table_width = 0 + + if column_length >= 1: + max_rule_length = max(len(r[0]) for r in rows) + max_rule_length = max_rule_length if max_rule_length > 4 else 4 + str_template += '%-' + str(max_rule_length) + 's' + table_width += max_rule_length + + if column_length >= 2: + max_endpoint_length = max(len(str(r[1])) for r in rows) + # max_endpoint_length = max(rows, key=len) + max_endpoint_length = max_endpoint_length if max_endpoint_length > 8 else 8 + str_template += ' %-' + str(max_endpoint_length) + 's' + table_width += 2 + max_endpoint_length + + if column_length >= 3: + max_arguments_length = max(len(str(r[2])) for r in rows) + max_arguments_length = max_arguments_length if max_arguments_length > 9 else 9 + str_template += ' %-' + str(max_arguments_length) + 's' + table_width += 2 + max_arguments_length + + print(str_template % (column_headers[:column_length])) + print('-' * table_width) + + for row in rows: + print(str_template % row[:column_length]) diff --git a/env/lib/python2.7/site-packages/flask_script/commands.pyc b/env/lib/python2.7/site-packages/flask_script/commands.pyc new file mode 100644 index 0000000..39c8d8e Binary files /dev/null and b/env/lib/python2.7/site-packages/flask_script/commands.pyc differ diff --git a/env/lib/python2.7/site-packages/flaskext/gae_mini_profiler/__init__.py b/env/lib/python2.7/site-packages/flaskext/gae_mini_profiler/__init__.py new file mode 100644 index 0000000..8b8f7df --- /dev/null +++ b/env/lib/python2.7/site-packages/flaskext/gae_mini_profiler/__init__.py @@ -0,0 +1,184 @@ +# -*- coding: utf-8 -*- +""" + flaskext.gae_mini_profiler + ~~~~~~~~~~~~~~~~~~~~~~~~~~ + + A Google App Engine profiler for `Flask ` based + on `gae_mini_profiler ` by + `Ben Kamens `. + + :copyright: (c) 2011 by Pascal Hartig. + :license: MIT, see LICENSE for more details. +""" + +import os +from flask.helpers import send_from_directory +from flask import request, jsonify +from flaskext.gae_mini_profiler import profiler +from jinja2 import Environment, FileSystemLoader + + +def replace_insensitive(string, target, replacement): + """Similar to string.replace() but is case insensitive + Code borrowed from: http://forums.devshed.com/python-programming-11/ + case-insensitive-string-replace-490921.html + """ + + no_case = string.lower() + index = no_case.rfind(target.lower()) + if index >= 0: + return string[:index] + replacement + string[index + len(target):] + else: + return string + + +class GAEMiniProfilerWSGIMiddleware(profiler.ProfilerWSGIMiddleware): + """Slightly adjusted WSGI middleware, using the flask app config rather + than a stand-alone config file. + """ + + def __init__(self, flask_app, *args, **kwargs): + self.flask_app = flask_app + profiler.ProfilerWSGIMiddleware.__init__(self, *args, **kwargs) + + def should_profile(self, environ): + """Check whether the currently processed page should be profiled or + not. + """ + + from google.appengine.api import users + + # Short-circuit! + if environ["PATH_INFO"].startswith("/_gae_mini_profiler/"): + return False + + if self.flask_app.config['GAEMINIPROFILER_PROFILER_ADMINS'] and \ + users.is_current_user_admin(): + return True + + user = users.get_current_user() + + return user and user.email() in \ + self.flask_app.config['GAEMINIPROFILER_PROFILER_EMAILS'] + + +class GAEMiniProfiler(object): + + PROFILER_URL_PREFIX = "/_gae_mini_profiler/" + + def __init__(self, app, *args, **kwargs): + self.app = app + + # Apply the middleware + app.wsgi_app = GAEMiniProfilerWSGIMiddleware(app, app.wsgi_app) + + # Set up config defaults + self.app.config.setdefault('GAEMINIPROFILER_PROFILER_EMAILS', [ + 'test@example.com' + ]) + self.app.config.setdefault('GAEMINIPROFILER_PROFILER_ADMINS', True) + + # Build the static path based on our current directory + base_dir = os.path.realpath(os.path.dirname(__file__)) + self._static_dir = os.path.join(base_dir, 'static') + + # Configure jinja for internal templates + self.jinja_env = Environment( + autoescape=True, + extensions=['jinja2.ext.i18n'], + loader=FileSystemLoader( + os.path.join(base_dir, 'templates') + ) + ) + + # Install the response hook + app.after_request(self._process_response) + + app.add_url_rule(self.PROFILER_URL_PREFIX + "static/", + '_gae_mini_profiler.static', self._send_static_file) + app.add_url_rule(self.PROFILER_URL_PREFIX + "request", + '_gae_mini_profiler.request', self._request_view) + app.add_url_rule(self.PROFILER_URL_PREFIX + "shared", + '_gae_mini_profiler.share', self._share_view) + + def _send_static_file(self, filename): + """Send an internal static file.""" + + return send_from_directory(self._static_dir, filename) + + def _process_response(self, response): + """Process response and append the profiler code if appropriate.""" + + if response.status_code != 200 or not response.is_sequence: + return response + + response_html = response.data.decode(response.charset) + profiler_html = self._render_profiler() + + # Inject the profiler HTML snippet right before the + response.response = [ + replace_insensitive( + response_html, + '', + profiler_html + '' + ) + ] + + return response + + def _render_profiler(self): + context = self._get_render_context() + context['request_id'] = profiler.request_id + return self._render("includes.html", context) + + def _get_render_context(self): + return { + 'js_path': "/_gae_mini_profiler/static/js/profiler.js", + 'css_path': "/_gae_mini_profiler/static/css/profiler.css" + } + + def _render(self, template_name, context): + """Render a jinja2 template within the application's environment.""" + + template = self.jinja_env.get_template(template_name) + return template.render(**context) + + def _request_view(self): + """Renders the request stats.""" + + request_ids = request.args['request_ids'] + + stats_list = [] + for request_id in request_ids.split(','): + request_stats = profiler.RequestStats.get(request_id) + + if request_stats and not request_stats.disabled: + dict_request_stats = {} + for property in profiler.RequestStats.serialized_properties: + dict_request_stats[property] = \ + request_stats.__getattribute__(property) + + stats_list.append(dict_request_stats) + + # Don't show temporary redirect profiles more than once + # automatically, as they are tied to URL params and may be + # copied around easily. + if request_stats.temporary_redirect: + request_stats.disabled = True + request_stats.store() + + # For security reasons, we return an object instead of a list as it is + # dont in the upstream module. + return jsonify(stats=stats_list) + + def _share_view(self): + """Renders the shared stats view.""" + + request_id = request.args['request_id'] + + if not profiler.RequestStats.get(request_id): + return u"Profiler stats no longer available." + + context = self._get_render_context() + context['request_id'] = request_id + return self._render("shared.html", context) diff --git a/env/lib/python2.7/site-packages/flaskext/gae_mini_profiler/__init__.pyc b/env/lib/python2.7/site-packages/flaskext/gae_mini_profiler/__init__.pyc new file mode 100644 index 0000000..b607a09 Binary files /dev/null and b/env/lib/python2.7/site-packages/flaskext/gae_mini_profiler/__init__.pyc differ diff --git a/env/lib/python2.7/site-packages/flaskext/gae_mini_profiler/profiler.py b/env/lib/python2.7/site-packages/flaskext/gae_mini_profiler/profiler.py new file mode 100644 index 0000000..20cb324 --- /dev/null +++ b/env/lib/python2.7/site-packages/flaskext/gae_mini_profiler/profiler.py @@ -0,0 +1,372 @@ +""" + gae_mini_profiler + ~~~~~~~~~~~~~~~~~ + + :copyright: (c) 2011, Ben Kamens + :url: https://github.com/kamens/gae_mini_profiler + :license: MIT, see LICENSE for more details +""" + +import datetime +import json +import os +import pickle +import re +import StringIO +from types import GeneratorType +import zlib + +from google.appengine.ext.webapp import RequestHandler +from google.appengine.api import memcache + +# request_id is a per-request identifier accessed by a couple other pieces of gae_mini_profiler +request_id = None + +class RequestStatsHandler(RequestHandler): + + def get(self): + + self.response.headers["Content-Type"] = "application/json" + + list_request_ids = [] + + request_ids = self.request.get("request_ids") + if request_ids: + list_request_ids = request_ids.split(",") + + list_request_stats = [] + + for request_id in list_request_ids: + + request_stats = RequestStats.get(request_id) + + if request_stats and not request_stats.disabled: + + dict_request_stats = {} + for property in RequestStats.serialized_properties: + dict_request_stats[property] = request_stats.__getattribute__(property) + + list_request_stats.append(dict_request_stats) + + # Don't show temporary redirect profiles more than once automatically, as they are + # tied to URL params and may be copied around easily. + if request_stats.temporary_redirect: + request_stats.disabled = True + request_stats.store() + + self.response.out.write(json.dumps(list_request_stats)) + +class RequestStats(object): + + serialized_properties = ["request_id", "url", "url_short", "s_dt", "profiler_results", "appstats_results", "temporary_redirect"] + + def __init__(self, request_id, environ, middleware): + self.request_id = request_id + + self.url = environ.get("PATH_INFO") + if environ.get("QUERY_STRING"): + self.url += "?%s" % environ.get("QUERY_STRING") + + self.url_short = self.url + if len(self.url_short) > 26: + self.url_short = self.url_short[:26] + "..." + + self.s_dt = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + + self.profiler_results = RequestStats.calc_profiler_results(middleware) + self.appstats_results = RequestStats.calc_appstats_results(middleware) + + self.temporary_redirect = middleware.temporary_redirect + self.disabled = False + + def store(self): + # Store compressed results so we stay under the memcache 1MB limit + pickled = pickle.dumps(self) + compressed_pickled = zlib.compress(pickled) + + return memcache.set(RequestStats.memcache_key(self.request_id), compressed_pickled) + + @staticmethod + def get(request_id): + if request_id: + + compressed_pickled = memcache.get(RequestStats.memcache_key(request_id)) + + if compressed_pickled: + pickled = zlib.decompress(compressed_pickled) + return pickle.loads(pickled) + + return None + + @staticmethod + def memcache_key(request_id): + if not request_id: + return None + return "__gae_mini_profiler_request_%s" % request_id + + @staticmethod + def seconds_fmt(f): + return RequestStats.milliseconds_fmt(f * 1000) + + @staticmethod + def milliseconds_fmt(f): + return ("%.5f" % f).rstrip("0").rstrip(".") + + @staticmethod + def short_method_fmt(s): + return s[s.rfind("/") + 1:] + + @staticmethod + def short_rpc_file_fmt(s): + if not s: + return "" + if "/" in s: + return s[s.find("/"):] + return s + + @staticmethod + def calc_profiler_results(middleware): + import pstats + + # Make sure nothing is printed to stdout + output = StringIO.StringIO() + stats = pstats.Stats(middleware.prof, stream=output) + stats.sort_stats("cumulative") + + results = { + "total_call_count": stats.total_calls, + "total_time": RequestStats.seconds_fmt(stats.total_tt), + "calls": [] + } + + width, list_func_names = stats.get_print_list([80]) + for func_name in list_func_names: + primitive_call_count, total_call_count, total_time, cumulative_time, callers = stats.stats[func_name] + + func_desc = pstats.func_std_string(func_name) + + callers_names = map(lambda func_name: pstats.func_std_string(func_name), callers.keys()) + callers_desc = map( + lambda name: {"func_desc": name, "func_desc_short": RequestStats.short_method_fmt(name)}, + callers_names) + + results["calls"].append({ + "primitive_call_count": primitive_call_count, + "total_call_count": total_call_count, + "total_time": RequestStats.seconds_fmt(total_time), + "per_call": RequestStats.seconds_fmt(total_time / total_call_count) if total_call_count else "", + "cumulative_time": RequestStats.seconds_fmt(cumulative_time), + "per_call_cumulative": RequestStats.seconds_fmt(cumulative_time / primitive_call_count) if primitive_call_count else "", + "func_desc": func_desc, + "func_desc_short": RequestStats.short_method_fmt(func_desc), + "callers_desc": callers_desc, + }) + + output.close() + + return results + + @staticmethod + def calc_appstats_results(middleware): + if middleware.recorder: + + total_call_count = 0 + total_time = 0 + calls = [] + service_totals_dict = {} + likely_dupes = False + end_offset_last = 0 + + dict_requests = {} + + appstats_key = long(middleware.recorder.start_timestamp * 1000) + + for trace in middleware.recorder.traces: + total_call_count += 1 + + total_time += trace.duration_milliseconds() + + # Don't accumulate total RPC time for traces that overlap asynchronously + if trace.start_offset_milliseconds() < end_offset_last: + total_time -= (end_offset_last - trace.start_offset_milliseconds()) + end_offset_last = trace.start_offset_milliseconds() + trace.duration_milliseconds() + + service_prefix = trace.service_call_name() + + if "." in service_prefix: + service_prefix = service_prefix[:service_prefix.find(".")] + + if not service_totals_dict.has_key(service_prefix): + service_totals_dict[service_prefix] = {"total_call_count": 0, "total_time": 0} + + service_totals_dict[service_prefix]["total_call_count"] += 1 + service_totals_dict[service_prefix]["total_time"] += trace.duration_milliseconds() + + stack_frames_desc = [] + for frame in trace.call_stack_: + stack_frames_desc.append("%s:%s %s" % + (RequestStats.short_rpc_file_fmt(frame.class_or_file_name()), + frame.line_number(), + frame.function_name())) + + request = trace.request_data_summary() + request_short = request + if len(request_short) > 100: + request_short = request_short[:100] + "..." + + likely_dupe = dict_requests.has_key(request) + likely_dupes = likely_dupes or likely_dupe + + dict_requests[request] = True + + response = trace.response_data_summary()[:100] + + calls.append({ + "service": trace.service_call_name(), + "start_offset": RequestStats.milliseconds_fmt(trace.start_offset_milliseconds()), + "total_time": RequestStats.milliseconds_fmt(trace.duration_milliseconds()), + "request": request, + "request_short": request_short, + "response": response, + "stack_frames_desc": stack_frames_desc, + "likely_dupe": likely_dupe, + }) + + service_totals = [] + for service_prefix in service_totals_dict: + service_totals.append({ + "service_prefix": service_prefix, + "total_call_count": service_totals_dict[service_prefix]["total_call_count"], + "total_time": RequestStats.milliseconds_fmt(service_totals_dict[service_prefix]["total_time"]), + }) + service_totals = sorted(service_totals, reverse=True, key=lambda service_total: float(service_total["total_time"])) + + return { + "total_call_count": total_call_count, + "total_time": RequestStats.milliseconds_fmt(total_time), + "calls": calls, + "service_totals": service_totals, + "likely_dupes": likely_dupes, + "appstats_key": appstats_key, + } + + return None + +class ProfilerWSGIMiddleware(object): + + def __init__(self, app): + self.app = app + self.app_clean = app + self.prof = None + self.recorder = None + self.temporary_redirect = False + + def __call__(self, environ, start_response): + + global request_id + request_id = None + + # Start w/ a non-profiled app at the beginning of each request + self.app = self.app_clean + self.prof = None + self.recorder = None + self.temporary_redirect = False + + if self.should_profile(environ): + + # Set a random ID for this request so we can look up stats later + import base64 + request_id = base64.urlsafe_b64encode(os.urandom(5)) + + # Send request id in headers so jQuery ajax calls can pick + # up profiles. + def profiled_start_response(status, headers, exc_info = None): + + if status.startswith("302 "): + # Temporary redirect. Add request identifier to redirect location + # so next rendered page can show this request's profile. + headers = ProfilerWSGIMiddleware.headers_with_modified_redirect(environ, headers) + self.temporary_redirect = True + + # Append headers used when displaying profiler results from ajax requests + headers.append(("X-MiniProfiler-Id", request_id)) + headers.append(("X-MiniProfiler-QS", environ.get("QUERY_STRING"))) + + return start_response(status, headers, exc_info) + + # Configure AppStats output, keeping a high level of request + # content so we can detect dupe RPCs more accurately + from google.appengine.ext.appstats import recording + recording.config.MAX_REPR = 750 + + # Turn on AppStats monitoring for this request + old_app = self.app + def wrapped_appstats_app(environ, start_response): + # Use this wrapper to grab the app stats recorder for RequestStats.save() + self.recorder = recording.recorder + return old_app(environ, start_response) + self.app = recording.appstats_wsgi_middleware(wrapped_appstats_app) + + # Turn on cProfile profiling for this request + import cProfile + self.prof = cProfile.Profile() + + # Get profiled wsgi result + result = self.prof.runcall(lambda *args, **kwargs: self.app(environ, profiled_start_response), None, None) + + self.recorder = recording.recorder + + # If we're dealing w/ a generator, profile all of the .next calls as well + if type(result) == GeneratorType: + + while True: + try: + yield self.prof.runcall(result.next) + except StopIteration: + break + + else: + for value in result: + yield value + + # Store stats for later access + RequestStats(request_id, environ, self).store() + + # Just in case we're using up memory in the recorder and profiler + self.recorder = None + self.prof = None + request_id = None + + else: + result = self.app(environ, start_response) + for value in result: + yield value + + @staticmethod + def headers_with_modified_redirect(environ, headers): + headers_modified = [] + + for header in headers: + if header[0] == "Location": + reg = re.compile("mp-r-id=([^&]+)") + + # Keep any chain of redirects around + request_id_chain = request_id + match = reg.search(environ.get("QUERY_STRING")) + if match: + request_id_chain = ",".join([match.groups()[0], request_id]) + + # Remove any pre-existing miniprofiler redirect id + location = header[1] + location = reg.sub("", location) + + # Add current request id as miniprofiler redirect id + location += ("&" if "?" in location else "?") + location = location.replace("&&", "&") + location += "mp-r-id=%s" % request_id_chain + + headers_modified.append((header[0], location)) + else: + headers_modified.append(header) + + return headers_modified diff --git a/env/lib/python2.7/site-packages/flaskext/gae_mini_profiler/profiler.pyc b/env/lib/python2.7/site-packages/flaskext/gae_mini_profiler/profiler.pyc new file mode 100644 index 0000000..9dd3e14 Binary files /dev/null and b/env/lib/python2.7/site-packages/flaskext/gae_mini_profiler/profiler.pyc differ diff --git a/env/lib/python2.7/site-packages/flaskext/gae_mini_profiler/static/css/profiler.css b/env/lib/python2.7/site-packages/flaskext/gae_mini_profiler/static/css/profiler.css new file mode 100644 index 0000000..18e51ca --- /dev/null +++ b/env/lib/python2.7/site-packages/flaskext/gae_mini_profiler/static/css/profiler.css @@ -0,0 +1,223 @@ + +.g-m-p-corner { + padding: 4px; + cursor: pointer; + text-align: right; +} + +.g-m-p-corner .ms, +.g-m-p .ms { + color: #777; +} + +.g-m-p-corner .entry.ajax { + border-top: 1px dotted #DDD; +} + +.g-m-p-corner .entry.expanded { + font-weight: bold; +} + +.g-m-p { + min-width: 410px; + padding: 9px; + padding-bottom: 18px; +} + +.g-m-p, .g-m-p-corner, .g-m-p-shared { + font-family: Helvetica, Arial, sans-serif; + font-size: 12px; + color: #444; + line-height: 18px; +} + +.g-m-p a, .g-m-p-corner a, .g-m-p-shared a { + color: #069; + text-decoration: none; +} + +.g-m-p a.uses_script, .g-m-p-corner a.uses_script, .g-m-p-shared a.uses_script { + border-bottom: dotted 1px #069 +} + +.g-m-p, .g-m-p-corner { + position: absolute; + left: 9px; + top: 0px; + + background: #F7F7F7; + + border-top: 0; + border-left: 1px solid #CCC; + border-bottom: 1px solid #DDD; + border-right: 1px solid #DDD; + + box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.25); + -moz-box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.25); + -webkit-box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.25); + + -webkit-border-bottom-left-radius: 12px; + -moz-border-radius-bottomleft: 12px; + border-bottom-left-radius: 12px; + + -webkit-border-bottom-right-radius: 12px; + -moz-border-radius-bottomright: 12px; + border-bottom-right-radius: 12px; + + z-index: 1000; +} + +.g-m-p .title { + font-size: 1.25em; + text-align: left; + font-weight: bold; + border-bottom: 1px solid #CCC; + padding: 0; +} + +.g-m-p .date_and_share { + text-align: right; + margin-bottom: 18px; +} + +.g-m-p .date_and_share .date { + font-style: italic; + color: #CCC; +} + +.g-m-p .appstats-link { + float:right; +} + +.g-m-p .url { + font-size: 125%; +} + +.g-m-p .total { + float: right; +} + +.g-m-p .dupe { + font-weight: bold; + color: red; +} + +.g-m-p .summary { + float:right; +} + +.g-m-p .details { + max-width: 800px; + max-height: 500px; + margin-bottom: 18px; + overflow: auto; +} + +.g-m-p .expand.expanded { + border-bottom: 1px solid #DDD; +} + +.g-m-p .expand.expanded a { + text-decoration: none; + border-bottom: 0; + font-weight: bold; +} + +.g-m-p .details table { + margin-top: 18px; + border-spacing: 0; + font-size: 12px; +} + +.g-m-p .details table tr:nth-child(odd) { + background-color: #FFF; +} + +.g-m-p .details table tr:nth-child(even) { + background-color: #F7F7F7; +} + +.g-m-p .details table .callers-label { + color: #CCC; +} + +.g-m-p .details th, .g-m-p .details th.header { + font-size: 12px; + background-color: #F7F7F7; + font-weight: bold; + cursor: pointer; + float: none; + position: static; +} + +.g-m-p .details th.headerSortUp, +.g-m-p .details th.headerSortDown { + text-decoration: underline; +} + +.g-m-p .details th, +.g-m-p .details td { + padding-right: 18px; + vertical-align: top; +} + +.g-m-p .details .left { + text-align: left; +} + +.g-m-p .details .right { + text-align: right; +} + +.g-m-p-shared .shared-teaser-container { + text-align: center; +} + +.g-m-p-shared .shared-teaser { + margin-left: auto; + margin-right: auto; + height: 400px; + width: 600px; + text-align: center; + padding: 200px 0; + font-size: 24px; + font-weight: bold; +} + +/* Borrowed from those smart guys @ Fog Creek. Props to Justin & Bobby */ +.g-m-p .fancy-scrollbar::-webkit-scrollbar { + height: 8px; + width: 8px; +} +.g-m-p .fancy-scrollbar::-webkit-scrollbar-button:start:decrement, +.g-m-p .fancy-scrollbar::-webkit-scrollbar-button:end:increment { + background: transparent; + display: none; +} +.g-m-p .fancy-scrollbar::-webkit-scrollbar-track-piece { + background: transparent; +} +.g-m-p .fancy-scrollbar::-webkit-scrollbar-track-piece:vertical:start { + -webkit-border-top-left-radius: 4px; + -webkit-border-top-right-radius: 4px; +} +.g-m-p .fancy-scrollbar::-webkit-scrollbar-track-piece:vertical:end { + -webkit-border-bottom-left-radius: 4px; + -webkit-border-bottom-right-radius: 4px; +} +.g-m-p .fancy-scrollbar::-webkit-scrollbar-track-piece:horizontal:start { + -webkit-border-top-left-radius: 4px; + -webkit-border-bottom-left-radius: 4px; +} +.g-m-p .fancy-scrollbar::-webkit-scrollbar-track-piece:horizontal:end { + -webkit-border-top-right-radius: 4px; + -webkit-border-bottom-right-radius: 4px; +} +.g-m-p .fancy-scrollbar::-webkit-scrollbar-thumb:vertical, +.g-m-p .fancy-scrollbar::-webkit-scrollbar-thumb:horizontal { + background: #ccc; + border: 1px solid #aaa; + -webkit-border-radius: 4px; + display: block; + height: 50px; +} diff --git a/env/lib/python2.7/site-packages/flaskext/gae_mini_profiler/static/js/profiler.js b/env/lib/python2.7/site-packages/flaskext/gae_mini_profiler/static/js/profiler.js new file mode 100644 index 0000000..de539e6 --- /dev/null +++ b/env/lib/python2.7/site-packages/flaskext/gae_mini_profiler/static/js/profiler.js @@ -0,0 +1,184 @@ + +var GaeMiniProfiler = { + + init: function(requestId, fShowImmediately) { + // Fetch profile results for any ajax calls + // (see http://code.google.com/p/mvc-mini-profiler/source/browse/MvcMiniProfiler/UI/Includes.js) + $(document).ajaxComplete(function (e, xhr, settings) { + if (xhr) { + var requestId = xhr.getResponseHeader('X-MiniProfiler-Id'); + if (requestId) { + var queryString = xhr.getResponseHeader('X-MiniProfiler-QS'); + GaeMiniProfiler.fetch(requestId, queryString); + } + } + }); + + GaeMiniProfiler.fetch(requestId, window.location.search, fShowImmediately); + }, + + appendRedirectIds: function(requestId, queryString) { + if (queryString) { + var re = /mp-r-id=([^&]+)/; + var matches = re.exec(queryString); + if (matches && matches.length) { + var sRedirectIds = matches[1]; + var list = sRedirectIds.split(","); + list[list.length] = requestId; + return list; + } + } + + return [requestId]; + }, + + fetch: function(requestId, queryString, fShowImmediately) { + var requestIds = this.appendRedirectIds(requestId, queryString); + + $.get( + "/_gae_mini_profiler/request", + { "request_ids": requestIds.join(",") }, + function(data) { + GaeMiniProfilerTemplate.init(function() { GaeMiniProfiler.finishFetch(data, fShowImmediately); }); + }, + "json" + ); + }, + + finishFetch: function(data, fShowImmediately) { + if (!data || !data.stats.length) return; + + for (var ix = 0; ix < data.stats.length; ix++) { + + var jCorner = this.renderCorner(data.stats[ix]); + + if (!jCorner.data("attached")) { + $('body') + .append(jCorner) + .click(function(e) { return GaeMiniProfiler.collapse(e); }); + jCorner + .data("attached", true); + } + + if (fShowImmediately) + jCorner.find(".entry").first().click(); + + } + }, + + collapse: function(e) { + if ($(".g-m-p").is(":visible")) { + $(".g-m-p").slideUp("fast"); + $(".g-m-p-corner").slideDown("fast") + .find(".expanded").removeClass("expanded"); + return false; + } + + return true; + }, + + expand: function(elEntry, data) { + var jPopup = $(".g-m-p"); + + if (jPopup.length) + jPopup.remove(); + else + $(document).keyup(function(e) { if (e.which == 27) GaeMiniProfiler.collapse() }); + + jPopup = this.renderPopup(data); + $('body').append(jPopup); + + var jCorner = $(".g-m-p-corner"); + jCorner.find(".expanded").removeClass("expanded"); + $(elEntry).addClass("expanded"); + + jPopup + .find(".profile-link") + .click(function() { GaeMiniProfiler.toggleSection(this, ".profiler-details"); return false; }).end() + .find(".rpc-link") + .click(function() { GaeMiniProfiler.toggleSection(this, ".rpc-details"); return false; }).end() + .find(".callers-link") + .click(function() { $(this).parents("td").find(".callers").slideToggle("fast"); return false; }).end() + .click(function(e) { e.stopPropagation(); }) + .css("left", jCorner.offset().left + jCorner.width() + 18) + .slideDown("fast"); + }, + + toggleSection: function(elLink, selector) { + + var fWasVisible = $(".g-m-p " + selector).is(":visible"); + + $(".g-m-p .expand").removeClass("expanded"); + $(".g-m-p .details:visible").slideUp(50); + + if (!fWasVisible) { + $(elLink).parents(".expand").addClass("expanded"); + $(selector).slideDown("fast", function() { + if (!GaeMiniProfiler.toggleSection["called_" + selector]) { + $(selector + " table").tablesorter(); + GaeMiniProfiler.toggleSection["called_" + selector] = true; + } + }); + } + }, + + renderPopup: function(data) { + return $("#profilerTemplate").tmpl(data); + }, + + renderCorner: function(data) { + if (data && data.profiler_results) { + var jCorner = $(".g-m-p-corner"); + + var fFirst = false; + if (!jCorner.length) { + jCorner = $("#profilerCornerTemplate").tmpl(); + fFirst = true; + } + + return jCorner.append( + $("#profilerCornerEntryTemplate") + .tmpl(data) + .addClass(fFirst ? "" : "ajax") + .click(function() { GaeMiniProfiler.expand(this, data); return false; }) + ); + } + return null; + }, +}; + +var GaeMiniProfilerTemplate = { + + template: null, + + init: function(callback) { + $.get("/_gae_mini_profiler/static/js/template.tmpl", function (data) { + if (data) { + $('body').append(data); + callback(); + } + }); + } + +}; + +/* + * jQuery Templates Plugin 1.0.0pre + * http://github.com/jquery/jquery-tmpl + * Requires jQuery 1.4.2 + * + * Copyright Software Freedom Conservancy, Inc. + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + */ +(function(a){var r=a.fn.domManip,d="_tmplitem",q=/^[^<]*(<[\w\W]+>)[^>]*$|\{\{\! /,b={},f={},e,p={key:0,data:{}},i=0,c=0,l=[];function g(g,d,h,e){var c={data:e||(e===0||e===false)?e:d?d.data:{},_wrap:d?d._wrap:null,tmpl:null,parent:d||null,nodes:[],calls:u,nest:w,wrap:x,html:v,update:t};g&&a.extend(c,g,{nodes:[],parent:d});if(h){c.tmpl=h;c._ctnt=c._ctnt||c.tmpl(a,c);c.key=++i;(l.length?f:b)[i]=c}return c}a.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(f,d){a.fn[f]=function(n){var g=[],i=a(n),k,h,m,l,j=this.length===1&&this[0].parentNode;e=b||{};if(j&&j.nodeType===11&&j.childNodes.length===1&&i.length===1){i[d](this[0]);g=this}else{for(h=0,m=i.length;h0?this.clone(true):this).get();a(i[h])[d](k);g=g.concat(k)}c=0;g=this.pushStack(g,f,i.selector)}l=e;e=null;a.tmpl.complete(l);return g}});a.fn.extend({tmpl:function(d,c,b){return a.tmpl(this[0],d,c,b)},tmplItem:function(){return a.tmplItem(this[0])},template:function(b){return a.template(b,this[0])},domManip:function(d,m,k){if(d[0]&&a.isArray(d[0])){var g=a.makeArray(arguments),h=d[0],j=h.length,i=0,f;while(i").join(">").split('"').join(""").split("'").join("'")}});a.extend(a.tmpl,{tag:{tmpl:{_default:{$2:"null"},open:"if($notnull_1){__=__.concat($item.nest($1,$2));}"},wrap:{_default:{$2:"null"},open:"$item.calls(__,$1,$2);__=[];",close:"call=$item.calls();__=call._.concat($item.wrap(call,__));"},each:{_default:{$2:"$index, $value"},open:"if($notnull_1){$.each($1a,function($2){with(this){",close:"}});}"},"if":{open:"if(($notnull_1) && $1a){",close:"}"},"else":{_default:{$1:"true"},open:"}else if(($notnull_1) && $1a){"},html:{open:"if($notnull_1){__.push($1a);}"},"=":{_default:{$1:"$data"},open:"if($notnull_1){__.push($.encode($1a));}"},"!":{open:""}},complete:function(){b={}},afterManip:function(f,b,d){var e=b.nodeType===11?a.makeArray(b.childNodes):b.nodeType===1?[b]:[];d.call(f,b);m(e);c++}});function j(e,g,f){var b,c=f?a.map(f,function(a){return typeof a==="string"?e.key?a.replace(/(<\w+)(?=[\s>])(?![^>]*_tmplitem)([^>]*)/g,"$1 "+d+'="'+e.key+'" $2'):a:j(a,e,a._ctnt)}):e;if(g)return c;c=c.join("");c.replace(/^\s*([^<\s][^<]*)?(<[\w\W]+>)([^>]*[^>\s])?\s*$/,function(f,c,e,d){b=a(e).get();m(b);if(c)b=k(c).concat(b);if(d)b=b.concat(k(d))});return b?b:k(c)}function k(c){var b=document.createElement("div");b.innerHTML=c;return a.makeArray(b.childNodes)}function o(b){return new Function("jQuery","$item","var $=jQuery,call,__=[],$data=$item.data;with($data){__.push('"+a.trim(b).replace(/([\\'])/g,"\\$1").replace(/[\r\t\n]/g," ").replace(/\$\{([^\}]*)\}/g,"{{= $1}}").replace(/\{\{(\/?)(\w+|.)(?:\(((?:[^\}]|\}(?!\}))*?)?\))?(?:\s+(.*?)?)?(\(((?:[^\}]|\}(?!\}))*?)\))?\s*\}\}/g,function(m,l,k,g,b,c,d){var j=a.tmpl.tag[k],i,e,f;if(!j)throw"Unknown template tag: "+k;i=j._default||[];if(c&&!/\w$/.test(b)){b+=c;c=""}if(b){b=h(b);d=d?","+h(d)+")":c?")":"";e=c?b.indexOf(".")>-1?b+h(c):"("+b+").call($item"+d:b;f=c?e:"(typeof("+b+")==='function'?("+b+").call($item):("+b+"))"}else f=e=i.$1||"null";g=h(g);return"');"+j[l?"close":"open"].split("$notnull_1").join(b?"typeof("+b+")!=='undefined' && ("+b+")!=null":"true").split("$1a").join(f).split("$1").join(e).split("$2").join(g||i.$2||"")+"__.push('"})+"');}return __;")}function n(c,b){c._wrap=j(c,true,a.isArray(b)?b:[q.test(b)?b:a(b).html()]).join("")}function h(a){return a?a.replace(/\\'/g,"'").replace(/\\\\/g,"\\"):null}function s(b){var a=document.createElement("div");a.appendChild(b.cloneNode(true));return a.innerHTML}function m(o){var n="_"+c,k,j,l={},e,p,h;for(e=0,p=o.length;e=0;h--)m(j[h]);m(k)}function m(j){var p,h=j,k,e,m;if(m=j.getAttribute(d)){while(h.parentNode&&(h=h.parentNode).nodeType===1&&!(p=h.getAttribute(d)));if(p!==m){h=h.parentNode?h.nodeType===11?0:h.getAttribute(d)||0:0;if(!(e=b[m])){e=f[m];e=g(e,b[h]||f[h]);e.key=++i;b[i]=e}c&&o(m)}j.removeAttribute(d)}else if(c&&(e=a.data(j,"tmplItem"))){o(e.key);b[e.key]=e;h=a.data(j.parentNode,"tmplItem");h=h?h.key:0}if(e){k=e;while(k&&k.key!=h){k.nodes.push(j);k=k.parent}delete e._ctnt;delete e._wrap;a.data(j,"tmplItem",e)}function o(a){a=a+n;e=l[a]=l[a]||g(e,b[e.parent.key+n]||e.parent)}}}function u(a,d,c,b){if(!a)return l.pop();l.push({_:a,tmpl:d,item:this,data:c,options:b})}function w(d,c,b){return a.tmpl(a.template(d),c,b,this)}function x(b,d){var c=b.options||{};c.wrapped=d;return a.tmpl(a.template(b.tmpl),b.data,c,b.item)}function v(d,c){var b=this._wrap;return a.map(a(a.isArray(b)?b.join(""):b).filter(d||"*"),function(a){return c?a.innerText||a.textContent:a.outerHTML||s(a)})}function t(){var b=this.nodes;a.tmpl(null,null,null,this).insertBefore(b[0]);a(b).remove()}})(jQuery); + +/* + * jQuery TableSorter plugin + * http://autobahn.tablesorter.com/jquery.tablesorter.min.js + */ + +(function($){$.extend({tablesorter:new +function(){var parsers=[],widgets=[];this.defaults={cssHeader:"header",cssAsc:"headerSortUp",cssDesc:"headerSortDown",cssChildRow:"expand-child",sortInitialOrder:"asc",sortMultiSortKey:"shiftKey",sortForce:null,sortAppend:null,sortLocaleCompare:true,textExtraction:"simple",parsers:{},widgets:[],widgetZebra:{css:["even","odd"]},headers:{},widthFixed:false,cancelSelection:true,sortList:[],headerList:[],dateFormat:"us",decimal:'/\.|\,/g',onRenderHeader:null,selectorHeaders:'thead th',debug:false};function benchmark(s,d){log(s+","+(new Date().getTime()-d.getTime())+"ms");}this.benchmark=benchmark;function log(s){if(typeof console!="undefined"&&typeof console.debug!="undefined"){console.log(s);}else{alert(s);}}function buildParserCache(table,$headers){if(table.config.debug){var parsersDebug="";}if(table.tBodies.length==0)return;var rows=table.tBodies[0].rows;if(rows[0]){var list=[],cells=rows[0].cells,l=cells.length;for(var i=0;i1){arr=arr.concat(checkCellColSpan(table,headerArr,row++));}else{if(table.tHead.length==1||(cell.rowSpan>1||!r[row+1])){arr.push(cell);}}}return arr;};function checkHeaderMetadata(cell){if(($.metadata)&&($(cell).metadata().sorter===false)){return true;};return false;}function checkHeaderOptions(table,i){if((table.config.headers[i])&&(table.config.headers[i].sorter===false)){return true;};return false;}function checkHeaderOptionsSortingLocked(table,i){if((table.config.headers[i])&&(table.config.headers[i].lockedOrder))return table.config.headers[i].lockedOrder;return false;}function applyWidget(table){var c=table.config.widgets;var l=c.length;for(var i=0;i');$("tr:first td",table.tBodies[0]).each(function(){colgroup.append($('').css('width',$(this).width()));});$(table).prepend(colgroup);};}function updateHeaderSortCount(table,sortList){var c=table.config,l=sortList.length;for(var i=0;i b["+i+"]) ? 1 : 0));";};function makeSortTextDesc(i){return"((b["+i+"] < a["+i+"]) ? -1 : ((b["+i+"] > a["+i+"]) ? 1 : 0));";};function makeSortNumeric(i){return"a["+i+"]-b["+i+"];";};function makeSortNumericDesc(i){return"b["+i+"]-a["+i+"];";};function sortText(a,b){if(table.config.sortLocaleCompare)return a.localeCompare(b);return((ab)?1:0));};function sortTextDesc(a,b){if(table.config.sortLocaleCompare)return b.localeCompare(a);return((ba)?1:0));};function sortNumeric(a,b){return a-b;};function sortNumericDesc(a,b){return b-a;};function getCachedSortType(parsers,i){return parsers[i].type;};this.construct=function(settings){return this.each(function(){if(!this.tHead||!this.tBodies)return;var $this,$document,$headers,cache,config,shiftDown=0,sortOrder;this.config={};config=$.extend(this.config,$.tablesorter.defaults,settings);$this=$(this);$.data(this,"tablesorter",config);$headers=buildHeaders(this);this.config.parsers=buildParserCache(this,$headers);cache=buildCache(this);var sortCSS=[config.cssDesc,config.cssAsc];fixColumnWidth(this);$headers.click(function(e){var totalRows=($this[0].tBodies[0]&&$this[0].tBodies[0].rows.length)||0;if(!this.sortDisabled&&totalRows>0){$this.trigger("sortStart");var $cell=$(this);var i=this.column;this.order=this.count++%2;if(this.lockedOrder)this.order=this.lockedOrder;if(!e[config.sortMultiSortKey]){config.sortList=[];if(config.sortForce!=null){var a=config.sortForce;for(var j=0;j0){$this.trigger("sorton",[config.sortList]);}applyWidget(this);});};this.addParser=function(parser){var l=parsers.length,a=true;for(var i=0;i +
+ + + + + diff --git a/env/lib/python2.7/site-packages/flaskext/gae_mini_profiler/templates/includes.html b/env/lib/python2.7/site-packages/flaskext/gae_mini_profiler/templates/includes.html new file mode 100644 index 0000000..c00db2c --- /dev/null +++ b/env/lib/python2.7/site-packages/flaskext/gae_mini_profiler/templates/includes.html @@ -0,0 +1,3 @@ + + + diff --git a/env/lib/python2.7/site-packages/flaskext/gae_mini_profiler/templates/shared.html b/env/lib/python2.7/site-packages/flaskext/gae_mini_profiler/templates/shared.html new file mode 100644 index 0000000..ca278aa --- /dev/null +++ b/env/lib/python2.7/site-packages/flaskext/gae_mini_profiler/templates/shared.html @@ -0,0 +1,15 @@ + + + + Shared Profiler Resutls + + + +
+
+ Shared profiler results +
+
+ {% include "includes.html" %} + + diff --git a/env/lib/python2.7/site-packages/gunicorn-19.3.0.dist-info/DESCRIPTION.rst b/env/lib/python2.7/site-packages/gunicorn-19.3.0.dist-info/DESCRIPTION.rst new file mode 100644 index 0000000..cdbd500 --- /dev/null +++ b/env/lib/python2.7/site-packages/gunicorn-19.3.0.dist-info/DESCRIPTION.rst @@ -0,0 +1,59 @@ +Gunicorn +-------- + +.. image:: + https://secure.travis-ci.org/benoitc/gunicorn.png?branch=master + :alt: Build Status + :target: https://travis-ci.org/benoitc/gunicorn + +Gunicorn 'Green Unicorn' is a Python WSGI HTTP Server for UNIX. It's a pre-fork +worker model ported from Ruby's Unicorn_ project. The Gunicorn server is broadly +compatible with various web frameworks, simply implemented, light on server +resource usage, and fairly speedy. + +Feel free to join us in `#gunicorn`_ on Freenode_. + +Documentation +------------- + +The documentation is hosted at http://docs.gunicorn.org. + +Installation +------------ + +Gunicorn requires **Python 2.x >= 2.6** or **Python 3.x >= 3.2**. + +Install from PyPI:: + + $ pip install gunicorn + + +Usage +----- + +Basic usage:: + + $ gunicorn [OPTIONS] APP_MODULE + +Where ``APP_MODULE`` is of the pattern ``$(MODULE_NAME):$(VARIABLE_NAME)``. The +module name can be a full dotted path. The variable name refers to a WSGI +callable that should be found in the specified module. + +Example with test app:: + + $ cd examples + $ gunicorn --workers=2 test:app + + +License +------- + +Gunicorn is released under the MIT License. See the LICENSE_ file for more +details. + +.. _Unicorn: http://unicorn.bogomips.org/ +.. _`#gunicorn`: http://webchat.freenode.net/?channels=gunicorn +.. _Freenode: http://freenode.net +.. _LICENSE: http://github.com/benoitc/gunicorn/blob/master/LICENSE + + diff --git a/env/lib/python2.7/site-packages/gunicorn-19.3.0.dist-info/METADATA b/env/lib/python2.7/site-packages/gunicorn-19.3.0.dist-info/METADATA new file mode 100644 index 0000000..16bd1b3 --- /dev/null +++ b/env/lib/python2.7/site-packages/gunicorn-19.3.0.dist-info/METADATA @@ -0,0 +1,90 @@ +Metadata-Version: 2.0 +Name: gunicorn +Version: 19.3.0 +Summary: WSGI HTTP Server for UNIX +Home-page: http://gunicorn.org +Author: Benoit Chesneau +Author-email: benoitc@e-engura.com +License: MIT +Platform: UNKNOWN +Classifier: Development Status :: 4 - Beta +Classifier: Environment :: Other Environment +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +Classifier: Operating System :: MacOS :: MacOS X +Classifier: Operating System :: POSIX +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 2.6 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.2 +Classifier: Programming Language :: Python :: 3.3 +Classifier: Programming Language :: Python :: 3.4 +Classifier: Topic :: Internet +Classifier: Topic :: Utilities +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Classifier: Topic :: Internet :: WWW/HTTP +Classifier: Topic :: Internet :: WWW/HTTP :: WSGI +Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Server +Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content + +Gunicorn +-------- + +.. image:: + https://secure.travis-ci.org/benoitc/gunicorn.png?branch=master + :alt: Build Status + :target: https://travis-ci.org/benoitc/gunicorn + +Gunicorn 'Green Unicorn' is a Python WSGI HTTP Server for UNIX. It's a pre-fork +worker model ported from Ruby's Unicorn_ project. The Gunicorn server is broadly +compatible with various web frameworks, simply implemented, light on server +resource usage, and fairly speedy. + +Feel free to join us in `#gunicorn`_ on Freenode_. + +Documentation +------------- + +The documentation is hosted at http://docs.gunicorn.org. + +Installation +------------ + +Gunicorn requires **Python 2.x >= 2.6** or **Python 3.x >= 3.2**. + +Install from PyPI:: + + $ pip install gunicorn + + +Usage +----- + +Basic usage:: + + $ gunicorn [OPTIONS] APP_MODULE + +Where ``APP_MODULE`` is of the pattern ``$(MODULE_NAME):$(VARIABLE_NAME)``. The +module name can be a full dotted path. The variable name refers to a WSGI +callable that should be found in the specified module. + +Example with test app:: + + $ cd examples + $ gunicorn --workers=2 test:app + + +License +------- + +Gunicorn is released under the MIT License. See the LICENSE_ file for more +details. + +.. _Unicorn: http://unicorn.bogomips.org/ +.. _`#gunicorn`: http://webchat.freenode.net/?channels=gunicorn +.. _Freenode: http://freenode.net +.. _LICENSE: http://github.com/benoitc/gunicorn/blob/master/LICENSE + + diff --git a/env/lib/python2.7/site-packages/gunicorn-19.3.0.dist-info/RECORD b/env/lib/python2.7/site-packages/gunicorn-19.3.0.dist-info/RECORD new file mode 100644 index 0000000..030561c --- /dev/null +++ b/env/lib/python2.7/site-packages/gunicorn-19.3.0.dist-info/RECORD @@ -0,0 +1,97 @@ +gunicorn/__init__.py,sha256=GygtFzXCbxzsk6qLigNVP2GEHizSzjHVp3EAwdTNF3E,255 +gunicorn/_compat.py,sha256=KRK67_uLNXygm2nzBagQlpd81bQz9_15YNmoJpgrggc,6067 +gunicorn/arbiter.py,sha256=3jHlYWNFDCT5pzSGYLOkmfQcujSnHUBbtLqf9Hye7QQ,17557 +gunicorn/argparse_compat.py,sha256=gsHDGwo4BSJWHdiaEXy0Emr96NKC0LDYmK5nB7PE8Qc,87791 +gunicorn/config.py,sha256=is0k9sgQ3Z-xJi1k0cUJ_SYAUThou5g08Q5ViSZIzwQ,45431 +gunicorn/debug.py,sha256=9z2i59LfELYi3VvbwyrIKezYrQuowVAsWfSLZI75TDI,2303 +gunicorn/errors.py,sha256=0uNcHwcl4_fTMGTvZiOOZxvnw3vCgz972emQP6V7TmE,544 +gunicorn/glogging.py,sha256=79aTas6k505-XVAfJ-QrEl6q0xzeXCUqY5vlPHlzRC4,11398 +gunicorn/pidfile.py,sha256=dQVqp0pifRGrRmutJfsArLlID3SUQbnmVdy-1uXK-JI,2248 +gunicorn/reloader.py,sha256=Z8XJ581yTqOmrPkheJav-qdsJZS9z2JdYojhocOY6Sc,1533 +gunicorn/selectors.py,sha256=14_UESrpE3AQKXWKeeAUG9vBTzJ0yTYDGtEo6xOtlDY,18997 +gunicorn/six.py,sha256=6N-6RCENPfBtMpN5UmgDfDKmJebbbuPu_Dk3Zf8ngww,27344 +gunicorn/sock.py,sha256=8rpqInKqXj3W9UwV2ynpzyP4VP2rs41WlmINLJDvj1M,6978 +gunicorn/util.py,sha256=62Ei6ya-fHsjsAAiauXI5uyawRpqNl7wDK3605d1xWo,15431 +gunicorn/app/__init__.py,sha256=GuqstqdkizeV4HRbd8aGMBn0Q8IDOyRU1wMMNqNe5GY,127 +gunicorn/app/base.py,sha256=Y9N8TkNcnf0b4XuOT6xdeJD8d_GEpb8kEU0Vf4I9Qng,5568 +gunicorn/app/django_wsgi.py,sha256=y57mCZPtg6_bZH8gXO_L5MdWs88kg__-6J8ewszmyLo,4363 +gunicorn/app/djangoapp.py,sha256=Py3aelYR6oTTxrERTR7ENTv7fG4ezIzoQ0yA-05_MDQ,5026 +gunicorn/app/pasterapp.py,sha256=1P08Ry8KiSyTcbWvyVWx478--caW3_gBIxoztiBK8ds,6214 +gunicorn/app/wsgiapp.py,sha256=GXVU4rl44bKZp7ZTyRdG5A4l7h_iUtQWM1dQDFQ_JO0,2156 +gunicorn/http/__init__.py,sha256=b4TF3x5F0VYOPTOeNYwRGR1EYHBaPMhZRMoNeuD5-n0,277 +gunicorn/http/_sendfile.py,sha256=lJZV7IsyJIjij9QQNlrSHIDClrcVrI1xD0vmILredwc,2256 +gunicorn/http/body.py,sha256=SbFMqhFR_V1AKg1Bm0grZL5gmhJu2zf_8Xz2Kaj-mao,7355 +gunicorn/http/errors.py,sha256=57KmM6CA7UldH7ZfYRSF8drsh95iI5sVoTEHoQrlcSI,2446 +gunicorn/http/message.py,sha256=uaKFFBeufRx56rdslp1s5L305iLtP63soRuOwwsZ8Q8,11293 +gunicorn/http/parser.py,sha256=IRMvp0veP4wL8Z4vgNV72CPydCNPdNNIy9u-DlDvvSo,1294 +gunicorn/http/unreader.py,sha256=1D9E3QD8BBkCrJ4BvIDUdZngT4n7Q1H-X-GLqF19iT4,2024 +gunicorn/http/wsgi.py,sha256=2Yv6drm85YBGV5FJZVHHfKh3iz0w2Sb2AeBdTG5LxY8,13493 +gunicorn/instrument/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +gunicorn/instrument/statsd.py,sha256=BDkxA_A1npnenxlRXKOgIkcjreIM2WLcD_xiw97Jz_0,4361 +gunicorn/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +gunicorn/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +gunicorn/management/commands/run_gunicorn.py,sha256=6E8HO-aV-0tQ2aRDO9xLbSMkKZmP-8WbHz9HxkZcuaU,3638 +gunicorn/workers/__init__.py,sha256=ZZZavJFMrmjSlBhjpTmgJGQwhReY3SsGaEvCU9YrFrs,716 +gunicorn/workers/_gaiohttp.py,sha256=cywm3Zi4b_BBTx-wZZ8SjrB__pGLtOT1Y8ux79rnRtE,3930 +gunicorn/workers/async.py,sha256=9CSEsQX4Zworuok6LlJxSKbSM4JlkukRf2ZDhaJzt2E,5513 +gunicorn/workers/base.py,sha256=kqS809hxSHuO6zsLeIvnDBxGLJOVrAe3kIN7LlygbdE,7331 +gunicorn/workers/gaiohttp.py,sha256=GIkR9AnyaZ8b1Pt7It35iIK6EPwe2JSUgt55u7nw9qo,500 +gunicorn/workers/geventlet.py,sha256=hHuWLz8Y5qTNqCYemTu6p4I1XpV77QGhLXkzVu-H1QI,3656 +gunicorn/workers/ggevent.py,sha256=j6-dfINUaxJVqBKdTXb3qZnknKHfgXuOz-hFhqsqKmo,6699 +gunicorn/workers/gthread.py,sha256=yqRpx9hI0pWjmdAposutKO4TQ421xSK7Elg_IxGfSyc,11557 +gunicorn/workers/gtornado.py,sha256=1A36p8L6PJ1zREKB1vfuVif3gZDYEZgEa3DxMoJcudY,3901 +gunicorn/workers/sync.py,sha256=qqFrf7IlQ6RfoF9K9ZPjldvnckLFJZj6Ei43eyWyQFY,7036 +gunicorn/workers/workertmp.py,sha256=6QINPBrriLvezgkC_hclOOeXLi_owMt_SOA5KPEIN-A,1459 +gunicorn-19.3.0.dist-info/DESCRIPTION.rst,sha256=gCCsiCS_cxp9BYBUISH3yQgOJ7Qwf1doRlU848N7V9A,1398 +gunicorn-19.3.0.dist-info/entry_points.txt,sha256=O4lN00p02r6nZQ_iK-fMXLjj_JK6fAWdl23_5czAK-I,231 +gunicorn-19.3.0.dist-info/METADATA,sha256=ycv3ivUJ9ZLE190uvPMKxWjwRe5oHjqS8urQbeCSEQw,2614 +gunicorn-19.3.0.dist-info/metadata.json,sha256=8wAR_s3z7VdoIIDCQA1L46jOFbgzpQr3uYiOuHQTuXs,1737 +gunicorn-19.3.0.dist-info/RECORD,, +gunicorn-19.3.0.dist-info/top_level.txt,sha256=cdMaa2yhxb8do-WioY9qRHUCfwf55YztjwQCncaInoE,9 +gunicorn-19.3.0.dist-info/WHEEL,sha256=AvR0WeTpDaxT645bl5FQxUK6NPsTls2ttpcGJg3j1Xg,110 +/Users/Boris/db_api/env/bin/gunicorn_paster,sha256=mhY0B3EACXq2Mgi2yI4xqpy7S1XMITve0hJ4kNzaGww,240 +/Users/Boris/db_api/env/bin/gunicorn,sha256=frEjfB-rPPhewBpvfaL3vLVP0jri0mgptcQK2Qy1bcI,238 +/Users/Boris/db_api/env/bin/gunicorn_django,sha256=bTbMaMj6iwu3PDYvcangbDfa1wokw5c9nscmeCytJTU,240 +gunicorn/instrument/__init__.pyc,, +gunicorn/http/unreader.pyc,, +gunicorn/app/__init__.pyc,, +gunicorn/app/base.pyc,, +gunicorn/pidfile.pyc,, +gunicorn/http/errors.pyc,, +gunicorn/glogging.pyc,, +gunicorn/app/pasterapp.pyc,, +gunicorn/workers/workertmp.pyc,, +gunicorn/http/message.pyc,, +gunicorn/management/__init__.pyc,, +gunicorn/management/commands/run_gunicorn.pyc,, +gunicorn/config.pyc,, +gunicorn/workers/gaiohttp.pyc,, +gunicorn/app/django_wsgi.pyc,, +gunicorn/workers/gthread.pyc,, +gunicorn/workers/gtornado.pyc,, +gunicorn/selectors.pyc,, +gunicorn/__init__.pyc,, +gunicorn/http/_sendfile.pyc,, +gunicorn/http/wsgi.pyc,, +gunicorn/workers/async.pyc,, +gunicorn/management/commands/__init__.pyc,, +gunicorn/app/djangoapp.pyc,, +gunicorn/workers/base.pyc,, +gunicorn/arbiter.pyc,, +gunicorn/six.pyc,, +gunicorn/_compat.pyc,, +gunicorn/instrument/statsd.pyc,, +gunicorn/http/parser.pyc,, +gunicorn/app/wsgiapp.pyc,, +gunicorn/sock.pyc,, +gunicorn/reloader.pyc,, +gunicorn/argparse_compat.pyc,, +gunicorn/debug.pyc,, +gunicorn/workers/sync.pyc,, +gunicorn/http/body.pyc,, +gunicorn/workers/ggevent.pyc,, +gunicorn/errors.pyc,, +gunicorn/workers/__init__.pyc,, +gunicorn/http/__init__.pyc,, +gunicorn/workers/geventlet.pyc,, +gunicorn/util.pyc,, diff --git a/env/lib/python2.7/site-packages/gunicorn-19.3.0.dist-info/WHEEL b/env/lib/python2.7/site-packages/gunicorn-19.3.0.dist-info/WHEEL new file mode 100644 index 0000000..9dff69d --- /dev/null +++ b/env/lib/python2.7/site-packages/gunicorn-19.3.0.dist-info/WHEEL @@ -0,0 +1,6 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.24.0) +Root-Is-Purelib: true +Tag: py2-none-any +Tag: py3-none-any + diff --git a/env/lib/python2.7/site-packages/gunicorn-19.3.0.dist-info/entry_points.txt b/env/lib/python2.7/site-packages/gunicorn-19.3.0.dist-info/entry_points.txt new file mode 100644 index 0000000..f150639 --- /dev/null +++ b/env/lib/python2.7/site-packages/gunicorn-19.3.0.dist-info/entry_points.txt @@ -0,0 +1,9 @@ + + [console_scripts] + gunicorn=gunicorn.app.wsgiapp:run + gunicorn_django=gunicorn.app.djangoapp:run + gunicorn_paster=gunicorn.app.pasterapp:run + + [paste.server_runner] + main=gunicorn.app.pasterapp:paste_server + \ No newline at end of file diff --git a/env/lib/python2.7/site-packages/gunicorn-19.3.0.dist-info/metadata.json b/env/lib/python2.7/site-packages/gunicorn-19.3.0.dist-info/metadata.json new file mode 100644 index 0000000..6bd95cd --- /dev/null +++ b/env/lib/python2.7/site-packages/gunicorn-19.3.0.dist-info/metadata.json @@ -0,0 +1 @@ +{"summary": "WSGI HTTP Server for UNIX", "generator": "bdist_wheel (0.24.0)", "extensions": {"python.commands": {"wrap_console": {"gunicorn_django": "gunicorn.app.djangoapp:run", "gunicorn_paster": "gunicorn.app.pasterapp:run", "gunicorn": "gunicorn.app.wsgiapp:run"}}, "python.details": {"document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "http://gunicorn.org"}, "contacts": [{"role": "author", "name": "Benoit Chesneau", "email": "benoitc@e-engura.com"}]}, "python.exports": {"paste.server_runner": {"main": "gunicorn.app.pasterapp:paste_server"}, "console_scripts": {"gunicorn_django": "gunicorn.app.djangoapp:run", "gunicorn_paster": "gunicorn.app.pasterapp:run", "gunicorn": "gunicorn.app.wsgiapp:run"}}}, "classifiers": ["Development Status :: 4 - Beta", "Environment :: Other Environment", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: MacOS :: MacOS X", "Operating System :: POSIX", "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.2", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Topic :: Internet", "Topic :: Utilities", "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: WSGI", "Topic :: Internet :: WWW/HTTP :: WSGI :: Server", "Topic :: Internet :: WWW/HTTP :: Dynamic Content"], "metadata_version": "2.0", "test_requires": [{"requires": ["pytest (==2.6.3)", "pytest-cov (==1.7.0)"]}], "name": "gunicorn", "version": "19.3.0", "license": "MIT"} \ No newline at end of file diff --git a/env/lib/python2.7/site-packages/gunicorn-19.3.0.dist-info/top_level.txt b/env/lib/python2.7/site-packages/gunicorn-19.3.0.dist-info/top_level.txt new file mode 100644 index 0000000..8f22dcc --- /dev/null +++ b/env/lib/python2.7/site-packages/gunicorn-19.3.0.dist-info/top_level.txt @@ -0,0 +1 @@ +gunicorn diff --git a/env/lib/python2.7/site-packages/gunicorn/__init__.py b/env/lib/python2.7/site-packages/gunicorn/__init__.py new file mode 100644 index 0000000..1ab5933 --- /dev/null +++ b/env/lib/python2.7/site-packages/gunicorn/__init__.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 - +# +# This file is part of gunicorn released under the MIT license. +# See the NOTICE for more information. + +version_info = (19, 3, 0) +__version__ = ".".join([str(v) for v in version_info]) +SERVER_SOFTWARE = "gunicorn/%s" % __version__ diff --git a/env/lib/python2.7/site-packages/gunicorn/__init__.pyc b/env/lib/python2.7/site-packages/gunicorn/__init__.pyc new file mode 100644 index 0000000..dc7d4ee Binary files /dev/null and b/env/lib/python2.7/site-packages/gunicorn/__init__.pyc differ diff --git a/env/lib/python2.7/site-packages/gunicorn/_compat.py b/env/lib/python2.7/site-packages/gunicorn/_compat.py new file mode 100644 index 0000000..9a4480c --- /dev/null +++ b/env/lib/python2.7/site-packages/gunicorn/_compat.py @@ -0,0 +1,202 @@ +import sys + +from gunicorn import six + +PY33 = (sys.version_info >= (3, 3)) + + +def _check_if_pyc(fname): + """Return True if the extension is .pyc, False if .py + and None if otherwise""" + from imp import find_module + from os.path import realpath, dirname, basename, splitext + + # Normalize the file-path for the find_module() + filepath = realpath(fname) + dirpath = dirname(filepath) + module_name = splitext(basename(filepath))[0] + + # Validate and fetch + try: + fileobj, fullpath, (_, _, pytype) = find_module(module_name, [dirpath]) + except ImportError: + raise IOError("Cannot find config file. " + "Path maybe incorrect! : {0}".format(filepath)) + return pytype, fileobj, fullpath + + +def _get_codeobj(pyfile): + """ Returns the code object, given a python file """ + from imp import PY_COMPILED, PY_SOURCE + + result, fileobj, fullpath = _check_if_pyc(pyfile) + + # WARNING: + # fp.read() can blowup if the module is extremely large file. + # Lookout for overflow errors. + try: + data = fileobj.read() + finally: + fileobj.close() + + # This is a .pyc file. Treat accordingly. + if result is PY_COMPILED: + # .pyc format is as follows: + # 0 - 4 bytes: Magic number, which changes with each create of .pyc file. + # First 2 bytes change with each marshal of .pyc file. Last 2 bytes is "\r\n". + # 4 - 8 bytes: Datetime value, when the .py was last changed. + # 8 - EOF: Marshalled code object data. + # So to get code object, just read the 8th byte onwards till EOF, and + # UN-marshal it. + import marshal + code_obj = marshal.loads(data[8:]) + + elif result is PY_SOURCE: + # This is a .py file. + code_obj = compile(data, fullpath, 'exec') + + else: + # Unsupported extension + raise Exception("Input file is unknown format: {0}".format(fullpath)) + + # Return code object + return code_obj + +if six.PY3: + def execfile_(fname, *args): + if fname.endswith(".pyc"): + code = _get_codeobj(fname) + else: + code = compile(open(fname, 'rb').read(), fname, 'exec') + return six.exec_(code, *args) + + def bytes_to_str(b): + if isinstance(b, six.text_type): + return b + return str(b, 'latin1') + + import urllib.parse + + def unquote_to_wsgi_str(string): + return _unquote_to_bytes(string).decode('latin-1') + + _unquote_to_bytes = urllib.parse.unquote_to_bytes + +else: + def execfile_(fname, *args): + """ Overriding PY2 execfile() implementation to support .pyc files """ + if fname.endswith(".pyc"): + return six.exec_(_get_codeobj(fname), *args) + return execfile(fname, *args) + + def bytes_to_str(s): + if isinstance(s, unicode): + return s.encode('utf-8') + return s + + import urllib + unquote_to_wsgi_str = urllib.unquote + + +# The following code adapted from trollius.py33_exceptions +def _wrap_error(exc, mapping, key): + if key not in mapping: + return + new_err_cls = mapping[key] + new_err = new_err_cls(*exc.args) + + # raise a new exception with the original traceback + if hasattr(exc, '__traceback__'): + traceback = exc.__traceback__ + else: + traceback = sys.exc_info()[2] + six.reraise(new_err_cls, new_err, traceback) + +if PY33: + import builtins + + BlockingIOError = builtins.BlockingIOError + BrokenPipeError = builtins.BrokenPipeError + ChildProcessError = builtins.ChildProcessError + ConnectionRefusedError = builtins.ConnectionRefusedError + ConnectionResetError = builtins.ConnectionResetError + InterruptedError = builtins.InterruptedError + ConnectionAbortedError = builtins.ConnectionAbortedError + PermissionError = builtins.PermissionError + FileNotFoundError = builtins.FileNotFoundError + ProcessLookupError = builtins.ProcessLookupError + + def wrap_error(func, *args, **kw): + return func(*args, **kw) +else: + import errno + import select + import socket + + class BlockingIOError(OSError): + pass + + class BrokenPipeError(OSError): + pass + + class ChildProcessError(OSError): + pass + + class ConnectionRefusedError(OSError): + pass + + class InterruptedError(OSError): + pass + + class ConnectionResetError(OSError): + pass + + class ConnectionAbortedError(OSError): + pass + + class PermissionError(OSError): + pass + + class FileNotFoundError(OSError): + pass + + class ProcessLookupError(OSError): + pass + + _MAP_ERRNO = { + errno.EACCES: PermissionError, + errno.EAGAIN: BlockingIOError, + errno.EALREADY: BlockingIOError, + errno.ECHILD: ChildProcessError, + errno.ECONNABORTED: ConnectionAbortedError, + errno.ECONNREFUSED: ConnectionRefusedError, + errno.ECONNRESET: ConnectionResetError, + errno.EINPROGRESS: BlockingIOError, + errno.EINTR: InterruptedError, + errno.ENOENT: FileNotFoundError, + errno.EPERM: PermissionError, + errno.EPIPE: BrokenPipeError, + errno.ESHUTDOWN: BrokenPipeError, + errno.EWOULDBLOCK: BlockingIOError, + errno.ESRCH: ProcessLookupError, + } + + def wrap_error(func, *args, **kw): + """ + Wrap socket.error, IOError, OSError, select.error to raise new specialized + exceptions of Python 3.3 like InterruptedError (PEP 3151). + """ + try: + return func(*args, **kw) + except (socket.error, IOError, OSError) as exc: + if hasattr(exc, 'winerror'): + _wrap_error(exc, _MAP_ERRNO, exc.winerror) + # _MAP_ERRNO does not contain all Windows errors. + # For some errors like "file not found", exc.errno should + # be used (ex: ENOENT). + _wrap_error(exc, _MAP_ERRNO, exc.errno) + raise + except select.error as exc: + if exc.args: + _wrap_error(exc, _MAP_ERRNO, exc.args[0]) + raise diff --git a/env/lib/python2.7/site-packages/gunicorn/_compat.pyc b/env/lib/python2.7/site-packages/gunicorn/_compat.pyc new file mode 100644 index 0000000..c214018 Binary files /dev/null and b/env/lib/python2.7/site-packages/gunicorn/_compat.pyc differ diff --git a/env/lib/python2.7/site-packages/gunicorn/app/__init__.py b/env/lib/python2.7/site-packages/gunicorn/app/__init__.py new file mode 100644 index 0000000..87f0611 --- /dev/null +++ b/env/lib/python2.7/site-packages/gunicorn/app/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 - +# +# This file is part of gunicorn released under the MIT license. +# See the NOTICE for more information. diff --git a/env/lib/python2.7/site-packages/gunicorn/app/__init__.pyc b/env/lib/python2.7/site-packages/gunicorn/app/__init__.pyc new file mode 100644 index 0000000..36f2f2b Binary files /dev/null and b/env/lib/python2.7/site-packages/gunicorn/app/__init__.pyc differ diff --git a/env/lib/python2.7/site-packages/gunicorn/app/base.py b/env/lib/python2.7/site-packages/gunicorn/app/base.py new file mode 100644 index 0000000..d1ee6c9 --- /dev/null +++ b/env/lib/python2.7/site-packages/gunicorn/app/base.py @@ -0,0 +1,189 @@ +# -*- coding: utf-8 - +# +# This file is part of gunicorn released under the MIT license. +# See the NOTICE for more information. +from __future__ import print_function + +import os +import sys +import traceback + +from gunicorn._compat import execfile_ +from gunicorn import util +from gunicorn.arbiter import Arbiter +from gunicorn.config import Config, get_default_config_file +from gunicorn import debug + +class BaseApplication(object): + """ + An application interface for configuring and loading + the various necessities for any given web framework. + """ + def __init__(self, usage=None, prog=None): + self.usage = usage + self.cfg = None + self.callable = None + self.prog = prog + self.logger = None + self.do_load_config() + + def do_load_config(self): + """ + Loads the configuration + """ + try: + self.load_default_config() + self.load_config() + except Exception as e: + print("\nError: %s" % str(e), file=sys.stderr) + sys.stderr.flush() + sys.exit(1) + + def load_default_config(self): + # init configuration + self.cfg = Config(self.usage, prog=self.prog) + + def init(self, parser, opts, args): + raise NotImplementedError + + def load(self): + raise NotImplementedError + + def load_config(self): + """ + This method is used to load the configuration from one or several input(s). + Custom Command line, configuration file. + You have to override this method in your class. + """ + raise NotImplementedError + + def reload(self): + self.do_load_config() + if self.cfg.spew: + debug.spew() + + def wsgi(self): + if self.callable is None: + self.callable = self.load() + return self.callable + + def run(self): + try: + Arbiter(self).run() + except RuntimeError as e: + print("\nError: %s\n" % e, file=sys.stderr) + sys.stderr.flush() + sys.exit(1) + +class Application(BaseApplication): + + def get_config_from_filename(self, filename): + + if not os.path.exists(filename): + raise RuntimeError("%r doesn't exist" % filename) + + cfg = { + "__builtins__": __builtins__, + "__name__": "__config__", + "__file__": filename, + "__doc__": None, + "__package__": None + } + try: + execfile_(filename, cfg, cfg) + except Exception: + print("Failed to read config file: %s" % filename, file=sys.stderr) + traceback.print_exc() + sys.stderr.flush() + sys.exit(1) + + return cfg + + def get_config_from_module_name(self, module_name): + return util.import_module(module_name).__dict__ + + def load_config_from_module_name_or_filename(self, location): + """ + Loads the configuration file: the file is a python file, otherwise raise an RuntimeError + Exception or stop the process if the configuration file contains a syntax error. + """ + + try: + cfg = self.get_config_from_module_name(module_name=location) + except ImportError: + cfg = self.get_config_from_filename(filename=location) + + for k, v in cfg.items(): + # Ignore unknown names + if k not in self.cfg.settings: + continue + try: + self.cfg.set(k.lower(), v) + except: + print("Invalid value for %s: %s\n" % (k, v), file=sys.stderr) + sys.stderr.flush() + raise + + return cfg + + def load_config_from_file(self, filename): + return self.load_config_from_module_name_or_filename( + location=filename + ) + + def load_config(self): + # parse console args + parser = self.cfg.parser() + args = parser.parse_args() + + # optional settings from apps + cfg = self.init(parser, args, args.args) + + # Load up the any app specific configuration + if cfg and cfg is not None: + for k, v in cfg.items(): + self.cfg.set(k.lower(), v) + + if args.config: + self.load_config_from_file(args.config) + else: + default_config = get_default_config_file() + if default_config is not None: + self.load_config_from_file(default_config) + + # Lastly, update the configuration with any command line + # settings. + for k, v in args.__dict__.items(): + if v is None: + continue + if k == "args": + continue + self.cfg.set(k.lower(), v) + + def run(self): + if self.cfg.check_config: + try: + self.load() + except: + msg = "\nError while loading the application:\n" + print(msg, file=sys.stderr) + traceback.print_exc() + sys.stderr.flush() + sys.exit(1) + sys.exit(0) + + if self.cfg.spew: + debug.spew() + + if self.cfg.daemon: + util.daemonize(self.cfg.enable_stdio_inheritance) + + # set python paths + if self.cfg.pythonpath and self.cfg.pythonpath is not None: + paths = self.cfg.pythonpath.split(",") + for path in paths: + pythonpath = os.path.abspath(path) + if pythonpath not in sys.path: + sys.path.insert(0, pythonpath) + + super(Application, self).run() diff --git a/env/lib/python2.7/site-packages/gunicorn/app/base.pyc b/env/lib/python2.7/site-packages/gunicorn/app/base.pyc new file mode 100644 index 0000000..7699e87 Binary files /dev/null and b/env/lib/python2.7/site-packages/gunicorn/app/base.pyc differ diff --git a/env/lib/python2.7/site-packages/gunicorn/app/django_wsgi.py b/env/lib/python2.7/site-packages/gunicorn/app/django_wsgi.py new file mode 100644 index 0000000..31d8aec --- /dev/null +++ b/env/lib/python2.7/site-packages/gunicorn/app/django_wsgi.py @@ -0,0 +1,120 @@ +# -*- coding: utf-8 - +# +# This file is part of gunicorn released under the MIT license. +# See the NOTICE for more information. + +""" module used to build the django wsgi application """ +from __future__ import print_function + +import os +import re +import sys +import time +try: + from StringIO import StringIO +except: + from io import StringIO + from imp import reload + + +from django.conf import settings +from django.core.management.validation import get_validation_errors +from django.utils import translation + +try: + from django.core.servers.basehttp import get_internal_wsgi_application + django14 = True +except ImportError: + from django.core.handlers.wsgi import WSGIHandler + django14 = False + +from gunicorn import util + + +def make_wsgi_application(): + # validate models + s = StringIO() + if get_validation_errors(s): + s.seek(0) + error = s.read() + msg = "One or more models did not validate:\n%s" % error + print(msg, file=sys.stderr) + sys.stderr.flush() + sys.exit(1) + + translation.activate(settings.LANGUAGE_CODE) + if django14: + return get_internal_wsgi_application() + return WSGIHandler() + + +def reload_django_settings(): + mod = util.import_module(os.environ['DJANGO_SETTINGS_MODULE']) + + # Reload module. + reload(mod) + + # Reload settings. + # Use code from django.settings.Settings module. + + # Settings that should be converted into tuples if they're mistakenly entered + # as strings. + tuple_settings = ("INSTALLED_APPS", "TEMPLATE_DIRS") + + for setting in dir(mod): + if setting == setting.upper(): + setting_value = getattr(mod, setting) + if setting in tuple_settings and type(setting_value) == str: + setting_value = (setting_value,) # In case the user forgot the comma. + setattr(settings, setting, setting_value) + + # Expand entries in INSTALLED_APPS like "django.contrib.*" to a list + # of all those apps. + new_installed_apps = [] + for app in settings.INSTALLED_APPS: + if app.endswith('.*'): + app_mod = util.import_module(app[:-2]) + appdir = os.path.dirname(app_mod.__file__) + app_subdirs = os.listdir(appdir) + name_pattern = re.compile(r'[a-zA-Z]\w*') + for d in sorted(app_subdirs): + if (name_pattern.match(d) and + os.path.isdir(os.path.join(appdir, d))): + new_installed_apps.append('%s.%s' % (app[:-2], d)) + else: + new_installed_apps.append(app) + setattr(settings, "INSTALLED_APPS", new_installed_apps) + + if hasattr(time, 'tzset') and settings.TIME_ZONE: + # When we can, attempt to validate the timezone. If we can't find + # this file, no check happens and it's harmless. + zoneinfo_root = '/usr/share/zoneinfo' + if (os.path.exists(zoneinfo_root) and not + os.path.exists(os.path.join(zoneinfo_root, + *(settings.TIME_ZONE.split('/'))))): + raise ValueError("Incorrect timezone setting: %s" % + settings.TIME_ZONE) + # Move the time zone info into os.environ. See ticket #2315 for why + # we don't do this unconditionally (breaks Windows). + os.environ['TZ'] = settings.TIME_ZONE + time.tzset() + + # Settings are configured, so we can set up the logger if required + if getattr(settings, 'LOGGING_CONFIG', False): + # First find the logging configuration function ... + logging_config_path, logging_config_func_name = settings.LOGGING_CONFIG.rsplit('.', 1) + logging_config_module = util.import_module(logging_config_path) + logging_config_func = getattr(logging_config_module, logging_config_func_name) + + # ... then invoke it with the logging settings + logging_config_func(settings.LOGGING) + + +def make_command_wsgi_application(admin_mediapath): + reload_django_settings() + + try: + from django.core.servers.basehttp import AdminMediaHandler + return AdminMediaHandler(make_wsgi_application(), admin_mediapath) + except ImportError: + return make_wsgi_application() diff --git a/env/lib/python2.7/site-packages/gunicorn/app/django_wsgi.pyc b/env/lib/python2.7/site-packages/gunicorn/app/django_wsgi.pyc new file mode 100644 index 0000000..bc548bf Binary files /dev/null and b/env/lib/python2.7/site-packages/gunicorn/app/django_wsgi.pyc differ diff --git a/env/lib/python2.7/site-packages/gunicorn/app/djangoapp.py b/env/lib/python2.7/site-packages/gunicorn/app/djangoapp.py new file mode 100644 index 0000000..d4824d0 --- /dev/null +++ b/env/lib/python2.7/site-packages/gunicorn/app/djangoapp.py @@ -0,0 +1,160 @@ +# -*- coding: utf-8 - +# +# This file is part of gunicorn released under the MIT license. +# See the NOTICE for more information. + +import os +import sys + +from gunicorn.app.base import Application +from gunicorn import util + + +def is_setting_mod(path): + return (os.path.isfile(os.path.join(path, "settings.py")) or + os.path.isfile(os.path.join(path, "settings.pyc"))) + + +def find_settings_module(path): + path = os.path.abspath(path) + project_path = None + settings_name = "settings" + + if os.path.isdir(path): + project_path = None + if not is_setting_mod(path): + for d in os.listdir(path): + if d in ('..', '.'): + continue + + root = os.path.join(path, d) + if is_setting_mod(root): + project_path = root + break + else: + project_path = path + elif os.path.isfile(path): + project_path = os.path.dirname(path) + settings_name, _ = os.path.splitext(os.path.basename(path)) + + return project_path, settings_name + + +def make_default_env(cfg): + if cfg.django_settings: + os.environ['DJANGO_SETTINGS_MODULE'] = cfg.django_settings + + if cfg.pythonpath and cfg.pythonpath is not None: + paths = cfg.pythonpath.split(",") + for path in paths: + pythonpath = os.path.abspath(cfg.pythonpath) + if pythonpath not in sys.path: + sys.path.insert(0, pythonpath) + + try: + os.environ['DJANGO_SETTINGS_MODULE'] + except KeyError: + # not settings env set, try to build one. + cwd = util.getcwd() + project_path, settings_name = find_settings_module(cwd) + + if not project_path: + raise RuntimeError("django project not found") + + pythonpath, project_name = os.path.split(project_path) + os.environ['DJANGO_SETTINGS_MODULE'] = "%s.%s" % (project_name, + settings_name) + if pythonpath not in sys.path: + sys.path.insert(0, pythonpath) + + if project_path not in sys.path: + sys.path.insert(0, project_path) + + +class DjangoApplication(Application): + + def init(self, parser, opts, args): + if args: + if ("." in args[0] and not (os.path.isfile(args[0]) + or os.path.isdir(args[0]))): + self.cfg.set("django_settings", args[0]) + else: + # not settings env set, try to build one. + project_path, settings_name = find_settings_module( + os.path.abspath(args[0])) + if project_path not in sys.path: + sys.path.insert(0, project_path) + + if not project_path: + raise RuntimeError("django project not found") + + pythonpath, project_name = os.path.split(project_path) + self.cfg.set("django_settings", "%s.%s" % (project_name, + settings_name)) + self.cfg.set("pythonpath", pythonpath) + + def load(self): + # chdir to the configured path before loading, + # default is the current dir + os.chdir(self.cfg.chdir) + + # set settings + make_default_env(self.cfg) + + # load wsgi application and return it. + mod = util.import_module("gunicorn.app.django_wsgi") + return mod.make_wsgi_application() + + +class DjangoApplicationCommand(Application): + + def __init__(self, options, admin_media_path): + self.usage = None + self.prog = None + self.cfg = None + self.config_file = options.get("config") or "" + self.options = options + self.admin_media_path = admin_media_path + self.callable = None + self.project_path = None + self.do_load_config() + + def init(self, *args): + if 'settings' in self.options: + self.options['django_settings'] = self.options.pop('settings') + + cfg = {} + for k, v in self.options.items(): + if k.lower() in self.cfg.settings and v is not None: + cfg[k.lower()] = v + return cfg + + def load(self): + # chdir to the configured path before loading, + # default is the current dir + os.chdir(self.cfg.chdir) + + # set settings + make_default_env(self.cfg) + + # load wsgi application and return it. + mod = util.import_module("gunicorn.app.django_wsgi") + return mod.make_command_wsgi_application(self.admin_media_path) + + +def run(): + """\ + The ``gunicorn_django`` command line runner for launching Django + applications. + """ + util.warn("""This command is deprecated. + + You should now run your application with the WSGI interface + installed with your project. Ex.: + + gunicorn myproject.wsgi:application + + See https://docs.djangoproject.com/en/1.5/howto/deployment/wsgi/gunicorn/ + for more info.""") + from gunicorn.app.djangoapp import DjangoApplication + DjangoApplication("%(prog)s [OPTIONS] [SETTINGS_PATH]").run() diff --git a/env/lib/python2.7/site-packages/gunicorn/app/djangoapp.pyc b/env/lib/python2.7/site-packages/gunicorn/app/djangoapp.pyc new file mode 100644 index 0000000..013d49b Binary files /dev/null and b/env/lib/python2.7/site-packages/gunicorn/app/djangoapp.pyc differ diff --git a/env/lib/python2.7/site-packages/gunicorn/app/pasterapp.py b/env/lib/python2.7/site-packages/gunicorn/app/pasterapp.py new file mode 100644 index 0000000..86aca9e --- /dev/null +++ b/env/lib/python2.7/site-packages/gunicorn/app/pasterapp.py @@ -0,0 +1,212 @@ +# -*- coding: utf-8 - +# +# This file is part of gunicorn released under the MIT license. +# See the NOTICE for more information. +from __future__ import print_function + +import os +import pkg_resources +import sys + +try: + import configparser as ConfigParser +except ImportError: + import ConfigParser + +from paste.deploy import loadapp, loadwsgi +SERVER = loadwsgi.SERVER + +from gunicorn.app.base import Application +from gunicorn.config import Config, get_default_config_file +from gunicorn import util + + +def _has_logging_config(paste_file): + cfg_parser = ConfigParser.ConfigParser() + cfg_parser.read([paste_file]) + return cfg_parser.has_section('loggers') + + +def paste_config(gconfig, config_url, relative_to, global_conf=None): + # add entry to pkg_resources + sys.path.insert(0, relative_to) + pkg_resources.working_set.add_entry(relative_to) + + config_url = config_url.split('#')[0] + cx = loadwsgi.loadcontext(SERVER, config_url, relative_to=relative_to, + global_conf=global_conf) + gc, lc = cx.global_conf.copy(), cx.local_conf.copy() + cfg = {} + + host, port = lc.pop('host', ''), lc.pop('port', '') + if host and port: + cfg['bind'] = '%s:%s' % (host, port) + elif host: + cfg['bind'] = host.split(',') + + cfg['workers'] = int(lc.get('workers', 1)) + cfg['umask'] = int(lc.get('umask', 0)) + cfg['default_proc_name'] = gc.get('__file__') + + # init logging configuration + config_file = config_url.split(':')[1] + if _has_logging_config(config_file): + cfg.setdefault('logconfig', config_file) + + for k, v in gc.items(): + if k not in gconfig.settings: + continue + cfg[k] = v + + for k, v in lc.items(): + if k not in gconfig.settings: + continue + cfg[k] = v + + return cfg + + +def load_pasteapp(config_url, relative_to, global_conf=None): + return loadapp(config_url, relative_to=relative_to, + global_conf=global_conf) + +class PasterBaseApplication(Application): + gcfg = None + + def app_config(self): + return paste_config(self.cfg, self.cfgurl, self.relpath, + global_conf=self.gcfg) + + def load_config(self): + super(PasterBaseApplication, self).load_config() + + # reload logging conf + if hasattr(self, "cfgfname"): + parser = ConfigParser.ConfigParser() + parser.read([self.cfgfname]) + if parser.has_section('loggers'): + from logging.config import fileConfig + config_file = os.path.abspath(self.cfgfname) + fileConfig(config_file, dict(__file__=config_file, + here=os.path.dirname(config_file))) + + +class PasterApplication(PasterBaseApplication): + + def init(self, parser, opts, args): + if len(args) != 1: + parser.error("No application name specified.") + + cwd = util.getcwd() + cfgfname = os.path.normpath(os.path.join(cwd, args[0])) + cfgfname = os.path.abspath(cfgfname) + if not os.path.exists(cfgfname): + parser.error("Config file not found: %s" % cfgfname) + + self.cfgurl = 'config:%s' % cfgfname + self.relpath = os.path.dirname(cfgfname) + self.cfgfname = cfgfname + + sys.path.insert(0, self.relpath) + pkg_resources.working_set.add_entry(self.relpath) + + return self.app_config() + + def load(self): + # chdir to the configured path before loading, + # default is the current dir + os.chdir(self.cfg.chdir) + + return load_pasteapp(self.cfgurl, self.relpath, global_conf=self.gcfg) + + +class PasterServerApplication(PasterBaseApplication): + + def __init__(self, app, gcfg=None, host="127.0.0.1", port=None, *args, **kwargs): + self.cfg = Config() + self.gcfg = gcfg # need to hold this for app_config + self.app = app + self.callable = None + + gcfg = gcfg or {} + cfgfname = gcfg.get("__file__") + if cfgfname is not None: + self.cfgurl = 'config:%s' % cfgfname + self.relpath = os.path.dirname(cfgfname) + self.cfgfname = cfgfname + + cfg = kwargs.copy() + + if port and not host.startswith("unix:"): + bind = "%s:%s" % (host, port) + else: + bind = host + cfg["bind"] = bind.split(',') + + if gcfg: + for k, v in gcfg.items(): + cfg[k] = v + cfg["default_proc_name"] = cfg['__file__'] + + try: + for k, v in cfg.items(): + if k.lower() in self.cfg.settings and v is not None: + self.cfg.set(k.lower(), v) + except Exception as e: + print("\nConfig error: %s" % str(e), file=sys.stderr) + sys.stderr.flush() + sys.exit(1) + + if cfg.get("config"): + self.load_config_from_file(cfg["config"]) + else: + default_config = get_default_config_file() + if default_config is not None: + self.load_config_from_file(default_config) + + def load(self): + # chdir to the configured path before loading, + # default is the current dir + os.chdir(self.cfg.chdir) + + return self.app + + +def run(): + """\ + The ``gunicorn_paster`` command for launching Paster compatible + applications like Pylons or Turbogears2 + """ + util.warn("""This command is deprecated. + + You should now use the `--paste` option. Ex.: + + gunicorn --paste development.ini + """) + + from gunicorn.app.pasterapp import PasterApplication + PasterApplication("%(prog)s [OPTIONS] pasteconfig.ini").run() + + +def paste_server(app, gcfg=None, host="127.0.0.1", port=None, *args, **kwargs): + """\ + A paster server. + + Then entry point in your paster ini file should looks like this: + + [server:main] + use = egg:gunicorn#main + host = 127.0.0.1 + port = 5000 + + """ + + util.warn("""This command is deprecated. + + You should now use the `--paste` option. Ex.: + + gunicorn --paste development.ini + """) + + from gunicorn.app.pasterapp import PasterServerApplication + PasterServerApplication(app, gcfg=gcfg, host=host, port=port, *args, **kwargs).run() diff --git a/env/lib/python2.7/site-packages/gunicorn/app/pasterapp.pyc b/env/lib/python2.7/site-packages/gunicorn/app/pasterapp.pyc new file mode 100644 index 0000000..7ce70ae Binary files /dev/null and b/env/lib/python2.7/site-packages/gunicorn/app/pasterapp.pyc differ diff --git a/env/lib/python2.7/site-packages/gunicorn/app/wsgiapp.py b/env/lib/python2.7/site-packages/gunicorn/app/wsgiapp.py new file mode 100644 index 0000000..cf4db1e --- /dev/null +++ b/env/lib/python2.7/site-packages/gunicorn/app/wsgiapp.py @@ -0,0 +1,78 @@ +# -*- coding: utf-8 - +# +# This file is part of gunicorn released under the MIT license. +# See the NOTICE for more information. + +import os +import sys + +from gunicorn.errors import ConfigError +from gunicorn.app.base import Application +from gunicorn import util + + +class WSGIApplication(Application): + def init(self, parser, opts, args): + if opts.paste and opts.paste is not None: + app_name = 'main' + path = opts.paste + if '#' in path: + path, app_name = path.split('#') + path = os.path.abspath(os.path.normpath( + os.path.join(util.getcwd(), path))) + + if not os.path.exists(path): + raise ConfigError("%r not found" % path) + + # paste application, load the config + self.cfgurl = 'config:%s#%s' % (path, app_name) + self.relpath = os.path.dirname(path) + + from .pasterapp import paste_config + return paste_config(self.cfg, self.cfgurl, self.relpath) + + if len(args) < 1: + parser.error("No application module specified.") + + self.cfg.set("default_proc_name", args[0]) + self.app_uri = args[0] + + def chdir(self): + # chdir to the configured path before loading, + # default is the current dir + os.chdir(self.cfg.chdir) + + # add the path to sys.path + sys.path.insert(0, self.cfg.chdir) + + def load_wsgiapp(self): + self.chdir() + + # load the app + return util.import_app(self.app_uri) + + def load_pasteapp(self): + self.chdir() + + # load the paste app + from .pasterapp import load_pasteapp + return load_pasteapp(self.cfgurl, self.relpath, global_conf=None) + + def load(self): + if self.cfg.paste is not None: + return self.load_pasteapp() + else: + return self.load_wsgiapp() + + +def run(): + """\ + The ``gunicorn`` command line runner for launching Gunicorn with + generic WSGI applications. + """ + from gunicorn.app.wsgiapp import WSGIApplication + WSGIApplication("%(prog)s [OPTIONS] [APP_MODULE]").run() + + +if __name__ == '__main__': + run() diff --git a/env/lib/python2.7/site-packages/gunicorn/app/wsgiapp.pyc b/env/lib/python2.7/site-packages/gunicorn/app/wsgiapp.pyc new file mode 100644 index 0000000..5cb1f39 Binary files /dev/null and b/env/lib/python2.7/site-packages/gunicorn/app/wsgiapp.pyc differ diff --git a/env/lib/python2.7/site-packages/gunicorn/arbiter.py b/env/lib/python2.7/site-packages/gunicorn/arbiter.py new file mode 100644 index 0000000..7ec4a3f --- /dev/null +++ b/env/lib/python2.7/site-packages/gunicorn/arbiter.py @@ -0,0 +1,570 @@ +# -*- coding: utf-8 - +# +# This file is part of gunicorn released under the MIT license. +# See the NOTICE for more information. +from __future__ import print_function + +import errno +import os +import random +import select +import signal +import sys +import time +import traceback + +from gunicorn.errors import HaltServer, AppImportError +from gunicorn.pidfile import Pidfile +from gunicorn.sock import create_sockets +from gunicorn import util + +from gunicorn import __version__, SERVER_SOFTWARE + + +class Arbiter(object): + """ + Arbiter maintain the workers processes alive. It launches or + kills them if needed. It also manages application reloading + via SIGHUP/USR2. + """ + + # A flag indicating if a worker failed to + # to boot. If a worker process exist with + # this error code, the arbiter will terminate. + WORKER_BOOT_ERROR = 3 + + # A flag indicating if an application failed to be loaded + APP_LOAD_ERROR = 4 + + START_CTX = {} + + LISTENERS = [] + WORKERS = {} + PIPE = [] + + # I love dynamic languages + SIG_QUEUE = [] + SIGNALS = [getattr(signal, "SIG%s" % x) + for x in "HUP QUIT INT TERM TTIN TTOU USR1 USR2 WINCH".split()] + SIG_NAMES = dict( + (getattr(signal, name), name[3:].lower()) for name in dir(signal) + if name[:3] == "SIG" and name[3] != "_" + ) + + def __init__(self, app): + os.environ["SERVER_SOFTWARE"] = SERVER_SOFTWARE + + self._num_workers = None + self.setup(app) + + self.pidfile = None + self.worker_age = 0 + self.reexec_pid = 0 + self.master_name = "Master" + + cwd = util.getcwd() + + args = sys.argv[:] + args.insert(0, sys.executable) + + # init start context + self.START_CTX = { + "args": args, + "cwd": cwd, + 0: sys.executable + } + + def _get_num_workers(self): + return self._num_workers + + def _set_num_workers(self, value): + old_value = self._num_workers + self._num_workers = value + self.cfg.nworkers_changed(self, value, old_value) + num_workers = property(_get_num_workers, _set_num_workers) + + def setup(self, app): + self.app = app + self.cfg = app.cfg + self.log = self.cfg.logger_class(app.cfg) + + # reopen files + if 'GUNICORN_FD' in os.environ: + self.log.reopen_files() + + self.worker_class = self.cfg.worker_class + self.address = self.cfg.address + self.num_workers = self.cfg.workers + self.timeout = self.cfg.timeout + self.proc_name = self.cfg.proc_name + + self.log.debug('Current configuration:\n{0}'.format( + '\n'.join( + ' {0}: {1}'.format(config, value.value) + for config, value + in sorted(self.cfg.settings.items(), + key=lambda setting: setting[1])))) + + # set enviroment' variables + if self.cfg.env: + for k, v in self.cfg.env.items(): + os.environ[k] = v + + if self.cfg.preload_app: + self.app.wsgi() + + def start(self): + """\ + Initialize the arbiter. Start listening and set pidfile if needed. + """ + self.log.info("Starting gunicorn %s", __version__) + + self.pid = os.getpid() + if self.cfg.pidfile is not None: + self.pidfile = Pidfile(self.cfg.pidfile) + self.pidfile.create(self.pid) + self.cfg.on_starting(self) + + self.init_signals() + if not self.LISTENERS: + self.LISTENERS = create_sockets(self.cfg, self.log) + + listeners_str = ",".join([str(l) for l in self.LISTENERS]) + self.log.debug("Arbiter booted") + self.log.info("Listening at: %s (%s)", listeners_str, self.pid) + self.log.info("Using worker: %s", self.cfg.worker_class_str) + + # check worker class requirements + if hasattr(self.worker_class, "check_config"): + self.worker_class.check_config(self.cfg, self.log) + + self.cfg.when_ready(self) + + def init_signals(self): + """\ + Initialize master signal handling. Most of the signals + are queued. Child signals only wake up the master. + """ + # close old PIPE + if self.PIPE: + [os.close(p) for p in self.PIPE] + + # initialize the pipe + self.PIPE = pair = os.pipe() + for p in pair: + util.set_non_blocking(p) + util.close_on_exec(p) + + self.log.close_on_exec() + + # initialize all signals + [signal.signal(s, self.signal) for s in self.SIGNALS] + signal.signal(signal.SIGCHLD, self.handle_chld) + + def signal(self, sig, frame): + if len(self.SIG_QUEUE) < 5: + self.SIG_QUEUE.append(sig) + self.wakeup() + + def run(self): + "Main master loop." + self.start() + util._setproctitle("master [%s]" % self.proc_name) + + self.manage_workers() + while True: + try: + sig = self.SIG_QUEUE.pop(0) if len(self.SIG_QUEUE) else None + if sig is None: + self.sleep() + self.murder_workers() + self.manage_workers() + continue + + if sig not in self.SIG_NAMES: + self.log.info("Ignoring unknown signal: %s", sig) + continue + + signame = self.SIG_NAMES.get(sig) + handler = getattr(self, "handle_%s" % signame, None) + if not handler: + self.log.error("Unhandled signal: %s", signame) + continue + self.log.info("Handling signal: %s", signame) + handler() + self.wakeup() + except StopIteration: + self.halt() + except KeyboardInterrupt: + self.halt() + except HaltServer as inst: + self.halt(reason=inst.reason, exit_status=inst.exit_status) + except SystemExit: + raise + except Exception: + self.log.info("Unhandled exception in main loop:\n%s", + traceback.format_exc()) + self.stop(False) + if self.pidfile is not None: + self.pidfile.unlink() + sys.exit(-1) + + def handle_chld(self, sig, frame): + "SIGCHLD handling" + self.reap_workers() + self.wakeup() + + def handle_hup(self): + """\ + HUP handling. + - Reload configuration + - Start the new worker processes with a new configuration + - Gracefully shutdown the old worker processes + """ + self.log.info("Hang up: %s", self.master_name) + self.reload() + + def handle_term(self): + "SIGTERM handling" + raise StopIteration + + def handle_int(self): + "SIGINT handling" + self.stop(False) + raise StopIteration + + def handle_quit(self): + "SIGQUIT handling" + self.stop(False) + raise StopIteration + + def handle_ttin(self): + """\ + SIGTTIN handling. + Increases the number of workers by one. + """ + self.num_workers += 1 + self.manage_workers() + + def handle_ttou(self): + """\ + SIGTTOU handling. + Decreases the number of workers by one. + """ + if self.num_workers <= 1: + return + self.num_workers -= 1 + self.manage_workers() + + def handle_usr1(self): + """\ + SIGUSR1 handling. + Kill all workers by sending them a SIGUSR1 + """ + self.log.reopen_files() + self.kill_workers(signal.SIGUSR1) + + def handle_usr2(self): + """\ + SIGUSR2 handling. + Creates a new master/worker set as a slave of the current + master without affecting old workers. Use this to do live + deployment with the ability to backout a change. + """ + self.reexec() + + def handle_winch(self): + "SIGWINCH handling" + if self.cfg.daemon: + self.log.info("graceful stop of workers") + self.num_workers = 0 + self.kill_workers(signal.SIGTERM) + else: + self.log.debug("SIGWINCH ignored. Not daemonized") + + def wakeup(self): + """\ + Wake up the arbiter by writing to the PIPE + """ + try: + os.write(self.PIPE[1], b'.') + except IOError as e: + if e.errno not in [errno.EAGAIN, errno.EINTR]: + raise + + def halt(self, reason=None, exit_status=0): + """ halt arbiter """ + self.stop() + self.log.info("Shutting down: %s", self.master_name) + if reason is not None: + self.log.info("Reason: %s", reason) + if self.pidfile is not None: + self.pidfile.unlink() + self.cfg.on_exit(self) + sys.exit(exit_status) + + def sleep(self): + """\ + Sleep until PIPE is readable or we timeout. + A readable PIPE means a signal occurred. + """ + try: + ready = select.select([self.PIPE[0]], [], [], 1.0) + if not ready[0]: + return + while os.read(self.PIPE[0], 1): + pass + except select.error as e: + if e.args[0] not in [errno.EAGAIN, errno.EINTR]: + raise + except OSError as e: + if e.errno not in [errno.EAGAIN, errno.EINTR]: + raise + except KeyboardInterrupt: + sys.exit() + + def stop(self, graceful=True): + """\ + Stop workers + + :attr graceful: boolean, If True (the default) workers will be + killed gracefully (ie. trying to wait for the current connection) + """ + self.LISTENERS = [] + sig = signal.SIGTERM + if not graceful: + sig = signal.SIGQUIT + limit = time.time() + self.cfg.graceful_timeout + # instruct the workers to exit + self.kill_workers(sig) + # wait until the graceful timeout + while self.WORKERS and time.time() < limit: + time.sleep(0.1) + + self.kill_workers(signal.SIGKILL) + + def reexec(self): + """\ + Relaunch the master and workers. + """ + if self.pidfile is not None: + self.pidfile.rename("%s.oldbin" % self.pidfile.fname) + + self.reexec_pid = os.fork() + if self.reexec_pid != 0: + self.master_name = "Old Master" + return + + environ = self.cfg.env_orig.copy() + fds = [l.fileno() for l in self.LISTENERS] + environ['GUNICORN_FD'] = ",".join([str(fd) for fd in fds]) + + os.chdir(self.START_CTX['cwd']) + self.cfg.pre_exec(self) + + # exec the process using the original environnement + os.execvpe(self.START_CTX[0], self.START_CTX['args'], environ) + + def reload(self): + old_address = self.cfg.address + + # reset old environement + for k in self.cfg.env: + if k in self.cfg.env_orig: + # reset the key to the value it had before + # we launched gunicorn + os.environ[k] = self.cfg.env_orig[k] + else: + # delete the value set by gunicorn + try: + del os.environ[k] + except KeyError: + pass + + # reload conf + self.app.reload() + self.setup(self.app) + + # reopen log files + self.log.reopen_files() + + # do we need to change listener ? + if old_address != self.cfg.address: + # close all listeners + [l.close() for l in self.LISTENERS] + # init new listeners + self.LISTENERS = create_sockets(self.cfg, self.log) + self.log.info("Listening at: %s", ",".join(str(self.LISTENERS))) + + # do some actions on reload + self.cfg.on_reload(self) + + # unlink pidfile + if self.pidfile is not None: + self.pidfile.unlink() + + # create new pidfile + if self.cfg.pidfile is not None: + self.pidfile = Pidfile(self.cfg.pidfile) + self.pidfile.create(self.pid) + + # set new proc_name + util._setproctitle("master [%s]" % self.proc_name) + + # spawn new workers + for i in range(self.cfg.workers): + self.spawn_worker() + + # manage workers + self.manage_workers() + + def murder_workers(self): + """\ + Kill unused/idle workers + """ + if not self.timeout: + return + workers = list(self.WORKERS.items()) + for (pid, worker) in workers: + try: + if time.time() - worker.tmp.last_update() <= self.timeout: + continue + except ValueError: + continue + + if not worker.aborted: + self.log.critical("WORKER TIMEOUT (pid:%s)", pid) + worker.aborted = True + self.kill_worker(pid, signal.SIGABRT) + else: + self.kill_worker(pid, signal.SIGKILL) + + def reap_workers(self): + """\ + Reap workers to avoid zombie processes + """ + try: + while True: + wpid, status = os.waitpid(-1, os.WNOHANG) + if not wpid: + break + if self.reexec_pid == wpid: + self.reexec_pid = 0 + else: + # A worker said it cannot boot. We'll shutdown + # to avoid infinite start/stop cycles. + exitcode = status >> 8 + if exitcode == self.WORKER_BOOT_ERROR: + reason = "Worker failed to boot." + raise HaltServer(reason, self.WORKER_BOOT_ERROR) + if exitcode == self.APP_LOAD_ERROR: + reason = "App failed to load." + raise HaltServer(reason, self.APP_LOAD_ERROR) + worker = self.WORKERS.pop(wpid, None) + if not worker: + continue + worker.tmp.close() + except OSError as e: + if e.errno != errno.ECHILD: + raise + + def manage_workers(self): + """\ + Maintain the number of workers by spawning or killing + as required. + """ + if len(self.WORKERS.keys()) < self.num_workers: + self.spawn_workers() + + workers = self.WORKERS.items() + workers = sorted(workers, key=lambda w: w[1].age) + while len(workers) > self.num_workers: + (pid, _) = workers.pop(0) + self.kill_worker(pid, signal.SIGTERM) + + self.log.debug("{0} workers".format(len(workers)), + extra={"metric": "gunicorn.workers", + "value": len(workers), + "mtype": "gauge"}) + + def spawn_worker(self): + self.worker_age += 1 + worker = self.worker_class(self.worker_age, self.pid, self.LISTENERS, + self.app, self.timeout / 2.0, + self.cfg, self.log) + self.cfg.pre_fork(self, worker) + pid = os.fork() + if pid != 0: + self.WORKERS[pid] = worker + return pid + + # Process Child + worker_pid = os.getpid() + try: + util._setproctitle("worker [%s]" % self.proc_name) + self.log.info("Booting worker with pid: %s", worker_pid) + self.cfg.post_fork(self, worker) + worker.init_process() + sys.exit(0) + except SystemExit: + raise + except AppImportError as e: + self.log.debug("Exception while loading the application: \n%s", + traceback.format_exc()) + print("%s" % e, file=sys.stderr) + sys.stderr.flush() + sys.exit(self.APP_LOAD_ERROR) + except: + self.log.exception("Exception in worker process:\n%s", + traceback.format_exc()) + if not worker.booted: + sys.exit(self.WORKER_BOOT_ERROR) + sys.exit(-1) + finally: + self.log.info("Worker exiting (pid: %s)", worker_pid) + try: + worker.tmp.close() + self.cfg.worker_exit(self, worker) + except: + pass + + def spawn_workers(self): + """\ + Spawn new workers as needed. + + This is where a worker process leaves the main loop + of the master process. + """ + + for i in range(self.num_workers - len(self.WORKERS.keys())): + self.spawn_worker() + time.sleep(0.1 * random.random()) + + def kill_workers(self, sig): + """\ + Kill all workers with the signal `sig` + :attr sig: `signal.SIG*` value + """ + worker_pids = list(self.WORKERS.keys()) + for pid in worker_pids: + self.kill_worker(pid, sig) + + def kill_worker(self, pid, sig): + """\ + Kill a worker + + :attr pid: int, worker pid + :attr sig: `signal.SIG*` value + """ + try: + os.kill(pid, sig) + except OSError as e: + if e.errno == errno.ESRCH: + try: + worker = self.WORKERS.pop(pid) + worker.tmp.close() + self.cfg.worker_exit(self, worker) + return + except (KeyError, OSError): + return + raise diff --git a/env/lib/python2.7/site-packages/gunicorn/arbiter.pyc b/env/lib/python2.7/site-packages/gunicorn/arbiter.pyc new file mode 100644 index 0000000..f5e2092 Binary files /dev/null and b/env/lib/python2.7/site-packages/gunicorn/arbiter.pyc differ diff --git a/env/lib/python2.7/site-packages/gunicorn/argparse_compat.py b/env/lib/python2.7/site-packages/gunicorn/argparse_compat.py new file mode 100644 index 0000000..32d948c --- /dev/null +++ b/env/lib/python2.7/site-packages/gunicorn/argparse_compat.py @@ -0,0 +1,2362 @@ +# Author: Steven J. Bethard . + +"""Command-line parsing library + +This module is an optparse-inspired command-line parsing library that: + + - handles both optional and positional arguments + - produces highly informative usage messages + - supports parsers that dispatch to sub-parsers + +The following is a simple usage example that sums integers from the +command-line and writes the result to a file:: + + parser = argparse.ArgumentParser( + description='sum the integers at the command line') + parser.add_argument( + 'integers', metavar='int', nargs='+', type=int, + help='an integer to be summed') + parser.add_argument( + '--log', default=sys.stdout, type=argparse.FileType('w'), + help='the file where the sum should be written') + args = parser.parse_args() + args.log.write('%s' % sum(args.integers)) + args.log.close() + +The module contains the following public classes: + + - ArgumentParser -- The main entry point for command-line parsing. As the + example above shows, the add_argument() method is used to populate + the parser with actions for optional and positional arguments. Then + the parse_args() method is invoked to convert the args at the + command-line into an object with attributes. + + - ArgumentError -- The exception raised by ArgumentParser objects when + there are errors with the parser's actions. Errors raised while + parsing the command-line are caught by ArgumentParser and emitted + as command-line messages. + + - FileType -- A factory for defining types of files to be created. As the + example above shows, instances of FileType are typically passed as + the type= argument of add_argument() calls. + + - Action -- The base class for parser actions. Typically actions are + selected by passing strings like 'store_true' or 'append_const' to + the action= argument of add_argument(). However, for greater + customization of ArgumentParser actions, subclasses of Action may + be defined and passed as the action= argument. + + - HelpFormatter, RawDescriptionHelpFormatter, RawTextHelpFormatter, + ArgumentDefaultsHelpFormatter -- Formatter classes which + may be passed as the formatter_class= argument to the + ArgumentParser constructor. HelpFormatter is the default, + RawDescriptionHelpFormatter and RawTextHelpFormatter tell the parser + not to change the formatting for help text, and + ArgumentDefaultsHelpFormatter adds information about argument defaults + to the help. + +All other classes in this module are considered implementation details. +(Also note that HelpFormatter and RawDescriptionHelpFormatter are only +considered public as object names -- the API of the formatter objects is +still considered an implementation detail.) +""" + +__version__ = '1.2.1' +__all__ = [ + 'ArgumentParser', + 'ArgumentError', + 'ArgumentTypeError', + 'FileType', + 'HelpFormatter', + 'ArgumentDefaultsHelpFormatter', + 'RawDescriptionHelpFormatter', + 'RawTextHelpFormatter', + 'Namespace', + 'Action', + 'ONE_OR_MORE', + 'OPTIONAL', + 'PARSER', + 'REMAINDER', + 'SUPPRESS', + 'ZERO_OR_MORE', +] + + +import copy as _copy +import os as _os +import re as _re +import sys as _sys +import textwrap as _textwrap + +from gettext import gettext as _ + +try: + set +except NameError: + # for python < 2.4 compatibility (sets module is there since 2.3): + from sets import Set as set + +try: + basestring +except NameError: + basestring = str + +try: + sorted +except NameError: + # for python < 2.4 compatibility: + def sorted(iterable, reverse=False): + result = list(iterable) + result.sort() + if reverse: + result.reverse() + return result + + +def _callable(obj): + return hasattr(obj, '__call__') or hasattr(obj, '__bases__') + + +SUPPRESS = '==SUPPRESS==' + +OPTIONAL = '?' +ZERO_OR_MORE = '*' +ONE_OR_MORE = '+' +PARSER = 'A...' +REMAINDER = '...' +_UNRECOGNIZED_ARGS_ATTR = '_unrecognized_args' + +# ============================= +# Utility functions and classes +# ============================= + +class _AttributeHolder(object): + """Abstract base class that provides __repr__. + + The __repr__ method returns a string in the format:: + ClassName(attr=name, attr=name, ...) + The attributes are determined either by a class-level attribute, + '_kwarg_names', or by inspecting the instance __dict__. + """ + + def __repr__(self): + type_name = type(self).__name__ + arg_strings = [] + for arg in self._get_args(): + arg_strings.append(repr(arg)) + for name, value in self._get_kwargs(): + arg_strings.append('%s=%r' % (name, value)) + return '%s(%s)' % (type_name, ', '.join(arg_strings)) + + def _get_kwargs(self): + return sorted(self.__dict__.items()) + + def _get_args(self): + return [] + + +def _ensure_value(namespace, name, value): + if getattr(namespace, name, None) is None: + setattr(namespace, name, value) + return getattr(namespace, name) + + +# =============== +# Formatting Help +# =============== + +class HelpFormatter(object): + """Formatter for generating usage messages and argument help strings. + + Only the name of this class is considered a public API. All the methods + provided by the class are considered an implementation detail. + """ + + def __init__(self, + prog, + indent_increment=2, + max_help_position=24, + width=None): + + # default setting for width + if width is None: + try: + width = int(_os.environ['COLUMNS']) + except (KeyError, ValueError): + width = 80 + width -= 2 + + self._prog = prog + self._indent_increment = indent_increment + self._max_help_position = max_help_position + self._width = width + + self._current_indent = 0 + self._level = 0 + self._action_max_length = 0 + + self._root_section = self._Section(self, None) + self._current_section = self._root_section + + self._whitespace_matcher = _re.compile(r'\s+') + self._long_break_matcher = _re.compile(r'\n\n\n+') + + # =============================== + # Section and indentation methods + # =============================== + def _indent(self): + self._current_indent += self._indent_increment + self._level += 1 + + def _dedent(self): + self._current_indent -= self._indent_increment + assert self._current_indent >= 0, 'Indent decreased below 0.' + self._level -= 1 + + class _Section(object): + + def __init__(self, formatter, parent, heading=None): + self.formatter = formatter + self.parent = parent + self.heading = heading + self.items = [] + + def format_help(self): + # format the indented section + if self.parent is not None: + self.formatter._indent() + join = self.formatter._join_parts + for func, args in self.items: + func(*args) + item_help = join([func(*args) for func, args in self.items]) + if self.parent is not None: + self.formatter._dedent() + + # return nothing if the section was empty + if not item_help: + return '' + + # add the heading if the section was non-empty + if self.heading is not SUPPRESS and self.heading is not None: + current_indent = self.formatter._current_indent + heading = '%*s%s:\n' % (current_indent, '', self.heading) + else: + heading = '' + + # join the section-initial newline, the heading and the help + return join(['\n', heading, item_help, '\n']) + + def _add_item(self, func, args): + self._current_section.items.append((func, args)) + + # ======================== + # Message building methods + # ======================== + def start_section(self, heading): + self._indent() + section = self._Section(self, self._current_section, heading) + self._add_item(section.format_help, []) + self._current_section = section + + def end_section(self): + self._current_section = self._current_section.parent + self._dedent() + + def add_text(self, text): + if text is not SUPPRESS and text is not None: + self._add_item(self._format_text, [text]) + + def add_usage(self, usage, actions, groups, prefix=None): + if usage is not SUPPRESS: + args = usage, actions, groups, prefix + self._add_item(self._format_usage, args) + + def add_argument(self, action): + if action.help is not SUPPRESS: + + # find all invocations + get_invocation = self._format_action_invocation + invocations = [get_invocation(action)] + for subaction in self._iter_indented_subactions(action): + invocations.append(get_invocation(subaction)) + + # update the maximum item length + invocation_length = max([len(s) for s in invocations]) + action_length = invocation_length + self._current_indent + self._action_max_length = max(self._action_max_length, + action_length) + + # add the item to the list + self._add_item(self._format_action, [action]) + + def add_arguments(self, actions): + for action in actions: + self.add_argument(action) + + # ======================= + # Help-formatting methods + # ======================= + def format_help(self): + help = self._root_section.format_help() + if help: + help = self._long_break_matcher.sub('\n\n', help) + help = help.strip('\n') + '\n' + return help + + def _join_parts(self, part_strings): + return ''.join([part + for part in part_strings + if part and part is not SUPPRESS]) + + def _format_usage(self, usage, actions, groups, prefix): + if prefix is None: + prefix = _('usage: ') + + # if usage is specified, use that + if usage is not None: + usage = usage % dict(prog=self._prog) + + # if no optionals or positionals are available, usage is just prog + elif usage is None and not actions: + usage = '%(prog)s' % dict(prog=self._prog) + + # if optionals and positionals are available, calculate usage + elif usage is None: + prog = '%(prog)s' % dict(prog=self._prog) + + # split optionals from positionals + optionals = [] + positionals = [] + for action in actions: + if action.option_strings: + optionals.append(action) + else: + positionals.append(action) + + # build full usage string + format = self._format_actions_usage + action_usage = format(optionals + positionals, groups) + usage = ' '.join([s for s in [prog, action_usage] if s]) + + # wrap the usage parts if it's too long + text_width = self._width - self._current_indent + if len(prefix) + len(usage) > text_width: + + # break usage into wrappable parts + part_regexp = r'\(.*?\)+|\[.*?\]+|\S+' + opt_usage = format(optionals, groups) + pos_usage = format(positionals, groups) + opt_parts = _re.findall(part_regexp, opt_usage) + pos_parts = _re.findall(part_regexp, pos_usage) + assert ' '.join(opt_parts) == opt_usage + assert ' '.join(pos_parts) == pos_usage + + # helper for wrapping lines + def get_lines(parts, indent, prefix=None): + lines = [] + line = [] + if prefix is not None: + line_len = len(prefix) - 1 + else: + line_len = len(indent) - 1 + for part in parts: + if line_len + 1 + len(part) > text_width: + lines.append(indent + ' '.join(line)) + line = [] + line_len = len(indent) - 1 + line.append(part) + line_len += len(part) + 1 + if line: + lines.append(indent + ' '.join(line)) + if prefix is not None: + lines[0] = lines[0][len(indent):] + return lines + + # if prog is short, follow it with optionals or positionals + if len(prefix) + len(prog) <= 0.75 * text_width: + indent = ' ' * (len(prefix) + len(prog) + 1) + if opt_parts: + lines = get_lines([prog] + opt_parts, indent, prefix) + lines.extend(get_lines(pos_parts, indent)) + elif pos_parts: + lines = get_lines([prog] + pos_parts, indent, prefix) + else: + lines = [prog] + + # if prog is long, put it on its own line + else: + indent = ' ' * len(prefix) + parts = opt_parts + pos_parts + lines = get_lines(parts, indent) + if len(lines) > 1: + lines = [] + lines.extend(get_lines(opt_parts, indent)) + lines.extend(get_lines(pos_parts, indent)) + lines = [prog] + lines + + # join lines into usage + usage = '\n'.join(lines) + + # prefix with 'usage:' + return '%s%s\n\n' % (prefix, usage) + + def _format_actions_usage(self, actions, groups): + # find group indices and identify actions in groups + group_actions = set() + inserts = {} + for group in groups: + try: + start = actions.index(group._group_actions[0]) + except ValueError: + continue + else: + end = start + len(group._group_actions) + if actions[start:end] == group._group_actions: + for action in group._group_actions: + group_actions.add(action) + if not group.required: + if start in inserts: + inserts[start] += ' [' + else: + inserts[start] = '[' + inserts[end] = ']' + else: + if start in inserts: + inserts[start] += ' (' + else: + inserts[start] = '(' + inserts[end] = ')' + for i in range(start + 1, end): + inserts[i] = '|' + + # collect all actions format strings + parts = [] + for i, action in enumerate(actions): + + # suppressed arguments are marked with None + # remove | separators for suppressed arguments + if action.help is SUPPRESS: + parts.append(None) + if inserts.get(i) == '|': + inserts.pop(i) + elif inserts.get(i + 1) == '|': + inserts.pop(i + 1) + + # produce all arg strings + elif not action.option_strings: + part = self._format_args(action, action.dest) + + # if it's in a group, strip the outer [] + if action in group_actions: + if part[0] == '[' and part[-1] == ']': + part = part[1:-1] + + # add the action string to the list + parts.append(part) + + # produce the first way to invoke the option in brackets + else: + option_string = action.option_strings[0] + + # if the Optional doesn't take a value, format is: + # -s or --long + if action.nargs == 0: + part = '%s' % option_string + + # if the Optional takes a value, format is: + # -s ARGS or --long ARGS + else: + default = action.dest.upper() + args_string = self._format_args(action, default) + part = '%s %s' % (option_string, args_string) + + # make it look optional if it's not required or in a group + if not action.required and action not in group_actions: + part = '[%s]' % part + + # add the action string to the list + parts.append(part) + + # insert things at the necessary indices + for i in sorted(inserts, reverse=True): + parts[i:i] = [inserts[i]] + + # join all the action items with spaces + text = ' '.join([item for item in parts if item is not None]) + + # clean up separators for mutually exclusive groups + open = r'[\[(]' + close = r'[\])]' + text = _re.sub(r'(%s) ' % open, r'\1', text) + text = _re.sub(r' (%s)' % close, r'\1', text) + text = _re.sub(r'%s *%s' % (open, close), r'', text) + text = _re.sub(r'\(([^|]*)\)', r'\1', text) + text = text.strip() + + # return the text + return text + + def _format_text(self, text): + if '%(prog)' in text: + text = text % dict(prog=self._prog) + text_width = self._width - self._current_indent + indent = ' ' * self._current_indent + return self._fill_text(text, text_width, indent) + '\n\n' + + def _format_action(self, action): + # determine the required width and the entry label + help_position = min(self._action_max_length + 2, + self._max_help_position) + help_width = self._width - help_position + action_width = help_position - self._current_indent - 2 + action_header = self._format_action_invocation(action) + + # ho nelp; start on same line and add a final newline + if not action.help: + tup = self._current_indent, '', action_header + action_header = '%*s%s\n' % tup + + # short action name; start on the same line and pad two spaces + elif len(action_header) <= action_width: + tup = self._current_indent, '', action_width, action_header + action_header = '%*s%-*s ' % tup + indent_first = 0 + + # long action name; start on the next line + else: + tup = self._current_indent, '', action_header + action_header = '%*s%s\n' % tup + indent_first = help_position + + # collect the pieces of the action help + parts = [action_header] + + # if there was help for the action, add lines of help text + if action.help: + help_text = self._expand_help(action) + help_lines = self._split_lines(help_text, help_width) + parts.append('%*s%s\n' % (indent_first, '', help_lines[0])) + for line in help_lines[1:]: + parts.append('%*s%s\n' % (help_position, '', line)) + + # or add a newline if the description doesn't end with one + elif not action_header.endswith('\n'): + parts.append('\n') + + # if there are any sub-actions, add their help as well + for subaction in self._iter_indented_subactions(action): + parts.append(self._format_action(subaction)) + + # return a single string + return self._join_parts(parts) + + def _format_action_invocation(self, action): + if not action.option_strings: + metavar, = self._metavar_formatter(action, action.dest)(1) + return metavar + + else: + parts = [] + + # if the Optional doesn't take a value, format is: + # -s, --long + if action.nargs == 0: + parts.extend(action.option_strings) + + # if the Optional takes a value, format is: + # -s ARGS, --long ARGS + else: + default = action.dest.upper() + args_string = self._format_args(action, default) + for option_string in action.option_strings: + parts.append('%s %s' % (option_string, args_string)) + + return ', '.join(parts) + + def _metavar_formatter(self, action, default_metavar): + if action.metavar is not None: + result = action.metavar + elif action.choices is not None: + choice_strs = [str(choice) for choice in action.choices] + result = '{%s}' % ','.join(choice_strs) + else: + result = default_metavar + + def format(tuple_size): + if isinstance(result, tuple): + return result + else: + return (result, ) * tuple_size + return format + + def _format_args(self, action, default_metavar): + get_metavar = self._metavar_formatter(action, default_metavar) + if action.nargs is None: + result = '%s' % get_metavar(1) + elif action.nargs == OPTIONAL: + result = '[%s]' % get_metavar(1) + elif action.nargs == ZERO_OR_MORE: + result = '[%s [%s ...]]' % get_metavar(2) + elif action.nargs == ONE_OR_MORE: + result = '%s [%s ...]' % get_metavar(2) + elif action.nargs == REMAINDER: + result = '...' + elif action.nargs == PARSER: + result = '%s ...' % get_metavar(1) + else: + formats = ['%s' for _ in range(action.nargs)] + result = ' '.join(formats) % get_metavar(action.nargs) + return result + + def _expand_help(self, action): + params = dict(vars(action), prog=self._prog) + for name in list(params): + if params[name] is SUPPRESS: + del params[name] + for name in list(params): + if hasattr(params[name], '__name__'): + params[name] = params[name].__name__ + if params.get('choices') is not None: + choices_str = ', '.join([str(c) for c in params['choices']]) + params['choices'] = choices_str + return self._get_help_string(action) % params + + def _iter_indented_subactions(self, action): + try: + get_subactions = action._get_subactions + except AttributeError: + pass + else: + self._indent() + for subaction in get_subactions(): + yield subaction + self._dedent() + + def _split_lines(self, text, width): + text = self._whitespace_matcher.sub(' ', text).strip() + return _textwrap.wrap(text, width) + + def _fill_text(self, text, width, indent): + text = self._whitespace_matcher.sub(' ', text).strip() + return _textwrap.fill(text, width, initial_indent=indent, + subsequent_indent=indent) + + def _get_help_string(self, action): + return action.help + + +class RawDescriptionHelpFormatter(HelpFormatter): + """Help message formatter which retains any formatting in descriptions. + + Only the name of this class is considered a public API. All the methods + provided by the class are considered an implementation detail. + """ + + def _fill_text(self, text, width, indent): + return ''.join([indent + line for line in text.splitlines(True)]) + + +class RawTextHelpFormatter(RawDescriptionHelpFormatter): + """Help message formatter which retains formatting of all help text. + + Only the name of this class is considered a public API. All the methods + provided by the class are considered an implementation detail. + """ + + def _split_lines(self, text, width): + return text.splitlines() + + +class ArgumentDefaultsHelpFormatter(HelpFormatter): + """Help message formatter which adds default values to argument help. + + Only the name of this class is considered a public API. All the methods + provided by the class are considered an implementation detail. + """ + + def _get_help_string(self, action): + help = action.help + if '%(default)' not in action.help: + if action.default is not SUPPRESS: + defaulting_nargs = [OPTIONAL, ZERO_OR_MORE] + if action.option_strings or action.nargs in defaulting_nargs: + help += ' (default: %(default)s)' + return help + + +# ===================== +# Options and Arguments +# ===================== + +def _get_action_name(argument): + if argument is None: + return None + elif argument.option_strings: + return '/'.join(argument.option_strings) + elif argument.metavar not in (None, SUPPRESS): + return argument.metavar + elif argument.dest not in (None, SUPPRESS): + return argument.dest + else: + return None + + +class ArgumentError(Exception): + """An error from creating or using an argument (optional or positional). + + The string value of this exception is the message, augmented with + information about the argument that caused it. + """ + + def __init__(self, argument, message): + self.argument_name = _get_action_name(argument) + self.message = message + + def __str__(self): + if self.argument_name is None: + format = '%(message)s' + else: + format = 'argument %(argument_name)s: %(message)s' + return format % dict(message=self.message, + argument_name=self.argument_name) + + +class ArgumentTypeError(Exception): + """An error from trying to convert a command line string to a type.""" + pass + + +# ============== +# Action classes +# ============== + +class Action(_AttributeHolder): + """Information about how to convert command line strings to Python objects. + + Action objects are used by an ArgumentParser to represent the information + needed to parse a single argument from one or more strings from the + command line. The keyword arguments to the Action constructor are also + all attributes of Action instances. + + Keyword Arguments: + + - option_strings -- A list of command-line option strings which + should be associated with this action. + + - dest -- The name of the attribute to hold the created object(s) + + - nargs -- The number of command-line arguments that should be + consumed. By default, one argument will be consumed and a single + value will be produced. Other values include: + - N (an integer) consumes N arguments (and produces a list) + - '?' consumes zero or one arguments + - '*' consumes zero or more arguments (and produces a list) + - '+' consumes one or more arguments (and produces a list) + Note that the difference between the default and nargs=1 is that + with the default, a single value will be produced, while with + nargs=1, a list containing a single value will be produced. + + - const -- The value to be produced if the option is specified and the + option uses an action that takes no values. + + - default -- The value to be produced if the option is not specified. + + - type -- The type which the command-line arguments should be converted + to, should be one of 'string', 'int', 'float', 'complex' or a + callable object that accepts a single string argument. If None, + 'string' is assumed. + + - choices -- A container of values that should be allowed. If not None, + after a command-line argument has been converted to the appropriate + type, an exception will be raised if it is not a member of this + collection. + + - required -- True if the action must always be specified at the + command line. This is only meaningful for optional command-line + arguments. + + - help -- The help string describing the argument. + + - metavar -- The name to be used for the option's argument with the + help string. If None, the 'dest' value will be used as the name. + """ + + def __init__(self, + option_strings, + dest, + nargs=None, + const=None, + default=None, + type=None, + choices=None, + required=False, + help=None, + metavar=None): + self.option_strings = option_strings + self.dest = dest + self.nargs = nargs + self.const = const + self.default = default + self.type = type + self.choices = choices + self.required = required + self.help = help + self.metavar = metavar + + def _get_kwargs(self): + names = [ + 'option_strings', + 'dest', + 'nargs', + 'const', + 'default', + 'type', + 'choices', + 'help', + 'metavar', + ] + return [(name, getattr(self, name)) for name in names] + + def __call__(self, parser, namespace, values, option_string=None): + raise NotImplementedError(_('.__call__() not defined')) + + +class _StoreAction(Action): + + def __init__(self, + option_strings, + dest, + nargs=None, + const=None, + default=None, + type=None, + choices=None, + required=False, + help=None, + metavar=None): + if nargs == 0: + raise ValueError('nargs for store actions must be > 0; if you ' + 'have nothing to store, actions such as store ' + 'true or store const may be more appropriate') + if const is not None and nargs != OPTIONAL: + raise ValueError('nargs must be %r to supply const' % OPTIONAL) + super(_StoreAction, self).__init__( + option_strings=option_strings, + dest=dest, + nargs=nargs, + const=const, + default=default, + type=type, + choices=choices, + required=required, + help=help, + metavar=metavar) + + def __call__(self, parser, namespace, values, option_string=None): + setattr(namespace, self.dest, values) + + +class _StoreConstAction(Action): + + def __init__(self, + option_strings, + dest, + const, + default=None, + required=False, + help=None, + metavar=None): + super(_StoreConstAction, self).__init__( + option_strings=option_strings, + dest=dest, + nargs=0, + const=const, + default=default, + required=required, + help=help) + + def __call__(self, parser, namespace, values, option_string=None): + setattr(namespace, self.dest, self.const) + + +class _StoreTrueAction(_StoreConstAction): + + def __init__(self, + option_strings, + dest, + default=False, + required=False, + help=None): + super(_StoreTrueAction, self).__init__( + option_strings=option_strings, + dest=dest, + const=True, + default=default, + required=required, + help=help) + + +class _StoreFalseAction(_StoreConstAction): + + def __init__(self, + option_strings, + dest, + default=True, + required=False, + help=None): + super(_StoreFalseAction, self).__init__( + option_strings=option_strings, + dest=dest, + const=False, + default=default, + required=required, + help=help) + + +class _AppendAction(Action): + + def __init__(self, + option_strings, + dest, + nargs=None, + const=None, + default=None, + type=None, + choices=None, + required=False, + help=None, + metavar=None): + if nargs == 0: + raise ValueError('nargs for append actions must be > 0; if arg ' + 'strings are not supplying the value to append, ' + 'the append const action may be more appropriate') + if const is not None and nargs != OPTIONAL: + raise ValueError('nargs must be %r to supply const' % OPTIONAL) + super(_AppendAction, self).__init__( + option_strings=option_strings, + dest=dest, + nargs=nargs, + const=const, + default=default, + type=type, + choices=choices, + required=required, + help=help, + metavar=metavar) + + def __call__(self, parser, namespace, values, option_string=None): + items = _copy.copy(_ensure_value(namespace, self.dest, [])) + items.append(values) + setattr(namespace, self.dest, items) + + +class _AppendConstAction(Action): + + def __init__(self, + option_strings, + dest, + const, + default=None, + required=False, + help=None, + metavar=None): + super(_AppendConstAction, self).__init__( + option_strings=option_strings, + dest=dest, + nargs=0, + const=const, + default=default, + required=required, + help=help, + metavar=metavar) + + def __call__(self, parser, namespace, values, option_string=None): + items = _copy.copy(_ensure_value(namespace, self.dest, [])) + items.append(self.const) + setattr(namespace, self.dest, items) + + +class _CountAction(Action): + + def __init__(self, + option_strings, + dest, + default=None, + required=False, + help=None): + super(_CountAction, self).__init__( + option_strings=option_strings, + dest=dest, + nargs=0, + default=default, + required=required, + help=help) + + def __call__(self, parser, namespace, values, option_string=None): + new_count = _ensure_value(namespace, self.dest, 0) + 1 + setattr(namespace, self.dest, new_count) + + +class _HelpAction(Action): + + def __init__(self, + option_strings, + dest=SUPPRESS, + default=SUPPRESS, + help=None): + super(_HelpAction, self).__init__( + option_strings=option_strings, + dest=dest, + default=default, + nargs=0, + help=help) + + def __call__(self, parser, namespace, values, option_string=None): + parser.print_help() + parser.exit() + + +class _VersionAction(Action): + + def __init__(self, + option_strings, + version=None, + dest=SUPPRESS, + default=SUPPRESS, + help="show program's version number and exit"): + super(_VersionAction, self).__init__( + option_strings=option_strings, + dest=dest, + default=default, + nargs=0, + help=help) + self.version = version + + def __call__(self, parser, namespace, values, option_string=None): + version = self.version + if version is None: + version = parser.version + formatter = parser._get_formatter() + formatter.add_text(version) + parser.exit(message=formatter.format_help()) + + +class _SubParsersAction(Action): + + class _ChoicesPseudoAction(Action): + + def __init__(self, name, help): + sup = super(_SubParsersAction._ChoicesPseudoAction, self) + sup.__init__(option_strings=[], dest=name, help=help) + + def __init__(self, + option_strings, + prog, + parser_class, + dest=SUPPRESS, + help=None, + metavar=None): + + self._prog_prefix = prog + self._parser_class = parser_class + self._name_parser_map = {} + self._choices_actions = [] + + super(_SubParsersAction, self).__init__( + option_strings=option_strings, + dest=dest, + nargs=PARSER, + choices=self._name_parser_map, + help=help, + metavar=metavar) + + def add_parser(self, name, **kwargs): + # set prog from the existing prefix + if kwargs.get('prog') is None: + kwargs['prog'] = '%s %s' % (self._prog_prefix, name) + + # create a pseudo-action to hold the choice help + if 'help' in kwargs: + help = kwargs.pop('help') + choice_action = self._ChoicesPseudoAction(name, help) + self._choices_actions.append(choice_action) + + # create the parser and add it to the map + parser = self._parser_class(**kwargs) + self._name_parser_map[name] = parser + return parser + + def _get_subactions(self): + return self._choices_actions + + def __call__(self, parser, namespace, values, option_string=None): + parser_name = values[0] + arg_strings = values[1:] + + # set the parser name if requested + if self.dest is not SUPPRESS: + setattr(namespace, self.dest, parser_name) + + # select the parser + try: + parser = self._name_parser_map[parser_name] + except KeyError: + tup = parser_name, ', '.join(self._name_parser_map) + msg = _('unknown parser %r (choices: %s)' % tup) + raise ArgumentError(self, msg) + + # parse all the remaining options into the namespace + # store any unrecognized options on the object, so that the top + # level parser can decide what to do with them + namespace, arg_strings = parser.parse_known_args(arg_strings, namespace) + if arg_strings: + vars(namespace).setdefault(_UNRECOGNIZED_ARGS_ATTR, []) + getattr(namespace, _UNRECOGNIZED_ARGS_ATTR).extend(arg_strings) + + +# ============== +# Type classes +# ============== + +class FileType(object): + """Factory for creating file object types + + Instances of FileType are typically passed as type= arguments to the + ArgumentParser add_argument() method. + + Keyword Arguments: + - mode -- A string indicating how the file is to be opened. Accepts the + same values as the builtin open() function. + - bufsize -- The file's desired buffer size. Accepts the same values as + the builtin open() function. + """ + + def __init__(self, mode='r', bufsize=None): + self._mode = mode + self._bufsize = bufsize + + def __call__(self, string): + # the special argument "-" means sys.std{in,out} + if string == '-': + if 'r' in self._mode: + return _sys.stdin + elif 'w' in self._mode: + return _sys.stdout + else: + msg = _('argument "-" with mode %r' % self._mode) + raise ValueError(msg) + + # all other arguments are used as file names + if self._bufsize: + return open(string, self._mode, self._bufsize) + else: + return open(string, self._mode) + + def __repr__(self): + args = [self._mode, self._bufsize] + args_str = ', '.join([repr(arg) for arg in args if arg is not None]) + return '%s(%s)' % (type(self).__name__, args_str) + +# =========================== +# Optional and Positional Parsing +# =========================== + +class Namespace(_AttributeHolder): + """Simple object for storing attributes. + + Implements equality by attribute names and values, and provides a simple + string representation. + """ + + def __init__(self, **kwargs): + for name in kwargs: + setattr(self, name, kwargs[name]) + + __hash__ = None + + def __eq__(self, other): + return vars(self) == vars(other) + + def __ne__(self, other): + return not (self == other) + + def __contains__(self, key): + return key in self.__dict__ + + +class _ActionsContainer(object): + + def __init__(self, + description, + prefix_chars, + argument_default, + conflict_handler): + super(_ActionsContainer, self).__init__() + + self.description = description + self.argument_default = argument_default + self.prefix_chars = prefix_chars + self.conflict_handler = conflict_handler + + # set up registries + self._registries = {} + + # register actions + self.register('action', None, _StoreAction) + self.register('action', 'store', _StoreAction) + self.register('action', 'store_const', _StoreConstAction) + self.register('action', 'store_true', _StoreTrueAction) + self.register('action', 'store_false', _StoreFalseAction) + self.register('action', 'append', _AppendAction) + self.register('action', 'append_const', _AppendConstAction) + self.register('action', 'count', _CountAction) + self.register('action', 'help', _HelpAction) + self.register('action', 'version', _VersionAction) + self.register('action', 'parsers', _SubParsersAction) + + # raise an exception if the conflict handler is invalid + self._get_handler() + + # action storage + self._actions = [] + self._option_string_actions = {} + + # groups + self._action_groups = [] + self._mutually_exclusive_groups = [] + + # defaults storage + self._defaults = {} + + # determines whether an "option" looks like a negative number + self._negative_number_matcher = _re.compile(r'^-\d+$|^-\d*\.\d+$') + + # whether or not there are any optionals that look like negative + # numbers -- uses a list so it can be shared and edited + self._has_negative_number_optionals = [] + + # ==================== + # Registration methods + # ==================== + def register(self, registry_name, value, object): + registry = self._registries.setdefault(registry_name, {}) + registry[value] = object + + def _registry_get(self, registry_name, value, default=None): + return self._registries[registry_name].get(value, default) + + # ================================== + # Namespace default accessor methods + # ================================== + def set_defaults(self, **kwargs): + self._defaults.update(kwargs) + + # if these defaults match any existing arguments, replace + # the previous default on the object with the new one + for action in self._actions: + if action.dest in kwargs: + action.default = kwargs[action.dest] + + def get_default(self, dest): + for action in self._actions: + if action.dest == dest and action.default is not None: + return action.default + return self._defaults.get(dest, None) + + + # ======================= + # Adding argument actions + # ======================= + def add_argument(self, *args, **kwargs): + """ + add_argument(dest, ..., name=value, ...) + add_argument(option_string, option_string, ..., name=value, ...) + """ + + # if no positional args are supplied or only one is supplied and + # it doesn't look like an option string, parse a positional + # argument + chars = self.prefix_chars + if not args or len(args) == 1 and args[0][0] not in chars: + if args and 'dest' in kwargs: + raise ValueError('dest supplied twice for positional argument') + kwargs = self._get_positional_kwargs(*args, **kwargs) + + # otherwise, we're adding an optional argument + else: + kwargs = self._get_optional_kwargs(*args, **kwargs) + + # if no default was supplied, use the parser-level default + if 'default' not in kwargs: + dest = kwargs['dest'] + if dest in self._defaults: + kwargs['default'] = self._defaults[dest] + elif self.argument_default is not None: + kwargs['default'] = self.argument_default + + # create the action object, and add it to the parser + action_class = self._pop_action_class(kwargs) + if not _callable(action_class): + raise ValueError('unknown action "%s"' % action_class) + action = action_class(**kwargs) + + # raise an error if the action type is not callable + type_func = self._registry_get('type', action.type, action.type) + if not _callable(type_func): + raise ValueError('%r is not callable' % type_func) + + return self._add_action(action) + + def add_argument_group(self, *args, **kwargs): + group = _ArgumentGroup(self, *args, **kwargs) + self._action_groups.append(group) + return group + + def add_mutually_exclusive_group(self, **kwargs): + group = _MutuallyExclusiveGroup(self, **kwargs) + self._mutually_exclusive_groups.append(group) + return group + + def _add_action(self, action): + # resolve any conflicts + self._check_conflict(action) + + # add to actions list + self._actions.append(action) + action.container = self + + # index the action by any option strings it has + for option_string in action.option_strings: + self._option_string_actions[option_string] = action + + # set the flag if any option strings look like negative numbers + for option_string in action.option_strings: + if self._negative_number_matcher.match(option_string): + if not self._has_negative_number_optionals: + self._has_negative_number_optionals.append(True) + + # return the created action + return action + + def _remove_action(self, action): + self._actions.remove(action) + + def _add_container_actions(self, container): + # collect groups by titles + title_group_map = {} + for group in self._action_groups: + if group.title in title_group_map: + msg = _('cannot merge actions - two groups are named %r') + raise ValueError(msg % (group.title)) + title_group_map[group.title] = group + + # map each action to its group + group_map = {} + for group in container._action_groups: + + # if a group with the title exists, use that, otherwise + # create a new group matching the container's group + if group.title not in title_group_map: + title_group_map[group.title] = self.add_argument_group( + title=group.title, + description=group.description, + conflict_handler=group.conflict_handler) + + # map the actions to their new group + for action in group._group_actions: + group_map[action] = title_group_map[group.title] + + # add container's mutually exclusive groups + # NOTE: if add_mutually_exclusive_group ever gains title= and + # description= then this code will need to be expanded as above + for group in container._mutually_exclusive_groups: + mutex_group = self.add_mutually_exclusive_group( + required=group.required) + + # map the actions to their new mutex group + for action in group._group_actions: + group_map[action] = mutex_group + + # add all actions to this container or their group + for action in container._actions: + group_map.get(action, self)._add_action(action) + + def _get_positional_kwargs(self, dest, **kwargs): + # make sure required is not specified + if 'required' in kwargs: + msg = _("'required' is an invalid argument for positionals") + raise TypeError(msg) + + # mark positional arguments as required if at least one is + # always required + if kwargs.get('nargs') not in [OPTIONAL, ZERO_OR_MORE]: + kwargs['required'] = True + if kwargs.get('nargs') == ZERO_OR_MORE and 'default' not in kwargs: + kwargs['required'] = True + + # return the keyword arguments with no option strings + return dict(kwargs, dest=dest, option_strings=[]) + + def _get_optional_kwargs(self, *args, **kwargs): + # determine short and long option strings + option_strings = [] + long_option_strings = [] + for option_string in args: + # error on strings that don't start with an appropriate prefix + if not option_string[0] in self.prefix_chars: + msg = _('invalid option string %r: ' + 'must start with a character %r') + tup = option_string, self.prefix_chars + raise ValueError(msg % tup) + + # strings starting with two prefix characters are long options + option_strings.append(option_string) + if option_string[0] in self.prefix_chars: + if len(option_string) > 1: + if option_string[1] in self.prefix_chars: + long_option_strings.append(option_string) + + # infer destination, '--foo-bar' -> 'foo_bar' and '-x' -> 'x' + dest = kwargs.pop('dest', None) + if dest is None: + if long_option_strings: + dest_option_string = long_option_strings[0] + else: + dest_option_string = option_strings[0] + dest = dest_option_string.lstrip(self.prefix_chars) + if not dest: + msg = _('dest= is required for options like %r') + raise ValueError(msg % option_string) + dest = dest.replace('-', '_') + + # return the updated keyword arguments + return dict(kwargs, dest=dest, option_strings=option_strings) + + def _pop_action_class(self, kwargs, default=None): + action = kwargs.pop('action', default) + return self._registry_get('action', action, action) + + def _get_handler(self): + # determine function from conflict handler string + handler_func_name = '_handle_conflict_%s' % self.conflict_handler + try: + return getattr(self, handler_func_name) + except AttributeError: + msg = _('invalid conflict_resolution value: %r') + raise ValueError(msg % self.conflict_handler) + + def _check_conflict(self, action): + + # find all options that conflict with this option + confl_optionals = [] + for option_string in action.option_strings: + if option_string in self._option_string_actions: + confl_optional = self._option_string_actions[option_string] + confl_optionals.append((option_string, confl_optional)) + + # resolve any conflicts + if confl_optionals: + conflict_handler = self._get_handler() + conflict_handler(action, confl_optionals) + + def _handle_conflict_error(self, action, conflicting_actions): + message = _('conflicting option string(s): %s') + conflict_string = ', '.join([option_string + for option_string, action + in conflicting_actions]) + raise ArgumentError(action, message % conflict_string) + + def _handle_conflict_resolve(self, action, conflicting_actions): + + # remove all conflicting options + for option_string, action in conflicting_actions: + + # remove the conflicting option + action.option_strings.remove(option_string) + self._option_string_actions.pop(option_string, None) + + # if the option now has no option string, remove it from the + # container holding it + if not action.option_strings: + action.container._remove_action(action) + + +class _ArgumentGroup(_ActionsContainer): + + def __init__(self, container, title=None, description=None, **kwargs): + # add any missing keyword arguments by checking the container + update = kwargs.setdefault + update('conflict_handler', container.conflict_handler) + update('prefix_chars', container.prefix_chars) + update('argument_default', container.argument_default) + super_init = super(_ArgumentGroup, self).__init__ + super_init(description=description, **kwargs) + + # group attributes + self.title = title + self._group_actions = [] + + # share most attributes with the container + self._registries = container._registries + self._actions = container._actions + self._option_string_actions = container._option_string_actions + self._defaults = container._defaults + self._has_negative_number_optionals = \ + container._has_negative_number_optionals + + def _add_action(self, action): + action = super(_ArgumentGroup, self)._add_action(action) + self._group_actions.append(action) + return action + + def _remove_action(self, action): + super(_ArgumentGroup, self)._remove_action(action) + self._group_actions.remove(action) + + +class _MutuallyExclusiveGroup(_ArgumentGroup): + + def __init__(self, container, required=False): + super(_MutuallyExclusiveGroup, self).__init__(container) + self.required = required + self._container = container + + def _add_action(self, action): + if action.required: + msg = _('mutually exclusive arguments must be optional') + raise ValueError(msg) + action = self._container._add_action(action) + self._group_actions.append(action) + return action + + def _remove_action(self, action): + self._container._remove_action(action) + self._group_actions.remove(action) + + +class ArgumentParser(_AttributeHolder, _ActionsContainer): + """Object for parsing command line strings into Python objects. + + Keyword Arguments: + - prog -- The name of the program (default: sys.argv[0]) + - usage -- A usage message (default: auto-generated from arguments) + - description -- A description of what the program does + - epilog -- Text following the argument descriptions + - parents -- Parsers whose arguments should be copied into this one + - formatter_class -- HelpFormatter class for printing help messages + - prefix_chars -- Characters that prefix optional arguments + - fromfile_prefix_chars -- Characters that prefix files containing + additional arguments + - argument_default -- The default value for all arguments + - conflict_handler -- String indicating how to handle conflicts + - add_help -- Add a -h/-help option + """ + + def __init__(self, + prog=None, + usage=None, + description=None, + epilog=None, + version=None, + parents=[], + formatter_class=HelpFormatter, + prefix_chars='-', + fromfile_prefix_chars=None, + argument_default=None, + conflict_handler='error', + add_help=True): + + if version is not None: + import warnings + warnings.warn( + """The "version" argument to ArgumentParser is deprecated. """ + """Please use """ + """"add_argument(..., action='version', version="N", ...)" """ + """instead""", DeprecationWarning) + + superinit = super(ArgumentParser, self).__init__ + superinit(description=description, + prefix_chars=prefix_chars, + argument_default=argument_default, + conflict_handler=conflict_handler) + + # default setting for prog + if prog is None: + prog = _os.path.basename(_sys.argv[0]) + + self.prog = prog + self.usage = usage + self.epilog = epilog + self.version = version + self.formatter_class = formatter_class + self.fromfile_prefix_chars = fromfile_prefix_chars + self.add_help = add_help + + add_group = self.add_argument_group + self._positionals = add_group(_('positional arguments')) + self._optionals = add_group(_('optional arguments')) + self._subparsers = None + + # register types + def identity(string): + return string + self.register('type', None, identity) + + # add help and version arguments if necessary + # (using explicit default to override global argument_default) + if '-' in prefix_chars: + default_prefix = '-' + else: + default_prefix = prefix_chars[0] + if self.add_help: + self.add_argument( + default_prefix+'h', default_prefix*2+'help', + action='help', default=SUPPRESS, + help=_('show this help message and exit')) + if self.version: + self.add_argument( + default_prefix+'v', default_prefix*2+'version', + action='version', default=SUPPRESS, + version=self.version, + help=_("show program's version number and exit")) + + # add parent arguments and defaults + for parent in parents: + self._add_container_actions(parent) + try: + defaults = parent._defaults + except AttributeError: + pass + else: + self._defaults.update(defaults) + + # ======================= + # Pretty __repr__ methods + # ======================= + def _get_kwargs(self): + names = [ + 'prog', + 'usage', + 'description', + 'version', + 'formatter_class', + 'conflict_handler', + 'add_help', + ] + return [(name, getattr(self, name)) for name in names] + + # ================================== + # Optional/Positional adding methods + # ================================== + def add_subparsers(self, **kwargs): + if self._subparsers is not None: + self.error(_('cannot have multiple subparser arguments')) + + # add the parser class to the arguments if it's not present + kwargs.setdefault('parser_class', type(self)) + + if 'title' in kwargs or 'description' in kwargs: + title = _(kwargs.pop('title', 'subcommands')) + description = _(kwargs.pop('description', None)) + self._subparsers = self.add_argument_group(title, description) + else: + self._subparsers = self._positionals + + # prog defaults to the usage message of this parser, skipping + # optional arguments and with no "usage:" prefix + if kwargs.get('prog') is None: + formatter = self._get_formatter() + positionals = self._get_positional_actions() + groups = self._mutually_exclusive_groups + formatter.add_usage(self.usage, positionals, groups, '') + kwargs['prog'] = formatter.format_help().strip() + + # create the parsers action and add it to the positionals list + parsers_class = self._pop_action_class(kwargs, 'parsers') + action = parsers_class(option_strings=[], **kwargs) + self._subparsers._add_action(action) + + # return the created parsers action + return action + + def _add_action(self, action): + if action.option_strings: + self._optionals._add_action(action) + else: + self._positionals._add_action(action) + return action + + def _get_optional_actions(self): + return [action + for action in self._actions + if action.option_strings] + + def _get_positional_actions(self): + return [action + for action in self._actions + if not action.option_strings] + + # ===================================== + # Command line argument parsing methods + # ===================================== + def parse_args(self, args=None, namespace=None): + args, argv = self.parse_known_args(args, namespace) + if argv: + msg = _('unrecognized arguments: %s') + self.error(msg % ' '.join(argv)) + return args + + def parse_known_args(self, args=None, namespace=None): + # args default to the system args + if args is None: + args = _sys.argv[1:] + + # default Namespace built from parser defaults + if namespace is None: + namespace = Namespace() + + # add any action defaults that aren't present + for action in self._actions: + if action.dest is not SUPPRESS: + if not hasattr(namespace, action.dest): + if action.default is not SUPPRESS: + default = action.default + if isinstance(action.default, basestring): + default = self._get_value(action, default) + setattr(namespace, action.dest, default) + + # add any parser defaults that aren't present + for dest in self._defaults: + if not hasattr(namespace, dest): + setattr(namespace, dest, self._defaults[dest]) + + # parse the arguments and exit if there are any errors + try: + namespace, args = self._parse_known_args(args, namespace) + if hasattr(namespace, _UNRECOGNIZED_ARGS_ATTR): + args.extend(getattr(namespace, _UNRECOGNIZED_ARGS_ATTR)) + delattr(namespace, _UNRECOGNIZED_ARGS_ATTR) + return namespace, args + except ArgumentError: + err = _sys.exc_info()[1] + self.error(str(err)) + + def _parse_known_args(self, arg_strings, namespace): + # replace arg strings that are file references + if self.fromfile_prefix_chars is not None: + arg_strings = self._read_args_from_files(arg_strings) + + # map all mutually exclusive arguments to the other arguments + # they can't occur with + action_conflicts = {} + for mutex_group in self._mutually_exclusive_groups: + group_actions = mutex_group._group_actions + for i, mutex_action in enumerate(mutex_group._group_actions): + conflicts = action_conflicts.setdefault(mutex_action, []) + conflicts.extend(group_actions[:i]) + conflicts.extend(group_actions[i + 1:]) + + # find all option indices, and determine the arg_string_pattern + # which has an 'O' if there is an option at an index, + # an 'A' if there is an argument, or a '-' if there is a '--' + option_string_indices = {} + arg_string_pattern_parts = [] + arg_strings_iter = iter(arg_strings) + for i, arg_string in enumerate(arg_strings_iter): + + # all args after -- are non-options + if arg_string == '--': + arg_string_pattern_parts.append('-') + for arg_string in arg_strings_iter: + arg_string_pattern_parts.append('A') + + # otherwise, add the arg to the arg strings + # and note the index if it was an option + else: + option_tuple = self._parse_optional(arg_string) + if option_tuple is None: + pattern = 'A' + else: + option_string_indices[i] = option_tuple + pattern = 'O' + arg_string_pattern_parts.append(pattern) + + # join the pieces together to form the pattern + arg_strings_pattern = ''.join(arg_string_pattern_parts) + + # converts arg strings to the appropriate and then takes the action + seen_actions = set() + seen_non_default_actions = set() + + def take_action(action, argument_strings, option_string=None): + seen_actions.add(action) + argument_values = self._get_values(action, argument_strings) + + # error if this argument is not allowed with other previously + # seen arguments, assuming that actions that use the default + # value don't really count as "present" + if argument_values is not action.default: + seen_non_default_actions.add(action) + for conflict_action in action_conflicts.get(action, []): + if conflict_action in seen_non_default_actions: + msg = _('not allowed with argument %s') + action_name = _get_action_name(conflict_action) + raise ArgumentError(action, msg % action_name) + + # take the action if we didn't receive a SUPPRESS value + # (e.g. from a default) + if argument_values is not SUPPRESS: + action(self, namespace, argument_values, option_string) + + # function to convert arg_strings into an optional action + def consume_optional(start_index): + + # get the optional identified at this index + option_tuple = option_string_indices[start_index] + action, option_string, explicit_arg = option_tuple + + # identify additional optionals in the same arg string + # (e.g. -xyz is the same as -x -y -z if no args are required) + match_argument = self._match_argument + action_tuples = [] + while True: + + # if we found no optional action, skip it + if action is None: + extras.append(arg_strings[start_index]) + return start_index + 1 + + # if there is an explicit argument, try to match the + # optional's string arguments to only this + if explicit_arg is not None: + arg_count = match_argument(action, 'A') + + # if the action is a single-dash option and takes no + # arguments, try to parse more single-dash options out + # of the tail of the option string + chars = self.prefix_chars + if arg_count == 0 and option_string[1] not in chars: + action_tuples.append((action, [], option_string)) + char = option_string[0] + option_string = char + explicit_arg[0] + new_explicit_arg = explicit_arg[1:] or None + optionals_map = self._option_string_actions + if option_string in optionals_map: + action = optionals_map[option_string] + explicit_arg = new_explicit_arg + else: + msg = _('ignored explicit argument %r') + raise ArgumentError(action, msg % explicit_arg) + + # if the action expect exactly one argument, we've + # successfully matched the option; exit the loop + elif arg_count == 1: + stop = start_index + 1 + args = [explicit_arg] + action_tuples.append((action, args, option_string)) + break + + # error if a double-dash option did not use the + # explicit argument + else: + msg = _('ignored explicit argument %r') + raise ArgumentError(action, msg % explicit_arg) + + # if there is no explicit argument, try to match the + # optional's string arguments with the following strings + # if successful, exit the loop + else: + start = start_index + 1 + selected_patterns = arg_strings_pattern[start:] + arg_count = match_argument(action, selected_patterns) + stop = start + arg_count + args = arg_strings[start:stop] + action_tuples.append((action, args, option_string)) + break + + # add the Optional to the list and return the index at which + # the Optional's string args stopped + assert action_tuples + for action, args, option_string in action_tuples: + take_action(action, args, option_string) + return stop + + # the list of Positionals left to be parsed; this is modified + # by consume_positionals() + positionals = self._get_positional_actions() + + # function to convert arg_strings into positional actions + def consume_positionals(start_index): + # match as many Positionals as possible + match_partial = self._match_arguments_partial + selected_pattern = arg_strings_pattern[start_index:] + arg_counts = match_partial(positionals, selected_pattern) + + # slice off the appropriate arg strings for each Positional + # and add the Positional and its args to the list + for action, arg_count in zip(positionals, arg_counts): + args = arg_strings[start_index: start_index + arg_count] + start_index += arg_count + take_action(action, args) + + # slice off the Positionals that we just parsed and return the + # index at which the Positionals' string args stopped + positionals[:] = positionals[len(arg_counts):] + return start_index + + # consume Positionals and Optionals alternately, until we have + # passed the last option string + extras = [] + start_index = 0 + if option_string_indices: + max_option_string_index = max(option_string_indices) + else: + max_option_string_index = -1 + while start_index <= max_option_string_index: + + # consume any Positionals preceding the next option + next_option_string_index = min([ + index + for index in option_string_indices + if index >= start_index]) + if start_index != next_option_string_index: + positionals_end_index = consume_positionals(start_index) + + # only try to parse the next optional if we didn't consume + # the option string during the positionals parsing + if positionals_end_index > start_index: + start_index = positionals_end_index + continue + else: + start_index = positionals_end_index + + # if we consumed all the positionals we could and we're not + # at the index of an option string, there were extra arguments + if start_index not in option_string_indices: + strings = arg_strings[start_index:next_option_string_index] + extras.extend(strings) + start_index = next_option_string_index + + # consume the next optional and any arguments for it + start_index = consume_optional(start_index) + + # consume any positionals following the last Optional + stop_index = consume_positionals(start_index) + + # if we didn't consume all the argument strings, there were extras + extras.extend(arg_strings[stop_index:]) + + # if we didn't use all the Positional objects, there were too few + # arg strings supplied. + if positionals: + self.error(_('too few arguments')) + + # make sure all required actions were present + for action in self._actions: + if action.required: + if action not in seen_actions: + name = _get_action_name(action) + self.error(_('argument %s is required') % name) + + # make sure all required groups had one option present + for group in self._mutually_exclusive_groups: + if group.required: + for action in group._group_actions: + if action in seen_non_default_actions: + break + + # if no actions were used, report the error + else: + names = [_get_action_name(action) + for action in group._group_actions + if action.help is not SUPPRESS] + msg = _('one of the arguments %s is required') + self.error(msg % ' '.join(names)) + + # return the updated namespace and the extra arguments + return namespace, extras + + def _read_args_from_files(self, arg_strings): + # expand arguments referencing files + new_arg_strings = [] + for arg_string in arg_strings: + + # for regular arguments, just add them back into the list + if arg_string[0] not in self.fromfile_prefix_chars: + new_arg_strings.append(arg_string) + + # replace arguments referencing files with the file content + else: + try: + args_file = open(arg_string[1:]) + try: + arg_strings = [] + for arg_line in args_file.read().splitlines(): + for arg in self.convert_arg_line_to_args(arg_line): + arg_strings.append(arg) + arg_strings = self._read_args_from_files(arg_strings) + new_arg_strings.extend(arg_strings) + finally: + args_file.close() + except IOError: + err = _sys.exc_info()[1] + self.error(str(err)) + + # return the modified argument list + return new_arg_strings + + def convert_arg_line_to_args(self, arg_line): + return [arg_line] + + def _match_argument(self, action, arg_strings_pattern): + # match the pattern for this action to the arg strings + nargs_pattern = self._get_nargs_pattern(action) + match = _re.match(nargs_pattern, arg_strings_pattern) + + # raise an exception if we weren't able to find a match + if match is None: + nargs_errors = { + None: _('expected one argument'), + OPTIONAL: _('expected at most one argument'), + ONE_OR_MORE: _('expected at least one argument'), + } + default = _('expected %s argument(s)') % action.nargs + msg = nargs_errors.get(action.nargs, default) + raise ArgumentError(action, msg) + + # return the number of arguments matched + return len(match.group(1)) + + def _match_arguments_partial(self, actions, arg_strings_pattern): + # progressively shorten the actions list by slicing off the + # final actions until we find a match + result = [] + for i in range(len(actions), 0, -1): + actions_slice = actions[:i] + pattern = ''.join([self._get_nargs_pattern(action) + for action in actions_slice]) + match = _re.match(pattern, arg_strings_pattern) + if match is not None: + result.extend([len(string) for string in match.groups()]) + break + + # return the list of arg string counts + return result + + def _parse_optional(self, arg_string): + # if it's an empty string, it was meant to be a positional + if not arg_string: + return None + + # if it doesn't start with a prefix, it was meant to be positional + if not arg_string[0] in self.prefix_chars: + return None + + # if the option string is present in the parser, return the action + if arg_string in self._option_string_actions: + action = self._option_string_actions[arg_string] + return action, arg_string, None + + # if it's just a single character, it was meant to be positional + if len(arg_string) == 1: + return None + + # if the option string before the "=" is present, return the action + if '=' in arg_string: + option_string, explicit_arg = arg_string.split('=', 1) + if option_string in self._option_string_actions: + action = self._option_string_actions[option_string] + return action, option_string, explicit_arg + + # search through all possible prefixes of the option string + # and all actions in the parser for possible interpretations + option_tuples = self._get_option_tuples(arg_string) + + # if multiple actions match, the option string was ambiguous + if len(option_tuples) > 1: + options = ', '.join([option_string + for action, option_string, explicit_arg in option_tuples]) + tup = arg_string, options + self.error(_('ambiguous option: %s could match %s') % tup) + + # if exactly one action matched, this segmentation is good, + # so return the parsed action + elif len(option_tuples) == 1: + option_tuple, = option_tuples + return option_tuple + + # if it was not found as an option, but it looks like a negative + # number, it was meant to be positional + # unless there are negative-number-like options + if self._negative_number_matcher.match(arg_string): + if not self._has_negative_number_optionals: + return None + + # if it contains a space, it was meant to be a positional + if ' ' in arg_string: + return None + + # it was meant to be an optional but there is no such option + # in this parser (though it might be a valid option in a subparser) + return None, arg_string, None + + def _get_option_tuples(self, option_string): + result = [] + + # option strings starting with two prefix characters are only + # split at the '=' + chars = self.prefix_chars + if option_string[0] in chars and option_string[1] in chars: + if '=' in option_string: + option_prefix, explicit_arg = option_string.split('=', 1) + else: + option_prefix = option_string + explicit_arg = None + for option_string in self._option_string_actions: + if option_string.startswith(option_prefix): + action = self._option_string_actions[option_string] + tup = action, option_string, explicit_arg + result.append(tup) + + # single character options can be concatenated with their arguments + # but multiple character options always have to have their argument + # separate + elif option_string[0] in chars and option_string[1] not in chars: + option_prefix = option_string + explicit_arg = None + short_option_prefix = option_string[:2] + short_explicit_arg = option_string[2:] + + for option_string in self._option_string_actions: + if option_string == short_option_prefix: + action = self._option_string_actions[option_string] + tup = action, option_string, short_explicit_arg + result.append(tup) + elif option_string.startswith(option_prefix): + action = self._option_string_actions[option_string] + tup = action, option_string, explicit_arg + result.append(tup) + + # shouldn't ever get here + else: + self.error(_('unexpected option string: %s') % option_string) + + # return the collected option tuples + return result + + def _get_nargs_pattern(self, action): + # in all examples below, we have to allow for '--' args + # which are represented as '-' in the pattern + nargs = action.nargs + + # the default (None) is assumed to be a single argument + if nargs is None: + nargs_pattern = '(-*A-*)' + + # allow zero or one arguments + elif nargs == OPTIONAL: + nargs_pattern = '(-*A?-*)' + + # allow zero or more arguments + elif nargs == ZERO_OR_MORE: + nargs_pattern = '(-*[A-]*)' + + # allow one or more arguments + elif nargs == ONE_OR_MORE: + nargs_pattern = '(-*A[A-]*)' + + # allow any number of options or arguments + elif nargs == REMAINDER: + nargs_pattern = '([-AO]*)' + + # allow one argument followed by any number of options or arguments + elif nargs == PARSER: + nargs_pattern = '(-*A[-AO]*)' + + # all others should be integers + else: + nargs_pattern = '(-*%s-*)' % '-*'.join('A' * nargs) + + # if this is an optional action, -- is not allowed + if action.option_strings: + nargs_pattern = nargs_pattern.replace('-*', '') + nargs_pattern = nargs_pattern.replace('-', '') + + # return the pattern + return nargs_pattern + + # ======================== + # Value conversion methods + # ======================== + def _get_values(self, action, arg_strings): + # for everything but PARSER args, strip out '--' + if action.nargs not in [PARSER, REMAINDER]: + arg_strings = [s for s in arg_strings if s != '--'] + + # optional argument produces a default when not present + if not arg_strings and action.nargs == OPTIONAL: + if action.option_strings: + value = action.const + else: + value = action.default + if isinstance(value, basestring): + value = self._get_value(action, value) + self._check_value(action, value) + + # when nargs='*' on a positional, if there were no command-line + # args, use the default if it is anything other than None + elif (not arg_strings and action.nargs == ZERO_OR_MORE and + not action.option_strings): + if action.default is not None: + value = action.default + else: + value = arg_strings + self._check_value(action, value) + + # single argument or optional argument produces a single value + elif len(arg_strings) == 1 and action.nargs in [None, OPTIONAL]: + arg_string, = arg_strings + value = self._get_value(action, arg_string) + self._check_value(action, value) + + # REMAINDER arguments convert all values, checking none + elif action.nargs == REMAINDER: + value = [self._get_value(action, v) for v in arg_strings] + + # PARSER arguments convert all values, but check only the first + elif action.nargs == PARSER: + value = [self._get_value(action, v) for v in arg_strings] + self._check_value(action, value[0]) + + # all other types of nargs produce a list + else: + value = [self._get_value(action, v) for v in arg_strings] + for v in value: + self._check_value(action, v) + + # return the converted value + return value + + def _get_value(self, action, arg_string): + type_func = self._registry_get('type', action.type, action.type) + if not _callable(type_func): + msg = _('%r is not callable') + raise ArgumentError(action, msg % type_func) + + # convert the value to the appropriate type + try: + result = type_func(arg_string) + + # ArgumentTypeErrors indicate errors + except ArgumentTypeError: + name = getattr(action.type, '__name__', repr(action.type)) + msg = str(_sys.exc_info()[1]) + raise ArgumentError(action, msg) + + # TypeErrors or ValueErrors also indicate errors + except (TypeError, ValueError): + name = getattr(action.type, '__name__', repr(action.type)) + msg = _('invalid %s value: %r') + raise ArgumentError(action, msg % (name, arg_string)) + + # return the converted value + return result + + def _check_value(self, action, value): + # converted value must be one of the choices (if specified) + if action.choices is not None and value not in action.choices: + tup = value, ', '.join(map(repr, action.choices)) + msg = _('invalid choice: %r (choose from %s)') % tup + raise ArgumentError(action, msg) + + # ======================= + # Help-formatting methods + # ======================= + def format_usage(self): + formatter = self._get_formatter() + formatter.add_usage(self.usage, self._actions, + self._mutually_exclusive_groups) + return formatter.format_help() + + def format_help(self): + formatter = self._get_formatter() + + # usage + formatter.add_usage(self.usage, self._actions, + self._mutually_exclusive_groups) + + # description + formatter.add_text(self.description) + + # positionals, optionals and user-defined groups + for action_group in self._action_groups: + formatter.start_section(action_group.title) + formatter.add_text(action_group.description) + formatter.add_arguments(action_group._group_actions) + formatter.end_section() + + # epilog + formatter.add_text(self.epilog) + + # determine help from format above + return formatter.format_help() + + def format_version(self): + import warnings + warnings.warn( + 'The format_version method is deprecated -- the "version" ' + 'argument to ArgumentParser is no longer supported.', + DeprecationWarning) + formatter = self._get_formatter() + formatter.add_text(self.version) + return formatter.format_help() + + def _get_formatter(self): + return self.formatter_class(prog=self.prog) + + # ===================== + # Help-printing methods + # ===================== + def print_usage(self, file=None): + if file is None: + file = _sys.stdout + self._print_message(self.format_usage(), file) + + def print_help(self, file=None): + if file is None: + file = _sys.stdout + self._print_message(self.format_help(), file) + + def print_version(self, file=None): + import warnings + warnings.warn( + 'The print_version method is deprecated -- the "version" ' + 'argument to ArgumentParser is no longer supported.', + DeprecationWarning) + self._print_message(self.format_version(), file) + + def _print_message(self, message, file=None): + if message: + if file is None: + file = _sys.stderr + file.write(message) + + # =============== + # Exiting methods + # =============== + def exit(self, status=0, message=None): + if message: + self._print_message(message, _sys.stderr) + _sys.exit(status) + + def error(self, message): + """error(message: string) + + Prints a usage message incorporating the message to stderr and + exits. + + If you override this in a subclass, it should not return -- it + should either exit or raise an exception. + """ + self.print_usage(_sys.stderr) + self.exit(2, _('%s: error: %s\n') % (self.prog, message)) diff --git a/env/lib/python2.7/site-packages/gunicorn/argparse_compat.pyc b/env/lib/python2.7/site-packages/gunicorn/argparse_compat.pyc new file mode 100644 index 0000000..b6e582a Binary files /dev/null and b/env/lib/python2.7/site-packages/gunicorn/argparse_compat.pyc differ diff --git a/env/lib/python2.7/site-packages/gunicorn/config.py b/env/lib/python2.7/site-packages/gunicorn/config.py new file mode 100644 index 0000000..ae387d3 --- /dev/null +++ b/env/lib/python2.7/site-packages/gunicorn/config.py @@ -0,0 +1,1691 @@ +# -*- coding: utf-8 - +# +# This file is part of gunicorn released under the MIT license. +# See the NOTICE for more information. + +# Please remember to run "make -C docs html" after update "desc" attributes. + +import copy +import grp +import inspect +try: + import argparse +except ImportError: # python 2.6 + from . import argparse_compat as argparse +import os +import pwd +import ssl +import sys +import textwrap + +from gunicorn import __version__ +from gunicorn import _compat +from gunicorn.errors import ConfigError +from gunicorn import six +from gunicorn import util + +KNOWN_SETTINGS = [] +PLATFORM = sys.platform + + +def wrap_method(func): + def _wrapped(instance, *args, **kwargs): + return func(*args, **kwargs) + return _wrapped + + +def make_settings(ignore=None): + settings = {} + ignore = ignore or () + for s in KNOWN_SETTINGS: + setting = s() + if setting.name in ignore: + continue + settings[setting.name] = setting.copy() + return settings + + +class Config(object): + + def __init__(self, usage=None, prog=None): + self.settings = make_settings() + self.usage = usage + self.prog = prog or os.path.basename(sys.argv[0]) + self.env_orig = os.environ.copy() + + def __getattr__(self, name): + if name not in self.settings: + raise AttributeError("No configuration setting for: %s" % name) + return self.settings[name].get() + + def __setattr__(self, name, value): + if name != "settings" and name in self.settings: + raise AttributeError("Invalid access!") + super(Config, self).__setattr__(name, value) + + def set(self, name, value): + if name not in self.settings: + raise AttributeError("No configuration setting for: %s" % name) + self.settings[name].set(value) + + def parser(self): + kwargs = { + "usage": self.usage, + "prog": self.prog + } + parser = argparse.ArgumentParser(**kwargs) + parser.add_argument("-v", "--version", + action="version", default=argparse.SUPPRESS, + version="%(prog)s (version " + __version__ + ")\n", + help="show program's version number and exit") + parser.add_argument("args", nargs="*", help=argparse.SUPPRESS) + + keys = sorted(self.settings, key=self.settings.__getitem__) + for k in keys: + self.settings[k].add_option(parser) + + return parser + + @property + def worker_class_str(self): + uri = self.settings['worker_class'].get() + + ## are we using a threaded worker? + is_sync = uri.endswith('SyncWorker') or uri == 'sync' + if is_sync and self.threads > 1: + return "threads" + return uri + + @property + def worker_class(self): + uri = self.settings['worker_class'].get() + + ## are we using a threaded worker? + is_sync = uri.endswith('SyncWorker') or uri == 'sync' + if is_sync and self.threads > 1: + uri = "gunicorn.workers.gthread.ThreadWorker" + + worker_class = util.load_class(uri) + if hasattr(worker_class, "setup"): + worker_class.setup() + return worker_class + + @property + def threads(self): + return self.settings['threads'].get() + + @property + def workers(self): + return self.settings['workers'].get() + + @property + def address(self): + s = self.settings['bind'].get() + return [util.parse_address(_compat.bytes_to_str(bind)) for bind in s] + + @property + def uid(self): + return self.settings['user'].get() + + @property + def gid(self): + return self.settings['group'].get() + + @property + def proc_name(self): + pn = self.settings['proc_name'].get() + if pn is not None: + return pn + else: + return self.settings['default_proc_name'].get() + + @property + def logger_class(self): + uri = self.settings['logger_class'].get() + if uri == "simple": + # support the default + uri = "gunicorn.glogging.Logger" + + # if statsd is on, automagically switch to the statsd logger + if 'statsd_host' in self.settings and self.settings['statsd_host'].value is not None: + logger_class = util.load_class("gunicorn.instrument.statsd.Statsd", + section="gunicorn.loggers") + else: + logger_class = util.load_class(uri, + default="gunicorn.glogging.Logger", + section="gunicorn.loggers") + + if hasattr(logger_class, "install"): + logger_class.install() + return logger_class + + @property + def is_ssl(self): + return self.certfile or self.keyfile + + @property + def ssl_options(self): + opts = {} + for name, value in self.settings.items(): + if value.section == 'Ssl': + opts[name] = value.get() + return opts + + @property + def env(self): + raw_env = self.settings['raw_env'].get() + env = {} + + if not raw_env: + return env + + for e in raw_env: + s = _compat.bytes_to_str(e) + try: + k, v = s.split('=', 1) + except ValueError: + raise RuntimeError("environment setting %r invalid" % s) + + env[k] = v + + return env + + +class SettingMeta(type): + def __new__(cls, name, bases, attrs): + super_new = super(SettingMeta, cls).__new__ + parents = [b for b in bases if isinstance(b, SettingMeta)] + if not parents: + return super_new(cls, name, bases, attrs) + + attrs["order"] = len(KNOWN_SETTINGS) + attrs["validator"] = wrap_method(attrs["validator"]) + + new_class = super_new(cls, name, bases, attrs) + new_class.fmt_desc(attrs.get("desc", "")) + KNOWN_SETTINGS.append(new_class) + return new_class + + def fmt_desc(cls, desc): + desc = textwrap.dedent(desc).strip() + setattr(cls, "desc", desc) + setattr(cls, "short", desc.splitlines()[0]) + + +class Setting(object): + name = None + value = None + section = None + cli = None + validator = None + type = None + meta = None + action = None + default = None + short = None + desc = None + nargs = None + const = None + + def __init__(self): + if self.default is not None: + self.set(self.default) + + def add_option(self, parser): + if not self.cli: + return + args = tuple(self.cli) + + help_txt = "%s [%s]" % (self.short, self.default) + help_txt = help_txt.replace("%", "%%") + + kwargs = { + "dest": self.name, + "action": self.action or "store", + "type": self.type or str, + "default": None, + "help": help_txt + } + + if self.meta is not None: + kwargs['metavar'] = self.meta + + if kwargs["action"] != "store": + kwargs.pop("type") + + if self.nargs is not None: + kwargs["nargs"] = self.nargs + + if self.const is not None: + kwargs["const"] = self.const + + parser.add_argument(*args, **kwargs) + + def copy(self): + return copy.copy(self) + + def get(self): + return self.value + + def set(self, val): + if not six.callable(self.validator): + raise TypeError('Invalid validator: %s' % self.name) + self.value = self.validator(val) + + def __lt__(self, other): + return (self.section == other.section and + self.order < other.order) + __cmp__ = __lt__ + +Setting = SettingMeta('Setting', (Setting,), {}) + + +def validate_bool(val): + if isinstance(val, bool): + return val + if not isinstance(val, six.string_types): + raise TypeError("Invalid type for casting: %s" % val) + if val.lower().strip() == "true": + return True + elif val.lower().strip() == "false": + return False + else: + raise ValueError("Invalid boolean: %s" % val) + + +def validate_dict(val): + if not isinstance(val, dict): + raise TypeError("Value is not a dictionary: %s " % val) + return val + + +def validate_pos_int(val): + if not isinstance(val, six.integer_types): + val = int(val, 0) + else: + # Booleans are ints! + val = int(val) + if val < 0: + raise ValueError("Value must be positive: %s" % val) + return val + + +def validate_string(val): + if val is None: + return None + if not isinstance(val, six.string_types): + raise TypeError("Not a string: %s" % val) + return val.strip() + + +def validate_list_string(val): + if not val: + return [] + + # legacy syntax + if isinstance(val, six.string_types): + val = [val] + + return [validate_string(v) for v in val] + + +def validate_string_to_list(val): + val = validate_string(val) + + if not val: + return [] + + return [v.strip() for v in val.split(",") if v] + + +def validate_class(val): + if inspect.isfunction(val) or inspect.ismethod(val): + val = val() + if inspect.isclass(val): + return val + return validate_string(val) + + +def validate_callable(arity): + def _validate_callable(val): + if isinstance(val, six.string_types): + try: + mod_name, obj_name = val.rsplit(".", 1) + except ValueError: + raise TypeError("Value '%s' is not import string. " + "Format: module[.submodules...].object" % val) + try: + mod = __import__(mod_name, fromlist=[obj_name]) + val = getattr(mod, obj_name) + except ImportError as e: + raise TypeError(str(e)) + except AttributeError: + raise TypeError("Can not load '%s' from '%s'" + "" % (obj_name, mod_name)) + if not six.callable(val): + raise TypeError("Value is not six.callable: %s" % val) + if arity != -1 and arity != len(inspect.getargspec(val)[0]): + raise TypeError("Value must have an arity of: %s" % arity) + return val + return _validate_callable + + +def validate_user(val): + if val is None: + return os.geteuid() + if isinstance(val, int): + return val + elif val.isdigit(): + return int(val) + else: + try: + return pwd.getpwnam(val).pw_uid + except KeyError: + raise ConfigError("No such user: '%s'" % val) + + +def validate_group(val): + if val is None: + return os.getegid() + + if isinstance(val, int): + return val + elif val.isdigit(): + return int(val) + else: + try: + return grp.getgrnam(val).gr_gid + except KeyError: + raise ConfigError("No such group: '%s'" % val) + + +def validate_post_request(val): + val = validate_callable(-1)(val) + + largs = len(inspect.getargspec(val)[0]) + if largs == 4: + return val + elif largs == 3: + return lambda worker, req, env, _r: val(worker, req, env) + elif largs == 2: + return lambda worker, req, _e, _r: val(worker, req) + else: + raise TypeError("Value must have an arity of: 4") + + +def validate_chdir(val): + # valid if the value is a string + val = validate_string(val) + + # transform relative paths + path = os.path.abspath(os.path.normpath(os.path.join(util.getcwd(), val))) + + # test if the path exists + if not os.path.exists(path): + raise ConfigError("can't chdir to %r" % val) + + return path + + +def validate_file(val): + if val is None: + return None + + # valid if the value is a string + val = validate_string(val) + + # transform relative paths + path = os.path.abspath(os.path.normpath(os.path.join(util.getcwd(), val))) + + # test if the path exists + if not os.path.exists(path): + raise ConfigError("%r not found" % val) + + return path + +def validate_hostport(val): + val = validate_string(val) + if val is None: + return None + elements = val.split(":") + if len(elements) == 2: + return (elements[0], int(elements[1])) + else: + raise TypeError("Value must consist of: hostname:port") + +def get_default_config_file(): + config_path = os.path.join(os.path.abspath(os.getcwd()), + 'gunicorn.conf.py') + if os.path.exists(config_path): + return config_path + return None + + +class ConfigFile(Setting): + name = "config" + section = "Config File" + cli = ["-c", "--config"] + meta = "FILE" + validator = validate_string + default = None + desc = """\ + The path to a Gunicorn config file, or python module. + + Only has an effect when specified on the command line or as part of an + application specific configuration. + """ + +class Bind(Setting): + name = "bind" + action = "append" + section = "Server Socket" + cli = ["-b", "--bind"] + meta = "ADDRESS" + validator = validate_list_string + + if 'PORT' in os.environ: + default = ['0.0.0.0:{0}'.format(os.environ.get('PORT'))] + else: + default = ['127.0.0.1:8000'] + + desc = """\ + The socket to bind. + + A string of the form: 'HOST', 'HOST:PORT', 'unix:PATH'. An IP is a valid + HOST. + + Multiple addresses can be bound. ex.:: + + $ gunicorn -b 127.0.0.1:8000 -b [::1]:8000 test:app + + will bind the `test:app` application on localhost both on ipv6 + and ipv4 interfaces. + """ + + +class Backlog(Setting): + name = "backlog" + section = "Server Socket" + cli = ["--backlog"] + meta = "INT" + validator = validate_pos_int + type = int + default = 2048 + desc = """\ + The maximum number of pending connections. + + This refers to the number of clients that can be waiting to be served. + Exceeding this number results in the client getting an error when + attempting to connect. It should only affect servers under significant + load. + + Must be a positive integer. Generally set in the 64-2048 range. + """ + + +class Workers(Setting): + name = "workers" + section = "Worker Processes" + cli = ["-w", "--workers"] + meta = "INT" + validator = validate_pos_int + type = int + default = int(os.environ.get('WEB_CONCURRENCY', 1)) + desc = """\ + The number of worker processes for handling requests. + + A positive integer generally in the 2-4 x $(NUM_CORES) range. You'll + want to vary this a bit to find the best for your particular + application's work load. + + By default, the value of the WEB_CONCURRENCY environment variable. If + it is not defined, the default is 1. + """ + + +class WorkerClass(Setting): + name = "worker_class" + section = "Worker Processes" + cli = ["-k", "--worker-class"] + meta = "STRING" + validator = validate_class + default = "sync" + desc = """\ + The type of workers to use. + + The default class (sync) should handle most 'normal' types of + workloads. You'll want to read + http://docs.gunicorn.org/en/latest/design.html for information + on when you might want to choose one of the other worker + classes. + + A string referring to one of the following bundled classes: + + * ``sync`` + * ``eventlet`` - Requires eventlet >= 0.9.7 + * ``gevent`` - Requires gevent >= 0.13 + * ``tornado`` - Requires tornado >= 0.2 + + Optionally, you can provide your own worker by giving gunicorn a + python path to a subclass of gunicorn.workers.base.Worker. This + alternative syntax will load the gevent class: + ``gunicorn.workers.ggevent.GeventWorker``. Alternatively the syntax + can also load the gevent class with ``egg:gunicorn#gevent`` + """ + +class WorkerThreads(Setting): + name = "threads" + section = "Worker Processes" + cli = ["--threads"] + meta = "INT" + validator = validate_pos_int + type = int + default = 1 + desc = """\ + The number of worker threads for handling requests. + + Run each worker with the specified number of threads. + + A positive integer generally in the 2-4 x $(NUM_CORES) range. You'll + want to vary this a bit to find the best for your particular + application's work load. + + If it is not defined, the default is 1. + """ + + +class WorkerConnections(Setting): + name = "worker_connections" + section = "Worker Processes" + cli = ["--worker-connections"] + meta = "INT" + validator = validate_pos_int + type = int + default = 1000 + desc = """\ + The maximum number of simultaneous clients. + + This setting only affects the Eventlet and Gevent worker types. + """ + + +class MaxRequests(Setting): + name = "max_requests" + section = "Worker Processes" + cli = ["--max-requests"] + meta = "INT" + validator = validate_pos_int + type = int + default = 0 + desc = """\ + The maximum number of requests a worker will process before restarting. + + Any value greater than zero will limit the number of requests a work + will process before automatically restarting. This is a simple method + to help limit the damage of memory leaks. + + If this is set to zero (the default) then the automatic worker + restarts are disabled. + """ + + +class MaxRequestsJitter(Setting): + name = "max_requests_jitter" + section = "Worker Processes" + cli = ["--max-requests-jitter"] + meta = "INT" + validator = validate_pos_int + type = int + default = 0 + desc = """\ + The maximum jitter to add to the max-requests setting. + + The jitter causes the restart per worker to be randomized by + ``randint(0, max_requests_jitter)``. This is intended to stagger worker + restarts to avoid all workers restarting at the same time. + + .. versionadded:: 19.2 + """ + + +class Timeout(Setting): + name = "timeout" + section = "Worker Processes" + cli = ["-t", "--timeout"] + meta = "INT" + validator = validate_pos_int + type = int + default = 30 + desc = """\ + Workers silent for more than this many seconds are killed and restarted. + + Generally set to thirty seconds. Only set this noticeably higher if + you're sure of the repercussions for sync workers. For the non sync + workers it just means that the worker process is still communicating and + is not tied to the length of time required to handle a single request. + """ + + +class GracefulTimeout(Setting): + name = "graceful_timeout" + section = "Worker Processes" + cli = ["--graceful-timeout"] + meta = "INT" + validator = validate_pos_int + type = int + default = 30 + desc = """\ + Timeout for graceful workers restart. + + Generally set to thirty seconds. How max time worker can handle + request after got restart signal. If the time is up worker will + be force killed. + """ + + +class Keepalive(Setting): + name = "keepalive" + section = "Worker Processes" + cli = ["--keep-alive"] + meta = "INT" + validator = validate_pos_int + type = int + default = 2 + desc = """\ + The number of seconds to wait for requests on a Keep-Alive connection. + + Generally set in the 1-5 seconds range. + """ + + +class LimitRequestLine(Setting): + name = "limit_request_line" + section = "Security" + cli = ["--limit-request-line"] + meta = "INT" + validator = validate_pos_int + type = int + default = 4094 + desc = """\ + The maximum size of HTTP request line in bytes. + + This parameter is used to limit the allowed size of a client's + HTTP request-line. Since the request-line consists of the HTTP + method, URI, and protocol version, this directive places a + restriction on the length of a request-URI allowed for a request + on the server. A server needs this value to be large enough to + hold any of its resource names, including any information that + might be passed in the query part of a GET request. Value is a number + from 0 (unlimited) to 8190. + + This parameter can be used to prevent any DDOS attack. + """ + + +class LimitRequestFields(Setting): + name = "limit_request_fields" + section = "Security" + cli = ["--limit-request-fields"] + meta = "INT" + validator = validate_pos_int + type = int + default = 100 + desc = """\ + Limit the number of HTTP headers fields in a request. + + This parameter is used to limit the number of headers in a request to + prevent DDOS attack. Used with the `limit_request_field_size` it allows + more safety. By default this value is 100 and can't be larger than + 32768. + """ + + +class LimitRequestFieldSize(Setting): + name = "limit_request_field_size" + section = "Security" + cli = ["--limit-request-field_size"] + meta = "INT" + validator = validate_pos_int + type = int + default = 8190 + desc = """\ + Limit the allowed size of an HTTP request header field. + + Value is a number from 0 (unlimited) to 8190. to set the limit + on the allowed size of an HTTP request header field. + """ + + +class Reload(Setting): + name = "reload" + section = 'Debugging' + cli = ['--reload'] + validator = validate_bool + action = 'store_true' + default = False + desc = '''\ + Restart workers when code changes. + + This setting is intended for development. It will cause workers to be + restarted whenever application code changes. + + The reloader is incompatible with application preloading. When using a + paste configuration be sure that the server block does not import any + application code or the reload will not work as designed. + ''' + + +class Spew(Setting): + name = "spew" + section = "Debugging" + cli = ["--spew"] + validator = validate_bool + action = "store_true" + default = False + desc = """\ + Install a trace function that spews every line executed by the server. + + This is the nuclear option. + """ + + +class ConfigCheck(Setting): + name = "check_config" + section = "Debugging" + cli = ["--check-config", ] + validator = validate_bool + action = "store_true" + default = False + desc = """\ + Check the configuration. + """ + + +class PreloadApp(Setting): + name = "preload_app" + section = "Server Mechanics" + cli = ["--preload"] + validator = validate_bool + action = "store_true" + default = False + desc = """\ + Load application code before the worker processes are forked. + + By preloading an application you can save some RAM resources as well as + speed up server boot times. Although, if you defer application loading + to each worker process, you can reload your application code easily by + restarting workers. + """ + +class Sendfile(Setting): + name = "sendfile" + section = "Server Mechanics" + cli = ["--sendfile"] + validator = validate_bool + action = "store_true" + default = True + desc = """\ + Enables or disables the use of ``sendfile()``. + + .. versionadded:: 19.2 + """ + +class Chdir(Setting): + name = "chdir" + section = "Server Mechanics" + cli = ["--chdir"] + validator = validate_chdir + default = util.getcwd() + desc = """\ + Chdir to specified directory before apps loading. + """ + + +class Daemon(Setting): + name = "daemon" + section = "Server Mechanics" + cli = ["-D", "--daemon"] + validator = validate_bool + action = "store_true" + default = False + desc = """\ + Daemonize the Gunicorn process. + + Detaches the server from the controlling terminal and enters the + background. + """ + +class Env(Setting): + name = "raw_env" + action = "append" + section = "Server Mechanics" + cli = ["-e", "--env"] + meta = "ENV" + validator = validate_list_string + default = [] + + desc = """\ + Set environment variable (key=value). + + Pass variables to the execution environment. Ex.:: + + $ gunicorn -b 127.0.0.1:8000 --env FOO=1 test:app + + and test for the foo variable environment in your application. + """ + + +class Pidfile(Setting): + name = "pidfile" + section = "Server Mechanics" + cli = ["-p", "--pid"] + meta = "FILE" + validator = validate_string + default = None + desc = """\ + A filename to use for the PID file. + + If not set, no PID file will be written. + """ + +class WorkerTmpDir(Setting): + name = "worker_tmp_dir" + section = "Server Mechanics" + cli = ["--worker-tmp-dir"] + meta = "DIR" + validator = validate_string + default = None + desc = """\ + A directory to use for the worker heartbeat temporary file. + + If not set, the default temporary directory will be used. + """ + +class User(Setting): + name = "user" + section = "Server Mechanics" + cli = ["-u", "--user"] + meta = "USER" + validator = validate_user + default = os.geteuid() + desc = """\ + Switch worker processes to run as this user. + + A valid user id (as an integer) or the name of a user that can be + retrieved with a call to pwd.getpwnam(value) or None to not change + the worker process user. + """ + + +class Group(Setting): + name = "group" + section = "Server Mechanics" + cli = ["-g", "--group"] + meta = "GROUP" + validator = validate_group + default = os.getegid() + desc = """\ + Switch worker process to run as this group. + + A valid group id (as an integer) or the name of a user that can be + retrieved with a call to pwd.getgrnam(value) or None to not change + the worker processes group. + """ + + +class Umask(Setting): + name = "umask" + section = "Server Mechanics" + cli = ["-m", "--umask"] + meta = "INT" + validator = validate_pos_int + type = int + default = 0 + desc = """\ + A bit mask for the file mode on files written by Gunicorn. + + Note that this affects unix socket permissions. + + A valid value for the os.umask(mode) call or a string compatible with + int(value, 0) (0 means Python guesses the base, so values like "0", + "0xFF", "0022" are valid for decimal, hex, and octal representations) + """ + + +class TmpUploadDir(Setting): + name = "tmp_upload_dir" + section = "Server Mechanics" + meta = "DIR" + validator = validate_string + default = None + desc = """\ + Directory to store temporary request data as they are read. + + This may disappear in the near future. + + This path should be writable by the process permissions set for Gunicorn + workers. If not specified, Gunicorn will choose a system generated + temporary directory. + """ + + +class SecureSchemeHeader(Setting): + name = "secure_scheme_headers" + section = "Server Mechanics" + validator = validate_dict + default = { + "X-FORWARDED-PROTOCOL": "ssl", + "X-FORWARDED-PROTO": "https", + "X-FORWARDED-SSL": "on" + } + desc = """\ + + A dictionary containing headers and values that the front-end proxy + uses to indicate HTTPS requests. These tell gunicorn to set + wsgi.url_scheme to "https", so your application can tell that the + request is secure. + + The dictionary should map upper-case header names to exact string + values. The value comparisons are case-sensitive, unlike the header + names, so make sure they're exactly what your front-end proxy sends + when handling HTTPS requests. + + It is important that your front-end proxy configuration ensures that + the headers defined here can not be passed directly from the client. + """ + + +class ForwardedAllowIPS(Setting): + name = "forwarded_allow_ips" + section = "Server Mechanics" + cli = ["--forwarded-allow-ips"] + meta = "STRING" + validator = validate_string_to_list + default = "127.0.0.1" + desc = """\ + Front-end's IPs from which allowed to handle set secure headers. + (comma separate). + + Set to "*" to disable checking of Front-end IPs (useful for setups + where you don't know in advance the IP address of Front-end, but + you still trust the environment) + """ + + +class AccessLog(Setting): + name = "accesslog" + section = "Logging" + cli = ["--access-logfile"] + meta = "FILE" + validator = validate_string + default = None + desc = """\ + The Access log file to write to. + + "-" means log to stderr. + """ + + +class AccessLogFormat(Setting): + name = "access_log_format" + section = "Logging" + cli = ["--access-logformat"] + meta = "STRING" + validator = validate_string + default = '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"' + desc = """\ + The access log format. + + ========== =========== + Identifier Description + ========== =========== + h remote address + l '-' + u currently '-', may be user name in future releases + t date of the request + r status line (e.g. ``GET / HTTP/1.1``) + s status + b response length or '-' + f referer + a user agent + T request time in seconds + D request time in microseconds + L request time in decimal seconds + p process ID + {Header}i request header + {Header}o response header + ========== =========== + """ + + +class ErrorLog(Setting): + name = "errorlog" + section = "Logging" + cli = ["--error-logfile", "--log-file"] + meta = "FILE" + validator = validate_string + default = '-' + desc = """\ + The Error log file to write to. + + "-" means log to stderr. + + .. versionchanged:: 19.2 + Log to ``stderr`` by default. + + """ + + +class Loglevel(Setting): + name = "loglevel" + section = "Logging" + cli = ["--log-level"] + meta = "LEVEL" + validator = validate_string + default = "info" + desc = """\ + The granularity of Error log outputs. + + Valid level names are: + + * debug + * info + * warning + * error + * critical + """ + + +class LoggerClass(Setting): + name = "logger_class" + section = "Logging" + cli = ["--logger-class"] + meta = "STRING" + validator = validate_class + default = "gunicorn.glogging.Logger" + desc = """\ + The logger you want to use to log events in gunicorn. + + The default class (``gunicorn.glogging.Logger``) handle most of + normal usages in logging. It provides error and access logging. + + You can provide your own worker by giving gunicorn a + python path to a subclass like gunicorn.glogging.Logger. + Alternatively the syntax can also load the Logger class + with `egg:gunicorn#simple` + """ + + +class LogConfig(Setting): + name = "logconfig" + section = "Logging" + cli = ["--log-config"] + meta = "FILE" + validator = validate_string + default = None + desc = """\ + The log config file to use. + Gunicorn uses the standard Python logging module's Configuration + file format. + """ + + +class SyslogTo(Setting): + name = "syslog_addr" + section = "Logging" + cli = ["--log-syslog-to"] + meta = "SYSLOG_ADDR" + validator = validate_string + + if PLATFORM == "darwin": + default = "unix:///var/run/syslog" + elif PLATFORM in ('freebsd', 'dragonfly', ): + default = "unix:///var/run/log" + elif PLATFORM == "openbsd": + default = "unix:///dev/log" + else: + default = "udp://localhost:514" + + desc = """\ + Address to send syslog messages. + + Address is a string of the form: + + * 'unix://PATH#TYPE' : for unix domain socket. TYPE can be 'stream' + for the stream driver or 'dgram' for the dgram driver. + 'stream' is the default. + * 'udp://HOST:PORT' : for UDP sockets + * 'tcp://HOST:PORT' : for TCP sockets + + """ + + +class Syslog(Setting): + name = "syslog" + section = "Logging" + cli = ["--log-syslog"] + validator = validate_bool + action = 'store_true' + default = False + desc = """\ + Send *Gunicorn* logs to syslog. + """ + + +class SyslogPrefix(Setting): + name = "syslog_prefix" + section = "Logging" + cli = ["--log-syslog-prefix"] + meta = "SYSLOG_PREFIX" + validator = validate_string + default = None + desc = """\ + Makes gunicorn use the parameter as program-name in the syslog entries. + + All entries will be prefixed by gunicorn.. By default the program + name is the name of the process. + """ + + +class SyslogFacility(Setting): + name = "syslog_facility" + section = "Logging" + cli = ["--log-syslog-facility"] + meta = "SYSLOG_FACILITY" + validator = validate_string + default = "user" + desc = """\ + Syslog facility name + """ + + +class EnableStdioInheritance(Setting): + name = "enable_stdio_inheritance" + section = "Logging" + cli = ["-R", "--enable-stdio-inheritance"] + validator = validate_bool + default = False + action = "store_true" + desc = """\ + Enable stdio inheritance + + Enable inheritance for stdio file descriptors in daemon mode. + + Note: To disable the python stdout buffering, you can to set the user + environment variable ``PYTHONUNBUFFERED`` . + """ + + +# statsD monitoring +class StatsdHost(Setting): + name = "statsd_host" + section = "Logging" + cli = ["--statsd-host"] + meta = "STATSD_ADDR" + default = None + validator = validate_hostport + desc = """\ + ``host:port`` of the statsd server to log to. + + .. versionadded:: 19.1 + """ + +class StatsdPrefix(Setting): + name = "statsd_prefix" + section = "Logging" + cli = ["--statsd-prefix"] + meta = "STATSD_PREFIX" + default = "" + validator = validate_string + desc = """\ + Prefix to use when emitting statsd metrics (a trailing ``.`` is added, + if not provided). + + .. versionadded:: 19.2 + """ + + +class Procname(Setting): + name = "proc_name" + section = "Process Naming" + cli = ["-n", "--name"] + meta = "STRING" + validator = validate_string + default = None + desc = """\ + A base to use with setproctitle for process naming. + + This affects things like ``ps`` and ``top``. If you're going to be + running more than one instance of Gunicorn you'll probably want to set a + name to tell them apart. This requires that you install the setproctitle + module. + + It defaults to 'gunicorn'. + """ + + +class DefaultProcName(Setting): + name = "default_proc_name" + section = "Process Naming" + validator = validate_string + default = "gunicorn" + desc = """\ + Internal setting that is adjusted for each type of application. + """ + + +class DjangoSettings(Setting): + name = "django_settings" + section = "Django" + cli = ["--settings"] + meta = "STRING" + validator = validate_string + default = None + desc = """\ + The Python path to a Django settings module. (deprecated) + + e.g. 'myproject.settings.main'. If this isn't provided, the + DJANGO_SETTINGS_MODULE environment variable will be used. + + **DEPRECATED**: use the --env argument instead. + """ + + +class PythonPath(Setting): + name = "pythonpath" + section = "Server Mechanics" + cli = ["--pythonpath"] + meta = "STRING" + validator = validate_string + default = None + desc = """\ + A directory to add to the Python path. + + e.g. + '/home/djangoprojects/myproject'. + """ + + +class Paste(Setting): + name = "paste" + section = "Server Mechanics" + cli = ["--paste", "--paster"] + meta = "STRING" + validator = validate_string + default = None + desc = """\ + Load a paste.deploy config file. The argument may contain a "#" symbol + followed by the name of an app section from the config file, e.g. + "production.ini#admin". + + At this time, using alternate server blocks is not supported. Use the + command line arguments to control server configuration instead. + """ + + +class OnStarting(Setting): + name = "on_starting" + section = "Server Hooks" + validator = validate_callable(1) + type = six.callable + + def on_starting(server): + pass + default = staticmethod(on_starting) + desc = """\ + Called just before the master process is initialized. + + The callable needs to accept a single instance variable for the Arbiter. + """ + + +class OnReload(Setting): + name = "on_reload" + section = "Server Hooks" + validator = validate_callable(1) + type = six.callable + + def on_reload(server): + pass + default = staticmethod(on_reload) + desc = """\ + Called to recycle workers during a reload via SIGHUP. + + The callable needs to accept a single instance variable for the Arbiter. + """ + + +class WhenReady(Setting): + name = "when_ready" + section = "Server Hooks" + validator = validate_callable(1) + type = six.callable + + def when_ready(server): + pass + default = staticmethod(when_ready) + desc = """\ + Called just after the server is started. + + The callable needs to accept a single instance variable for the Arbiter. + """ + + +class Prefork(Setting): + name = "pre_fork" + section = "Server Hooks" + validator = validate_callable(2) + type = six.callable + + def pre_fork(server, worker): + pass + default = staticmethod(pre_fork) + desc = """\ + Called just before a worker is forked. + + The callable needs to accept two instance variables for the Arbiter and + new Worker. + """ + + +class Postfork(Setting): + name = "post_fork" + section = "Server Hooks" + validator = validate_callable(2) + type = six.callable + + def post_fork(server, worker): + pass + default = staticmethod(post_fork) + desc = """\ + Called just after a worker has been forked. + + The callable needs to accept two instance variables for the Arbiter and + new Worker. + """ + + +class PostWorkerInit(Setting): + name = "post_worker_init" + section = "Server Hooks" + validator = validate_callable(1) + type = six.callable + + def post_worker_init(worker): + pass + + default = staticmethod(post_worker_init) + desc = """\ + Called just after a worker has initialized the application. + + The callable needs to accept one instance variable for the initialized + Worker. + """ + +class WorkerInt(Setting): + name = "worker_int" + section = "Server Hooks" + validator = validate_callable(1) + type = six.callable + + def worker_int(worker): + pass + + default = staticmethod(worker_int) + desc = """\ + Called just after a worker exited on SIGINT or SIGQUIT. + + The callable needs to accept one instance variable for the initialized + Worker. + """ + + +class WorkerAbort(Setting): + name = "worker_abort" + section = "Server Hooks" + validator = validate_callable(1) + type = six.callable + + def worker_abort(worker): + pass + + default = staticmethod(worker_abort) + desc = """\ + Called when a worker received the SIGABRT signal. + + This call generally happens on timeout. + + The callable needs to accept one instance variable for the initialized + Worker. + """ + + +class PreExec(Setting): + name = "pre_exec" + section = "Server Hooks" + validator = validate_callable(1) + type = six.callable + + def pre_exec(server): + pass + default = staticmethod(pre_exec) + desc = """\ + Called just before a new master process is forked. + + The callable needs to accept a single instance variable for the Arbiter. + """ + + +class PreRequest(Setting): + name = "pre_request" + section = "Server Hooks" + validator = validate_callable(2) + type = six.callable + + def pre_request(worker, req): + worker.log.debug("%s %s" % (req.method, req.path)) + default = staticmethod(pre_request) + desc = """\ + Called just before a worker processes the request. + + The callable needs to accept two instance variables for the Worker and + the Request. + """ + + +class PostRequest(Setting): + name = "post_request" + section = "Server Hooks" + validator = validate_post_request + type = six.callable + + def post_request(worker, req, environ, resp): + pass + default = staticmethod(post_request) + desc = """\ + Called after a worker processes the request. + + The callable needs to accept two instance variables for the Worker and + the Request. + """ + + +class WorkerExit(Setting): + name = "worker_exit" + section = "Server Hooks" + validator = validate_callable(2) + type = six.callable + + def worker_exit(server, worker): + pass + default = staticmethod(worker_exit) + desc = """\ + Called just after a worker has been exited. + + The callable needs to accept two instance variables for the Arbiter and + the just-exited Worker. + """ + + +class NumWorkersChanged(Setting): + name = "nworkers_changed" + section = "Server Hooks" + validator = validate_callable(3) + type = six.callable + + def nworkers_changed(server, new_value, old_value): + pass + default = staticmethod(nworkers_changed) + desc = """\ + Called just after num_workers has been changed. + + The callable needs to accept an instance variable of the Arbiter and + two integers of number of workers after and before change. + + If the number of workers is set for the first time, old_value would be + None. + """ + +class OnExit(Setting): + name = "on_exit" + section = "Server Hooks" + validator = validate_callable(1) + + def on_exit(server): + pass + + default = staticmethod(on_exit) + desc = """\ + Called just before exiting gunicorn. + + The callable needs to accept a single instance variable for the Arbiter. + """ + + +class ProxyProtocol(Setting): + name = "proxy_protocol" + section = "Server Mechanics" + cli = ["--proxy-protocol"] + validator = validate_bool + default = False + action = "store_true" + desc = """\ + Enable detect PROXY protocol (PROXY mode). + + Allow using Http and Proxy together. It may be useful for work with + stunnel as https frontend and gunicorn as http server. + + PROXY protocol: http://haproxy.1wt.eu/download/1.5/doc/proxy-protocol.txt + + Example for stunnel config:: + + [https] + protocol = proxy + accept = 443 + connect = 80 + cert = /etc/ssl/certs/stunnel.pem + key = /etc/ssl/certs/stunnel.key + """ + + +class ProxyAllowFrom(Setting): + name = "proxy_allow_ips" + section = "Server Mechanics" + cli = ["--proxy-allow-from"] + validator = validate_string_to_list + default = "127.0.0.1" + desc = """\ + Front-end's IPs from which allowed accept proxy requests (comma separate). + + Set to "*" to disable checking of Front-end IPs (useful for setups + where you don't know in advance the IP address of Front-end, but + you still trust the environment) + """ + + +class KeyFile(Setting): + name = "keyfile" + section = "Ssl" + cli = ["--keyfile"] + meta = "FILE" + validator = validate_string + default = None + desc = """\ + SSL key file + """ + + +class CertFile(Setting): + name = "certfile" + section = "Ssl" + cli = ["--certfile"] + meta = "FILE" + validator = validate_string + default = None + desc = """\ + SSL certificate file + """ + +class SSLVersion(Setting): + name = "ssl_version" + section = "Ssl" + cli = ["--ssl-version"] + validator = validate_pos_int + default = ssl.PROTOCOL_TLSv1 + desc = """\ + SSL version to use (see stdlib ssl module's) + """ + +class CertReqs(Setting): + name = "cert_reqs" + section = "Ssl" + cli = ["--cert-reqs"] + validator = validate_pos_int + default = ssl.CERT_NONE + desc = """\ + Whether client certificate is required (see stdlib ssl module's) + """ + +class CACerts(Setting): + name = "ca_certs" + section = "Ssl" + cli = ["--ca-certs"] + meta = "FILE" + validator = validate_string + default = None + desc = """\ + CA certificates file + """ + +class SuppressRaggedEOFs(Setting): + name = "suppress_ragged_eofs" + section = "Ssl" + cli = ["--suppress-ragged-eofs"] + action = "store_true" + default = True + validator = validate_bool + desc = """\ + Suppress ragged EOFs (see stdlib ssl module's) + """ + +class DoHandshakeOnConnect(Setting): + name = "do_handshake_on_connect" + section = "Ssl" + cli = ["--do-handshake-on-connect"] + validator = validate_bool + action = "store_true" + default = False + desc = """\ + Whether to perform SSL handshake on socket connect (see stdlib ssl module's) + """ + +if sys.version_info >= (2, 7): + class Ciphers(Setting): + name = "ciphers" + section = "Ssl" + cli = ["--ciphers"] + validator = validate_string + default = 'TLSv1' + desc = """\ + Ciphers to use (see stdlib ssl module's) + """ diff --git a/env/lib/python2.7/site-packages/gunicorn/config.pyc b/env/lib/python2.7/site-packages/gunicorn/config.pyc new file mode 100644 index 0000000..c47340e Binary files /dev/null and b/env/lib/python2.7/site-packages/gunicorn/config.pyc differ diff --git a/env/lib/python2.7/site-packages/gunicorn/debug.py b/env/lib/python2.7/site-packages/gunicorn/debug.py new file mode 100644 index 0000000..1b7834f --- /dev/null +++ b/env/lib/python2.7/site-packages/gunicorn/debug.py @@ -0,0 +1,70 @@ +# -*- coding: utf-8 - +# +# This file is part of gunicorn released under the MIT license. +# See the NOTICE for more information. + +"""The debug module contains utilities and functions for better +debugging Gunicorn.""" + +import sys +import linecache +import re +import inspect + +__all__ = ['spew', 'unspew'] + +_token_spliter = re.compile('\W+') + + +class Spew(object): + """ + """ + def __init__(self, trace_names=None, show_values=True): + self.trace_names = trace_names + self.show_values = show_values + + def __call__(self, frame, event, arg): + if event == 'line': + lineno = frame.f_lineno + if '__file__' in frame.f_globals: + filename = frame.f_globals['__file__'] + if (filename.endswith('.pyc') or + filename.endswith('.pyo')): + filename = filename[:-1] + name = frame.f_globals['__name__'] + line = linecache.getline(filename, lineno) + else: + name = '[unknown]' + try: + src = inspect.getsourcelines(frame) + line = src[lineno] + except IOError: + line = 'Unknown code named [%s]. VM instruction #%d' % ( + frame.f_code.co_name, frame.f_lasti) + if self.trace_names is None or name in self.trace_names: + print('%s:%s: %s' % (name, lineno, line.rstrip())) + if not self.show_values: + return self + details = [] + tokens = _token_spliter.split(line) + for tok in tokens: + if tok in frame.f_globals: + details.append('%s=%r' % (tok, frame.f_globals[tok])) + if tok in frame.f_locals: + details.append('%s=%r' % (tok, frame.f_locals[tok])) + if details: + print("\t%s" % ' '.join(details)) + return self + + +def spew(trace_names=None, show_values=False): + """Install a trace hook which writes incredibly detailed logs + about what code is being executed to stdout. + """ + sys.settrace(Spew(trace_names, show_values)) + + +def unspew(): + """Remove the trace hook installed by spew. + """ + sys.settrace(None) diff --git a/env/lib/python2.7/site-packages/gunicorn/debug.pyc b/env/lib/python2.7/site-packages/gunicorn/debug.pyc new file mode 100644 index 0000000..6da51d1 Binary files /dev/null and b/env/lib/python2.7/site-packages/gunicorn/debug.pyc differ diff --git a/env/lib/python2.7/site-packages/gunicorn/errors.py b/env/lib/python2.7/site-packages/gunicorn/errors.py new file mode 100644 index 0000000..7465edb --- /dev/null +++ b/env/lib/python2.7/site-packages/gunicorn/errors.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 - +# +# This file is part of gunicorn released under the MIT license. +# See the NOTICE for more information. + + +class HaltServer(BaseException): + def __init__(self, reason, exit_status=1): + self.reason = reason + self.exit_status = exit_status + + def __str__(self): + return "" % (self.reason, self.exit_status) + + +class ConfigError(BaseException): + """ Exception raised on config error """ + + +class AppImportError(Exception): + """ Exception raised when loading an application """ diff --git a/env/lib/python2.7/site-packages/gunicorn/errors.pyc b/env/lib/python2.7/site-packages/gunicorn/errors.pyc new file mode 100644 index 0000000..b85cf5b Binary files /dev/null and b/env/lib/python2.7/site-packages/gunicorn/errors.pyc differ diff --git a/env/lib/python2.7/site-packages/gunicorn/glogging.py b/env/lib/python2.7/site-packages/gunicorn/glogging.py new file mode 100644 index 0000000..2c0bb57 --- /dev/null +++ b/env/lib/python2.7/site-packages/gunicorn/glogging.py @@ -0,0 +1,371 @@ +# -*- coding: utf-8 - +# +# This file is part of gunicorn released under the MIT license. +# See the NOTICE for more information. + +import time +import logging +logging.Logger.manager.emittedNoHandlerWarning = 1 +from logging.config import fileConfig +import os +import socket +import sys +import traceback + +from gunicorn import util +from gunicorn.six import string_types + + +# syslog facility codes +SYSLOG_FACILITIES = { + "auth": 4, + "authpriv": 10, + "cron": 9, + "daemon": 3, + "ftp": 11, + "kern": 0, + "lpr": 6, + "mail": 2, + "news": 7, + "security": 4, # DEPRECATED + "syslog": 5, + "user": 1, + "uucp": 8, + "local0": 16, + "local1": 17, + "local2": 18, + "local3": 19, + "local4": 20, + "local5": 21, + "local6": 22, + "local7": 23 + } + + +CONFIG_DEFAULTS = dict( + version=1, + disable_existing_loggers=False, + + loggers={ + "root": {"level": "INFO", "handlers": ["console"]}, + "gunicorn.error": { + "level": "INFO", + "handlers": ["console"], + "propagate": True, + "qualname": "gunicorn.error" + } + }, + handlers={ + "console": { + "class": "logging.StreamHandler", + "formatter": "generic", + "stream": "sys.stdout" + } + }, + formatters={ + "generic": { + "format": "%(asctime)s [%(process)d] [%(levelname)s] %(message)s", + "datefmt": "[%Y-%m-%d %H:%M:%S %z]", + "class": "logging.Formatter" + } + } +) + + +def loggers(): + """ get list of all loggers """ + root = logging.root + existing = root.manager.loggerDict.keys() + return [logging.getLogger(name) for name in existing] + + +class SafeAtoms(dict): + + def __init__(self, atoms): + dict.__init__(self) + for key, value in atoms.items(): + if isinstance(value, string_types): + self[key] = value.replace('"', '\\"') + else: + self[key] = value + + def __getitem__(self, k): + if k.startswith("{"): + kl = k.lower() + if kl in self: + return super(SafeAtoms, self).__getitem__(kl) + else: + return "-" + if k in self: + return super(SafeAtoms, self).__getitem__(k) + else: + return '-' + + +def parse_syslog_address(addr): + + if addr.startswith("unix://"): + sock_type = socket.SOCK_STREAM + + # are we using a different socket type? + parts = addr.split("#", 1) + if len(parts) == 2: + addr = parts[0] + if parts[1] == "dgram": + sock_type = socket.SOCK_DGRAM + + return (sock_type, addr.split("unix://")[1]) + + if addr.startswith("udp://"): + addr = addr.split("udp://")[1] + socktype = socket.SOCK_DGRAM + elif addr.startswith("tcp://"): + addr = addr.split("tcp://")[1] + socktype = socket.SOCK_STREAM + else: + raise RuntimeError("invalid syslog address") + + if '[' in addr and ']' in addr: + host = addr.split(']')[0][1:].lower() + elif ':' in addr: + host = addr.split(':')[0].lower() + elif addr == "": + host = "localhost" + else: + host = addr.lower() + + addr = addr.split(']')[-1] + if ":" in addr: + port = addr.split(':', 1)[1] + if not port.isdigit(): + raise RuntimeError("%r is not a valid port number." % port) + port = int(port) + else: + port = 514 + + return (socktype, (host, port)) + + +class Logger(object): + + LOG_LEVELS = { + "critical": logging.CRITICAL, + "error": logging.ERROR, + "warning": logging.WARNING, + "info": logging.INFO, + "debug": logging.DEBUG + } + loglevel = logging.INFO + + error_fmt = r"%(asctime)s [%(process)d] [%(levelname)s] %(message)s" + datefmt = r"[%Y-%m-%d %H:%M:%S %z]" + + access_fmt = "%(message)s" + syslog_fmt = "[%(process)d] %(message)s" + + atoms_wrapper_class = SafeAtoms + + def __init__(self, cfg): + self.error_log = logging.getLogger("gunicorn.error") + self.error_log.propagate = False + self.access_log = logging.getLogger("gunicorn.access") + self.access_log.propagate = False + self.error_handlers = [] + self.access_handlers = [] + self.cfg = cfg + self.setup(cfg) + + def setup(self, cfg): + self.loglevel = self.LOG_LEVELS.get(cfg.loglevel.lower(), logging.INFO) + self.error_log.setLevel(self.loglevel) + self.access_log.setLevel(logging.INFO) + + # set gunicorn.error handler + self._set_handler(self.error_log, cfg.errorlog, + logging.Formatter(self.error_fmt, self.datefmt)) + + # set gunicorn.access handler + if cfg.accesslog is not None: + self._set_handler(self.access_log, cfg.accesslog, + fmt=logging.Formatter(self.access_fmt)) + + # set syslog handler + if cfg.syslog: + self._set_syslog_handler( + self.error_log, cfg, self.syslog_fmt, "error" + ) + self._set_syslog_handler( + self.access_log, cfg, self.syslog_fmt, "access" + ) + + if cfg.logconfig: + if os.path.exists(cfg.logconfig): + defaults = CONFIG_DEFAULTS.copy() + defaults['__file__'] = cfg.logconfig + defaults['here'] = os.path.dirname(cfg.logconfig) + fileConfig(cfg.logconfig, defaults=defaults, + disable_existing_loggers=False) + else: + msg = "Error: log config '%s' not found" + raise RuntimeError(msg % cfg.logconfig) + + def critical(self, msg, *args, **kwargs): + self.error_log.critical(msg, *args, **kwargs) + + def error(self, msg, *args, **kwargs): + self.error_log.error(msg, *args, **kwargs) + + def warning(self, msg, *args, **kwargs): + self.error_log.warning(msg, *args, **kwargs) + + def info(self, msg, *args, **kwargs): + self.error_log.info(msg, *args, **kwargs) + + def debug(self, msg, *args, **kwargs): + self.error_log.debug(msg, *args, **kwargs) + + def exception(self, msg, *args): + self.error_log.exception(msg, *args) + + def log(self, lvl, msg, *args, **kwargs): + if isinstance(lvl, string_types): + lvl = self.LOG_LEVELS.get(lvl.lower(), logging.INFO) + self.error_log.log(lvl, msg, *args, **kwargs) + + def atoms(self, resp, req, environ, request_time): + """ Gets atoms for log formating. + """ + status = resp.status.split(None, 1)[0] + atoms = { + 'h': environ.get('REMOTE_ADDR', '-'), + 'l': '-', + 'u': '-', # would be cool to get username from basic auth header + 't': self.now(), + 'r': "%s %s %s" % (environ['REQUEST_METHOD'], + environ['RAW_URI'], environ["SERVER_PROTOCOL"]), + 's': status, + 'b': resp.sent and str(resp.sent) or '-', + 'f': environ.get('HTTP_REFERER', '-'), + 'a': environ.get('HTTP_USER_AGENT', '-'), + 'T': request_time.seconds, + 'D': (request_time.seconds*1000000) + request_time.microseconds, + 'L': "%d.%06d" % (request_time.seconds, request_time.microseconds), + 'p': "<%s>" % os.getpid() + } + + # add request headers + if hasattr(req, 'headers'): + req_headers = req.headers + else: + req_headers = req + + atoms.update(dict([("{%s}i" % k.lower(), v) for k, v in req_headers])) + + # add response headers + atoms.update(dict([("{%s}o" % k.lower(), v) for k, v in resp.headers])) + + return atoms + + def access(self, resp, req, environ, request_time): + """ See http://httpd.apache.org/docs/2.0/logs.html#combined + for format details + """ + + if not self.cfg.accesslog and not self.cfg.logconfig: + return + + # wrap atoms: + # - make sure atoms will be test case insensitively + # - if atom doesn't exist replace it by '-' + safe_atoms = self.atoms_wrapper_class(self.atoms(resp, req, environ, + request_time)) + + try: + self.access_log.info(self.cfg.access_log_format % safe_atoms) + except: + self.error(traceback.format_exc()) + + def now(self): + """ return date in Apache Common Log Format """ + return time.strftime('[%d/%b/%Y:%H:%M:%S %z]') + + def reopen_files(self): + for log in loggers(): + for handler in log.handlers: + if isinstance(handler, logging.FileHandler): + handler.acquire() + try: + if handler.stream: + handler.stream.close() + handler.stream = open(handler.baseFilename, + handler.mode) + finally: + handler.release() + + def close_on_exec(self): + for log in loggers(): + for handler in log.handlers: + if isinstance(handler, logging.FileHandler): + handler.acquire() + try: + if handler.stream: + util.close_on_exec(handler.stream.fileno()) + finally: + handler.release() + + def _get_gunicorn_handler(self, log): + for h in log.handlers: + if getattr(h, "_gunicorn", False): + return h + + def _set_handler(self, log, output, fmt): + # remove previous gunicorn log handler + h = self._get_gunicorn_handler(log) + if h: + log.handlers.remove(h) + + if output is not None: + if output == "-": + h = logging.StreamHandler() + else: + util.check_is_writeable(output) + h = logging.FileHandler(output) + + h.setFormatter(fmt) + h._gunicorn = True + log.addHandler(h) + + def _set_syslog_handler(self, log, cfg, fmt, name): + # setup format + if not cfg.syslog_prefix: + prefix = cfg.proc_name.replace(":", ".") + else: + prefix = cfg.syslog_prefix + + prefix = "gunicorn.%s.%s" % (prefix, name) + + # set format + fmt = logging.Formatter(r"%s: %s" % (prefix, fmt)) + + # syslog facility + try: + facility = SYSLOG_FACILITIES[cfg.syslog_facility.lower()] + except KeyError: + raise RuntimeError("unknown facility name") + + # parse syslog address + socktype, addr = parse_syslog_address(cfg.syslog_addr) + + # finally setup the syslog handler + if sys.version_info >= (2, 7): + h = logging.handlers.SysLogHandler(address=addr, + facility=facility, socktype=socktype) + else: + # socktype is only supported in 2.7 and sup + # fix issue #541 + h = logging.handlers.SysLogHandler(address=addr, + facility=facility) + + h.setFormatter(fmt) + h._gunicorn = True + log.addHandler(h) diff --git a/env/lib/python2.7/site-packages/gunicorn/glogging.pyc b/env/lib/python2.7/site-packages/gunicorn/glogging.pyc new file mode 100644 index 0000000..5f2a2c0 Binary files /dev/null and b/env/lib/python2.7/site-packages/gunicorn/glogging.pyc differ diff --git a/env/lib/python2.7/site-packages/gunicorn/http/__init__.py b/env/lib/python2.7/site-packages/gunicorn/http/__init__.py new file mode 100644 index 0000000..1da6f3e --- /dev/null +++ b/env/lib/python2.7/site-packages/gunicorn/http/__init__.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 - +# +# This file is part of gunicorn released under the MIT license. +# See the NOTICE for more information. + +from gunicorn.http.message import Message, Request +from gunicorn.http.parser import RequestParser + +__all__ = ['Message', 'Request', 'RequestParser'] diff --git a/env/lib/python2.7/site-packages/gunicorn/http/__init__.pyc b/env/lib/python2.7/site-packages/gunicorn/http/__init__.pyc new file mode 100644 index 0000000..f951d28 Binary files /dev/null and b/env/lib/python2.7/site-packages/gunicorn/http/__init__.pyc differ diff --git a/env/lib/python2.7/site-packages/gunicorn/http/_sendfile.py b/env/lib/python2.7/site-packages/gunicorn/http/_sendfile.py new file mode 100644 index 0000000..14eef2f --- /dev/null +++ b/env/lib/python2.7/site-packages/gunicorn/http/_sendfile.py @@ -0,0 +1,68 @@ +# -*- coding: utf-8 - +# +# This file is part of gunicorn released under the MIT license. +# See the NOTICE for more information. + +import errno +import os +import sys + +try: + import ctypes + import ctypes.util +except MemoryError: + # selinux execmem denial + # https://bugzilla.redhat.com/show_bug.cgi?id=488396 + raise ImportError + +SUPPORTED_PLATFORMS = ( + 'darwin', + 'freebsd', + 'dragonfly', + 'linux2') + +if sys.version_info < (2, 6) or \ + sys.platform not in SUPPORTED_PLATFORMS: + raise ImportError("sendfile isn't supported on this platform") + +_libc = ctypes.CDLL(ctypes.util.find_library("c"), use_errno=True) +_sendfile = _libc.sendfile + + +def sendfile(fdout, fdin, offset, nbytes): + if sys.platform == 'darwin': + _sendfile.argtypes = [ctypes.c_int, ctypes.c_int, ctypes.c_uint64, + ctypes.POINTER(ctypes.c_uint64), ctypes.c_voidp, + ctypes.c_int] + _nbytes = ctypes.c_uint64(nbytes) + result = _sendfile(fdin, fdout, offset, _nbytes, None, 0) + + if result == -1: + e = ctypes.get_errno() + if e == errno.EAGAIN and _nbytes.value is not None: + return _nbytes.value + raise OSError(e, os.strerror(e)) + return _nbytes.value + elif sys.platform in ('freebsd', 'dragonfly',): + _sendfile.argtypes = [ctypes.c_int, ctypes.c_int, ctypes.c_uint64, + ctypes.c_uint64, ctypes.c_voidp, + ctypes.POINTER(ctypes.c_uint64), ctypes.c_int] + _sbytes = ctypes.c_uint64() + result = _sendfile(fdin, fdout, offset, nbytes, None, _sbytes, 0) + if result == -1: + e = ctypes.get_errno() + if e == errno.EAGAIN and _sbytes.value is not None: + return _sbytes.value + raise OSError(e, os.strerror(e)) + return _sbytes.value + + else: + _sendfile.argtypes = [ctypes.c_int, ctypes.c_int, + ctypes.POINTER(ctypes.c_uint64), ctypes.c_size_t] + + _offset = ctypes.c_uint64(offset) + sent = _sendfile(fdout, fdin, _offset, nbytes) + if sent == -1: + e = ctypes.get_errno() + raise OSError(e, os.strerror(e)) + return sent diff --git a/env/lib/python2.7/site-packages/gunicorn/http/_sendfile.pyc b/env/lib/python2.7/site-packages/gunicorn/http/_sendfile.pyc new file mode 100644 index 0000000..5662782 Binary files /dev/null and b/env/lib/python2.7/site-packages/gunicorn/http/_sendfile.pyc differ diff --git a/env/lib/python2.7/site-packages/gunicorn/http/body.py b/env/lib/python2.7/site-packages/gunicorn/http/body.py new file mode 100644 index 0000000..e00d924 --- /dev/null +++ b/env/lib/python2.7/site-packages/gunicorn/http/body.py @@ -0,0 +1,259 @@ +# -*- coding: utf-8 - +# +# This file is part of gunicorn released under the MIT license. +# See the NOTICE for more information. + +from gunicorn.http.errors import (NoMoreData, ChunkMissingTerminator, + InvalidChunkSize) +from gunicorn import six + + +class ChunkedReader(object): + def __init__(self, req, unreader): + self.req = req + self.parser = self.parse_chunked(unreader) + self.buf = six.BytesIO() + + def read(self, size): + if not isinstance(size, six.integer_types): + raise TypeError("size must be an integral type") + if size < 0: + raise ValueError("Size must be positive.") + if size == 0: + return b"" + + if self.parser: + while self.buf.tell() < size: + try: + self.buf.write(six.next(self.parser)) + except StopIteration: + self.parser = None + break + + data = self.buf.getvalue() + ret, rest = data[:size], data[size:] + self.buf = six.BytesIO() + self.buf.write(rest) + return ret + + def parse_trailers(self, unreader, data): + buf = six.BytesIO() + buf.write(data) + + idx = buf.getvalue().find(b"\r\n\r\n") + done = buf.getvalue()[:2] == b"\r\n" + while idx < 0 and not done: + self.get_data(unreader, buf) + idx = buf.getvalue().find(b"\r\n\r\n") + done = buf.getvalue()[:2] == b"\r\n" + if done: + unreader.unread(buf.getvalue()[2:]) + return b"" + self.req.trailers = self.req.parse_headers(buf.getvalue()[:idx]) + unreader.unread(buf.getvalue()[idx + 4:]) + + def parse_chunked(self, unreader): + (size, rest) = self.parse_chunk_size(unreader) + while size > 0: + while size > len(rest): + size -= len(rest) + yield rest + rest = unreader.read() + if not rest: + raise NoMoreData() + yield rest[:size] + # Remove \r\n after chunk + rest = rest[size:] + while len(rest) < 2: + rest += unreader.read() + if rest[:2] != b'\r\n': + raise ChunkMissingTerminator(rest[:2]) + (size, rest) = self.parse_chunk_size(unreader, data=rest[2:]) + + def parse_chunk_size(self, unreader, data=None): + buf = six.BytesIO() + if data is not None: + buf.write(data) + + idx = buf.getvalue().find(b"\r\n") + while idx < 0: + self.get_data(unreader, buf) + idx = buf.getvalue().find(b"\r\n") + + data = buf.getvalue() + line, rest_chunk = data[:idx], data[idx + 2:] + + chunk_size = line.split(b";", 1)[0].strip() + try: + chunk_size = int(chunk_size, 16) + except ValueError: + raise InvalidChunkSize(chunk_size) + + if chunk_size == 0: + try: + self.parse_trailers(unreader, rest_chunk) + except NoMoreData: + pass + return (0, None) + return (chunk_size, rest_chunk) + + def get_data(self, unreader, buf): + data = unreader.read() + if not data: + raise NoMoreData() + buf.write(data) + + +class LengthReader(object): + def __init__(self, unreader, length): + self.unreader = unreader + self.length = length + + def read(self, size): + if not isinstance(size, six.integer_types): + raise TypeError("size must be an integral type") + + size = min(self.length, size) + if size < 0: + raise ValueError("Size must be positive.") + if size == 0: + return b"" + + buf = six.BytesIO() + data = self.unreader.read() + while data: + buf.write(data) + if buf.tell() >= size: + break + data = self.unreader.read() + + buf = buf.getvalue() + ret, rest = buf[:size], buf[size:] + self.unreader.unread(rest) + self.length -= size + return ret + + +class EOFReader(object): + def __init__(self, unreader): + self.unreader = unreader + self.buf = six.BytesIO() + self.finished = False + + def read(self, size): + if not isinstance(size, six.integer_types): + raise TypeError("size must be an integral type") + if size < 0: + raise ValueError("Size must be positive.") + if size == 0: + return b"" + + if self.finished: + data = self.buf.getvalue() + ret, rest = data[:size], data[size:] + self.buf = six.BytesIO() + self.buf.write(rest) + return ret + + data = self.unreader.read() + while data: + self.buf.write(data) + if self.buf.tell() > size: + break + data = self.unreader.read() + + if not data: + self.finished = True + + data = self.buf.getvalue() + ret, rest = data[:size], data[size:] + self.buf = six.BytesIO() + self.buf.write(rest) + return ret + + +class Body(object): + def __init__(self, reader): + self.reader = reader + self.buf = six.BytesIO() + + def __iter__(self): + return self + + def __next__(self): + ret = self.readline() + if not ret: + raise StopIteration() + return ret + next = __next__ + + def getsize(self, size): + if size is None: + return six.MAXSIZE + elif not isinstance(size, six.integer_types): + raise TypeError("size must be an integral type") + elif size < 0: + return six.MAXSIZE + return size + + def read(self, size=None): + size = self.getsize(size) + if size == 0: + return b"" + + if size < self.buf.tell(): + data = self.buf.getvalue() + ret, rest = data[:size], data[size:] + self.buf = six.BytesIO() + self.buf.write(rest) + return ret + + while size > self.buf.tell(): + data = self.reader.read(1024) + if not len(data): + break + self.buf.write(data) + + data = self.buf.getvalue() + ret, rest = data[:size], data[size:] + self.buf = six.BytesIO() + self.buf.write(rest) + return ret + + def readline(self, size=None): + size = self.getsize(size) + if size == 0: + return b"" + + data = self.buf.getvalue() + self.buf = six.BytesIO() + + ret = [] + while 1: + idx = data.find(b"\n", 0, size) + idx = idx + 1 if idx >= 0 else size if len(data) >= size else 0 + if idx: + ret.append(data[:idx]) + self.buf.write(data[idx:]) + break + + ret.append(data) + size -= len(data) + data = self.reader.read(min(1024, size)) + if not data: + break + + return b"".join(ret) + + def readlines(self, size=None): + ret = [] + data = self.read() + while len(data): + pos = data.find(b"\n") + if pos < 0: + ret.append(data) + data = b"" + else: + line, data = data[:pos + 1], data[pos + 1:] + ret.append(line) + return ret diff --git a/env/lib/python2.7/site-packages/gunicorn/http/body.pyc b/env/lib/python2.7/site-packages/gunicorn/http/body.pyc new file mode 100644 index 0000000..724b4f5 Binary files /dev/null and b/env/lib/python2.7/site-packages/gunicorn/http/body.pyc differ diff --git a/env/lib/python2.7/site-packages/gunicorn/http/errors.py b/env/lib/python2.7/site-packages/gunicorn/http/errors.py new file mode 100644 index 0000000..bab7856 --- /dev/null +++ b/env/lib/python2.7/site-packages/gunicorn/http/errors.py @@ -0,0 +1,109 @@ +# -*- coding: utf-8 - +# +# This file is part of gunicorn released under the MIT license. +# See the NOTICE for more information. + + +class ParseException(Exception): + pass + + +class NoMoreData(IOError): + def __init__(self, buf=None): + self.buf = buf + + def __str__(self): + return "No more data after: %r" % self.buf + + +class InvalidRequestLine(ParseException): + def __init__(self, req): + self.req = req + self.code = 400 + + def __str__(self): + return "Invalid HTTP request line: %r" % self.req + + +class InvalidRequestMethod(ParseException): + def __init__(self, method): + self.method = method + + def __str__(self): + return "Invalid HTTP method: %r" % self.method + + +class InvalidHTTPVersion(ParseException): + def __init__(self, version): + self.version = version + + def __str__(self): + return "Invalid HTTP Version: %r" % self.version + + +class InvalidHeader(ParseException): + def __init__(self, hdr, req=None): + self.hdr = hdr + self.req = req + + def __str__(self): + return "Invalid HTTP Header: %r" % self.hdr + + +class InvalidHeaderName(ParseException): + def __init__(self, hdr): + self.hdr = hdr + + def __str__(self): + return "Invalid HTTP header name: %r" % self.hdr + + +class InvalidChunkSize(IOError): + def __init__(self, data): + self.data = data + + def __str__(self): + return "Invalid chunk size: %r" % self.data + + +class ChunkMissingTerminator(IOError): + def __init__(self, term): + self.term = term + + def __str__(self): + return "Invalid chunk terminator is not '\\r\\n': %r" % self.term + + +class LimitRequestLine(ParseException): + def __init__(self, size, max_size): + self.size = size + self.max_size = max_size + + def __str__(self): + return "Request Line is too large (%s > %s)" % (self.size, self.max_size) + + +class LimitRequestHeaders(ParseException): + def __init__(self, msg): + self.msg = msg + + def __str__(self): + return self.msg + + +class InvalidProxyLine(ParseException): + def __init__(self, line): + self.line = line + self.code = 400 + + def __str__(self): + return "Invalid PROXY line: %r" % self.line + + +class ForbiddenProxyRequest(ParseException): + def __init__(self, host): + self.host = host + self.code = 403 + + def __str__(self): + return "Proxy request from %r not allowed" % self.host diff --git a/env/lib/python2.7/site-packages/gunicorn/http/errors.pyc b/env/lib/python2.7/site-packages/gunicorn/http/errors.pyc new file mode 100644 index 0000000..fe0fed5 Binary files /dev/null and b/env/lib/python2.7/site-packages/gunicorn/http/errors.pyc differ diff --git a/env/lib/python2.7/site-packages/gunicorn/http/message.py b/env/lib/python2.7/site-packages/gunicorn/http/message.py new file mode 100644 index 0000000..0f42eee --- /dev/null +++ b/env/lib/python2.7/site-packages/gunicorn/http/message.py @@ -0,0 +1,340 @@ +# -*- coding: utf-8 - +# +# This file is part of gunicorn released under the MIT license. +# See the NOTICE for more information. + +import re +import socket +from errno import ENOTCONN + +from gunicorn._compat import bytes_to_str +from gunicorn.http.unreader import SocketUnreader +from gunicorn.http.body import ChunkedReader, LengthReader, EOFReader, Body +from gunicorn.http.errors import (InvalidHeader, InvalidHeaderName, NoMoreData, + InvalidRequestLine, InvalidRequestMethod, InvalidHTTPVersion, + LimitRequestLine, LimitRequestHeaders) +from gunicorn.http.errors import InvalidProxyLine, ForbiddenProxyRequest +from gunicorn.six import BytesIO +from gunicorn.six.moves.urllib.parse import urlsplit + +MAX_REQUEST_LINE = 8190 +MAX_HEADERS = 32768 +MAX_HEADERFIELD_SIZE = 8190 + +HEADER_RE = re.compile("[\x00-\x1F\x7F()<>@,;:\[\]={} \t\\\\\"]") +METH_RE = re.compile(r"[A-Z0-9$-_.]{3,20}") +VERSION_RE = re.compile(r"HTTP/(\d+).(\d+)") + + +class Message(object): + def __init__(self, cfg, unreader): + self.cfg = cfg + self.unreader = unreader + self.version = None + self.headers = [] + self.trailers = [] + self.body = None + + # set headers limits + self.limit_request_fields = cfg.limit_request_fields + if (self.limit_request_fields <= 0 + or self.limit_request_fields > MAX_HEADERS): + self.limit_request_fields = MAX_HEADERS + self.limit_request_field_size = cfg.limit_request_field_size + if (self.limit_request_field_size < 0 + or self.limit_request_field_size > MAX_HEADERFIELD_SIZE): + self.limit_request_field_size = MAX_HEADERFIELD_SIZE + + # set max header buffer size + max_header_field_size = self.limit_request_field_size or MAX_HEADERFIELD_SIZE + self.max_buffer_headers = self.limit_request_fields * \ + (max_header_field_size + 2) + 4 + + unused = self.parse(self.unreader) + self.unreader.unread(unused) + self.set_body_reader() + + def parse(self): + raise NotImplementedError() + + def parse_headers(self, data): + headers = [] + + # Split lines on \r\n keeping the \r\n on each line + lines = [bytes_to_str(line) + "\r\n" for line in data.split(b"\r\n")] + + # Parse headers into key/value pairs paying attention + # to continuation lines. + while len(lines): + if len(headers) >= self.limit_request_fields: + raise LimitRequestHeaders("limit request headers fields") + + # Parse initial header name : value pair. + curr = lines.pop(0) + header_length = len(curr) + if curr.find(":") < 0: + raise InvalidHeader(curr.strip()) + name, value = curr.split(":", 1) + name = name.rstrip(" \t").upper() + if HEADER_RE.search(name): + raise InvalidHeaderName(name) + + name, value = name.strip(), [value.lstrip()] + + # Consume value continuation lines + while len(lines) and lines[0].startswith((" ", "\t")): + curr = lines.pop(0) + header_length += len(curr) + if header_length > self.limit_request_field_size > 0: + raise LimitRequestHeaders("limit request headers " + + "fields size") + value.append(curr) + value = ''.join(value).rstrip() + + if header_length > self.limit_request_field_size > 0: + raise LimitRequestHeaders("limit request headers fields size") + headers.append((name, value)) + return headers + + def set_body_reader(self): + chunked = False + content_length = None + for (name, value) in self.headers: + if name == "CONTENT-LENGTH": + content_length = value + elif name == "TRANSFER-ENCODING": + chunked = value.lower() == "chunked" + elif name == "SEC-WEBSOCKET-KEY1": + content_length = 8 + + if chunked: + self.body = Body(ChunkedReader(self, self.unreader)) + elif content_length is not None: + try: + content_length = int(content_length) + except ValueError: + raise InvalidHeader("CONTENT-LENGTH", req=self) + + if content_length < 0: + raise InvalidHeader("CONTENT-LENGTH", req=self) + + self.body = Body(LengthReader(self.unreader, content_length)) + else: + self.body = Body(EOFReader(self.unreader)) + + def should_close(self): + for (h, v) in self.headers: + if h == "CONNECTION": + v = v.lower().strip() + if v == "close": + return True + elif v == "keep-alive": + return False + break + return self.version <= (1, 0) + + +class Request(Message): + def __init__(self, cfg, unreader, req_number=1): + self.method = None + self.uri = None + self.path = None + self.query = None + self.fragment = None + + # get max request line size + self.limit_request_line = cfg.limit_request_line + if (self.limit_request_line < 0 + or self.limit_request_line >= MAX_REQUEST_LINE): + self.limit_request_line = MAX_REQUEST_LINE + + self.req_number = req_number + self.proxy_protocol_info = None + super(Request, self).__init__(cfg, unreader) + + def get_data(self, unreader, buf, stop=False): + data = unreader.read() + if not data: + if stop: + raise StopIteration() + raise NoMoreData(buf.getvalue()) + buf.write(data) + + def parse(self, unreader): + buf = BytesIO() + self.get_data(unreader, buf, stop=True) + + # get request line + line, rbuf = self.read_line(unreader, buf, self.limit_request_line) + + # proxy protocol + if self.proxy_protocol(bytes_to_str(line)): + # get next request line + buf = BytesIO() + buf.write(rbuf) + line, rbuf = self.read_line(unreader, buf, self.limit_request_line) + + self.parse_request_line(bytes_to_str(line)) + buf = BytesIO() + buf.write(rbuf) + + # Headers + data = buf.getvalue() + idx = data.find(b"\r\n\r\n") + + done = data[:2] == b"\r\n" + while True: + idx = data.find(b"\r\n\r\n") + done = data[:2] == b"\r\n" + + if idx < 0 and not done: + self.get_data(unreader, buf) + data = buf.getvalue() + if len(data) > self.max_buffer_headers: + raise LimitRequestHeaders("max buffer headers") + else: + break + + if done: + self.unreader.unread(data[2:]) + return b"" + + self.headers = self.parse_headers(data[:idx]) + + ret = data[idx + 4:] + buf = BytesIO() + return ret + + def read_line(self, unreader, buf, limit=0): + data = buf.getvalue() + + while True: + idx = data.find(b"\r\n") + if idx >= 0: + # check if the request line is too large + if idx > limit > 0: + raise LimitRequestLine(idx, limit) + break + elif len(data) - 2 > limit > 0: + raise LimitRequestLine(len(data), limit) + self.get_data(unreader, buf) + data = buf.getvalue() + + return (data[:idx], # request line, + data[idx + 2:]) # residue in the buffer, skip \r\n + + def proxy_protocol(self, line): + """\ + Detect, check and parse proxy protocol. + + :raises: ForbiddenProxyRequest, InvalidProxyLine. + :return: True for proxy protocol line else False + """ + if not self.cfg.proxy_protocol: + return False + + if self.req_number != 1: + return False + + if not line.startswith("PROXY"): + return False + + self.proxy_protocol_access_check() + self.parse_proxy_protocol(line) + + return True + + def proxy_protocol_access_check(self): + # check in allow list + if isinstance(self.unreader, SocketUnreader): + try: + remote_host = self.unreader.sock.getpeername()[0] + except socket.error as e: + if e.args[0] == ENOTCONN: + raise ForbiddenProxyRequest("UNKNOW") + raise + if ("*" not in self.cfg.proxy_allow_ips and + remote_host not in self.cfg.proxy_allow_ips): + raise ForbiddenProxyRequest(remote_host) + + def parse_proxy_protocol(self, line): + bits = line.split() + + if len(bits) != 6: + raise InvalidProxyLine(line) + + # Extract data + proto = bits[1] + s_addr = bits[2] + d_addr = bits[3] + + # Validation + if proto not in ["TCP4", "TCP6"]: + raise InvalidProxyLine("protocol '%s' not supported" % proto) + if proto == "TCP4": + try: + socket.inet_pton(socket.AF_INET, s_addr) + socket.inet_pton(socket.AF_INET, d_addr) + except socket.error: + raise InvalidProxyLine(line) + elif proto == "TCP6": + try: + socket.inet_pton(socket.AF_INET6, s_addr) + socket.inet_pton(socket.AF_INET6, d_addr) + except socket.error: + raise InvalidProxyLine(line) + + try: + s_port = int(bits[4]) + d_port = int(bits[5]) + except ValueError: + raise InvalidProxyLine("invalid port %s" % line) + + if not ((0 <= s_port <= 65535) and (0 <= d_port <= 65535)): + raise InvalidProxyLine("invalid port %s" % line) + + # Set data + self.proxy_protocol_info = { + "proxy_protocol": proto, + "client_addr": s_addr, + "client_port": s_port, + "proxy_addr": d_addr, + "proxy_port": d_port + } + + def parse_request_line(self, line): + bits = line.split(None, 2) + if len(bits) != 3: + raise InvalidRequestLine(line) + + # Method + if not METH_RE.match(bits[0]): + raise InvalidRequestMethod(bits[0]) + self.method = bits[0].upper() + + # URI + # When the path starts with //, urlsplit considers it as a + # relative uri while the RDF says it shouldnt + # http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.2 + # considers it as an absolute url. + # fix issue #297 + if bits[1].startswith("//"): + self.uri = bits[1][1:] + else: + self.uri = bits[1] + + parts = urlsplit(self.uri) + self.path = parts.path or "" + self.query = parts.query or "" + self.fragment = parts.fragment or "" + + # Version + match = VERSION_RE.match(bits[2]) + if match is None: + raise InvalidHTTPVersion(bits[2]) + self.version = (int(match.group(1)), int(match.group(2))) + + def set_body_reader(self): + super(Request, self).set_body_reader() + if isinstance(self.body.reader, EOFReader): + self.body = Body(LengthReader(self.unreader, 0)) diff --git a/env/lib/python2.7/site-packages/gunicorn/http/message.pyc b/env/lib/python2.7/site-packages/gunicorn/http/message.pyc new file mode 100644 index 0000000..82fbedc Binary files /dev/null and b/env/lib/python2.7/site-packages/gunicorn/http/message.pyc differ diff --git a/env/lib/python2.7/site-packages/gunicorn/http/parser.py b/env/lib/python2.7/site-packages/gunicorn/http/parser.py new file mode 100644 index 0000000..a4a0f1e --- /dev/null +++ b/env/lib/python2.7/site-packages/gunicorn/http/parser.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 - +# +# This file is part of gunicorn released under the MIT license. +# See the NOTICE for more information. + +from gunicorn.http.message import Request +from gunicorn.http.unreader import SocketUnreader, IterUnreader + + +class Parser(object): + + mesg_class = None + + def __init__(self, cfg, source): + self.cfg = cfg + if hasattr(source, "recv"): + self.unreader = SocketUnreader(source) + else: + self.unreader = IterUnreader(source) + self.mesg = None + + # request counter (for keepalive connetions) + self.req_count = 0 + + def __iter__(self): + return self + + def __next__(self): + # Stop if HTTP dictates a stop. + if self.mesg and self.mesg.should_close(): + raise StopIteration() + + # Discard any unread body of the previous message + if self.mesg: + data = self.mesg.body.read(8192) + while data: + data = self.mesg.body.read(8192) + + # Parse the next request + self.req_count += 1 + self.mesg = self.mesg_class(self.cfg, self.unreader, self.req_count) + if not self.mesg: + raise StopIteration() + return self.mesg + + next = __next__ + + +class RequestParser(Parser): + + mesg_class = Request diff --git a/env/lib/python2.7/site-packages/gunicorn/http/parser.pyc b/env/lib/python2.7/site-packages/gunicorn/http/parser.pyc new file mode 100644 index 0000000..335c763 Binary files /dev/null and b/env/lib/python2.7/site-packages/gunicorn/http/parser.pyc differ diff --git a/env/lib/python2.7/site-packages/gunicorn/http/unreader.py b/env/lib/python2.7/site-packages/gunicorn/http/unreader.py new file mode 100644 index 0000000..d7e411a --- /dev/null +++ b/env/lib/python2.7/site-packages/gunicorn/http/unreader.py @@ -0,0 +1,80 @@ +# -*- coding: utf-8 - +# +# This file is part of gunicorn released under the MIT license. +# See the NOTICE for more information. + +import os + +from gunicorn import six + +# Classes that can undo reading data from +# a given type of data source. + + +class Unreader(object): + def __init__(self): + self.buf = six.BytesIO() + + def chunk(self): + raise NotImplementedError() + + def read(self, size=None): + if size is not None and not isinstance(size, six.integer_types): + raise TypeError("size parameter must be an int or long.") + + if size is not None: + if size == 0: + return b"" + if size < 0: + size = None + + self.buf.seek(0, os.SEEK_END) + + if size is None and self.buf.tell(): + ret = self.buf.getvalue() + self.buf = six.BytesIO() + return ret + if size is None: + d = self.chunk() + return d + + while self.buf.tell() < size: + chunk = self.chunk() + if not len(chunk): + ret = self.buf.getvalue() + self.buf = six.BytesIO() + return ret + self.buf.write(chunk) + data = self.buf.getvalue() + self.buf = six.BytesIO() + self.buf.write(data[size:]) + return data[:size] + + def unread(self, data): + self.buf.seek(0, os.SEEK_END) + self.buf.write(data) + + +class SocketUnreader(Unreader): + def __init__(self, sock, max_chunk=8192): + super(SocketUnreader, self).__init__() + self.sock = sock + self.mxchunk = max_chunk + + def chunk(self): + return self.sock.recv(self.mxchunk) + + +class IterUnreader(Unreader): + def __init__(self, iterable): + super(IterUnreader, self).__init__() + self.iter = iter(iterable) + + def chunk(self): + if not self.iter: + return b"" + try: + return six.next(self.iter) + except StopIteration: + self.iter = None + return b"" diff --git a/env/lib/python2.7/site-packages/gunicorn/http/unreader.pyc b/env/lib/python2.7/site-packages/gunicorn/http/unreader.pyc new file mode 100644 index 0000000..6990251 Binary files /dev/null and b/env/lib/python2.7/site-packages/gunicorn/http/unreader.pyc differ diff --git a/env/lib/python2.7/site-packages/gunicorn/http/wsgi.py b/env/lib/python2.7/site-packages/gunicorn/http/wsgi.py new file mode 100644 index 0000000..d9cef76 --- /dev/null +++ b/env/lib/python2.7/site-packages/gunicorn/http/wsgi.py @@ -0,0 +1,425 @@ +# -*- coding: utf-8 - +# +# This file is part of gunicorn released under the MIT license. +# See the NOTICE for more information. + +import io +import logging +import os +import re +import sys + +from gunicorn._compat import unquote_to_wsgi_str +from gunicorn.six import string_types, binary_type, reraise +from gunicorn import SERVER_SOFTWARE +import gunicorn.six as six +import gunicorn.util as util + +try: + # Python 3.3 has os.sendfile(). + from os import sendfile +except ImportError: + try: + from ._sendfile import sendfile + except ImportError: + sendfile = None + +NORMALIZE_SPACE = re.compile(r'(?:\r\n)?[ \t]+') + +log = logging.getLogger(__name__) + + +class FileWrapper(object): + + def __init__(self, filelike, blksize=8192): + self.filelike = filelike + self.blksize = blksize + if hasattr(filelike, 'close'): + self.close = filelike.close + + def __getitem__(self, key): + data = self.filelike.read(self.blksize) + if data: + return data + raise IndexError + + +class WSGIErrorsWrapper(io.RawIOBase): + + def __init__(self, cfg): + errorlog = logging.getLogger("gunicorn.error") + handlers = errorlog.handlers + self.streams = [] + + if cfg.errorlog == "-": + self.streams.append(sys.stderr) + handlers = handlers[1:] + + for h in handlers: + if hasattr(h, "stream"): + self.streams.append(h.stream) + + def write(self, data): + for stream in self.streams: + try: + stream.write(data) + except UnicodeError: + stream.write(data.encode("UTF-8")) + stream.flush() + + +def base_environ(cfg): + return { + "wsgi.errors": WSGIErrorsWrapper(cfg), + "wsgi.version": (1, 0), + "wsgi.multithread": False, + "wsgi.multiprocess": (cfg.workers > 1), + "wsgi.run_once": False, + "wsgi.file_wrapper": FileWrapper, + "SERVER_SOFTWARE": SERVER_SOFTWARE, + } + + +def default_environ(req, sock, cfg): + env = base_environ(cfg) + env.update({ + "wsgi.input": req.body, + "gunicorn.socket": sock, + "REQUEST_METHOD": req.method, + "QUERY_STRING": req.query, + "RAW_URI": req.uri, + "SERVER_PROTOCOL": "HTTP/%s" % ".".join([str(v) for v in req.version]) + }) + return env + + +def proxy_environ(req): + info = req.proxy_protocol_info + + if not info: + return {} + + return { + "PROXY_PROTOCOL": info["proxy_protocol"], + "REMOTE_ADDR": info["client_addr"], + "REMOTE_PORT": str(info["client_port"]), + "PROXY_ADDR": info["proxy_addr"], + "PROXY_PORT": str(info["proxy_port"]), + } + + +def create(req, sock, client, server, cfg): + resp = Response(req, sock, cfg) + + # set initial environ + environ = default_environ(req, sock, cfg) + + # default variables + host = None + url_scheme = "https" if cfg.is_ssl else "http" + script_name = os.environ.get("SCRIPT_NAME", "") + + # set secure_headers + secure_headers = cfg.secure_scheme_headers + if client and not isinstance(client, string_types): + if ('*' not in cfg.forwarded_allow_ips + and client[0] not in cfg.forwarded_allow_ips): + secure_headers = {} + + # add the headers tot the environ + for hdr_name, hdr_value in req.headers: + if hdr_name == "EXPECT": + # handle expect + if hdr_value.lower() == "100-continue": + sock.send(b"HTTP/1.1 100 Continue\r\n\r\n") + elif secure_headers and (hdr_name in secure_headers and + hdr_value == secure_headers[hdr_name]): + url_scheme = "https" + elif hdr_name == 'HOST': + host = hdr_value + elif hdr_name == "SCRIPT_NAME": + script_name = hdr_value + elif hdr_name == "CONTENT-TYPE": + environ['CONTENT_TYPE'] = hdr_value + continue + elif hdr_name == "CONTENT-LENGTH": + environ['CONTENT_LENGTH'] = hdr_value + continue + + key = 'HTTP_' + hdr_name.replace('-', '_') + if key in environ: + hdr_value = "%s,%s" % (environ[key], hdr_value) + environ[key] = hdr_value + + # set the url schejeme + environ['wsgi.url_scheme'] = url_scheme + + # set the REMOTE_* keys in environ + # authors should be aware that REMOTE_HOST and REMOTE_ADDR + # may not qualify the remote addr: + # http://www.ietf.org/rfc/rfc3875 + if isinstance(client, string_types): + environ['REMOTE_ADDR'] = client + elif isinstance(client, binary_type): + environ['REMOTE_ADDR'] = str(client) + else: + environ['REMOTE_ADDR'] = client[0] + environ['REMOTE_PORT'] = str(client[1]) + + # handle the SERVER_* + # Normally only the application should use the Host header but since the + # WSGI spec doesn't support unix sockets, we are using it to create + # viable SERVER_* if possible. + if isinstance(server, string_types): + server = server.split(":") + if len(server) == 1: + # unix socket + if host and host is not None: + server = host.split(':') + if len(server) == 1: + if url_scheme == "http": + server.append(80), + elif url_scheme == "https": + server.append(443) + else: + server.append('') + else: + # no host header given which means that we are not behind a + # proxy, so append an empty port. + server.append('') + environ['SERVER_NAME'] = server[0] + environ['SERVER_PORT'] = str(server[1]) + + # set the path and script name + path_info = req.path + if script_name: + path_info = path_info.split(script_name, 1)[1] + environ['PATH_INFO'] = unquote_to_wsgi_str(path_info) + environ['SCRIPT_NAME'] = script_name + + # override the environ with the correct remote and server address if + # we are behind a proxy using the proxy protocol. + environ.update(proxy_environ(req)) + return resp, environ + + +class Response(object): + + def __init__(self, req, sock, cfg): + self.req = req + self.sock = sock + self.version = SERVER_SOFTWARE + self.status = None + self.chunked = False + self.must_close = False + self.headers = [] + self.headers_sent = False + self.response_length = None + self.sent = 0 + self.upgrade = False + self.cfg = cfg + + def force_close(self): + self.must_close = True + + def should_close(self): + if self.must_close or self.req.should_close(): + return True + if self.response_length is not None or self.chunked: + return False + if self.status_code < 200 or self.status_code in (204, 304): + return False + return True + + def start_response(self, status, headers, exc_info=None): + if exc_info: + try: + if self.status and self.headers_sent: + reraise(exc_info[0], exc_info[1], exc_info[2]) + finally: + exc_info = None + elif self.status is not None: + raise AssertionError("Response headers already set!") + + self.status = status + + # get the status code from the response here so we can use it to check + # the need for the connection header later without parsing the string + # each time. + try: + self.status_code = int(self.status.split()[0]) + except ValueError: + self.status_code = None + + self.process_headers(headers) + self.chunked = self.is_chunked() + return self.write + + def process_headers(self, headers): + for name, value in headers: + if not isinstance(name, string_types): + raise TypeError('%r is not a string' % name) + value = str(value).strip() + lname = name.lower().strip() + if lname == "content-length": + self.response_length = int(value) + elif util.is_hoppish(name): + if lname == "connection": + # handle websocket + if value.lower().strip() == "upgrade": + self.upgrade = True + elif lname == "upgrade": + if value.lower().strip() == "websocket": + self.headers.append((name.strip(), value)) + + # ignore hopbyhop headers + continue + self.headers.append((name.strip(), value)) + + def is_chunked(self): + # Only use chunked responses when the client is + # speaking HTTP/1.1 or newer and there was + # no Content-Length header set. + if self.response_length is not None: + return False + elif self.req.version <= (1, 0): + return False + elif self.status_code in (204, 304): + # Do not use chunked responses when the response is guaranteed to + # not have a response body. + return False + return True + + def default_headers(self): + # set the connection header + if self.upgrade: + connection = "upgrade" + elif self.should_close(): + connection = "close" + else: + connection = "keep-alive" + + headers = [ + "HTTP/%s.%s %s\r\n" % (self.req.version[0], + self.req.version[1], self.status), + "Server: %s\r\n" % self.version, + "Date: %s\r\n" % util.http_date(), + "Connection: %s\r\n" % connection + ] + if self.chunked: + headers.append("Transfer-Encoding: chunked\r\n") + return headers + + def send_headers(self): + if self.headers_sent: + return + tosend = self.default_headers() + tosend.extend(["%s: %s\r\n" % (k, v) for k, v in self.headers]) + + header_str = "%s\r\n" % "".join(tosend) + util.write(self.sock, util.to_bytestring(header_str)) + self.headers_sent = True + + def write(self, arg): + self.send_headers() + if not isinstance(arg, binary_type): + raise TypeError('%r is not a byte' % arg) + arglen = len(arg) + tosend = arglen + if self.response_length is not None: + if self.sent >= self.response_length: + # Never write more than self.response_length bytes + return + + tosend = min(self.response_length - self.sent, tosend) + if tosend < arglen: + arg = arg[:tosend] + + # Sending an empty chunk signals the end of the + # response and prematurely closes the response + if self.chunked and tosend == 0: + return + + self.sent += tosend + util.write(self.sock, arg, self.chunked) + + def can_sendfile(self): + return (self.cfg.sendfile and (sendfile is not None)) + + def sendfile_all(self, fileno, sockno, offset, nbytes): + # Send file in at most 1GB blocks as some operating + # systems can have problems with sending files in blocks + # over 2GB. + + BLKSIZE = 0x3FFFFFFF + + if nbytes > BLKSIZE: + for m in range(0, nbytes, BLKSIZE): + self.sendfile_all(fileno, sockno, offset, min(nbytes, BLKSIZE)) + offset += BLKSIZE + nbytes -= BLKSIZE + else: + sent = 0 + sent += sendfile(sockno, fileno, offset + sent, nbytes - sent) + while sent != nbytes: + sent += sendfile(sockno, fileno, offset + sent, nbytes - sent) + + def sendfile_use_send(self, fileno, fo_offset, nbytes): + + # send file in blocks of 8182 bytes + BLKSIZE = 8192 + + sent = 0 + while sent != nbytes: + data = os.read(fileno, BLKSIZE) + if not data: + break + + sent += len(data) + if sent > nbytes: + data = data[:nbytes - sent] + + util.write(self.sock, data, self.chunked) + + def write_file(self, respiter): + if self.can_sendfile() and util.is_fileobject(respiter.filelike): + # sometimes the fileno isn't a callable + if six.callable(respiter.filelike.fileno): + fileno = respiter.filelike.fileno() + else: + fileno = respiter.filelike.fileno + + fd_offset = os.lseek(fileno, 0, os.SEEK_CUR) + fo_offset = respiter.filelike.tell() + nbytes = max(os.fstat(fileno).st_size - fo_offset, 0) + + if self.response_length: + nbytes = min(nbytes, self.response_length) + + if nbytes == 0: + return + + self.send_headers() + + if self.cfg.is_ssl: + self.sendfile_use_send(fileno, fo_offset, nbytes) + else: + if self.is_chunked(): + chunk_size = "%X\r\n" % nbytes + self.sock.sendall(chunk_size.encode('utf-8')) + + self.sendfile_all(fileno, self.sock.fileno(), fo_offset, nbytes) + + if self.is_chunked(): + self.sock.sendall(b"\r\n") + + os.lseek(fileno, fd_offset, os.SEEK_SET) + else: + for item in respiter: + self.write(item) + + def close(self): + if not self.headers_sent: + self.send_headers() + if self.chunked: + util.write_chunk(self.sock, b"") diff --git a/env/lib/python2.7/site-packages/gunicorn/http/wsgi.pyc b/env/lib/python2.7/site-packages/gunicorn/http/wsgi.pyc new file mode 100644 index 0000000..7cb3788 Binary files /dev/null and b/env/lib/python2.7/site-packages/gunicorn/http/wsgi.pyc differ diff --git a/env/lib/python2.7/site-packages/gunicorn/instrument/__init__.py b/env/lib/python2.7/site-packages/gunicorn/instrument/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/env/lib/python2.7/site-packages/gunicorn/instrument/__init__.pyc b/env/lib/python2.7/site-packages/gunicorn/instrument/__init__.pyc new file mode 100644 index 0000000..32600b8 Binary files /dev/null and b/env/lib/python2.7/site-packages/gunicorn/instrument/__init__.pyc differ diff --git a/env/lib/python2.7/site-packages/gunicorn/instrument/statsd.py b/env/lib/python2.7/site-packages/gunicorn/instrument/statsd.py new file mode 100644 index 0000000..0baf343 --- /dev/null +++ b/env/lib/python2.7/site-packages/gunicorn/instrument/statsd.py @@ -0,0 +1,126 @@ +# -*- coding: utf-8 - +# +# This file is part of gunicorn released under the MIT license. +# See the NOTICE for more information. + +"Bare-bones implementation of statsD's protocol, client-side" + +import socket +import logging +from re import sub +from gunicorn.glogging import Logger + +# Instrumentation constants +STATSD_DEFAULT_PORT = 8125 +METRIC_VAR = "metric" +VALUE_VAR = "value" +MTYPE_VAR = "mtype" +GAUGE_TYPE = "gauge" +COUNTER_TYPE = "counter" +HISTOGRAM_TYPE = "histogram" + +class Statsd(Logger): + """statsD-based instrumentation, that passes as a logger + """ + def __init__(self, cfg): + """host, port: statsD server + """ + Logger.__init__(self, cfg) + self.prefix = sub(r"^(.+[^.]+)\.*$", "\g<1>.", cfg.statsd_prefix) + try: + host, port = cfg.statsd_host + self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + self.sock.connect((host, int(port))) + except Exception: + self.sock = None + + # Log errors and warnings + def critical(self, msg, *args, **kwargs): + Logger.critical(self, msg, *args, **kwargs) + self.increment("gunicorn.log.critical", 1) + + def error(self, msg, *args, **kwargs): + Logger.error(self, msg, *args, **kwargs) + self.increment("gunicorn.log.error", 1) + + def warning(self, msg, *args, **kwargs): + Logger.warning(self, msg, *args, **kwargs) + self.increment("gunicorn.log.warning", 1) + + def exception(self, msg, *args, **kwargs): + Logger.exception(self, msg, *args, **kwargs) + self.increment("gunicorn.log.exception", 1) + + # Special treatement for info, the most common log level + def info(self, msg, *args, **kwargs): + self.log(logging.INFO, msg, *args, **kwargs) + + # skip the run-of-the-mill logs + def debug(self, msg, *args, **kwargs): + self.log(logging.DEBUG, msg, *args, **kwargs) + + def log(self, lvl, msg, *args, **kwargs): + """Log a given statistic if metric, value and type are present + """ + try: + extra = kwargs.get("extra", None) + if extra is not None: + metric = extra.get(METRIC_VAR, None) + value = extra.get(VALUE_VAR, None) + typ = extra.get(MTYPE_VAR, None) + if metric and value and typ: + if typ == GAUGE_TYPE: + self.gauge(metric, value) + elif typ == COUNTER_TYPE: + self.increment(metric, value) + elif typ == HISTOGRAM_TYPE: + self.histogram(metric, value) + else: + pass + + # Log to parent logger only if there is something to say + if msg is not None and len(msg) > 0: + Logger.log(self, lvl, msg, *args, **kwargs) + except Exception: + pass + + # access logging + def access(self, resp, req, environ, request_time): + """Measure request duration + request_time is a datetime.timedelta + """ + Logger.access(self, resp, req, environ, request_time) + duration_in_ms = request_time.seconds * 1000 + float(request_time.microseconds) / 10 ** 3 + self.histogram("gunicorn.request.duration", duration_in_ms) + self.increment("gunicorn.requests", 1) + self.increment("gunicorn.request.status.%d" % int(resp.status.split()[0]), 1) + + # statsD methods + # you can use those directly if you want + def gauge(self, name, value): + try: + if self.sock: + self.sock.send("{0}{1}:{2}|g".format(self.prefix, name, value)) + except Exception: + pass + + def increment(self, name, value, sampling_rate=1.0): + try: + if self.sock: + self.sock.send("{0}{1}:{2}|c|@{3}".format(self.prefix, name, value, sampling_rate)) + except Exception: + pass + + def decrement(self, name, value, sampling_rate=1.0): + try: + if self.sock: + self.sock.send("{0){1}:-{2}|c|@{3}".format(self.prefix, name, value, sampling_rate)) + except Exception: + pass + + def histogram(self, name, value): + try: + if self.sock: + self.sock.send("{0}{1}:{2}|ms".format(self.prefix, name, value)) + except Exception: + pass diff --git a/env/lib/python2.7/site-packages/gunicorn/instrument/statsd.pyc b/env/lib/python2.7/site-packages/gunicorn/instrument/statsd.pyc new file mode 100644 index 0000000..0528fa3 Binary files /dev/null and b/env/lib/python2.7/site-packages/gunicorn/instrument/statsd.pyc differ diff --git a/env/lib/python2.7/site-packages/gunicorn/management/__init__.py b/env/lib/python2.7/site-packages/gunicorn/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/env/lib/python2.7/site-packages/gunicorn/management/__init__.pyc b/env/lib/python2.7/site-packages/gunicorn/management/__init__.pyc new file mode 100644 index 0000000..8f90372 Binary files /dev/null and b/env/lib/python2.7/site-packages/gunicorn/management/__init__.pyc differ diff --git a/env/lib/python2.7/site-packages/gunicorn/management/commands/__init__.py b/env/lib/python2.7/site-packages/gunicorn/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/env/lib/python2.7/site-packages/gunicorn/management/commands/__init__.pyc b/env/lib/python2.7/site-packages/gunicorn/management/commands/__init__.pyc new file mode 100644 index 0000000..eaed342 Binary files /dev/null and b/env/lib/python2.7/site-packages/gunicorn/management/commands/__init__.pyc differ diff --git a/env/lib/python2.7/site-packages/gunicorn/management/commands/run_gunicorn.py b/env/lib/python2.7/site-packages/gunicorn/management/commands/run_gunicorn.py new file mode 100644 index 0000000..64c7908 --- /dev/null +++ b/env/lib/python2.7/site-packages/gunicorn/management/commands/run_gunicorn.py @@ -0,0 +1,113 @@ +# -*- coding: utf-8 - +# +# This file is part of gunicorn released under the MIT license. +# See the NOTICE for more information. + +from optparse import make_option +import sys + +from django.core.management.base import BaseCommand, CommandError + +from gunicorn.app.djangoapp import DjangoApplicationCommand +from gunicorn.config import make_settings +from gunicorn import util + + +# monkey patch django. +# This patch make sure that we use real threads to get the ident which +# is going to happen if we are using gevent or eventlet. +try: + from django.db.backends import BaseDatabaseWrapper, DatabaseError + + if "validate_thread_sharing" in BaseDatabaseWrapper.__dict__: + import thread + _get_ident = thread.get_ident + + __old__init__ = BaseDatabaseWrapper.__init__ + + def _init(self, *args, **kwargs): + __old__init__(self, *args, **kwargs) + self._thread_ident = _get_ident() + + def _validate_thread_sharing(self): + if (not self.allow_thread_sharing + and self._thread_ident != _get_ident()): + raise DatabaseError("DatabaseWrapper objects created in a " + "thread can only be used in that same thread. The object " + "with alias '%s' was created in thread id %s and this is " + "thread id %s." + % (self.alias, self._thread_ident, _get_ident())) + + BaseDatabaseWrapper.__init__ = _init + BaseDatabaseWrapper.validate_thread_sharing = _validate_thread_sharing +except ImportError: + pass + + +def make_options(): + opts = [ + make_option('--adminmedia', dest='admin_media_path', default='', + help='Specifies the directory from which to serve admin media.') + ] + + g_settings = make_settings(ignore=("version")) + keys = g_settings.keys() + for k in keys: + if k in ('pythonpath', 'django_settings',): + continue + + setting = g_settings[k] + if not setting.cli: + continue + + args = tuple(setting.cli) + + kwargs = { + "dest": setting.name, + "metavar": setting.meta or None, + "action": setting.action or "store", + "type": setting.type or "string", + "default": None, + "help": "%s [%s]" % (setting.short, setting.default) + } + if kwargs["action"] != "store": + kwargs.pop("type") + + opts.append(make_option(*args, **kwargs)) + + return tuple(opts) + +GUNICORN_OPTIONS = make_options() + + +class Command(BaseCommand): + option_list = BaseCommand.option_list + GUNICORN_OPTIONS + help = "Starts a fully-functional Web server using gunicorn." + args = '[optional port number, or ipaddr:port or unix:/path/to/sockfile]' + + # Validation is called explicitly each time the server is reloaded. + requires_model_validation = False + + def handle(self, addrport=None, *args, **options): + + # deprecation warning to announce future deletion in R21 + util.warn("""This command is deprecated. + + You should now run your application with the WSGI interface + installed with your project. Ex.: + + gunicorn myproject.wsgi:application + + See https://docs.djangoproject.com/en/1.5/howto/deployment/wsgi/gunicorn/ + for more info.""") + + if args: + raise CommandError('Usage is run_gunicorn %s' % self.args) + + if addrport: + sys.argv = sys.argv[:-1] + options['bind'] = addrport + + admin_media_path = options.pop('admin_media_path', '') + + DjangoApplicationCommand(options, admin_media_path).run() diff --git a/env/lib/python2.7/site-packages/gunicorn/management/commands/run_gunicorn.pyc b/env/lib/python2.7/site-packages/gunicorn/management/commands/run_gunicorn.pyc new file mode 100644 index 0000000..2efbb8f Binary files /dev/null and b/env/lib/python2.7/site-packages/gunicorn/management/commands/run_gunicorn.pyc differ diff --git a/env/lib/python2.7/site-packages/gunicorn/pidfile.py b/env/lib/python2.7/site-packages/gunicorn/pidfile.py new file mode 100644 index 0000000..4cf603c --- /dev/null +++ b/env/lib/python2.7/site-packages/gunicorn/pidfile.py @@ -0,0 +1,84 @@ +# -*- coding: utf-8 - +# +# This file is part of gunicorn released under the MIT license. +# See the NOTICE for more information. + +import errno +import os +import tempfile + + +class Pidfile(object): + """\ + Manage a PID file. If a specific name is provided + it and '"%s.oldpid" % name' will be used. Otherwise + we create a temp file using os.mkstemp. + """ + + def __init__(self, fname): + self.fname = fname + self.pid = None + + def create(self, pid): + oldpid = self.validate() + if oldpid: + if oldpid == os.getpid(): + return + msg = "Already running on PID %s (or pid file '%s' is stale)" + raise RuntimeError(msg % (oldpid, self.fname)) + + self.pid = pid + + # Write pidfile + fdir = os.path.dirname(self.fname) + if fdir and not os.path.isdir(fdir): + raise RuntimeError("%s doesn't exist. Can't create pidfile." % fdir) + fd, fname = tempfile.mkstemp(dir=fdir) + os.write(fd, ("%s\n" % self.pid).encode('utf-8')) + if self.fname: + os.rename(fname, self.fname) + else: + self.fname = fname + os.close(fd) + + # set permissions to -rw-r--r-- + os.chmod(self.fname, 420) + + def rename(self, path): + self.unlink() + self.fname = path + self.create(self.pid) + + def unlink(self): + """ delete pidfile""" + try: + with open(self.fname, "r") as f: + pid1 = int(f.read() or 0) + + if pid1 == self.pid: + os.unlink(self.fname) + except: + pass + + def validate(self): + """ Validate pidfile and make it stale if needed""" + if not self.fname: + return + try: + with open(self.fname, "r") as f: + wpid = int(f.read() or 0) + + if wpid <= 0: + return + + try: + os.kill(wpid, 0) + return wpid + except OSError as e: + if e.args[0] == errno.ESRCH: + return + raise + except IOError as e: + if e.args[0] == errno.ENOENT: + return + raise diff --git a/env/lib/python2.7/site-packages/gunicorn/pidfile.pyc b/env/lib/python2.7/site-packages/gunicorn/pidfile.pyc new file mode 100644 index 0000000..c42d4dc Binary files /dev/null and b/env/lib/python2.7/site-packages/gunicorn/pidfile.pyc differ diff --git a/env/lib/python2.7/site-packages/gunicorn/reloader.py b/env/lib/python2.7/site-packages/gunicorn/reloader.py new file mode 100644 index 0000000..130e3d6 --- /dev/null +++ b/env/lib/python2.7/site-packages/gunicorn/reloader.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 - +# +# This file is part of gunicorn released under the MIT license. +# See the NOTICE for more information. + +import os +import re +import sys +import time +import threading + + +class Reloader(threading.Thread): + def __init__(self, extra_files=None, interval=1, callback=None): + super(Reloader, self).__init__() + self.setDaemon(True) + self._extra_files = set(extra_files or ()) + self._extra_files_lock = threading.RLock() + self._interval = interval + self._callback = callback + + def add_extra_file(self, filename): + with self._extra_files_lock: + self._extra_files.add(filename) + + def get_files(self): + fnames = [ + re.sub('py[co]$', 'py', module.__file__) + for module in list(sys.modules.values()) + if hasattr(module, '__file__') + ] + + with self._extra_files_lock: + fnames.extend(self._extra_files) + + return fnames + + def run(self): + mtimes = {} + while True: + for filename in self.get_files(): + try: + mtime = os.stat(filename).st_mtime + except OSError: + continue + old_time = mtimes.get(filename) + if old_time is None: + mtimes[filename] = mtime + continue + elif mtime > old_time: + if self._callback: + self._callback(filename) + time.sleep(self._interval) diff --git a/env/lib/python2.7/site-packages/gunicorn/reloader.pyc b/env/lib/python2.7/site-packages/gunicorn/reloader.pyc new file mode 100644 index 0000000..dcdff25 Binary files /dev/null and b/env/lib/python2.7/site-packages/gunicorn/reloader.pyc differ diff --git a/env/lib/python2.7/site-packages/gunicorn/selectors.py b/env/lib/python2.7/site-packages/gunicorn/selectors.py new file mode 100644 index 0000000..cdae569 --- /dev/null +++ b/env/lib/python2.7/site-packages/gunicorn/selectors.py @@ -0,0 +1,592 @@ +"""Selectors module. + +This module allows high-level and efficient I/O multiplexing, built upon the +`select` module primitives. + +The following code adapted from trollius.selectors. +""" + + +from abc import ABCMeta, abstractmethod +from collections import namedtuple, Mapping +import math +import select +import sys + +from gunicorn._compat import wrap_error, InterruptedError +from gunicorn import six + + +# generic events, that must be mapped to implementation-specific ones +EVENT_READ = (1 << 0) +EVENT_WRITE = (1 << 1) + + +def _fileobj_to_fd(fileobj): + """Return a file descriptor from a file object. + + Parameters: + fileobj -- file object or file descriptor + + Returns: + corresponding file descriptor + + Raises: + ValueError if the object is invalid + """ + if isinstance(fileobj, six.integer_types): + fd = fileobj + else: + try: + fd = int(fileobj.fileno()) + except (AttributeError, TypeError, ValueError): + raise ValueError("Invalid file object: " + "{0!r}".format(fileobj)) + if fd < 0: + raise ValueError("Invalid file descriptor: {0}".format(fd)) + return fd + + +SelectorKey = namedtuple('SelectorKey', ['fileobj', 'fd', 'events', 'data']) +"""Object used to associate a file object to its backing file descriptor, +selected event mask and attached data.""" + + +class _SelectorMapping(Mapping): + """Mapping of file objects to selector keys.""" + + def __init__(self, selector): + self._selector = selector + + def __len__(self): + return len(self._selector._fd_to_key) + + def __getitem__(self, fileobj): + try: + fd = self._selector._fileobj_lookup(fileobj) + return self._selector._fd_to_key[fd] + except KeyError: + raise KeyError("{0!r} is not registered".format(fileobj)) + + def __iter__(self): + return iter(self._selector._fd_to_key) + + +class BaseSelector(six.with_metaclass(ABCMeta)): + """Selector abstract base class. + + A selector supports registering file objects to be monitored for specific + I/O events. + + A file object is a file descriptor or any object with a `fileno()` method. + An arbitrary object can be attached to the file object, which can be used + for example to store context information, a callback, etc. + + A selector can use various implementations (select(), poll(), epoll()...) + depending on the platform. The default `Selector` class uses the most + efficient implementation on the current platform. + """ + + @abstractmethod + def register(self, fileobj, events, data=None): + """Register a file object. + + Parameters: + fileobj -- file object or file descriptor + events -- events to monitor (bitwise mask of EVENT_READ|EVENT_WRITE) + data -- attached data + + Returns: + SelectorKey instance + + Raises: + ValueError if events is invalid + KeyError if fileobj is already registered + OSError if fileobj is closed or otherwise is unacceptable to + the underlying system call (if a system call is made) + + Note: + OSError may or may not be raised + """ + raise NotImplementedError + + @abstractmethod + def unregister(self, fileobj): + """Unregister a file object. + + Parameters: + fileobj -- file object or file descriptor + + Returns: + SelectorKey instance + + Raises: + KeyError if fileobj is not registered + + Note: + If fileobj is registered but has since been closed this does + *not* raise OSError (even if the wrapped syscall does) + """ + raise NotImplementedError + + def modify(self, fileobj, events, data=None): + """Change a registered file object monitored events or attached data. + + Parameters: + fileobj -- file object or file descriptor + events -- events to monitor (bitwise mask of EVENT_READ|EVENT_WRITE) + data -- attached data + + Returns: + SelectorKey instance + + Raises: + Anything that unregister() or register() raises + """ + self.unregister(fileobj) + return self.register(fileobj, events, data) + + @abstractmethod + def select(self, timeout=None): + """Perform the actual selection, until some monitored file objects are + ready or a timeout expires. + + Parameters: + timeout -- if timeout > 0, this specifies the maximum wait time, in + seconds + if timeout <= 0, the select() call won't block, and will + report the currently ready file objects + if timeout is None, select() will block until a monitored + file object becomes ready + + Returns: + list of (key, events) for ready file objects + `events` is a bitwise mask of EVENT_READ|EVENT_WRITE + """ + raise NotImplementedError + + def close(self): + """Close the selector. + + This must be called to make sure that any underlying resource is freed. + """ + pass + + def get_key(self, fileobj): + """Return the key associated to a registered file object. + + Returns: + SelectorKey for this file object + """ + mapping = self.get_map() + try: + return mapping[fileobj] + except KeyError: + raise KeyError("{0!r} is not registered".format(fileobj)) + + @abstractmethod + def get_map(self): + """Return a mapping of file objects to selector keys.""" + raise NotImplementedError + + def __enter__(self): + return self + + def __exit__(self, *args): + self.close() + + +class _BaseSelectorImpl(BaseSelector): + """Base selector implementation.""" + + def __init__(self): + # this maps file descriptors to keys + self._fd_to_key = {} + # read-only mapping returned by get_map() + self._map = _SelectorMapping(self) + + def _fileobj_lookup(self, fileobj): + """Return a file descriptor from a file object. + + This wraps _fileobj_to_fd() to do an exhaustive search in case + the object is invalid but we still have it in our map. This + is used by unregister() so we can unregister an object that + was previously registered even if it is closed. It is also + used by _SelectorMapping. + """ + try: + return _fileobj_to_fd(fileobj) + except ValueError: + # Do an exhaustive search. + for key in self._fd_to_key.values(): + if key.fileobj is fileobj: + return key.fd + # Raise ValueError after all. + raise + + def register(self, fileobj, events, data=None): + if (not events) or (events & ~(EVENT_READ | EVENT_WRITE)): + raise ValueError("Invalid events: {0!r}".format(events)) + + key = SelectorKey(fileobj, self._fileobj_lookup(fileobj), events, data) + + if key.fd in self._fd_to_key: + raise KeyError("{0!r} (FD {1}) is already registered" + .format(fileobj, key.fd)) + + self._fd_to_key[key.fd] = key + return key + + def unregister(self, fileobj): + try: + key = self._fd_to_key.pop(self._fileobj_lookup(fileobj)) + except KeyError: + raise KeyError("{0!r} is not registered".format(fileobj)) + return key + + def modify(self, fileobj, events, data=None): + # TODO: Subclasses can probably optimize this even further. + try: + key = self._fd_to_key[self._fileobj_lookup(fileobj)] + except KeyError: + raise KeyError("{0!r} is not registered".format(fileobj)) + if events != key.events: + self.unregister(fileobj) + key = self.register(fileobj, events, data) + elif data != key.data: + # Use a shortcut to update the data. + key = key._replace(data=data) + self._fd_to_key[key.fd] = key + return key + + def close(self): + self._fd_to_key.clear() + + def get_map(self): + return self._map + + def _key_from_fd(self, fd): + """Return the key associated to a given file descriptor. + + Parameters: + fd -- file descriptor + + Returns: + corresponding key, or None if not found + """ + try: + return self._fd_to_key[fd] + except KeyError: + return None + + +class SelectSelector(_BaseSelectorImpl): + """Select-based selector.""" + + def __init__(self): + super(SelectSelector, self).__init__() + self._readers = set() + self._writers = set() + + def register(self, fileobj, events, data=None): + key = super(SelectSelector, self).register(fileobj, events, data) + if events & EVENT_READ: + self._readers.add(key.fd) + if events & EVENT_WRITE: + self._writers.add(key.fd) + return key + + def unregister(self, fileobj): + key = super(SelectSelector, self).unregister(fileobj) + self._readers.discard(key.fd) + self._writers.discard(key.fd) + return key + + if sys.platform == 'win32': + def _select(self, r, w, _, timeout=None): + r, w, x = select.select(r, w, w, timeout) + return r, w + x, [] + else: + _select = select.select + + def select(self, timeout=None): + timeout = None if timeout is None else max(timeout, 0) + ready = [] + try: + r, w, _ = wrap_error(self._select, + self._readers, self._writers, [], timeout) + except InterruptedError: + return ready + r = set(r) + w = set(w) + for fd in r | w: + events = 0 + if fd in r: + events |= EVENT_READ + if fd in w: + events |= EVENT_WRITE + + key = self._key_from_fd(fd) + if key: + ready.append((key, events & key.events)) + return ready + + +if hasattr(select, 'poll'): + + class PollSelector(_BaseSelectorImpl): + """Poll-based selector.""" + + def __init__(self): + super(PollSelector, self).__init__() + self._poll = select.poll() + + def register(self, fileobj, events, data=None): + key = super(PollSelector, self).register(fileobj, events, data) + poll_events = 0 + if events & EVENT_READ: + poll_events |= select.POLLIN + if events & EVENT_WRITE: + poll_events |= select.POLLOUT + self._poll.register(key.fd, poll_events) + return key + + def unregister(self, fileobj): + key = super(PollSelector, self).unregister(fileobj) + self._poll.unregister(key.fd) + return key + + def select(self, timeout=None): + if timeout is None: + timeout = None + elif timeout <= 0: + timeout = 0 + else: + # poll() has a resolution of 1 millisecond, round away from + # zero to wait *at least* timeout seconds. + timeout = int(math.ceil(timeout * 1e3)) + ready = [] + try: + fd_event_list = wrap_error(self._poll.poll, timeout) + except InterruptedError: + return ready + for fd, event in fd_event_list: + events = 0 + if event & ~select.POLLIN: + events |= EVENT_WRITE + if event & ~select.POLLOUT: + events |= EVENT_READ + + key = self._key_from_fd(fd) + if key: + ready.append((key, events & key.events)) + return ready + + +if hasattr(select, 'epoll'): + + class EpollSelector(_BaseSelectorImpl): + """Epoll-based selector.""" + + def __init__(self): + super(EpollSelector, self).__init__() + self._epoll = select.epoll() + + def fileno(self): + return self._epoll.fileno() + + def register(self, fileobj, events, data=None): + key = super(EpollSelector, self).register(fileobj, events, data) + epoll_events = 0 + if events & EVENT_READ: + epoll_events |= select.EPOLLIN + if events & EVENT_WRITE: + epoll_events |= select.EPOLLOUT + self._epoll.register(key.fd, epoll_events) + return key + + def unregister(self, fileobj): + key = super(EpollSelector, self).unregister(fileobj) + try: + self._epoll.unregister(key.fd) + except OSError: + # This can happen if the FD was closed since it + # was registered. + pass + return key + + def select(self, timeout=None): + if timeout is None: + timeout = -1 + elif timeout <= 0: + timeout = 0 + else: + # epoll_wait() has a resolution of 1 millisecond, round away + # from zero to wait *at least* timeout seconds. + timeout = math.ceil(timeout * 1e3) * 1e-3 + max_ev = len(self._fd_to_key) + ready = [] + try: + fd_event_list = wrap_error(self._epoll.poll, timeout, max_ev) + except InterruptedError: + return ready + for fd, event in fd_event_list: + events = 0 + if event & ~select.EPOLLIN: + events |= EVENT_WRITE + if event & ~select.EPOLLOUT: + events |= EVENT_READ + + key = self._key_from_fd(fd) + if key: + ready.append((key, events & key.events)) + return ready + + def close(self): + self._epoll.close() + super(EpollSelector, self).close() + + +if hasattr(select, 'devpoll'): + + class DevpollSelector(_BaseSelectorImpl): + """Solaris /dev/poll selector.""" + + def __init__(self): + super(DevpollSelector, self).__init__() + self._devpoll = select.devpoll() + + def fileno(self): + return self._devpoll.fileno() + + def register(self, fileobj, events, data=None): + key = super(DevpollSelector, self).register(fileobj, events, data) + poll_events = 0 + if events & EVENT_READ: + poll_events |= select.POLLIN + if events & EVENT_WRITE: + poll_events |= select.POLLOUT + self._devpoll.register(key.fd, poll_events) + return key + + def unregister(self, fileobj): + key = super(DevpollSelector, self).unregister(fileobj) + self._devpoll.unregister(key.fd) + return key + + def select(self, timeout=None): + if timeout is None: + timeout = None + elif timeout <= 0: + timeout = 0 + else: + # devpoll() has a resolution of 1 millisecond, round away from + # zero to wait *at least* timeout seconds. + timeout = math.ceil(timeout * 1e3) + ready = [] + try: + fd_event_list = self._devpoll.poll(timeout) + except InterruptedError: + return ready + for fd, event in fd_event_list: + events = 0 + if event & ~select.POLLIN: + events |= EVENT_WRITE + if event & ~select.POLLOUT: + events |= EVENT_READ + + key = self._key_from_fd(fd) + if key: + ready.append((key, events & key.events)) + return ready + + def close(self): + self._devpoll.close() + super(DevpollSelector, self).close() + + +if hasattr(select, 'kqueue'): + + class KqueueSelector(_BaseSelectorImpl): + """Kqueue-based selector.""" + + def __init__(self): + super(KqueueSelector, self).__init__() + self._kqueue = select.kqueue() + + def fileno(self): + return self._kqueue.fileno() + + def register(self, fileobj, events, data=None): + key = super(KqueueSelector, self).register(fileobj, events, data) + if events & EVENT_READ: + kev = select.kevent(key.fd, select.KQ_FILTER_READ, + select.KQ_EV_ADD) + self._kqueue.control([kev], 0, 0) + if events & EVENT_WRITE: + kev = select.kevent(key.fd, select.KQ_FILTER_WRITE, + select.KQ_EV_ADD) + self._kqueue.control([kev], 0, 0) + return key + + def unregister(self, fileobj): + key = super(KqueueSelector, self).unregister(fileobj) + if key.events & EVENT_READ: + kev = select.kevent(key.fd, select.KQ_FILTER_READ, + select.KQ_EV_DELETE) + try: + self._kqueue.control([kev], 0, 0) + except OSError: + # This can happen if the FD was closed since it + # was registered. + pass + if key.events & EVENT_WRITE: + kev = select.kevent(key.fd, select.KQ_FILTER_WRITE, + select.KQ_EV_DELETE) + try: + self._kqueue.control([kev], 0, 0) + except OSError: + # See comment above. + pass + return key + + def select(self, timeout=None): + timeout = None if timeout is None else max(timeout, 0) + max_ev = len(self._fd_to_key) + ready = [] + try: + kev_list = wrap_error(self._kqueue.control, + None, max_ev, timeout) + except InterruptedError: + return ready + for kev in kev_list: + fd = kev.ident + flag = kev.filter + events = 0 + if flag == select.KQ_FILTER_READ: + events |= EVENT_READ + if flag == select.KQ_FILTER_WRITE: + events |= EVENT_WRITE + + key = self._key_from_fd(fd) + if key: + ready.append((key, events & key.events)) + return ready + + def close(self): + self._kqueue.close() + super(KqueueSelector, self).close() + + +# Choose the best implementation: roughly, epoll|kqueue|devpoll > poll > select. +# select() also can't accept a FD > FD_SETSIZE (usually around 1024) +if 'KqueueSelector' in globals(): + DefaultSelector = KqueueSelector +elif 'EpollSelector' in globals(): + DefaultSelector = EpollSelector +elif 'DevpollSelector' in globals(): + DefaultSelector = DevpollSelector +elif 'PollSelector' in globals(): + DefaultSelector = PollSelector +else: + DefaultSelector = SelectSelector diff --git a/env/lib/python2.7/site-packages/gunicorn/selectors.pyc b/env/lib/python2.7/site-packages/gunicorn/selectors.pyc new file mode 100644 index 0000000..32cddf5 Binary files /dev/null and b/env/lib/python2.7/site-packages/gunicorn/selectors.pyc differ diff --git a/env/lib/python2.7/site-packages/gunicorn/six.py b/env/lib/python2.7/site-packages/gunicorn/six.py new file mode 100644 index 0000000..21b0e80 --- /dev/null +++ b/env/lib/python2.7/site-packages/gunicorn/six.py @@ -0,0 +1,762 @@ +"""Utilities for writing code that runs on Python 2 and 3""" + +# Copyright (c) 2010-2014 Benjamin Peterson +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from __future__ import absolute_import + +import functools +import operator +import sys +import types + +__author__ = "Benjamin Peterson " +__version__ = "1.8.0" + + +# Useful for very coarse version differentiation. +PY2 = sys.version_info[0] == 2 +PY3 = sys.version_info[0] == 3 + +if PY3: + string_types = str, + integer_types = int, + class_types = type, + text_type = str + binary_type = bytes + + MAXSIZE = sys.maxsize +else: + string_types = basestring, + integer_types = (int, long) + class_types = (type, types.ClassType) + text_type = unicode + binary_type = str + + if sys.platform.startswith("java"): + # Jython always uses 32 bits. + MAXSIZE = int((1 << 31) - 1) + else: + # It's possible to have sizeof(long) != sizeof(Py_ssize_t). + class X(object): + def __len__(self): + return 1 << 31 + try: + len(X()) + except OverflowError: + # 32-bit + MAXSIZE = int((1 << 31) - 1) + else: + # 64-bit + MAXSIZE = int((1 << 63) - 1) + del X + + +def _add_doc(func, doc): + """Add documentation to a function.""" + func.__doc__ = doc + + +def _import_module(name): + """Import module, returning the module after the last dot.""" + __import__(name) + return sys.modules[name] + + +class _LazyDescr(object): + + def __init__(self, name): + self.name = name + + def __get__(self, obj, tp): + result = self._resolve() + setattr(obj, self.name, result) # Invokes __set__. + # This is a bit ugly, but it avoids running this again. + delattr(obj.__class__, self.name) + return result + + +class MovedModule(_LazyDescr): + + def __init__(self, name, old, new=None): + super(MovedModule, self).__init__(name) + if PY3: + if new is None: + new = name + self.mod = new + else: + self.mod = old + + def _resolve(self): + return _import_module(self.mod) + + def __getattr__(self, attr): + _module = self._resolve() + value = getattr(_module, attr) + setattr(self, attr, value) + return value + + +class _LazyModule(types.ModuleType): + + def __init__(self, name): + super(_LazyModule, self).__init__(name) + self.__doc__ = self.__class__.__doc__ + + def __dir__(self): + attrs = ["__doc__", "__name__"] + attrs += [attr.name for attr in self._moved_attributes] + return attrs + + # Subclasses should override this + _moved_attributes = [] + + +class MovedAttribute(_LazyDescr): + + def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None): + super(MovedAttribute, self).__init__(name) + if PY3: + if new_mod is None: + new_mod = name + self.mod = new_mod + if new_attr is None: + if old_attr is None: + new_attr = name + else: + new_attr = old_attr + self.attr = new_attr + else: + self.mod = old_mod + if old_attr is None: + old_attr = name + self.attr = old_attr + + def _resolve(self): + module = _import_module(self.mod) + return getattr(module, self.attr) + + +class _SixMetaPathImporter(object): + """ + A meta path importer to import six.moves and its submodules. + + This class implements a PEP302 finder and loader. It should be compatible + with Python 2.5 and all existing versions of Python3 + """ + def __init__(self, six_module_name): + self.name = six_module_name + self.known_modules = {} + + def _add_module(self, mod, *fullnames): + for fullname in fullnames: + self.known_modules[self.name + "." + fullname] = mod + + def _get_module(self, fullname): + return self.known_modules[self.name + "." + fullname] + + def find_module(self, fullname, path=None): + if fullname in self.known_modules: + return self + return None + + def __get_module(self, fullname): + try: + return self.known_modules[fullname] + except KeyError: + raise ImportError("This loader does not know module " + fullname) + + def load_module(self, fullname): + try: + # in case of a reload + return sys.modules[fullname] + except KeyError: + pass + mod = self.__get_module(fullname) + if isinstance(mod, MovedModule): + mod = mod._resolve() + else: + mod.__loader__ = self + sys.modules[fullname] = mod + return mod + + def is_package(self, fullname): + """ + Return true, if the named module is a package. + + We need this method to get correct spec objects with + Python 3.4 (see PEP451) + """ + return hasattr(self.__get_module(fullname), "__path__") + + def get_code(self, fullname): + """Return None + + Required, if is_package is implemented""" + self.__get_module(fullname) # eventually raises ImportError + return None + get_source = get_code # same as get_code + +_importer = _SixMetaPathImporter(__name__) + + +class _MovedItems(_LazyModule): + """Lazy loading of moved objects""" + __path__ = [] # mark as package + + +_moved_attributes = [ + MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"), + MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"), + MovedAttribute("filterfalse", "itertools", "itertools", "ifilterfalse", "filterfalse"), + MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"), + MovedAttribute("intern", "__builtin__", "sys"), + MovedAttribute("map", "itertools", "builtins", "imap", "map"), + MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"), + MovedAttribute("reload_module", "__builtin__", "imp", "reload"), + MovedAttribute("reduce", "__builtin__", "functools"), + MovedAttribute("shlex_quote", "pipes", "shlex", "quote"), + MovedAttribute("StringIO", "StringIO", "io"), + MovedAttribute("UserDict", "UserDict", "collections"), + MovedAttribute("UserList", "UserList", "collections"), + MovedAttribute("UserString", "UserString", "collections"), + MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"), + MovedAttribute("zip", "itertools", "builtins", "izip", "zip"), + MovedAttribute("zip_longest", "itertools", "itertools", "izip_longest", "zip_longest"), + + MovedModule("builtins", "__builtin__"), + MovedModule("configparser", "ConfigParser"), + MovedModule("copyreg", "copy_reg"), + MovedModule("dbm_gnu", "gdbm", "dbm.gnu"), + MovedModule("_dummy_thread", "dummy_thread", "_dummy_thread"), + MovedModule("http_cookiejar", "cookielib", "http.cookiejar"), + MovedModule("http_cookies", "Cookie", "http.cookies"), + MovedModule("html_entities", "htmlentitydefs", "html.entities"), + MovedModule("html_parser", "HTMLParser", "html.parser"), + MovedModule("http_client", "httplib", "http.client"), + MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"), + MovedModule("email_mime_nonmultipart", "email.MIMENonMultipart", "email.mime.nonmultipart"), + MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"), + MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"), + MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"), + MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"), + MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"), + MovedModule("cPickle", "cPickle", "pickle"), + MovedModule("queue", "Queue"), + MovedModule("reprlib", "repr"), + MovedModule("socketserver", "SocketServer"), + MovedModule("_thread", "thread", "_thread"), + MovedModule("tkinter", "Tkinter"), + MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"), + MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"), + MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"), + MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"), + MovedModule("tkinter_tix", "Tix", "tkinter.tix"), + MovedModule("tkinter_ttk", "ttk", "tkinter.ttk"), + MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"), + MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"), + MovedModule("tkinter_colorchooser", "tkColorChooser", + "tkinter.colorchooser"), + MovedModule("tkinter_commondialog", "tkCommonDialog", + "tkinter.commondialog"), + MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"), + MovedModule("tkinter_font", "tkFont", "tkinter.font"), + MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"), + MovedModule("tkinter_tksimpledialog", "tkSimpleDialog", + "tkinter.simpledialog"), + MovedModule("urllib_parse", __name__ + ".moves.urllib_parse", "urllib.parse"), + MovedModule("urllib_error", __name__ + ".moves.urllib_error", "urllib.error"), + MovedModule("urllib", __name__ + ".moves.urllib", __name__ + ".moves.urllib"), + MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"), + MovedModule("xmlrpc_client", "xmlrpclib", "xmlrpc.client"), + MovedModule("xmlrpc_server", "SimpleXMLRPCServer", "xmlrpc.server"), + MovedModule("winreg", "_winreg"), +] +for attr in _moved_attributes: + setattr(_MovedItems, attr.name, attr) + if isinstance(attr, MovedModule): + _importer._add_module(attr, "moves." + attr.name) +del attr + +_MovedItems._moved_attributes = _moved_attributes + +moves = _MovedItems(__name__ + ".moves") +_importer._add_module(moves, "moves") + + +class Module_six_moves_urllib_parse(_LazyModule): + """Lazy loading of moved objects in six.moves.urllib_parse""" + + +_urllib_parse_moved_attributes = [ + MovedAttribute("ParseResult", "urlparse", "urllib.parse"), + MovedAttribute("SplitResult", "urlparse", "urllib.parse"), + MovedAttribute("parse_qs", "urlparse", "urllib.parse"), + MovedAttribute("parse_qsl", "urlparse", "urllib.parse"), + MovedAttribute("urldefrag", "urlparse", "urllib.parse"), + MovedAttribute("urljoin", "urlparse", "urllib.parse"), + MovedAttribute("urlparse", "urlparse", "urllib.parse"), + MovedAttribute("urlsplit", "urlparse", "urllib.parse"), + MovedAttribute("urlunparse", "urlparse", "urllib.parse"), + MovedAttribute("urlunsplit", "urlparse", "urllib.parse"), + MovedAttribute("quote", "urllib", "urllib.parse"), + MovedAttribute("quote_plus", "urllib", "urllib.parse"), + MovedAttribute("unquote", "urllib", "urllib.parse"), + MovedAttribute("unquote_plus", "urllib", "urllib.parse"), + MovedAttribute("urlencode", "urllib", "urllib.parse"), + MovedAttribute("splitquery", "urllib", "urllib.parse"), + MovedAttribute("splittag", "urllib", "urllib.parse"), + MovedAttribute("splituser", "urllib", "urllib.parse"), + MovedAttribute("uses_fragment", "urlparse", "urllib.parse"), + MovedAttribute("uses_netloc", "urlparse", "urllib.parse"), + MovedAttribute("uses_params", "urlparse", "urllib.parse"), + MovedAttribute("uses_query", "urlparse", "urllib.parse"), + MovedAttribute("uses_relative", "urlparse", "urllib.parse"), +] +for attr in _urllib_parse_moved_attributes: + setattr(Module_six_moves_urllib_parse, attr.name, attr) +del attr + +Module_six_moves_urllib_parse._moved_attributes = _urllib_parse_moved_attributes + +_importer._add_module(Module_six_moves_urllib_parse(__name__ + ".moves.urllib_parse"), + "moves.urllib_parse", "moves.urllib.parse") + + +class Module_six_moves_urllib_error(_LazyModule): + """Lazy loading of moved objects in six.moves.urllib_error""" + + +_urllib_error_moved_attributes = [ + MovedAttribute("URLError", "urllib2", "urllib.error"), + MovedAttribute("HTTPError", "urllib2", "urllib.error"), + MovedAttribute("ContentTooShortError", "urllib", "urllib.error"), +] +for attr in _urllib_error_moved_attributes: + setattr(Module_six_moves_urllib_error, attr.name, attr) +del attr + +Module_six_moves_urllib_error._moved_attributes = _urllib_error_moved_attributes + +_importer._add_module(Module_six_moves_urllib_error(__name__ + ".moves.urllib.error"), + "moves.urllib_error", "moves.urllib.error") + + +class Module_six_moves_urllib_request(_LazyModule): + """Lazy loading of moved objects in six.moves.urllib_request""" + + +_urllib_request_moved_attributes = [ + MovedAttribute("urlopen", "urllib2", "urllib.request"), + MovedAttribute("install_opener", "urllib2", "urllib.request"), + MovedAttribute("build_opener", "urllib2", "urllib.request"), + MovedAttribute("pathname2url", "urllib", "urllib.request"), + MovedAttribute("url2pathname", "urllib", "urllib.request"), + MovedAttribute("getproxies", "urllib", "urllib.request"), + MovedAttribute("Request", "urllib2", "urllib.request"), + MovedAttribute("OpenerDirector", "urllib2", "urllib.request"), + MovedAttribute("HTTPDefaultErrorHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPRedirectHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPCookieProcessor", "urllib2", "urllib.request"), + MovedAttribute("ProxyHandler", "urllib2", "urllib.request"), + MovedAttribute("BaseHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPPasswordMgr", "urllib2", "urllib.request"), + MovedAttribute("HTTPPasswordMgrWithDefaultRealm", "urllib2", "urllib.request"), + MovedAttribute("AbstractBasicAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPBasicAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("ProxyBasicAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("AbstractDigestAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPDigestAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("ProxyDigestAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPSHandler", "urllib2", "urllib.request"), + MovedAttribute("FileHandler", "urllib2", "urllib.request"), + MovedAttribute("FTPHandler", "urllib2", "urllib.request"), + MovedAttribute("CacheFTPHandler", "urllib2", "urllib.request"), + MovedAttribute("UnknownHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPErrorProcessor", "urllib2", "urllib.request"), + MovedAttribute("urlretrieve", "urllib", "urllib.request"), + MovedAttribute("urlcleanup", "urllib", "urllib.request"), + MovedAttribute("URLopener", "urllib", "urllib.request"), + MovedAttribute("FancyURLopener", "urllib", "urllib.request"), + MovedAttribute("proxy_bypass", "urllib", "urllib.request"), +] +for attr in _urllib_request_moved_attributes: + setattr(Module_six_moves_urllib_request, attr.name, attr) +del attr + +Module_six_moves_urllib_request._moved_attributes = _urllib_request_moved_attributes + +_importer._add_module(Module_six_moves_urllib_request(__name__ + ".moves.urllib.request"), + "moves.urllib_request", "moves.urllib.request") + + +class Module_six_moves_urllib_response(_LazyModule): + """Lazy loading of moved objects in six.moves.urllib_response""" + + +_urllib_response_moved_attributes = [ + MovedAttribute("addbase", "urllib", "urllib.response"), + MovedAttribute("addclosehook", "urllib", "urllib.response"), + MovedAttribute("addinfo", "urllib", "urllib.response"), + MovedAttribute("addinfourl", "urllib", "urllib.response"), +] +for attr in _urllib_response_moved_attributes: + setattr(Module_six_moves_urllib_response, attr.name, attr) +del attr + +Module_six_moves_urllib_response._moved_attributes = _urllib_response_moved_attributes + +_importer._add_module(Module_six_moves_urllib_response(__name__ + ".moves.urllib.response"), + "moves.urllib_response", "moves.urllib.response") + + +class Module_six_moves_urllib_robotparser(_LazyModule): + """Lazy loading of moved objects in six.moves.urllib_robotparser""" + + +_urllib_robotparser_moved_attributes = [ + MovedAttribute("RobotFileParser", "robotparser", "urllib.robotparser"), +] +for attr in _urllib_robotparser_moved_attributes: + setattr(Module_six_moves_urllib_robotparser, attr.name, attr) +del attr + +Module_six_moves_urllib_robotparser._moved_attributes = _urllib_robotparser_moved_attributes + +_importer._add_module(Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib.robotparser"), + "moves.urllib_robotparser", "moves.urllib.robotparser") + + +class Module_six_moves_urllib(types.ModuleType): + """Create a six.moves.urllib namespace that resembles the Python 3 namespace""" + __path__ = [] # mark as package + parse = _importer._get_module("moves.urllib_parse") + error = _importer._get_module("moves.urllib_error") + request = _importer._get_module("moves.urllib_request") + response = _importer._get_module("moves.urllib_response") + robotparser = _importer._get_module("moves.urllib_robotparser") + + def __dir__(self): + return ['parse', 'error', 'request', 'response', 'robotparser'] + +_importer._add_module(Module_six_moves_urllib(__name__ + ".moves.urllib"), + "moves.urllib") + + +def add_move(move): + """Add an item to six.moves.""" + setattr(_MovedItems, move.name, move) + + +def remove_move(name): + """Remove item from six.moves.""" + try: + delattr(_MovedItems, name) + except AttributeError: + try: + del moves.__dict__[name] + except KeyError: + raise AttributeError("no such move, %r" % (name,)) + + +if PY3: + _meth_func = "__func__" + _meth_self = "__self__" + + _func_closure = "__closure__" + _func_code = "__code__" + _func_defaults = "__defaults__" + _func_globals = "__globals__" +else: + _meth_func = "im_func" + _meth_self = "im_self" + + _func_closure = "func_closure" + _func_code = "func_code" + _func_defaults = "func_defaults" + _func_globals = "func_globals" + + +try: + advance_iterator = next +except NameError: + def advance_iterator(it): + return it.next() +next = advance_iterator + + +try: + callable = callable +except NameError: + def callable(obj): + return any("__call__" in klass.__dict__ for klass in type(obj).__mro__) + + +if PY3: + def get_unbound_function(unbound): + return unbound + + create_bound_method = types.MethodType + + Iterator = object +else: + def get_unbound_function(unbound): + return unbound.im_func + + def create_bound_method(func, obj): + return types.MethodType(func, obj, obj.__class__) + + class Iterator(object): + + def next(self): + return type(self).__next__(self) + + callable = callable +_add_doc(get_unbound_function, + """Get the function out of a possibly unbound function""") + + +get_method_function = operator.attrgetter(_meth_func) +get_method_self = operator.attrgetter(_meth_self) +get_function_closure = operator.attrgetter(_func_closure) +get_function_code = operator.attrgetter(_func_code) +get_function_defaults = operator.attrgetter(_func_defaults) +get_function_globals = operator.attrgetter(_func_globals) + + +if PY3: + def iterkeys(d, **kw): + return iter(d.keys(**kw)) + + def itervalues(d, **kw): + return iter(d.values(**kw)) + + def iteritems(d, **kw): + return iter(d.items(**kw)) + + def iterlists(d, **kw): + return iter(d.lists(**kw)) +else: + def iterkeys(d, **kw): + return iter(d.iterkeys(**kw)) + + def itervalues(d, **kw): + return iter(d.itervalues(**kw)) + + def iteritems(d, **kw): + return iter(d.iteritems(**kw)) + + def iterlists(d, **kw): + return iter(d.iterlists(**kw)) + +_add_doc(iterkeys, "Return an iterator over the keys of a dictionary.") +_add_doc(itervalues, "Return an iterator over the values of a dictionary.") +_add_doc(iteritems, + "Return an iterator over the (key, value) pairs of a dictionary.") +_add_doc(iterlists, + "Return an iterator over the (key, [values]) pairs of a dictionary.") + + +if PY3: + def b(s): + return s.encode("latin-1") + def u(s): + return s + unichr = chr + if sys.version_info[1] <= 1: + def int2byte(i): + return bytes((i,)) + else: + # This is about 2x faster than the implementation above on 3.2+ + int2byte = operator.methodcaller("to_bytes", 1, "big") + byte2int = operator.itemgetter(0) + indexbytes = operator.getitem + iterbytes = iter + import io + StringIO = io.StringIO + BytesIO = io.BytesIO +else: + def b(s): + return s + # Workaround for standalone backslash + def u(s): + return unicode(s.replace(r'\\', r'\\\\'), "unicode_escape") + unichr = unichr + int2byte = chr + def byte2int(bs): + return ord(bs[0]) + def indexbytes(buf, i): + return ord(buf[i]) + def iterbytes(buf): + return (ord(byte) for byte in buf) + import StringIO + StringIO = BytesIO = StringIO.StringIO +_add_doc(b, """Byte literal""") +_add_doc(u, """Text literal""") + + +if PY3: + exec_ = getattr(moves.builtins, "exec") + + + def reraise(tp, value, tb=None): + if value is None: + value = tp() + if value.__traceback__ is not tb: + raise value.with_traceback(tb) + raise value + +else: + def exec_(_code_, _globs_=None, _locs_=None): + """Execute code in a namespace.""" + if _globs_ is None: + frame = sys._getframe(1) + _globs_ = frame.f_globals + if _locs_ is None: + _locs_ = frame.f_locals + del frame + elif _locs_ is None: + _locs_ = _globs_ + exec("""exec _code_ in _globs_, _locs_""") + + + exec_("""def reraise(tp, value, tb=None): + raise tp, value, tb +""") + + +print_ = getattr(moves.builtins, "print", None) +if print_ is None: + def print_(*args, **kwargs): + """The new-style print function for Python 2.4 and 2.5.""" + fp = kwargs.pop("file", sys.stdout) + if fp is None: + return + def write(data): + if not isinstance(data, basestring): + data = str(data) + # If the file has an encoding, encode unicode with it. + if (isinstance(fp, file) and + isinstance(data, unicode) and + fp.encoding is not None): + errors = getattr(fp, "errors", None) + if errors is None: + errors = "strict" + data = data.encode(fp.encoding, errors) + fp.write(data) + want_unicode = False + sep = kwargs.pop("sep", None) + if sep is not None: + if isinstance(sep, unicode): + want_unicode = True + elif not isinstance(sep, str): + raise TypeError("sep must be None or a string") + end = kwargs.pop("end", None) + if end is not None: + if isinstance(end, unicode): + want_unicode = True + elif not isinstance(end, str): + raise TypeError("end must be None or a string") + if kwargs: + raise TypeError("invalid keyword arguments to print()") + if not want_unicode: + for arg in args: + if isinstance(arg, unicode): + want_unicode = True + break + if want_unicode: + newline = unicode("\n") + space = unicode(" ") + else: + newline = "\n" + space = " " + if sep is None: + sep = space + if end is None: + end = newline + for i, arg in enumerate(args): + if i: + write(sep) + write(arg) + write(end) + +_add_doc(reraise, """Reraise an exception.""") + +if sys.version_info[0:2] < (3, 4): + def wraps(wrapped, assigned=functools.WRAPPER_ASSIGNMENTS, + updated=functools.WRAPPER_UPDATES): + def wrapper(f): + f = functools.wraps(wrapped)(f) + f.__wrapped__ = wrapped + return f + return wrapper +else: + wraps = functools.wraps + +def with_metaclass(meta, *bases): + """Create a base class with a metaclass.""" + # This requires a bit of explanation: the basic idea is to make a dummy + # metaclass for one level of class instantiation that replaces itself with + # the actual metaclass. + class metaclass(meta): + def __new__(cls, name, this_bases, d): + return meta(name, bases, d) + return type.__new__(metaclass, 'temporary_class', (), {}) + + +def add_metaclass(metaclass): + """Class decorator for creating a class with a metaclass.""" + def wrapper(cls): + orig_vars = cls.__dict__.copy() + slots = orig_vars.get('__slots__') + if slots is not None: + if isinstance(slots, str): + slots = [slots] + for slots_var in slots: + orig_vars.pop(slots_var) + orig_vars.pop('__dict__', None) + orig_vars.pop('__weakref__', None) + return metaclass(cls.__name__, cls.__bases__, orig_vars) + return wrapper + +# Complete the moves implementation. +# This code is at the end of this module to speed up module loading. +# Turn this module into a package. +__path__ = [] # required for PEP 302 and PEP 451 +__package__ = __name__ # see PEP 366 @ReservedAssignment +if globals().get("__spec__") is not None: + __spec__.submodule_search_locations = [] # PEP 451 @UndefinedVariable +# Remove other six meta path importers, since they cause problems. This can +# happen if six is removed from sys.modules and then reloaded. (Setuptools does +# this for some reason.) +if sys.meta_path: + for i, importer in enumerate(sys.meta_path): + # Here's some real nastiness: Another "instance" of the six module might + # be floating around. Therefore, we can't use isinstance() to check for + # the six meta path importer, since the other six instance will have + # inserted an importer with different class. + if (type(importer).__name__ == "_SixMetaPathImporter" and + importer.name == __name__): + del sys.meta_path[i] + break + del i, importer +# Finally, add the importer to the meta path import hook. +sys.meta_path.append(_importer) diff --git a/env/lib/python2.7/site-packages/gunicorn/six.pyc b/env/lib/python2.7/site-packages/gunicorn/six.pyc new file mode 100644 index 0000000..23f871b Binary files /dev/null and b/env/lib/python2.7/site-packages/gunicorn/six.pyc differ diff --git a/env/lib/python2.7/site-packages/gunicorn/sock.py b/env/lib/python2.7/site-packages/gunicorn/sock.py new file mode 100644 index 0000000..221126b --- /dev/null +++ b/env/lib/python2.7/site-packages/gunicorn/sock.py @@ -0,0 +1,230 @@ +# -*- coding: utf-8 - +# +# This file is part of gunicorn released under the MIT license. +# See the NOTICE for more information. + +import errno +import os +import socket +import stat +import sys +import time + +from gunicorn import util +from gunicorn.six import string_types + +SD_LISTEN_FDS_START = 3 + + +class BaseSocket(object): + + def __init__(self, address, conf, log, fd=None): + self.log = log + self.conf = conf + + self.cfg_addr = address + if fd is None: + sock = socket.socket(self.FAMILY, socket.SOCK_STREAM) + else: + sock = socket.fromfd(fd, self.FAMILY, socket.SOCK_STREAM) + + self.sock = self.set_options(sock, bound=(fd is not None)) + + def __str__(self, name): + return "" % self.sock.fileno() + + def __getattr__(self, name): + return getattr(self.sock, name) + + def set_options(self, sock, bound=False): + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + if not bound: + self.bind(sock) + sock.setblocking(0) + + # make sure that the socket can be inherited + if hasattr(sock, "set_inheritable"): + sock.set_inheritable(True) + + sock.listen(self.conf.backlog) + return sock + + def bind(self, sock): + sock.bind(self.cfg_addr) + + def close(self): + try: + self.sock.close() + except socket.error as e: + self.log.info("Error while closing socket %s", str(e)) + time.sleep(0.3) + del self.sock + + +class TCPSocket(BaseSocket): + + FAMILY = socket.AF_INET + + def __str__(self): + if self.conf.is_ssl: + scheme = "https" + else: + scheme = "http" + + addr = self.sock.getsockname() + return "%s://%s:%d" % (scheme, addr[0], addr[1]) + + def set_options(self, sock, bound=False): + sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + return super(TCPSocket, self).set_options(sock, bound=bound) + + +class TCP6Socket(TCPSocket): + + FAMILY = socket.AF_INET6 + + def __str__(self): + (host, port, fl, sc) = self.sock.getsockname() + return "http://[%s]:%d" % (host, port) + + +class UnixSocket(BaseSocket): + + FAMILY = socket.AF_UNIX + + def __init__(self, addr, conf, log, fd=None): + if fd is None: + try: + st = os.stat(addr) + except OSError as e: + if e.args[0] != errno.ENOENT: + raise + else: + if stat.S_ISSOCK(st.st_mode): + os.remove(addr) + else: + raise ValueError("%r is not a socket" % addr) + super(UnixSocket, self).__init__(addr, conf, log, fd=fd) + + def __str__(self): + return "unix:%s" % self.cfg_addr + + def bind(self, sock): + old_umask = os.umask(self.conf.umask) + sock.bind(self.cfg_addr) + util.chown(self.cfg_addr, self.conf.uid, self.conf.gid) + os.umask(old_umask) + + + def close(self): + super(UnixSocket, self).close() + os.unlink(self.cfg_addr) + + +def _sock_type(addr): + if isinstance(addr, tuple): + if util.is_ipv6(addr[0]): + sock_type = TCP6Socket + else: + sock_type = TCPSocket + elif isinstance(addr, string_types): + sock_type = UnixSocket + else: + raise TypeError("Unable to create socket from: %r" % addr) + return sock_type + + +def create_sockets(conf, log): + """ + Create a new socket for the given address. If the + address is a tuple, a TCP socket is created. If it + is a string, a Unix socket is created. Otherwise + a TypeError is raised. + """ + + # Systemd support, use the sockets managed by systemd and passed to + # gunicorn. + # http://www.freedesktop.org/software/systemd/man/systemd.socket.html + listeners = [] + if ('LISTEN_PID' in os.environ + and int(os.environ.get('LISTEN_PID')) == os.getpid()): + for i in range(int(os.environ.get('LISTEN_FDS', 0))): + fd = i + SD_LISTEN_FDS_START + try: + sock = socket.fromfd(fd, socket.AF_UNIX, socket.SOCK_STREAM) + sockname = sock.getsockname() + if isinstance(sockname, str) and sockname.startswith('/'): + listeners.append(UnixSocket(sockname, conf, log, fd=fd)) + elif len(sockname) == 2 and '.' in sockname[0]: + listeners.append(TCPSocket("%s:%s" % sockname, conf, log, + fd=fd)) + elif len(sockname) == 4 and ':' in sockname[0]: + listeners.append(TCP6Socket("[%s]:%s" % sockname[:2], conf, + log, fd=fd)) + except socket.error: + pass + del os.environ['LISTEN_PID'], os.environ['LISTEN_FDS'] + + if listeners: + log.debug('Socket activation sockets: %s', + ",".join([str(l) for l in listeners])) + return listeners + + # get it only once + laddr = conf.address + + # check ssl config early to raise the error on startup + # only the certfile is needed since it can contains the keyfile + if conf.certfile and not os.path.exists(conf.certfile): + raise ValueError('certfile "%s" does not exist' % conf.certfile) + + if conf.keyfile and not os.path.exists(conf.keyfile): + raise ValueError('keyfile "%s" does not exist' % conf.keyfile) + + # sockets are already bound + if 'GUNICORN_FD' in os.environ: + fds = os.environ.pop('GUNICORN_FD').split(',') + for i, fd in enumerate(fds): + fd = int(fd) + addr = laddr[i] + sock_type = _sock_type(addr) + + try: + listeners.append(sock_type(addr, conf, log, fd=fd)) + except socket.error as e: + if e.args[0] == errno.ENOTCONN: + log.error("GUNICORN_FD should refer to an open socket.") + else: + raise + return listeners + + # no sockets is bound, first initialization of gunicorn in this env. + for addr in laddr: + sock_type = _sock_type(addr) + + # If we fail to create a socket from GUNICORN_FD + # we fall through and try and open the socket + # normally. + sock = None + for i in range(5): + try: + sock = sock_type(addr, conf, log) + except socket.error as e: + if e.args[0] == errno.EADDRINUSE: + log.error("Connection in use: %s", str(addr)) + if e.args[0] == errno.EADDRNOTAVAIL: + log.error("Invalid address: %s", str(addr)) + sys.exit(1) + if i < 5: + log.error("Retrying in 1 second.") + time.sleep(1) + else: + break + + if sock is None: + log.error("Can't connect to %s", str(addr)) + sys.exit(1) + + listeners.append(sock) + + return listeners diff --git a/env/lib/python2.7/site-packages/gunicorn/sock.pyc b/env/lib/python2.7/site-packages/gunicorn/sock.pyc new file mode 100644 index 0000000..238d66c Binary files /dev/null and b/env/lib/python2.7/site-packages/gunicorn/sock.pyc differ diff --git a/env/lib/python2.7/site-packages/gunicorn/util.py b/env/lib/python2.7/site-packages/gunicorn/util.py new file mode 100644 index 0000000..13e6d65 --- /dev/null +++ b/env/lib/python2.7/site-packages/gunicorn/util.py @@ -0,0 +1,534 @@ +# -*- coding: utf-8 - +# +# This file is part of gunicorn released under the MIT license. +# See the NOTICE for more information. + +from __future__ import print_function + +import email.utils +import fcntl +import io +import os +import pkg_resources +import random +import resource +import socket +import sys +import textwrap +import time +import traceback +import inspect +import errno +import warnings +import cgi + +from gunicorn.errors import AppImportError +from gunicorn.six import text_type +from gunicorn.workers import SUPPORTED_WORKERS + + +MAXFD = 1024 +REDIRECT_TO = getattr(os, 'devnull', '/dev/null') + +timeout_default = object() + +CHUNK_SIZE = (16 * 1024) + +MAX_BODY = 1024 * 132 + +# Server and Date aren't technically hop-by-hop +# headers, but they are in the purview of the +# origin server which the WSGI spec says we should +# act like. So we drop them and add our own. +# +# In the future, concatenation server header values +# might be better, but nothing else does it and +# dropping them is easier. +hop_headers = set(""" + connection keep-alive proxy-authenticate proxy-authorization + te trailers transfer-encoding upgrade + server date + """.split()) + +try: + from setproctitle import setproctitle + def _setproctitle(title): + setproctitle("gunicorn: %s" % title) +except ImportError: + def _setproctitle(title): + return + + +try: + from importlib import import_module +except ImportError: + def _resolve_name(name, package, level): + """Return the absolute name of the module to be imported.""" + if not hasattr(package, 'rindex'): + raise ValueError("'package' not set to a string") + dot = len(package) + for x in range(level, 1, -1): + try: + dot = package.rindex('.', 0, dot) + except ValueError: + msg = "attempted relative import beyond top-level package" + raise ValueError(msg) + return "%s.%s" % (package[:dot], name) + + def import_module(name, package=None): + """Import a module. + +The 'package' argument is required when performing a relative import. It +specifies the package to use as the anchor point from which to resolve the +relative import to an absolute import. + +""" + if name.startswith('.'): + if not package: + raise TypeError("relative imports require the 'package' argument") + level = 0 + for character in name: + if character != '.': + break + level += 1 + name = _resolve_name(name[level:], package, level) + __import__(name) + return sys.modules[name] + + +def load_class(uri, default="gunicorn.workers.sync.SyncWorker", + section="gunicorn.workers"): + if inspect.isclass(uri): + return uri + if uri.startswith("egg:"): + # uses entry points + entry_str = uri.split("egg:")[1] + try: + dist, name = entry_str.rsplit("#", 1) + except ValueError: + dist = entry_str + name = default + + try: + return pkg_resources.load_entry_point(dist, section, name) + except: + exc = traceback.format_exc() + msg = "class uri %r invalid or not found: \n\n[%s]" + raise RuntimeError(msg % (uri, exc)) + else: + components = uri.split('.') + if len(components) == 1: + while True: + if uri.startswith("#"): + uri = uri[1:] + + if uri in SUPPORTED_WORKERS: + components = SUPPORTED_WORKERS[uri].split(".") + break + + try: + return pkg_resources.load_entry_point("gunicorn", + section, uri) + except: + exc = traceback.format_exc() + msg = "class uri %r invalid or not found: \n\n[%s]" + raise RuntimeError(msg % (uri, exc)) + + klass = components.pop(-1) + + try: + mod = import_module('.'.join(components)) + except: + exc = traceback.format_exc() + msg = "class uri %r invalid or not found: \n\n[%s]" + raise RuntimeError(msg % (uri, exc)) + return getattr(mod, klass) + + +def set_owner_process(uid, gid): + """ set user and group of workers processes """ + if gid: + # versions of python < 2.6.2 don't manage unsigned int for + # groups like on osx or fedora + gid = abs(gid) & 0x7FFFFFFF + os.setgid(gid) + if uid: + os.setuid(uid) + + +def chown(path, uid, gid): + gid = abs(gid) & 0x7FFFFFFF # see note above. + os.chown(path, uid, gid) + + +if sys.platform.startswith("win"): + def _waitfor(func, pathname, waitall=False): + # Peform the operation + func(pathname) + # Now setup the wait loop + if waitall: + dirname = pathname + else: + dirname, name = os.path.split(pathname) + dirname = dirname or '.' + # Check for `pathname` to be removed from the filesystem. + # The exponential backoff of the timeout amounts to a total + # of ~1 second after which the deletion is probably an error + # anyway. + # Testing on a i7@4.3GHz shows that usually only 1 iteration is + # required when contention occurs. + timeout = 0.001 + while timeout < 1.0: + # Note we are only testing for the existance of the file(s) in + # the contents of the directory regardless of any security or + # access rights. If we have made it this far, we have sufficient + # permissions to do that much using Python's equivalent of the + # Windows API FindFirstFile. + # Other Windows APIs can fail or give incorrect results when + # dealing with files that are pending deletion. + L = os.listdir(dirname) + if not (L if waitall else name in L): + return + # Increase the timeout and try again + time.sleep(timeout) + timeout *= 2 + warnings.warn('tests may fail, delete still pending for ' + pathname, + RuntimeWarning, stacklevel=4) + + def _unlink(filename): + _waitfor(os.unlink, filename) +else: + _unlink = os.unlink + + +def unlink(filename): + try: + _unlink(filename) + except OSError as error: + # The filename need not exist. + if error.errno not in (errno.ENOENT, errno.ENOTDIR): + raise + + +def is_ipv6(addr): + try: + socket.inet_pton(socket.AF_INET6, addr) + except socket.error: # not a valid address + return False + except ValueError: # ipv6 not supported on this platform + return False + return True + + +def parse_address(netloc, default_port=8000): + if netloc.startswith("unix://"): + return netloc.split("unix://")[1] + + if netloc.startswith("unix:"): + return netloc.split("unix:")[1] + + if netloc.startswith("tcp://"): + netloc = netloc.split("tcp://")[1] + + # get host + if '[' in netloc and ']' in netloc: + host = netloc.split(']')[0][1:].lower() + elif ':' in netloc: + host = netloc.split(':')[0].lower() + elif netloc == "": + host = "0.0.0.0" + else: + host = netloc.lower() + + #get port + netloc = netloc.split(']')[-1] + if ":" in netloc: + port = netloc.split(':', 1)[1] + if not port.isdigit(): + raise RuntimeError("%r is not a valid port number." % port) + port = int(port) + else: + port = default_port + return (host, port) + +def get_maxfd(): + maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1] + if (maxfd == resource.RLIM_INFINITY): + maxfd = MAXFD + return maxfd + + +def close_on_exec(fd): + flags = fcntl.fcntl(fd, fcntl.F_GETFD) + flags |= fcntl.FD_CLOEXEC + fcntl.fcntl(fd, fcntl.F_SETFD, flags) + + +def set_non_blocking(fd): + flags = fcntl.fcntl(fd, fcntl.F_GETFL) | os.O_NONBLOCK + fcntl.fcntl(fd, fcntl.F_SETFL, flags) + +def close(sock): + try: + sock.close() + except socket.error: + pass + +try: + from os import closerange +except ImportError: + def closerange(fd_low, fd_high): + # Iterate through and close all file descriptors. + for fd in range(fd_low, fd_high): + try: + os.close(fd) + except OSError: # ERROR, fd wasn't open to begin with (ignored) + pass + + +def write_chunk(sock, data): + if isinstance(data, text_type): + data = data.encode('utf-8') + chunk_size = "%X\r\n" % len(data) + chunk = b"".join([chunk_size.encode('utf-8'), data, b"\r\n"]) + sock.sendall(chunk) + + +def write(sock, data, chunked=False): + if chunked: + return write_chunk(sock, data) + sock.sendall(data) + + +def write_nonblock(sock, data, chunked=False): + timeout = sock.gettimeout() + if timeout != 0.0: + try: + sock.setblocking(0) + return write(sock, data, chunked) + finally: + sock.setblocking(1) + else: + return write(sock, data, chunked) + + +def writelines(sock, lines, chunked=False): + for line in list(lines): + write(sock, line, chunked) + + +def write_error(sock, status_int, reason, mesg): + html = textwrap.dedent("""\ + + + %(reason)s + + +

%(reason)s

+ %(mesg)s + + + """) % {"reason": reason, "mesg": cgi.escape(mesg)} + + http = textwrap.dedent("""\ + HTTP/1.1 %s %s\r + Connection: close\r + Content-Type: text/html\r + Content-Length: %d\r + \r + %s""") % (str(status_int), reason, len(html), html) + write_nonblock(sock, http.encode('latin1')) + + +def normalize_name(name): + return "-".join([w.lower().capitalize() for w in name.split("-")]) + + +def import_app(module): + parts = module.split(":", 1) + if len(parts) == 1: + module, obj = module, "application" + else: + module, obj = parts[0], parts[1] + + try: + __import__(module) + except ImportError: + if module.endswith(".py") and os.path.exists(module): + msg = "Failed to find application, did you mean '%s:%s'?" + raise ImportError(msg % (module.rsplit(".", 1)[0], obj)) + else: + raise + + mod = sys.modules[module] + + try: + app = eval(obj, mod.__dict__) + except NameError: + raise AppImportError("Failed to find application: %r" % module) + + if app is None: + raise AppImportError("Failed to find application object: %r" % obj) + + if not callable(app): + raise AppImportError("Application object must be callable.") + return app + + +def getcwd(): + # get current path, try to use PWD env first + try: + a = os.stat(os.environ['PWD']) + b = os.stat(os.getcwd()) + if a.st_ino == b.st_ino and a.st_dev == b.st_dev: + cwd = os.environ['PWD'] + else: + cwd = os.getcwd() + except: + cwd = os.getcwd() + return cwd + + +def http_date(timestamp=None): + """Return the current date and time formatted for a message header.""" + if timestamp is None: + timestamp = time.time() + s = email.utils.formatdate(timestamp, localtime=False, usegmt=True) + return s + + +def is_hoppish(header): + return header.lower().strip() in hop_headers + + +def daemonize(enable_stdio_inheritance=False): + """\ + Standard daemonization of a process. + http://www.svbug.com/documentation/comp.unix.programmer-FAQ/faq_2.html#SEC16 + """ + if 'GUNICORN_FD' not in os.environ: + if os.fork(): + os._exit(0) + os.setsid() + + if os.fork(): + os._exit(0) + + os.umask(0o22) + + # In both the following any file descriptors above stdin + # stdout and stderr are left untouched. The inheritence + # option simply allows one to have output go to a file + # specified by way of shell redirection when not wanting + # to use --error-log option. + + if not enable_stdio_inheritance: + # Remap all of stdin, stdout and stderr on to + # /dev/null. The expectation is that users have + # specified the --error-log option. + + closerange(0, 3) + + fd_null = os.open(REDIRECT_TO, os.O_RDWR) + + if fd_null != 0: + os.dup2(fd_null, 0) + + os.dup2(fd_null, 1) + os.dup2(fd_null, 2) + + else: + fd_null = os.open(REDIRECT_TO, os.O_RDWR) + + # Always redirect stdin to /dev/null as we would + # never expect to need to read interactive input. + + if fd_null != 0: + os.close(0) + os.dup2(fd_null, 0) + + # If stdout and stderr are still connected to + # their original file descriptors we check to see + # if they are associated with terminal devices. + # When they are we map them to /dev/null so that + # are still detached from any controlling terminal + # properly. If not we preserve them as they are. + # + # If stdin and stdout were not hooked up to the + # original file descriptors, then all bets are + # off and all we can really do is leave them as + # they were. + # + # This will allow 'gunicorn ... > output.log 2>&1' + # to work with stdout/stderr going to the file + # as expected. + # + # Note that if using --error-log option, the log + # file specified through shell redirection will + # only be used up until the log file specified + # by the option takes over. As it replaces stdout + # and stderr at the file descriptor level, then + # anything using stdout or stderr, including having + # cached a reference to them, will still work. + + def redirect(stream, fd_expect): + try: + fd = stream.fileno() + if fd == fd_expect and stream.isatty(): + os.close(fd) + os.dup2(fd_null, fd) + except AttributeError: + pass + + redirect(sys.stdout, 1) + redirect(sys.stderr, 2) + + +def seed(): + try: + random.seed(os.urandom(64)) + except NotImplementedError: + random.seed('%s.%s' % (time.time(), os.getpid())) + + +def check_is_writeable(path): + try: + f = open(path, 'a') + except IOError as e: + raise RuntimeError("Error: '%s' isn't writable [%r]" % (path, e)) + f.close() + + +def to_bytestring(value): + """Converts a string argument to a byte string""" + if isinstance(value, bytes): + return value + if not isinstance(value, text_type): + raise TypeError('%r is not a string' % value) + return value.encode("utf-8") + + +def is_fileobject(obj): + if not hasattr(obj, "tell") or not hasattr(obj, "fileno"): + return False + + # check BytesIO case and maybe others + try: + obj.fileno() + except (IOError, io.UnsupportedOperation): + return False + + return True + + +def warn(msg): + print("!!!", file=sys.stderr) + + lines = msg.splitlines() + for i, line in enumerate(lines): + if i == 0: + line = "WARNING: %s" % line + print("!!! %s" % line, file=sys.stderr) + + print("!!!\n", file=sys.stderr) + sys.stderr.flush() diff --git a/env/lib/python2.7/site-packages/gunicorn/util.pyc b/env/lib/python2.7/site-packages/gunicorn/util.pyc new file mode 100644 index 0000000..6ec939f Binary files /dev/null and b/env/lib/python2.7/site-packages/gunicorn/util.pyc differ diff --git a/env/lib/python2.7/site-packages/gunicorn/workers/__init__.py b/env/lib/python2.7/site-packages/gunicorn/workers/__init__.py new file mode 100644 index 0000000..074839e --- /dev/null +++ b/env/lib/python2.7/site-packages/gunicorn/workers/__init__.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 - +# +# This file is part of gunicorn released under the MIT license. +# See the NOTICE for more information. + +import sys + +# supported gunicorn workers. +SUPPORTED_WORKERS = { + "sync": "gunicorn.workers.sync.SyncWorker", + "eventlet": "gunicorn.workers.geventlet.EventletWorker", + "gevent": "gunicorn.workers.ggevent.GeventWorker", + "gevent_wsgi": "gunicorn.workers.ggevent.GeventPyWSGIWorker", + "gevent_pywsgi": "gunicorn.workers.ggevent.GeventPyWSGIWorker", + "tornado": "gunicorn.workers.gtornado.TornadoWorker"} + + +if sys.version_info >= (3, 3): + # gaiohttp worker can be used with Python 3.3+ only. + SUPPORTED_WORKERS["gaiohttp"] = "gunicorn.workers.gaiohttp.AiohttpWorker" diff --git a/env/lib/python2.7/site-packages/gunicorn/workers/__init__.pyc b/env/lib/python2.7/site-packages/gunicorn/workers/__init__.pyc new file mode 100644 index 0000000..8c096a1 Binary files /dev/null and b/env/lib/python2.7/site-packages/gunicorn/workers/__init__.pyc differ diff --git a/env/lib/python2.7/site-packages/gunicorn/workers/_gaiohttp.py b/env/lib/python2.7/site-packages/gunicorn/workers/_gaiohttp.py new file mode 100644 index 0000000..df59c9c --- /dev/null +++ b/env/lib/python2.7/site-packages/gunicorn/workers/_gaiohttp.py @@ -0,0 +1,132 @@ +# -*- coding: utf-8 - +# +# This file is part of gunicorn released under the MIT license. +# See the NOTICE for more information. + +import asyncio +import functools +import logging +import os +import gunicorn.workers.base as base + +from aiohttp.wsgi import WSGIServerHttpProtocol + + +class AiohttpWorker(base.Worker): + + def __init__(self, *args, **kw): # pragma: no cover + super().__init__(*args, **kw) + + self.servers = [] + self.connections = {} + + def init_process(self): + # create new event_loop after fork + asyncio.get_event_loop().close() + + self.loop = asyncio.new_event_loop() + asyncio.set_event_loop(self.loop) + + super().init_process() + + def run(self): + self._runner = asyncio.async(self._run(), loop=self.loop) + + try: + self.loop.run_until_complete(self._runner) + finally: + self.loop.close() + + def wrap_protocol(self, proto): + proto.connection_made = _wrp( + proto, proto.connection_made, self.connections) + proto.connection_lost = _wrp( + proto, proto.connection_lost, self.connections, False) + return proto + + def factory(self, wsgi, addr): + # are we in debug level + is_debug = self.log.loglevel == logging.DEBUG + + proto = WSGIServerHttpProtocol( + wsgi, readpayload=True, + loop=self.loop, + log=self.log, + debug=is_debug, + keep_alive=self.cfg.keepalive, + access_log=self.log.access_log, + access_log_format=self.cfg.access_log_format) + return self.wrap_protocol(proto) + + def get_factory(self, sock, addr): + return functools.partial(self.factory, self.wsgi, addr) + + @asyncio.coroutine + def close(self): + try: + if hasattr(self.wsgi, 'close'): + yield from self.wsgi.close() + except: + self.log.exception('Process shutdown exception') + + @asyncio.coroutine + def _run(self): + for sock in self.sockets: + factory = self.get_factory(sock.sock, sock.cfg_addr) + self.servers.append( + (yield from self.loop.create_server(factory, sock=sock.sock))) + + # If our parent changed then we shut down. + pid = os.getpid() + try: + while self.alive or self.connections: + self.notify() + + if (self.alive and + pid == os.getpid() and self.ppid != os.getppid()): + self.log.info("Parent changed, shutting down: %s", self) + self.alive = False + + # stop accepting requests + if not self.alive: + if self.servers: + self.log.info( + "Stopping server: %s, connections: %s", + pid, len(self.connections)) + for server in self.servers: + server.close() + self.servers.clear() + + # prepare connections for closing + for conn in self.connections.values(): + if hasattr(conn, 'closing'): + conn.closing() + + yield from asyncio.sleep(1.0, loop=self.loop) + except KeyboardInterrupt: + pass + + if self.servers: + for server in self.servers: + server.close() + + yield from self.close() + + +class _wrp: + + def __init__(self, proto, meth, tracking, add=True): + self._proto = proto + self._id = id(proto) + self._meth = meth + self._tracking = tracking + self._add = add + + def __call__(self, *args): + if self._add: + self._tracking[self._id] = self._proto + elif self._id in self._tracking: + del self._tracking[self._id] + + conn = self._meth(*args) + return conn diff --git a/env/lib/python2.7/site-packages/gunicorn/workers/async.py b/env/lib/python2.7/site-packages/gunicorn/workers/async.py new file mode 100644 index 0000000..d823d5d --- /dev/null +++ b/env/lib/python2.7/site-packages/gunicorn/workers/async.py @@ -0,0 +1,145 @@ +# -*- coding: utf-8 - +# +# This file is part of gunicorn released under the MIT license. +# See the NOTICE for more information. + +from datetime import datetime +import errno +import socket +import ssl +import sys + +import gunicorn.http as http +import gunicorn.http.wsgi as wsgi +import gunicorn.util as util +import gunicorn.workers.base as base +from gunicorn import six + +ALREADY_HANDLED = object() + + +class AsyncWorker(base.Worker): + + def __init__(self, *args, **kwargs): + super(AsyncWorker, self).__init__(*args, **kwargs) + self.worker_connections = self.cfg.worker_connections + + def timeout_ctx(self): + raise NotImplementedError() + + def handle(self, listener, client, addr): + req = None + try: + parser = http.RequestParser(self.cfg, client) + try: + listener_name = listener.getsockname() + if not self.cfg.keepalive: + req = six.next(parser) + self.handle_request(listener_name, req, client, addr) + else: + # keepalive loop + proxy_protocol_info = {} + while True: + req = None + with self.timeout_ctx(): + req = six.next(parser) + if not req: + break + if req.proxy_protocol_info: + proxy_protocol_info = req.proxy_protocol_info + else: + req.proxy_protocol_info = proxy_protocol_info + self.handle_request(listener_name, req, client, addr) + except http.errors.NoMoreData as e: + self.log.debug("Ignored premature client disconnection. %s", e) + except StopIteration as e: + self.log.debug("Closing connection. %s", e) + except ssl.SSLError: + exc_info = sys.exc_info() + # pass to next try-except level + six.reraise(exc_info[0], exc_info[1], exc_info[2]) + except socket.error: + exc_info = sys.exc_info() + # pass to next try-except level + six.reraise(exc_info[0], exc_info[1], exc_info[2]) + except Exception as e: + self.handle_error(req, client, addr, e) + except ssl.SSLError as e: + if e.args[0] == ssl.SSL_ERROR_EOF: + self.log.debug("ssl connection closed") + client.close() + else: + self.log.debug("Error processing SSL request.") + self.handle_error(req, client, addr, e) + except socket.error as e: + if e.args[0] not in (errno.EPIPE, errno.ECONNRESET): + self.log.exception("Socket error processing request.") + else: + if e.args[0] == errno.ECONNRESET: + self.log.debug("Ignoring connection reset") + else: + self.log.debug("Ignoring EPIPE") + except Exception as e: + self.handle_error(req, client, addr, e) + finally: + util.close(client) + + def handle_request(self, listener_name, req, sock, addr): + request_start = datetime.now() + environ = {} + resp = None + try: + self.cfg.pre_request(self, req) + resp, environ = wsgi.create(req, sock, addr, + listener_name, self.cfg) + environ["wsgi.multithread"] = True + self.nr += 1 + if self.alive and self.nr >= self.max_requests: + self.log.info("Autorestarting worker after current request.") + resp.force_close() + self.alive = False + + if not self.cfg.keepalive: + resp.force_close() + + respiter = self.wsgi(environ, resp.start_response) + if respiter == ALREADY_HANDLED: + return False + try: + if isinstance(respiter, environ['wsgi.file_wrapper']): + resp.write_file(respiter) + else: + for item in respiter: + resp.write(item) + resp.close() + request_time = datetime.now() - request_start + self.log.access(resp, req, environ, request_time) + finally: + if hasattr(respiter, "close"): + respiter.close() + if resp.should_close(): + raise StopIteration() + except StopIteration: + raise + except socket.error: + # If the original exception was a socket.error we delegate + # handling it to the caller (where handle() might ignore it) + six.reraise(*sys.exc_info()) + except Exception: + if resp and resp.headers_sent: + # If the requests have already been sent, we should close the + # connection to indicate the error. + self.log.exception("Error handling request") + try: + sock.shutdown(socket.SHUT_RDWR) + sock.close() + except socket.error: + pass + raise StopIteration() + raise + finally: + try: + self.cfg.post_request(self, req, environ, resp) + except Exception: + self.log.exception("Exception in post_request hook") + return True diff --git a/env/lib/python2.7/site-packages/gunicorn/workers/async.pyc b/env/lib/python2.7/site-packages/gunicorn/workers/async.pyc new file mode 100644 index 0000000..2803c8e Binary files /dev/null and b/env/lib/python2.7/site-packages/gunicorn/workers/async.pyc differ diff --git a/env/lib/python2.7/site-packages/gunicorn/workers/base.py b/env/lib/python2.7/site-packages/gunicorn/workers/base.py new file mode 100644 index 0000000..3e61000 --- /dev/null +++ b/env/lib/python2.7/site-packages/gunicorn/workers/base.py @@ -0,0 +1,219 @@ +# -*- coding: utf-8 - +# +# This file is part of gunicorn released under the MIT license. +# See the NOTICE for more information. + +from datetime import datetime +import os +import signal +import sys +import time +from random import randint + + +from gunicorn import util +from gunicorn.workers.workertmp import WorkerTmp +from gunicorn.reloader import Reloader +from gunicorn.http.errors import ( + InvalidHeader, InvalidHeaderName, InvalidRequestLine, InvalidRequestMethod, + InvalidHTTPVersion, LimitRequestLine, LimitRequestHeaders, +) +from gunicorn.http.errors import InvalidProxyLine, ForbiddenProxyRequest +from gunicorn.http.wsgi import default_environ, Response +from gunicorn.six import MAXSIZE + + +class Worker(object): + + SIGNALS = [getattr(signal, "SIG%s" % x) + for x in "ABRT HUP QUIT INT TERM USR1 USR2 WINCH CHLD".split()] + + PIPE = [] + + def __init__(self, age, ppid, sockets, app, timeout, cfg, log): + """\ + This is called pre-fork so it shouldn't do anything to the + current process. If there's a need to make process wide + changes you'll want to do that in ``self.init_process()``. + """ + self.age = age + self.ppid = ppid + self.sockets = sockets + self.app = app + self.timeout = timeout + self.cfg = cfg + self.booted = False + self.aborted = False + self.reloader = None + + self.nr = 0 + jitter = randint(0, cfg.max_requests_jitter) + self.max_requests = cfg.max_requests + jitter or MAXSIZE + self.alive = True + self.log = log + self.tmp = WorkerTmp(cfg) + + def __str__(self): + return "" % self.pid + + @property + def pid(self): + return os.getpid() + + def notify(self): + """\ + Your worker subclass must arrange to have this method called + once every ``self.timeout`` seconds. If you fail in accomplishing + this task, the master process will murder your workers. + """ + self.tmp.notify() + + def run(self): + """\ + This is the mainloop of a worker process. You should override + this method in a subclass to provide the intended behaviour + for your particular evil schemes. + """ + raise NotImplementedError() + + def init_process(self): + """\ + If you override this method in a subclass, the last statement + in the function should be to call this method with + super(MyWorkerClass, self).init_process() so that the ``run()`` + loop is initiated. + """ + + # start the reloader + if self.cfg.reload: + def changed(fname): + self.log.info("Worker reloading: %s modified", fname) + os.kill(self.pid, signal.SIGQUIT) + self.reloader = Reloader(callback=changed).start() + + # set environment' variables + if self.cfg.env: + for k, v in self.cfg.env.items(): + os.environ[k] = v + + util.set_owner_process(self.cfg.uid, self.cfg.gid) + + # Reseed the random number generator + util.seed() + + # For waking ourselves up + self.PIPE = os.pipe() + for p in self.PIPE: + util.set_non_blocking(p) + util.close_on_exec(p) + + # Prevent fd inheritance + [util.close_on_exec(s) for s in self.sockets] + util.close_on_exec(self.tmp.fileno()) + + self.log.close_on_exec() + + self.init_signals() + + self.wsgi = self.app.wsgi() + + self.cfg.post_worker_init(self) + + # Enter main run loop + self.booted = True + self.run() + + def init_signals(self): + # reset signaling + [signal.signal(s, signal.SIG_DFL) for s in self.SIGNALS] + # init new signaling + signal.signal(signal.SIGQUIT, self.handle_quit) + signal.signal(signal.SIGTERM, self.handle_exit) + signal.signal(signal.SIGINT, self.handle_quit) + signal.signal(signal.SIGWINCH, self.handle_winch) + signal.signal(signal.SIGUSR1, self.handle_usr1) + signal.signal(signal.SIGABRT, self.handle_abort) + + # Don't let SIGTERM and SIGUSR1 disturb active requests + # by interrupting system calls + if hasattr(signal, 'siginterrupt'): # python >= 2.6 + signal.siginterrupt(signal.SIGTERM, False) + signal.siginterrupt(signal.SIGUSR1, False) + + def handle_usr1(self, sig, frame): + self.log.reopen_files() + + def handle_exit(self, sig, frame): + self.alive = False + + def handle_quit(self, sig, frame): + self.alive = False + # worker_int callback + self.cfg.worker_int(self) + time.sleep(0.1) + sys.exit(0) + + def handle_abort(self, sig, frame): + self.alive = False + self.cfg.worker_abort(self) + sys.exit(1) + + def handle_error(self, req, client, addr, exc): + request_start = datetime.now() + addr = addr or ('', -1) # unix socket case + if isinstance(exc, (InvalidRequestLine, InvalidRequestMethod, + InvalidHTTPVersion, InvalidHeader, InvalidHeaderName, + LimitRequestLine, LimitRequestHeaders, + InvalidProxyLine, ForbiddenProxyRequest)): + + status_int = 400 + reason = "Bad Request" + + if isinstance(exc, InvalidRequestLine): + mesg = "Invalid Request Line '%s'" % str(exc) + elif isinstance(exc, InvalidRequestMethod): + mesg = "Invalid Method '%s'" % str(exc) + elif isinstance(exc, InvalidHTTPVersion): + mesg = "Invalid HTTP Version '%s'" % str(exc) + elif isinstance(exc, (InvalidHeaderName, InvalidHeader,)): + mesg = "%s" % str(exc) + if not req and hasattr(exc, "req"): + req = exc.req # for access log + elif isinstance(exc, LimitRequestLine): + mesg = "%s" % str(exc) + elif isinstance(exc, LimitRequestHeaders): + mesg = "Error parsing headers: '%s'" % str(exc) + elif isinstance(exc, InvalidProxyLine): + mesg = "'%s'" % str(exc) + elif isinstance(exc, ForbiddenProxyRequest): + reason = "Forbidden" + mesg = "Request forbidden" + status_int = 403 + + msg = "Invalid request from ip={ip}: {error}" + self.log.debug(msg.format(ip=addr[0], error=str(exc))) + else: + self.log.exception("Error handling request") + + status_int = 500 + reason = "Internal Server Error" + mesg = "" + + if req is not None: + request_time = datetime.now() - request_start + environ = default_environ(req, client, self.cfg) + environ['REMOTE_ADDR'] = addr[0] + environ['REMOTE_PORT'] = str(addr[1]) + resp = Response(req, client, self.cfg) + resp.status = "%s %s" % (status_int, reason) + resp.response_length = len(mesg) + self.log.access(resp, req, environ, request_time) + + try: + util.write_error(client, status_int, reason, mesg) + except: + self.log.debug("Failed to send error message.") + + def handle_winch(self, sig, fname): + # Ignore SIGWINCH in worker. Fixes a crash on OpenBSD. + return diff --git a/env/lib/python2.7/site-packages/gunicorn/workers/base.pyc b/env/lib/python2.7/site-packages/gunicorn/workers/base.pyc new file mode 100644 index 0000000..f0c98ad Binary files /dev/null and b/env/lib/python2.7/site-packages/gunicorn/workers/base.pyc differ diff --git a/env/lib/python2.7/site-packages/gunicorn/workers/gaiohttp.py b/env/lib/python2.7/site-packages/gunicorn/workers/gaiohttp.py new file mode 100644 index 0000000..899e9a3 --- /dev/null +++ b/env/lib/python2.7/site-packages/gunicorn/workers/gaiohttp.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 - +# +# This file is part of gunicorn released under the MIT license. +# See the NOTICE for more information. + +import sys + +if sys.version_info >= (3, 3): + try: + import aiohttp # NOQA + except ImportError: + raise RuntimeError("You need aiohttp installed to use this worker.") + else: + from gunicorn.workers._gaiohttp import AiohttpWorker + __all__ = ['AiohttpWorker'] +else: + raise RuntimeError("You need Python >= 3.3 to use the asyncio worker") diff --git a/env/lib/python2.7/site-packages/gunicorn/workers/gaiohttp.pyc b/env/lib/python2.7/site-packages/gunicorn/workers/gaiohttp.pyc new file mode 100644 index 0000000..345bddb Binary files /dev/null and b/env/lib/python2.7/site-packages/gunicorn/workers/gaiohttp.pyc differ diff --git a/env/lib/python2.7/site-packages/gunicorn/workers/geventlet.py b/env/lib/python2.7/site-packages/gunicorn/workers/geventlet.py new file mode 100644 index 0000000..48b63fb --- /dev/null +++ b/env/lib/python2.7/site-packages/gunicorn/workers/geventlet.py @@ -0,0 +1,132 @@ +# -*- coding: utf-8 - +# +# This file is part of gunicorn released under the MIT license. +# See the NOTICE for more information. + +from functools import partial +import errno +import sys + +try: + import eventlet +except ImportError: + raise RuntimeError("You need eventlet installed to use this worker.") + +# validate the eventlet version +if eventlet.version_info < (0, 9, 7): + raise RuntimeError("You need eventlet >= 0.9.7") + + +from eventlet import hubs, greenthread +from eventlet.greenio import GreenSocket +from eventlet.hubs import trampoline +import greenlet + +from gunicorn.http.wsgi import sendfile as o_sendfile +from gunicorn.workers.async import AsyncWorker + + +def _eventlet_sendfile(fdout, fdin, offset, nbytes): + while True: + try: + return o_sendfile(fdout, fdin, offset, nbytes) + except OSError as e: + if e.args[0] == errno.EAGAIN: + trampoline(fdout, write=True) + else: + raise + + +def _eventlet_serve(sock, handle, concurrency): + """ + Serve requests forever. + + This code is nearly identical to ``eventlet.convenience.serve`` except + that it attempts to join the pool at the end, which allows for gunicorn + graceful shutdowns. + """ + pool = eventlet.greenpool.GreenPool(concurrency) + server_gt = eventlet.greenthread.getcurrent() + + while True: + try: + conn, addr = sock.accept() + gt = pool.spawn(handle, conn, addr) + gt.link(_eventlet_stop, server_gt, conn) + conn, addr, gt = None, None, None + except eventlet.StopServe: + pool.waitall() + return + + +def _eventlet_stop(client, server, conn): + """ + Stop a greenlet handling a request and close its connection. + + This code is lifted from eventlet so as not to depend on undocumented + functions in the library. + """ + try: + try: + client.wait() + finally: + conn.close() + except greenlet.GreenletExit: + pass + except Exception: + greenthread.kill(server, *sys.exc_info()) + + +def patch_sendfile(): + from gunicorn.http import wsgi + + if o_sendfile is not None: + setattr(wsgi, "sendfile", _eventlet_sendfile) + + +class EventletWorker(AsyncWorker): + + def patch(self): + eventlet.monkey_patch(os=False) + patch_sendfile() + + def init_process(self): + hubs.use_hub() + self.patch() + super(EventletWorker, self).init_process() + + def timeout_ctx(self): + return eventlet.Timeout(self.cfg.keepalive or None, False) + + def handle(self, listener, client, addr): + if self.cfg.is_ssl: + client = eventlet.wrap_ssl(client, server_side=True, + **self.cfg.ssl_options) + + super(EventletWorker, self).handle(listener, client, addr) + + def run(self): + acceptors = [] + for sock in self.sockets: + gsock = GreenSocket(sock) + gsock.setblocking(1) + hfun = partial(self.handle, gsock) + acceptor = eventlet.spawn(_eventlet_serve, gsock, hfun, + self.worker_connections) + + acceptors.append(acceptor) + eventlet.sleep(0.0) + + while self.alive: + self.notify() + eventlet.sleep(1.0) + + self.notify() + try: + with eventlet.Timeout(self.cfg.graceful_timeout) as t: + [a.kill(eventlet.StopServe()) for a in acceptors] + [a.wait() for a in acceptors] + except eventlet.Timeout as te: + if te != t: + raise + [a.kill() for a in acceptors] diff --git a/env/lib/python2.7/site-packages/gunicorn/workers/geventlet.pyc b/env/lib/python2.7/site-packages/gunicorn/workers/geventlet.pyc new file mode 100644 index 0000000..85e44e0 Binary files /dev/null and b/env/lib/python2.7/site-packages/gunicorn/workers/geventlet.pyc differ diff --git a/env/lib/python2.7/site-packages/gunicorn/workers/ggevent.py b/env/lib/python2.7/site-packages/gunicorn/workers/ggevent.py new file mode 100644 index 0000000..8dd971f --- /dev/null +++ b/env/lib/python2.7/site-packages/gunicorn/workers/ggevent.py @@ -0,0 +1,235 @@ +# -*- coding: utf-8 - +# +# This file is part of gunicorn released under the MIT license. +# See the NOTICE for more information. + +import errno +import os +import sys +from datetime import datetime +from functools import partial +import time + +_socket = __import__("socket") + +# workaround on osx, disable kqueue +if sys.platform == "darwin": + os.environ['EVENT_NOKQUEUE'] = "1" + +try: + import gevent +except ImportError: + raise RuntimeError("You need gevent installed to use this worker.") +from gevent.pool import Pool +from gevent.server import StreamServer +from gevent.socket import wait_write, socket +from gevent import pywsgi + +import gunicorn +from gunicorn.http.wsgi import base_environ +from gunicorn.workers.async import AsyncWorker +from gunicorn.http.wsgi import sendfile as o_sendfile + +VERSION = "gevent/%s gunicorn/%s" % (gevent.__version__, gunicorn.__version__) + +def _gevent_sendfile(fdout, fdin, offset, nbytes): + while True: + try: + return o_sendfile(fdout, fdin, offset, nbytes) + except OSError as e: + if e.args[0] == errno.EAGAIN: + wait_write(fdout) + else: + raise + +def patch_sendfile(): + from gunicorn.http import wsgi + + if o_sendfile is not None: + setattr(wsgi, "sendfile", _gevent_sendfile) + + +class GeventWorker(AsyncWorker): + + server_class = None + wsgi_handler = None + + def patch(self): + from gevent import monkey + monkey.noisy = False + + # if the new version is used make sure to patch subprocess + if gevent.version_info[0] == 0: + monkey.patch_all() + else: + monkey.patch_all(subprocess=True) + + # monkey patch sendfile to make it none blocking + patch_sendfile() + + # patch sockets + sockets = [] + for s in self.sockets: + sockets.append(socket(s.FAMILY, _socket.SOCK_STREAM, + _sock=s)) + self.sockets = sockets + + def notify(self): + super(GeventWorker, self).notify() + if self.ppid != os.getppid(): + self.log.info("Parent changed, shutting down: %s", self) + sys.exit(0) + + def timeout_ctx(self): + return gevent.Timeout(self.cfg.keepalive, False) + + def run(self): + servers = [] + ssl_args = {} + + if self.cfg.is_ssl: + ssl_args = dict(server_side=True, **self.cfg.ssl_options) + + for s in self.sockets: + s.setblocking(1) + pool = Pool(self.worker_connections) + if self.server_class is not None: + environ = base_environ(self.cfg) + environ.update({ + "wsgi.multithread": True, + "SERVER_SOFTWARE": VERSION, + }) + server = self.server_class( + s, application=self.wsgi, spawn=pool, log=self.log, + handler_class=self.wsgi_handler, environ=environ, + **ssl_args) + else: + hfun = partial(self.handle, s) + server = StreamServer(s, handle=hfun, spawn=pool, **ssl_args) + + server.start() + servers.append(server) + + try: + while self.alive: + self.notify() + gevent.sleep(1.0) + + except KeyboardInterrupt: + pass + except: + for server in servers: + try: + server.stop() + except: + pass + raise + + try: + # Stop accepting requests + for server in servers: + if hasattr(server, 'close'): # gevent 1.0 + server.close() + if hasattr(server, 'kill'): # gevent < 1.0 + server.kill() + + # Handle current requests until graceful_timeout + ts = time.time() + while time.time() - ts <= self.cfg.graceful_timeout: + accepting = 0 + for server in servers: + if server.pool.free_count() != server.pool.size: + accepting += 1 + + # if no server is accepting a connection, we can exit + if not accepting: + return + + self.notify() + gevent.sleep(1.0) + + # Force kill all active the handlers + self.log.warning("Worker graceful timeout (pid:%s)" % self.pid) + [server.stop(timeout=1) for server in servers] + except: + pass + + def handle_request(self, *args): + try: + super(GeventWorker, self).handle_request(*args) + except gevent.GreenletExit: + pass + except SystemExit: + pass + + if gevent.version_info[0] == 0: + + def init_process(self): + # monkey patch here + self.patch() + + # reinit the hub + import gevent.core + gevent.core.reinit() + + #gevent 0.13 and older doesn't reinitialize dns for us after forking + #here's the workaround + gevent.core.dns_shutdown(fail_requests=1) + gevent.core.dns_init() + super(GeventWorker, self).init_process() + + else: + + def init_process(self): + # monkey patch here + self.patch() + + # reinit the hub + from gevent import hub + hub.reinit() + + # then initialize the process + super(GeventWorker, self).init_process() + + +class GeventResponse(object): + + status = None + headers = None + sent = None + + def __init__(self, status, headers, clength): + self.status = status + self.headers = headers + self.sent = clength + + +class PyWSGIHandler(pywsgi.WSGIHandler): + + def log_request(self): + start = datetime.fromtimestamp(self.time_start) + finish = datetime.fromtimestamp(self.time_finish) + response_time = finish - start + resp_headers = getattr(self, 'response_headers', {}) + resp = GeventResponse(self.status, resp_headers, self.response_length) + if hasattr(self, 'headers'): + req_headers = [h.split(":", 1) for h in self.headers.headers] + else: + req_headers = [] + self.server.log.access(resp, req_headers, self.environ, response_time) + + def get_environ(self): + env = super(PyWSGIHandler, self).get_environ() + env['gunicorn.sock'] = self.socket + env['RAW_URI'] = self.path + return env + + +class PyWSGIServer(pywsgi.WSGIServer): + pass + + +class GeventPyWSGIWorker(GeventWorker): + "The Gevent StreamServer based workers." + server_class = PyWSGIServer + wsgi_handler = PyWSGIHandler diff --git a/env/lib/python2.7/site-packages/gunicorn/workers/ggevent.pyc b/env/lib/python2.7/site-packages/gunicorn/workers/ggevent.pyc new file mode 100644 index 0000000..7625f94 Binary files /dev/null and b/env/lib/python2.7/site-packages/gunicorn/workers/ggevent.pyc differ diff --git a/env/lib/python2.7/site-packages/gunicorn/workers/gthread.py b/env/lib/python2.7/site-packages/gunicorn/workers/gthread.py new file mode 100644 index 0000000..deed485 --- /dev/null +++ b/env/lib/python2.7/site-packages/gunicorn/workers/gthread.py @@ -0,0 +1,362 @@ +# -*- coding: utf-8 - +# +# This file is part of gunicorn released under the MIT license. +# See the NOTICE for more information. + +# design: +# a threaded worker accepts connections in the main loop, accepted +# connections are are added to the thread pool as a connection job. On +# keepalive connections are put back in the loop waiting for an event. +# If no event happen after the keep alive timeout, the connectoin is +# closed. + +from collections import deque +from datetime import datetime +import errno +from functools import partial +import os +import socket +import ssl +import sys +from threading import RLock +import time + +from .. import http +from ..http import wsgi +from .. import util +from . import base +from .. import six + + +try: + import concurrent.futures as futures +except ImportError: + raise RuntimeError(""" + You need to install the 'futures' package to use this worker with this + Python version. + """) + +try: + from asyncio import selectors +except ImportError: + from gunicorn import selectors + + +class TConn(object): + + def __init__(self, cfg, listener, sock, addr): + self.cfg = cfg + self.listener = listener + self.sock = sock + self.addr = addr + + self.timeout = None + self.parser = None + + # set the socket to non blocking + self.sock.setblocking(False) + + def init(self): + self.sock.setblocking(True) + if self.parser is None: + # wrap the socket if needed + if self.cfg.is_ssl: + self.sock = ssl.wrap_socket(self.sock, server_side=True, + **self.cfg.ssl_options) + + # initialize the parser + self.parser = http.RequestParser(self.cfg, self.sock) + + def set_timeout(self): + # set the timeout + self.timeout = time.time() + self.cfg.keepalive + + def close(self): + util.close(self.sock) + + def __lt__(self, other): + return self.timeout < other.timeout + + __cmp__ = __lt__ + + +class ThreadWorker(base.Worker): + + def __init__(self, *args, **kwargs): + super(ThreadWorker, self).__init__(*args, **kwargs) + self.worker_connections = self.cfg.worker_connections + self.max_keepalived = self.cfg.worker_connections - self.cfg.threads + # initialise the pool + self.tpool = None + self.poller = None + self._lock = None + self.futures = deque() + self._keep = deque() + + @classmethod + def check_config(cls, cfg, log): + max_keepalived = cfg.worker_connections - cfg.threads + + if max_keepalived <= 0 and cfg.keepalive: + log.warning("No keepalived connections can be handled. " + + "Check the number of worker connections and threads.") + + def init_process(self): + self.tpool = futures.ThreadPoolExecutor(max_workers=self.cfg.threads) + self.poller = selectors.DefaultSelector() + self._lock = RLock() + super(ThreadWorker, self).init_process() + + def handle_quit(self, sig, frame): + self.alive = False + # worker_int callback + self.cfg.worker_int(self) + self.tpool.shutdown(False) + time.sleep(0.1) + sys.exit(0) + + def _wrap_future(self, fs, conn): + fs.conn = conn + self.futures.append(fs) + fs.add_done_callback(self.finish_request) + + def enqueue_req(self, conn): + conn.init() + # submit the connection to a worker + fs = self.tpool.submit(self.handle, conn) + self._wrap_future(fs, conn) + + def accept(self, listener): + try: + client, addr = listener.accept() + # initialize the connection object + conn = TConn(self.cfg, listener, client, addr) + self.nr += 1 + # enqueue the job + self.enqueue_req(conn) + except socket.error as e: + if e.args[0] not in (errno.EAGAIN, + errno.ECONNABORTED, errno.EWOULDBLOCK): + raise + + def reuse_connection(self, conn, client): + with self._lock: + # unregister the client from the poller + self.poller.unregister(client) + # remove the connection from keepalive + try: + self._keep.remove(conn) + except ValueError: + # race condition + return + + # submit the connection to a worker + self.enqueue_req(conn) + + def murder_keepalived(self): + now = time.time() + while True: + with self._lock: + try: + # remove the connection from the queue + conn = self._keep.popleft() + except IndexError: + break + + delta = conn.timeout - now + if delta > 0: + # add the connection back to the queue + with self._lock: + self._keep.appendleft(conn) + break + else: + self.nr -= 1 + # remove the socket from the poller + with self._lock: + try: + self.poller.unregister(conn.sock) + except socket.error as e: + if e.args[0] != errno.EBADF: + raise + + # close the socket + conn.close() + + def is_parent_alive(self): + # If our parent changed then we shut down. + if self.ppid != os.getppid(): + self.log.info("Parent changed, shutting down: %s", self) + return False + return True + + def run(self): + # init listeners, add them to the event loop + for s in self.sockets: + s.setblocking(False) + self.poller.register(s, selectors.EVENT_READ, self.accept) + + timeout = self.cfg.timeout or 0.5 + + while self.alive: + # notify the arbiter we are alive + self.notify() + + # can we accept more connections? + if self.nr < self.worker_connections: + # wait for an event + events = self.poller.select(0.02) + for key, mask in events: + callback = key.data + callback(key.fileobj) + + if not self.is_parent_alive(): + break + + # hanle keepalive timeouts + self.murder_keepalived() + + # if the number of connections is < to the max we can handle at + # the same time there is no need to wait for one + if len(self.futures) < self.cfg.threads: + continue + + result = futures.wait(self.futures, timeout=timeout, + return_when=futures.FIRST_COMPLETED) + + if not result.done: + break + else: + [self.futures.remove(f) for f in result.done] + + self.tpool.shutdown(False) + self.poller.close() + + def finish_request(self, fs): + if fs.cancelled(): + fs.conn.close() + return + + try: + (keepalive, conn) = fs.result() + # if the connection should be kept alived add it + # to the eventloop and record it + if keepalive: + # flag the socket as non blocked + conn.sock.setblocking(False) + + # register the connection + conn.set_timeout() + with self._lock: + self._keep.append(conn) + + # add the socket to the event loop + self.poller.register(conn.sock, selectors.EVENT_READ, + partial(self.reuse_connection, conn)) + else: + self.nr -= 1 + conn.close() + except: + # an exception happened, make sure to close the + # socket. + self.nr -= 1 + fs.conn.close() + + def handle(self, conn): + keepalive = False + req = None + try: + req = six.next(conn.parser) + if not req: + return (False, conn) + + # handle the request + keepalive = self.handle_request(req, conn) + if keepalive: + return (keepalive, conn) + except http.errors.NoMoreData as e: + self.log.debug("Ignored premature client disconnection. %s", e) + + except StopIteration as e: + self.log.debug("Closing connection. %s", e) + except ssl.SSLError as e: + if e.args[0] == ssl.SSL_ERROR_EOF: + self.log.debug("ssl connection closed") + conn.sock.close() + else: + self.log.debug("Error processing SSL request.") + self.handle_error(req, conn.sock, conn.addr, e) + + except socket.error as e: + if e.args[0] not in (errno.EPIPE, errno.ECONNRESET): + self.log.exception("Socket error processing request.") + else: + if e.args[0] == errno.ECONNRESET: + self.log.debug("Ignoring connection reset") + else: + self.log.debug("Ignoring connection epipe") + except Exception as e: + self.handle_error(req, conn.sock, conn.addr, e) + + return (False, conn) + + def handle_request(self, req, conn): + environ = {} + resp = None + try: + self.cfg.pre_request(self, req) + request_start = datetime.now() + resp, environ = wsgi.create(req, conn.sock, conn.addr, + conn.listener.getsockname(), self.cfg) + environ["wsgi.multithread"] = True + + if self.alive and self.nr >= self.max_requests: + self.log.info("Autorestarting worker after current request.") + resp.force_close() + self.alive = False + + if not self.cfg.keepalive: + resp.force_close() + elif len(self._keep) >= self.max_keepalived: + resp.force_close() + + respiter = self.wsgi(environ, resp.start_response) + try: + if isinstance(respiter, environ['wsgi.file_wrapper']): + resp.write_file(respiter) + else: + for item in respiter: + resp.write(item) + + resp.close() + request_time = datetime.now() - request_start + self.log.access(resp, req, environ, request_time) + finally: + if hasattr(respiter, "close"): + respiter.close() + + if resp.should_close(): + self.log.debug("Closing connection.") + return False + except socket.error: + exc_info = sys.exc_info() + # pass to next try-except level + six.reraise(exc_info[0], exc_info[1], exc_info[2]) + except Exception: + if resp and resp.headers_sent: + # If the requests have already been sent, we should close the + # connection to indicate the error. + self.log.exception("Error handling request") + try: + conn.sock.shutdown(socket.SHUT_RDWR) + conn.sock.close() + except socket.error: + pass + raise StopIteration() + raise + finally: + try: + self.cfg.post_request(self, req, environ, resp) + except Exception: + self.log.exception("Exception in post_request hook") + + return True diff --git a/env/lib/python2.7/site-packages/gunicorn/workers/gthread.pyc b/env/lib/python2.7/site-packages/gunicorn/workers/gthread.pyc new file mode 100644 index 0000000..21a522e Binary files /dev/null and b/env/lib/python2.7/site-packages/gunicorn/workers/gthread.pyc differ diff --git a/env/lib/python2.7/site-packages/gunicorn/workers/gtornado.py b/env/lib/python2.7/site-packages/gunicorn/workers/gtornado.py new file mode 100644 index 0000000..f686abf --- /dev/null +++ b/env/lib/python2.7/site-packages/gunicorn/workers/gtornado.py @@ -0,0 +1,121 @@ +# -*- coding: utf-8 - +# +# This file is part of gunicorn released under the MIT license. +# See the NOTICE for more information. + +import os +import sys + +try: + import tornado.web +except ImportError: + raise RuntimeError("You need tornado installed to use this worker.") +import tornado.httpserver +from tornado.ioloop import IOLoop, PeriodicCallback +from tornado.wsgi import WSGIContainer +from gunicorn.workers.base import Worker +from gunicorn import __version__ as gversion + + +class TornadoWorker(Worker): + + @classmethod + def setup(cls): + web = sys.modules.pop("tornado.web") + old_clear = web.RequestHandler.clear + + def clear(self): + old_clear(self) + self._headers["Server"] += " (Gunicorn/%s)" % gversion + web.RequestHandler.clear = clear + sys.modules["tornado.web"] = web + + def handle_exit(self, sig, frame): + if self.alive: + super(TornadoWorker, self).handle_exit(sig, frame) + self.stop() + + def handle_request(self): + self.nr += 1 + if self.alive and self.nr >= self.max_requests: + self.alive = False + self.log.info("Autorestarting worker after current request.") + self.stop() + + def watchdog(self): + if self.alive: + self.notify() + + if self.ppid != os.getppid(): + self.log.info("Parent changed, shutting down: %s", self) + self.stop() + + def run(self): + self.ioloop = IOLoop.instance() + self.alive = True + PeriodicCallback(self.watchdog, 1000, io_loop=self.ioloop).start() + + # Assume the app is a WSGI callable if its not an + # instance of tornado.web.Application or is an + # instance of tornado.wsgi.WSGIApplication + app = self.wsgi + if not isinstance(app, tornado.web.Application) or \ + isinstance(app, tornado.wsgi.WSGIApplication): + app = WSGIContainer(app) + + # Monkey-patching HTTPConnection.finish to count the + # number of requests being handled by Tornado. This + # will help gunicorn shutdown the worker if max_requests + # is exceeded. + httpserver = sys.modules["tornado.httpserver"] + if hasattr(httpserver, 'HTTPConnection'): + old_connection_finish = httpserver.HTTPConnection.finish + + def finish(other): + self.handle_request() + old_connection_finish(other) + httpserver.HTTPConnection.finish = finish + sys.modules["tornado.httpserver"] = httpserver + + server_class = tornado.httpserver.HTTPServer + else: + + class _HTTPServer(tornado.httpserver.HTTPServer): + + def on_close(instance, server_conn): + self.handle_request() + super(_HTTPServer, instance).on_close(server_conn) + + server_class = _HTTPServer + + if self.cfg.is_ssl: + server = server_class(app, io_loop=self.ioloop, + ssl_options=self.cfg.ssl_options) + else: + server = server_class(app, io_loop=self.ioloop) + + self.server = server + + for s in self.sockets: + s.setblocking(0) + if hasattr(server, "add_socket"): # tornado > 2.0 + server.add_socket(s) + elif hasattr(server, "_sockets"): # tornado 2.0 + server._sockets[s.fileno()] = s + + server.no_keep_alive = self.cfg.keepalive <= 0 + server.start(num_processes=1) + + self.ioloop.start() + + def stop(self): + if hasattr(self, 'server'): + try: + self.server.stop() + except Exception: + pass + PeriodicCallback(self.stop_ioloop, 1000, io_loop=self.ioloop).start() + + def stop_ioloop(self): + if not self.ioloop._callbacks and len(self.ioloop._timeouts) <= 1: + self.ioloop.stop() diff --git a/env/lib/python2.7/site-packages/gunicorn/workers/gtornado.pyc b/env/lib/python2.7/site-packages/gunicorn/workers/gtornado.pyc new file mode 100644 index 0000000..898b017 Binary files /dev/null and b/env/lib/python2.7/site-packages/gunicorn/workers/gtornado.pyc differ diff --git a/env/lib/python2.7/site-packages/gunicorn/workers/sync.py b/env/lib/python2.7/site-packages/gunicorn/workers/sync.py new file mode 100644 index 0000000..84886b9 --- /dev/null +++ b/env/lib/python2.7/site-packages/gunicorn/workers/sync.py @@ -0,0 +1,204 @@ +# -*- coding: utf-8 - +# +# This file is part of gunicorn released under the MIT license. +# See the NOTICE for more information. +# + +from datetime import datetime +import errno +import os +import select +import socket +import ssl +import sys + +import gunicorn.http as http +import gunicorn.http.wsgi as wsgi +import gunicorn.util as util +import gunicorn.workers.base as base +from gunicorn import six + +class StopWaiting(Exception): + """ exception raised to stop waiting for a connnection """ + +class SyncWorker(base.Worker): + + def accept(self, listener): + client, addr = listener.accept() + client.setblocking(1) + util.close_on_exec(client) + self.handle(listener, client, addr) + + def wait(self, timeout): + try: + self.notify() + ret = select.select(self.sockets, [], self.PIPE, timeout) + if ret[0]: + return ret[0] + + except select.error as e: + if e.args[0] == errno.EINTR: + return self.sockets + if e.args[0] == errno.EBADF: + if self.nr < 0: + return self.sockets + else: + raise StopWaiting + raise + + def is_parent_alive(self): + # If our parent changed then we shut down. + if self.ppid != os.getppid(): + self.log.info("Parent changed, shutting down: %s", self) + return False + return True + + def run_for_one(self, timeout): + listener = self.sockets[0] + while self.alive: + self.notify() + + # Accept a connection. If we get an error telling us + # that no connection is waiting we fall down to the + # select which is where we'll wait for a bit for new + # workers to come give us some love. + try: + self.accept(listener) + # Keep processing clients until no one is waiting. This + # prevents the need to select() for every client that we + # process. + continue + + except socket.error as e: + if e.args[0] not in (errno.EAGAIN, errno.ECONNABORTED, + errno.EWOULDBLOCK): + raise + + if not self.is_parent_alive(): + return + + try: + self.wait(timeout) + except StopWaiting: + return + + def run_for_multiple(self, timeout): + while self.alive: + self.notify() + + try: + ready = self.wait(timeout) + except StopWaiting: + return + + if ready is not None: + for listener in ready: + try: + self.accept(listener) + except socket.error as e: + if e.args[0] not in (errno.EAGAIN, errno.ECONNABORTED, + errno.EWOULDBLOCK): + raise + + if not self.is_parent_alive(): + return + + def run(self): + # if no timeout is given the worker will never wait and will + # use the CPU for nothing. This minimal timeout prevent it. + timeout = self.timeout or 0.5 + + # self.socket appears to lose its blocking status after + # we fork in the arbiter. Reset it here. + for s in self.sockets: + s.setblocking(0) + + if len(self.sockets) > 1: + self.run_for_multiple(timeout) + else: + self.run_for_one(timeout) + + def handle(self, listener, client, addr): + req = None + try: + if self.cfg.is_ssl: + client = ssl.wrap_socket(client, server_side=True, + **self.cfg.ssl_options) + + parser = http.RequestParser(self.cfg, client) + req = six.next(parser) + self.handle_request(listener, req, client, addr) + except http.errors.NoMoreData as e: + self.log.debug("Ignored premature client disconnection. %s", e) + except StopIteration as e: + self.log.debug("Closing connection. %s", e) + except ssl.SSLError as e: + if e.args[0] == ssl.SSL_ERROR_EOF: + self.log.debug("ssl connection closed") + client.close() + else: + self.log.debug("Error processing SSL request.") + self.handle_error(req, client, addr, e) + except socket.error as e: + if e.args[0] not in (errno.EPIPE, errno.ECONNRESET): + self.log.exception("Socket error processing request.") + else: + if e.args[0] == errno.ECONNRESET: + self.log.debug("Ignoring connection reset") + else: + self.log.debug("Ignoring EPIPE") + except Exception as e: + self.handle_error(req, client, addr, e) + finally: + util.close(client) + + def handle_request(self, listener, req, client, addr): + environ = {} + resp = None + try: + self.cfg.pre_request(self, req) + request_start = datetime.now() + resp, environ = wsgi.create(req, client, addr, + listener.getsockname(), self.cfg) + # Force the connection closed until someone shows + # a buffering proxy that supports Keep-Alive to + # the backend. + resp.force_close() + self.nr += 1 + if self.nr >= self.max_requests: + self.log.info("Autorestarting worker after current request.") + self.alive = False + respiter = self.wsgi(environ, resp.start_response) + try: + if isinstance(respiter, environ['wsgi.file_wrapper']): + resp.write_file(respiter) + else: + for item in respiter: + resp.write(item) + resp.close() + request_time = datetime.now() - request_start + self.log.access(resp, req, environ, request_time) + finally: + if hasattr(respiter, "close"): + respiter.close() + except socket.error: + exc_info = sys.exc_info() + # pass to next try-except level + six.reraise(exc_info[0], exc_info[1], exc_info[2]) + except Exception: + if resp and resp.headers_sent: + # If the requests have already been sent, we should close the + # connection to indicate the error. + self.log.exception("Error handling request") + try: + client.shutdown(socket.SHUT_RDWR) + client.close() + except socket.error: + pass + raise StopIteration() + raise + finally: + try: + self.cfg.post_request(self, req, environ, resp) + except Exception: + self.log.exception("Exception in post_request hook") diff --git a/env/lib/python2.7/site-packages/gunicorn/workers/sync.pyc b/env/lib/python2.7/site-packages/gunicorn/workers/sync.pyc new file mode 100644 index 0000000..b937341 Binary files /dev/null and b/env/lib/python2.7/site-packages/gunicorn/workers/sync.pyc differ diff --git a/env/lib/python2.7/site-packages/gunicorn/workers/workertmp.py b/env/lib/python2.7/site-packages/gunicorn/workers/workertmp.py new file mode 100644 index 0000000..36bc97a --- /dev/null +++ b/env/lib/python2.7/site-packages/gunicorn/workers/workertmp.py @@ -0,0 +1,56 @@ +# -*- coding: utf-8 - +# +# This file is part of gunicorn released under the MIT license. +# See the NOTICE for more information. + +import os +import platform +import tempfile + +from gunicorn import util + +PLATFORM = platform.system() +IS_CYGWIN = PLATFORM.startswith('CYGWIN') + + +class WorkerTmp(object): + + def __init__(self, cfg): + old_umask = os.umask(cfg.umask) + fdir = cfg.worker_tmp_dir + if fdir and not os.path.isdir(fdir): + raise RuntimeError("%s doesn't exist. Can't create workertmp." % fdir) + fd, name = tempfile.mkstemp(prefix="wgunicorn-", dir=fdir) + + # allows the process to write to the file + util.chown(name, cfg.uid, cfg.gid) + os.umask(old_umask) + + # unlink the file so we don't leak tempory files + try: + if not IS_CYGWIN: + util.unlink(name) + self._tmp = os.fdopen(fd, 'w+b', 1) + except: + os.close(fd) + raise + + self.spinner = 0 + + def notify(self): + try: + self.spinner = (self.spinner + 1) % 2 + os.fchmod(self._tmp.fileno(), self.spinner) + except AttributeError: + # python < 2.6 + self._tmp.truncate(0) + os.write(self._tmp.fileno(), b"X") + + def last_update(self): + return os.fstat(self._tmp.fileno()).st_ctime + + def fileno(self): + return self._tmp.fileno() + + def close(self): + return self._tmp.close() diff --git a/env/lib/python2.7/site-packages/gunicorn/workers/workertmp.pyc b/env/lib/python2.7/site-packages/gunicorn/workers/workertmp.pyc new file mode 100644 index 0000000..a65709f Binary files /dev/null and b/env/lib/python2.7/site-packages/gunicorn/workers/workertmp.pyc differ diff --git a/env/lib/python2.7/site-packages/nose-1.3.7.dist-info/DESCRIPTION.rst b/env/lib/python2.7/site-packages/nose-1.3.7.dist-info/DESCRIPTION.rst new file mode 100644 index 0000000..13ab296 --- /dev/null +++ b/env/lib/python2.7/site-packages/nose-1.3.7.dist-info/DESCRIPTION.rst @@ -0,0 +1,21 @@ +nose extends the test loading and running features of unittest, making +it easier to write, find and run tests. + +By default, nose will run tests in files or directories under the current +working directory whose names include "test" or "Test" at a word boundary +(like "test_this" or "functional_test" or "TestClass" but not +"libtest"). Test output is similar to that of unittest, but also includes +captured stdout output from failing tests, for easy print-style debugging. + +These features, and many more, are customizable through the use of +plugins. Plugins included with nose provide support for doctest, code +coverage and profiling, flexible attribute-based test selection, +output capture and more. More information about writing plugins may be +found on in the nose API documentation, here: +http://readthedocs.org/docs/nose/ + +If you have recently reported a bug marked as fixed, or have a craving for +the very latest, you may want the development version instead: +https://github.com/nose-devs/nose/tarball/master#egg=nose-dev + + diff --git a/env/lib/python2.7/site-packages/nose-1.3.7.dist-info/METADATA b/env/lib/python2.7/site-packages/nose-1.3.7.dist-info/METADATA new file mode 100644 index 0000000..8333866 --- /dev/null +++ b/env/lib/python2.7/site-packages/nose-1.3.7.dist-info/METADATA @@ -0,0 +1,40 @@ +Metadata-Version: 2.0 +Name: nose +Version: 1.3.7 +Summary: nose extends unittest to make testing easier +Home-page: http://readthedocs.org/docs/nose/ +Author: Jason Pellerin +Author-email: jpellerin+nose@gmail.com +License: GNU LGPL +Keywords: test unittest doctest automatic discovery +Platform: UNKNOWN +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL) +Classifier: Natural Language :: English +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Topic :: Software Development :: Testing + +nose extends the test loading and running features of unittest, making +it easier to write, find and run tests. + +By default, nose will run tests in files or directories under the current +working directory whose names include "test" or "Test" at a word boundary +(like "test_this" or "functional_test" or "TestClass" but not +"libtest"). Test output is similar to that of unittest, but also includes +captured stdout output from failing tests, for easy print-style debugging. + +These features, and many more, are customizable through the use of +plugins. Plugins included with nose provide support for doctest, code +coverage and profiling, flexible attribute-based test selection, +output capture and more. More information about writing plugins may be +found on in the nose API documentation, here: +http://readthedocs.org/docs/nose/ + +If you have recently reported a bug marked as fixed, or have a craving for +the very latest, you may want the development version instead: +https://github.com/nose-devs/nose/tarball/master#egg=nose-dev + + diff --git a/env/lib/python2.7/site-packages/nose-1.3.7.dist-info/RECORD b/env/lib/python2.7/site-packages/nose-1.3.7.dist-info/RECORD new file mode 100644 index 0000000..a5157a3 --- /dev/null +++ b/env/lib/python2.7/site-packages/nose-1.3.7.dist-info/RECORD @@ -0,0 +1,105 @@ +nose/__init__.py,sha256=qhgaKOTaJE9w0AnhaFTKdGAkvfkt5vD4DH20uWhughg,404 +nose/__main__.py,sha256=ieZJh6ovt73G_DtCQxDG0lg-2ZSBqvBWCY9oWEy29RE,144 +nose/case.py,sha256=EleBbL0ZxUsxqlOumo1onYdAzh6ZBXZHcQmrD0Vvz6E,13171 +nose/commands.py,sha256=FlDxfTcthx-9GAFkZlv0MvZDppgg_CNvPKDTmiMIzI0,6310 +nose/config.py,sha256=5mcQVwjPkKJvmiO-_afza_FloYqUC-k-i-JLlL1pz9k,25238 +nose/core.py,sha256=c4ezVtMQNy9IFbODWN0wSs-vLwH4daPvJ6_09OlZC1U,13093 +nose/exc.py,sha256=OxhCxUpcVPtAWtEZ4Se86IuRNVJJRMS_hYQY33HAZ1k,376 +nose/failure.py,sha256=bO5Zk501t9SrVC4fTilMG4ppTD70vKyWsWModdnxjyw,1249 +nose/importer.py,sha256=z1stmS3G-90Z6JTz6ZA5Thj-ugDgdK9M5s3eq6Y8CYk,5978 +nose/inspector.py,sha256=I6lPUSi9yc_4rmE_IY2tQUZ683MEJO_NEqKA1hl1tQU,6986 +nose/loader.py,sha256=WRduLu_9_fXQKjkgHEoQ-2GCllwG3d-qiS8VeyC994A,25526 +nose/proxy.py,sha256=-sxR0Vvi2YJ4ur0xT7woqvBbcMVcm-Lkksx_7M9qUMI,6879 +nose/pyversion.py,sha256=_rx5TcPjByVq-fUhQoJEoEBitlyjKQ4veXlcKprZq2M,7478 +nose/result.py,sha256=b7J31JHaw-uZTMmSFjwVglR6LaGcelypHazRzjxzu3E,6711 +nose/selector.py,sha256=MiWihR7At1d-Kb7ZDS-6V5hVTa-7Pfs2qQnam6jPaKw,9100 +nose/suite.py,sha256=pxDzPA8kVGkItX3NmlrvJ_4lZ5c7dUIRaBkQ0zHl73A,22341 +nose/twistedtools.py,sha256=xsmYEyAnIPdxSkGO2UJUdeOHAMSRZcEX8li45bYTF_Q,5525 +nose/usage.txt,sha256=WYXrrcHjIjXPb4QXzhvHbD6rDIsKjc1P4uAzAHt4cp0,4425 +nose/util.py,sha256=Vn1TtJUxufQKxNLwj2MABWbZTQin_fuJgeP9moZAOcI,20310 +nose/ext/__init__.py,sha256=XrfzqUEk4R6CRykXi8ix3Gq37uknxsJ-6pDVk3PEm6Y,33 +nose/ext/dtcompat.py,sha256=luarL98_YsS80a82t64IEkUnb_gcH3NBBpixZOBpNcg,88063 +nose/plugins/__init__.py,sha256=bFMd0YnXvl9uj_Ybaa7B2IP6UPUBho3rqy7tE0hI3gs,6291 +nose/plugins/allmodules.py,sha256=z2BP0klSMyoNSv3Q5Zr8Z1LhmgxLZ_48azpmuY77dpM,1720 +nose/plugins/attrib.py,sha256=swaW9J473HugiNS3mV1mxN9ZF8T5QXTgYu9VKmVxzbo,9660 +nose/plugins/base.py,sha256=Pu2orZzrmwbNky71LhNye-eBG_-pJr2GZkJX2Wt9FbY,26056 +nose/plugins/builtin.py,sha256=xPjg3EUHuzdgxovSnlZJanJ62Qbe9wXy8V0EN9X-_M4,1021 +nose/plugins/capture.py,sha256=U5hf6dese8KQzncMpIiW4KCUQuZGlwr0mA6w8kyj2fs,3373 +nose/plugins/collect.py,sha256=Zk2UK1ipM6T_CGBSiarlcP1cNoYam97orktWvlPeXPU,3113 +nose/plugins/cover.py,sha256=oOBLxHkrsk68HIEMWN3uPmeqgBUFImbQSydb19-r72o,11673 +nose/plugins/debug.py,sha256=wbIEnp-wDm-ipKWqoUa25y8IwbqzefvIbD8we3KBCeU,2272 +nose/plugins/deprecated.py,sha256=roSxy1LFssR7DfMaRUiVAjr1jIYbck6zXqk5AtinlyQ,1551 +nose/plugins/doctests.py,sha256=_dIlgVWa4sMMm3Esnj-N-1ZbyzLRQLdXCXZsD3_XJD0,17601 +nose/plugins/errorclass.py,sha256=9YRvgAYHwZHs2yFzg5TRTRXYYnW8Ig0zXHXlu4Ebr9Q,7279 +nose/plugins/failuredetail.py,sha256=oZ0EebxwNTo7hh7hYKOeZwKN608Oc6nOLAxa8_4T6zk,1635 +nose/plugins/isolate.py,sha256=t3VViOHsA6BTYGk86n5Dpp_1Uo1fa9wI2L_g9jdJcEE,3750 +nose/plugins/logcapture.py,sha256=QY3l48m88oGgxyW6tKjEMygl6yFUSnjZowwmi2TSPQI,9353 +nose/plugins/manager.py,sha256=sjiKkjV4h0T5W13U55SjIzA0AHN8FdWcaeICR4jbIGY,15589 +nose/plugins/multiprocess.py,sha256=O-BM3g0kjDUg4EWInriNGkgdqUnzJY8tWo4_MYv86xw,35281 +nose/plugins/plugintest.py,sha256=xFn2BFxaynNHR3wJBTLVFY4HByVkGePMK26wwgk6RL4,13536 +nose/plugins/prof.py,sha256=zTsm5blrK6v71f4sRp3vMAFydkPZWIN3iTfDYctgyWw,5357 +nose/plugins/skip.py,sha256=j-qlWuL-5PGGl0Nu1zeg_agC5MOAx85-kCXlsB-wdrA,2142 +nose/plugins/testid.py,sha256=zMh_Myk9ITnaLlUNZSHrinGwGW9tgbHinNLHNqN82QY,9916 +nose/plugins/xunit.py,sha256=lGoZemm5v4YWq-buHXTixzmZIVQGTanzoPs4MM8qVjo,11667 +nose/sphinx/__init__.py,sha256=n1bnYdeb_bNDBKASWGywTRa0Ne9hMAkal3AuVZJgovI,5 +nose/sphinx/pluginopts.py,sha256=l6Cs3eIMkwaEsMwUf71OyP0PKhnEBNT7CoyFu8AcBbk,5638 +nose/tools/__init__.py,sha256=fyZ3FOSR1Zqeu63pMpfkdMwZaT4oqBG-1df_wBhqBp4,436 +nose/tools/nontrivial.py,sha256=1mFU7pUGHj6VFNUw6FvAR5TsJ7CKFPyQ3x7zTulPPNw,4171 +nose/tools/trivial.py,sha256=Q1iNjE7Dbd-xxfYn26kEAz3Lpj8i3HBq2xp3_s7OU6Y,1184 +../../../man/man1/nosetests.1,sha256=zdSAnt6Al0zeJY5LKTD0_Eg2kqj68v1chi2yjHKRd7Q,17679 +nose-1.3.7.dist-info/DESCRIPTION.rst,sha256=-tUD7I_ukgpEs4fRHvl6CrXvyvdc2rSbRB98MCjKsbo,1028 +nose-1.3.7.dist-info/entry_points.txt,sha256=lF8Ztg-IZH-kSt3JikbZh3d7GoF7hGk0eVjr1AQwu6k,133 +nose-1.3.7.dist-info/METADATA,sha256=56Jl3r8V75K1YPYmXOGwEyyOMu_6xRRvlAluT3-7KxI,1748 +nose-1.3.7.dist-info/metadata.json,sha256=MnjZJswzinean8QyCI6FNiMr5qXj8SXOPyKAp1_ndfA,1142 +nose-1.3.7.dist-info/RECORD,, +nose-1.3.7.dist-info/top_level.txt,sha256=l-72QyZodzeMXgqL5ci-WLmVdcd-1PiTIfMH9nbeS1k,5 +nose-1.3.7.dist-info/WHEEL,sha256=54bVun1KfEBTJ68SHUmbxNPj80VxlQ0sHi4gZdGZXEY,92 +/Users/Boris/db_api/env/bin/nosetests,sha256=26BTelLZBO9F_5aNRYSabelMAScWrUQO8NNXuVrXfOM,232 +/Users/Boris/db_api/env/bin/nosetests-2.7,sha256=26BTelLZBO9F_5aNRYSabelMAScWrUQO8NNXuVrXfOM,232 +nose/util.pyc,, +nose/__init__.pyc,, +nose/plugins/allmodules.pyc,, +nose/config.pyc,, +nose/sphinx/pluginopts.pyc,, +nose/plugins/failuredetail.pyc,, +nose/plugins/attrib.pyc,, +nose/result.pyc,, +nose/plugins/deprecated.pyc,, +nose/plugins/xunit.pyc,, +nose/plugins/capture.pyc,, +nose/ext/dtcompat.pyc,, +nose/pyversion.pyc,, +nose/plugins/logcapture.pyc,, +nose/plugins/collect.pyc,, +nose/plugins/debug.pyc,, +nose/selector.pyc,, +nose/ext/__init__.pyc,, +nose/case.pyc,, +nose/__main__.pyc,, +nose/core.pyc,, +nose/plugins/errorclass.pyc,, +nose/plugins/manager.pyc,, +nose/exc.pyc,, +nose/plugins/isolate.pyc,, +nose/plugins/testid.pyc,, +nose/plugins/__init__.pyc,, +nose/plugins/plugintest.pyc,, +nose/tools/trivial.pyc,, +nose/plugins/multiprocess.pyc,, +nose/plugins/cover.pyc,, +nose/inspector.pyc,, +nose/tools/__init__.pyc,, +nose/proxy.pyc,, +nose/failure.pyc,, +nose/plugins/builtin.pyc,, +nose/suite.pyc,, +nose/sphinx/__init__.pyc,, +nose/plugins/skip.pyc,, +nose/twistedtools.pyc,, +nose/commands.pyc,, +nose/loader.pyc,, +nose/importer.pyc,, +nose/plugins/base.pyc,, +nose/plugins/prof.pyc,, +nose/plugins/doctests.pyc,, +nose/tools/nontrivial.pyc,, diff --git a/env/lib/python2.7/site-packages/nose-1.3.7.dist-info/WHEEL b/env/lib/python2.7/site-packages/nose-1.3.7.dist-info/WHEEL new file mode 100644 index 0000000..45a0cd8 --- /dev/null +++ b/env/lib/python2.7/site-packages/nose-1.3.7.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.24.0) +Root-Is-Purelib: true +Tag: py2-none-any + diff --git a/env/lib/python2.7/site-packages/nose-1.3.7.dist-info/entry_points.txt b/env/lib/python2.7/site-packages/nose-1.3.7.dist-info/entry_points.txt new file mode 100644 index 0000000..07c6548 --- /dev/null +++ b/env/lib/python2.7/site-packages/nose-1.3.7.dist-info/entry_points.txt @@ -0,0 +1,7 @@ +[console_scripts] +nosetests = nose:run_exit +nosetests-2.7 = nose:run_exit + +[distutils.commands] +nosetests = nose.commands:nosetests + diff --git a/env/lib/python2.7/site-packages/nose-1.3.7.dist-info/metadata.json b/env/lib/python2.7/site-packages/nose-1.3.7.dist-info/metadata.json new file mode 100644 index 0000000..475de80 --- /dev/null +++ b/env/lib/python2.7/site-packages/nose-1.3.7.dist-info/metadata.json @@ -0,0 +1 @@ +{"license": "GNU LGPL", "name": "nose", "metadata_version": "2.0", "generator": "bdist_wheel (0.24.0)", "summary": "nose extends unittest to make testing easier", "version": "1.3.7", "extensions": {"python.details": {"project_urls": {"Home": "http://readthedocs.org/docs/nose/"}, "document_names": {"description": "DESCRIPTION.rst"}, "contacts": [{"role": "author", "email": "jpellerin+nose@gmail.com", "name": "Jason Pellerin"}]}, "python.commands": {"wrap_console": {"nosetests": "nose:run_exit", "nosetests-2.7": "nose:run_exit"}}, "python.exports": {"console_scripts": {"nosetests": "nose:run_exit", "nosetests-2.7": "nose:run_exit"}, "distutils.commands": {"nosetests": "nose.commands:nosetests"}}}, "keywords": ["test", "unittest", "doctest", "automatic", "discovery"], "classifiers": ["Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)", "Natural Language :: English", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", "Topic :: Software Development :: Testing"]} \ No newline at end of file diff --git a/env/lib/python2.7/site-packages/nose-1.3.7.dist-info/top_level.txt b/env/lib/python2.7/site-packages/nose-1.3.7.dist-info/top_level.txt new file mode 100644 index 0000000..f3c7e8e --- /dev/null +++ b/env/lib/python2.7/site-packages/nose-1.3.7.dist-info/top_level.txt @@ -0,0 +1 @@ +nose diff --git a/env/lib/python2.7/site-packages/nose/__init__.py b/env/lib/python2.7/site-packages/nose/__init__.py new file mode 100644 index 0000000..1ae1362 --- /dev/null +++ b/env/lib/python2.7/site-packages/nose/__init__.py @@ -0,0 +1,15 @@ +from nose.core import collector, main, run, run_exit, runmodule +# backwards compatibility +from nose.exc import SkipTest, DeprecatedTest +from nose.tools import with_setup + +__author__ = 'Jason Pellerin' +__versioninfo__ = (1, 3, 7) +__version__ = '.'.join(map(str, __versioninfo__)) + +__all__ = [ + 'main', 'run', 'run_exit', 'runmodule', 'with_setup', + 'SkipTest', 'DeprecatedTest', 'collector' + ] + + diff --git a/env/lib/python2.7/site-packages/nose/__init__.pyc b/env/lib/python2.7/site-packages/nose/__init__.pyc new file mode 100644 index 0000000..19435cb Binary files /dev/null and b/env/lib/python2.7/site-packages/nose/__init__.pyc differ diff --git a/env/lib/python2.7/site-packages/nose/__main__.py b/env/lib/python2.7/site-packages/nose/__main__.py new file mode 100644 index 0000000..b402d9d --- /dev/null +++ b/env/lib/python2.7/site-packages/nose/__main__.py @@ -0,0 +1,8 @@ +import sys + +from nose.core import run_exit + +if sys.argv[0].endswith('__main__.py'): + sys.argv[0] = '%s -m nose' % sys.executable + +run_exit() diff --git a/env/lib/python2.7/site-packages/nose/__main__.pyc b/env/lib/python2.7/site-packages/nose/__main__.pyc new file mode 100644 index 0000000..9f3a22e Binary files /dev/null and b/env/lib/python2.7/site-packages/nose/__main__.pyc differ diff --git a/env/lib/python2.7/site-packages/nose/case.py b/env/lib/python2.7/site-packages/nose/case.py new file mode 100644 index 0000000..cffa4ab --- /dev/null +++ b/env/lib/python2.7/site-packages/nose/case.py @@ -0,0 +1,397 @@ +"""nose unittest.TestCase subclasses. It is not necessary to subclass these +classes when writing tests; they are used internally by nose.loader.TestLoader +to create test cases from test functions and methods in test classes. +""" +import logging +import sys +import unittest +from inspect import isfunction +from nose.config import Config +from nose.failure import Failure # for backwards compatibility +from nose.util import resolve_name, test_address, try_run + +log = logging.getLogger(__name__) + + +__all__ = ['Test'] + + +class Test(unittest.TestCase): + """The universal test case wrapper. + + When a plugin sees a test, it will always see an instance of this + class. To access the actual test case that will be run, access the + test property of the nose.case.Test instance. + """ + __test__ = False # do not collect + def __init__(self, test, config=None, resultProxy=None): + # sanity check + if not callable(test): + raise TypeError("nose.case.Test called with argument %r that " + "is not callable. A callable is required." + % test) + self.test = test + if config is None: + config = Config() + self.config = config + self.tbinfo = None + self.capturedOutput = None + self.resultProxy = resultProxy + self.plugins = config.plugins + self.passed = None + unittest.TestCase.__init__(self) + + def __call__(self, *arg, **kwarg): + return self.run(*arg, **kwarg) + + def __str__(self): + name = self.plugins.testName(self) + if name is not None: + return name + return str(self.test) + + def __repr__(self): + return "Test(%r)" % self.test + + def afterTest(self, result): + """Called after test is complete (after result.stopTest) + """ + try: + afterTest = result.afterTest + except AttributeError: + pass + else: + afterTest(self.test) + + def beforeTest(self, result): + """Called before test is run (before result.startTest) + """ + try: + beforeTest = result.beforeTest + except AttributeError: + pass + else: + beforeTest(self.test) + + def exc_info(self): + """Extract exception info. + """ + exc, exv, tb = sys.exc_info() + return (exc, exv, tb) + + def id(self): + """Get a short(er) description of the test + """ + return self.test.id() + + def address(self): + """Return a round-trip name for this test, a name that can be + fed back as input to loadTestByName and (assuming the same + plugin configuration) result in the loading of this test. + """ + if hasattr(self.test, 'address'): + return self.test.address() + else: + # not a nose case + return test_address(self.test) + + def _context(self): + try: + return self.test.context + except AttributeError: + pass + try: + return self.test.__class__ + except AttributeError: + pass + try: + return resolve_name(self.test.__module__) + except AttributeError: + pass + return None + context = property(_context, None, None, + """Get the context object of this test (if any).""") + + def run(self, result): + """Modified run for the test wrapper. + + From here we don't call result.startTest or stopTest or + addSuccess. The wrapper calls addError/addFailure only if its + own setup or teardown fails, or running the wrapped test fails + (eg, if the wrapped "test" is not callable). + + Two additional methods are called, beforeTest and + afterTest. These give plugins a chance to modify the wrapped + test before it is called and do cleanup after it is + called. They are called unconditionally. + """ + if self.resultProxy: + result = self.resultProxy(result, self) + try: + try: + self.beforeTest(result) + self.runTest(result) + except KeyboardInterrupt: + raise + except: + err = sys.exc_info() + result.addError(self, err) + finally: + self.afterTest(result) + + def runTest(self, result): + """Run the test. Plugins may alter the test by returning a + value from prepareTestCase. The value must be callable and + must accept one argument, the result instance. + """ + test = self.test + plug_test = self.config.plugins.prepareTestCase(self) + if plug_test is not None: + test = plug_test + test(result) + + def shortDescription(self): + desc = self.plugins.describeTest(self) + if desc is not None: + return desc + # work around bug in unittest.TestCase.shortDescription + # with multiline docstrings. + test = self.test + try: + test._testMethodDoc = test._testMethodDoc.strip()# 2.5 + except AttributeError: + try: + # 2.4 and earlier + test._TestCase__testMethodDoc = \ + test._TestCase__testMethodDoc.strip() + except AttributeError: + pass + # 2.7 compat: shortDescription() always returns something + # which is a change from 2.6 and below, and breaks the + # testName plugin call. + try: + desc = self.test.shortDescription() + except Exception: + # this is probably caused by a problem in test.__str__() and is + # only triggered by python 3.1's unittest! + pass + try: + if desc == str(self.test): + return + except Exception: + # If str() triggers an exception then ignore it. + # see issue 422 + pass + return desc + + +class TestBase(unittest.TestCase): + """Common functionality for FunctionTestCase and MethodTestCase. + """ + __test__ = False # do not collect + + def id(self): + return str(self) + + def runTest(self): + self.test(*self.arg) + + def shortDescription(self): + if hasattr(self.test, 'description'): + return self.test.description + func, arg = self._descriptors() + doc = getattr(func, '__doc__', None) + if not doc: + doc = str(self) + return doc.strip().split("\n")[0].strip() + + +class FunctionTestCase(TestBase): + """TestCase wrapper for test functions. + + Don't use this class directly; it is used internally in nose to + create test cases for test functions. + """ + __test__ = False # do not collect + + def __init__(self, test, setUp=None, tearDown=None, arg=tuple(), + descriptor=None): + """Initialize the MethodTestCase. + + Required argument: + + * test -- the test function to call. + + Optional arguments: + + * setUp -- function to run at setup. + + * tearDown -- function to run at teardown. + + * arg -- arguments to pass to the test function. This is to support + generator functions that yield arguments. + + * descriptor -- the function, other than the test, that should be used + to construct the test name. This is to support generator functions. + """ + + self.test = test + self.setUpFunc = setUp + self.tearDownFunc = tearDown + self.arg = arg + self.descriptor = descriptor + TestBase.__init__(self) + + def address(self): + """Return a round-trip name for this test, a name that can be + fed back as input to loadTestByName and (assuming the same + plugin configuration) result in the loading of this test. + """ + if self.descriptor is not None: + return test_address(self.descriptor) + else: + return test_address(self.test) + + def _context(self): + return resolve_name(self.test.__module__) + context = property(_context, None, None, + """Get context (module) of this test""") + + def setUp(self): + """Run any setup function attached to the test function + """ + if self.setUpFunc: + self.setUpFunc() + else: + names = ('setup', 'setUp', 'setUpFunc') + try_run(self.test, names) + + def tearDown(self): + """Run any teardown function attached to the test function + """ + if self.tearDownFunc: + self.tearDownFunc() + else: + names = ('teardown', 'tearDown', 'tearDownFunc') + try_run(self.test, names) + + def __str__(self): + func, arg = self._descriptors() + if hasattr(func, 'compat_func_name'): + name = func.compat_func_name + else: + name = func.__name__ + name = "%s.%s" % (func.__module__, name) + if arg: + name = "%s%s" % (name, arg) + # FIXME need to include the full dir path to disambiguate + # in cases where test module of the same name was seen in + # another directory (old fromDirectory) + return name + __repr__ = __str__ + + def _descriptors(self): + """Get the descriptors of the test function: the function and + arguments that will be used to construct the test name. In + most cases, this is the function itself and no arguments. For + tests generated by generator functions, the original + (generator) function and args passed to the generated function + are returned. + """ + if self.descriptor: + return self.descriptor, self.arg + else: + return self.test, self.arg + + +class MethodTestCase(TestBase): + """Test case wrapper for test methods. + + Don't use this class directly; it is used internally in nose to + create test cases for test methods. + """ + __test__ = False # do not collect + + def __init__(self, method, test=None, arg=tuple(), descriptor=None): + """Initialize the MethodTestCase. + + Required argument: + + * method -- the method to call, may be bound or unbound. In either + case, a new instance of the method's class will be instantiated to + make the call. Note: In Python 3.x, if using an unbound method, you + must wrap it using pyversion.unbound_method. + + Optional arguments: + + * test -- the test function to call. If this is passed, it will be + called instead of getting a new bound method of the same name as the + desired method from the test instance. This is to support generator + methods that yield inline functions. + + * arg -- arguments to pass to the test function. This is to support + generator methods that yield arguments. + + * descriptor -- the function, other than the test, that should be used + to construct the test name. This is to support generator methods. + """ + self.method = method + self.test = test + self.arg = arg + self.descriptor = descriptor + if isfunction(method): + raise ValueError("Unbound methods must be wrapped using pyversion.unbound_method before passing to MethodTestCase") + self.cls = method.im_class + self.inst = self.cls() + if self.test is None: + method_name = self.method.__name__ + self.test = getattr(self.inst, method_name) + TestBase.__init__(self) + + def __str__(self): + func, arg = self._descriptors() + if hasattr(func, 'compat_func_name'): + name = func.compat_func_name + else: + name = func.__name__ + name = "%s.%s.%s" % (self.cls.__module__, + self.cls.__name__, + name) + if arg: + name = "%s%s" % (name, arg) + return name + __repr__ = __str__ + + def address(self): + """Return a round-trip name for this test, a name that can be + fed back as input to loadTestByName and (assuming the same + plugin configuration) result in the loading of this test. + """ + if self.descriptor is not None: + return test_address(self.descriptor) + else: + return test_address(self.method) + + def _context(self): + return self.cls + context = property(_context, None, None, + """Get context (class) of this test""") + + def setUp(self): + try_run(self.inst, ('setup', 'setUp')) + + def tearDown(self): + try_run(self.inst, ('teardown', 'tearDown')) + + def _descriptors(self): + """Get the descriptors of the test method: the method and + arguments that will be used to construct the test name. In + most cases, this is the method itself and no arguments. For + tests generated by generator methods, the original + (generator) method and args passed to the generated method + or function are returned. + """ + if self.descriptor: + return self.descriptor, self.arg + else: + return self.method, self.arg diff --git a/env/lib/python2.7/site-packages/nose/case.pyc b/env/lib/python2.7/site-packages/nose/case.pyc new file mode 100644 index 0000000..5a3ab45 Binary files /dev/null and b/env/lib/python2.7/site-packages/nose/case.pyc differ diff --git a/env/lib/python2.7/site-packages/nose/commands.py b/env/lib/python2.7/site-packages/nose/commands.py new file mode 100644 index 0000000..ef0e9ca --- /dev/null +++ b/env/lib/python2.7/site-packages/nose/commands.py @@ -0,0 +1,172 @@ +""" +nosetests setuptools command +---------------------------- + +The easiest way to run tests with nose is to use the `nosetests` setuptools +command:: + + python setup.py nosetests + +This command has one *major* benefit over the standard `test` command: *all +nose plugins are supported*. + +To configure the `nosetests` command, add a [nosetests] section to your +setup.cfg. The [nosetests] section can contain any command line arguments that +nosetests supports. The differences between issuing an option on the command +line and adding it to setup.cfg are: + +* In setup.cfg, the -- prefix must be excluded +* In setup.cfg, command line flags that take no arguments must be given an + argument flag (1, T or TRUE for active, 0, F or FALSE for inactive) + +Here's an example [nosetests] setup.cfg section:: + + [nosetests] + verbosity=1 + detailed-errors=1 + with-coverage=1 + cover-package=nose + debug=nose.loader + pdb=1 + pdb-failures=1 + +If you commonly run nosetests with a large number of options, using +the nosetests setuptools command and configuring with setup.cfg can +make running your tests much less tedious. (Note that the same options +and format supported in setup.cfg are supported in all other config +files, and the nosetests script will also load config files.) + +Another reason to run tests with the command is that the command will +install packages listed in your `tests_require`, as well as doing a +complete build of your package before running tests. For packages with +dependencies or that build C extensions, using the setuptools command +can be more convenient than building by hand and running the nosetests +script. + +Bootstrapping +------------- + +If you are distributing your project and want users to be able to run tests +without having to install nose themselves, add nose to the setup_requires +section of your setup():: + + setup( + # ... + setup_requires=['nose>=1.0'] + ) + +This will direct setuptools to download and activate nose during the setup +process, making the ``nosetests`` command available. + +""" +try: + from setuptools import Command +except ImportError: + Command = nosetests = None +else: + from nose.config import Config, option_blacklist, user_config_files, \ + flag, _bool + from nose.core import TestProgram + from nose.plugins import DefaultPluginManager + + + def get_user_options(parser): + """convert a optparse option list into a distutils option tuple list""" + opt_list = [] + for opt in parser.option_list: + if opt._long_opts[0][2:] in option_blacklist: + continue + long_name = opt._long_opts[0][2:] + if opt.action not in ('store_true', 'store_false'): + long_name = long_name + "=" + short_name = None + if opt._short_opts: + short_name = opt._short_opts[0][1:] + opt_list.append((long_name, short_name, opt.help or "")) + return opt_list + + + class nosetests(Command): + description = "Run unit tests using nosetests" + __config = Config(files=user_config_files(), + plugins=DefaultPluginManager()) + __parser = __config.getParser() + user_options = get_user_options(__parser) + + def initialize_options(self): + """create the member variables, but change hyphens to + underscores + """ + + self.option_to_cmds = {} + for opt in self.__parser.option_list: + cmd_name = opt._long_opts[0][2:] + option_name = cmd_name.replace('-', '_') + self.option_to_cmds[option_name] = cmd_name + setattr(self, option_name, None) + self.attr = None + + def finalize_options(self): + """nothing to do here""" + pass + + def run(self): + """ensure tests are capable of being run, then + run nose.main with a reconstructed argument list""" + if getattr(self.distribution, 'use_2to3', False): + # If we run 2to3 we can not do this inplace: + + # Ensure metadata is up-to-date + build_py = self.get_finalized_command('build_py') + build_py.inplace = 0 + build_py.run() + bpy_cmd = self.get_finalized_command("build_py") + build_path = bpy_cmd.build_lib + + # Build extensions + egg_info = self.get_finalized_command('egg_info') + egg_info.egg_base = build_path + egg_info.run() + + build_ext = self.get_finalized_command('build_ext') + build_ext.inplace = 0 + build_ext.run() + else: + self.run_command('egg_info') + + # Build extensions in-place + build_ext = self.get_finalized_command('build_ext') + build_ext.inplace = 1 + build_ext.run() + + if self.distribution.install_requires: + self.distribution.fetch_build_eggs( + self.distribution.install_requires) + if self.distribution.tests_require: + self.distribution.fetch_build_eggs( + self.distribution.tests_require) + + ei_cmd = self.get_finalized_command("egg_info") + argv = ['nosetests', '--where', ei_cmd.egg_base] + for (option_name, cmd_name) in self.option_to_cmds.items(): + if option_name in option_blacklist: + continue + value = getattr(self, option_name) + if value is not None: + argv.extend( + self.cfgToArg(option_name.replace('_', '-'), value)) + TestProgram(argv=argv, config=self.__config) + + def cfgToArg(self, optname, value): + argv = [] + long_optname = '--' + optname + opt = self.__parser.get_option(long_optname) + if opt.action in ('store_true', 'store_false'): + if not flag(value): + raise ValueError("Invalid value '%s' for '%s'" % ( + value, optname)) + if _bool(value): + argv.append(long_optname) + else: + argv.extend([long_optname, value]) + return argv diff --git a/env/lib/python2.7/site-packages/nose/commands.pyc b/env/lib/python2.7/site-packages/nose/commands.pyc new file mode 100644 index 0000000..0cb84bb Binary files /dev/null and b/env/lib/python2.7/site-packages/nose/commands.pyc differ diff --git a/env/lib/python2.7/site-packages/nose/config.py b/env/lib/python2.7/site-packages/nose/config.py new file mode 100644 index 0000000..125eb55 --- /dev/null +++ b/env/lib/python2.7/site-packages/nose/config.py @@ -0,0 +1,661 @@ +import logging +import optparse +import os +import re +import sys +import ConfigParser +from optparse import OptionParser +from nose.util import absdir, tolist +from nose.plugins.manager import NoPlugins +from warnings import warn, filterwarnings + +log = logging.getLogger(__name__) + +# not allowed in config files +option_blacklist = ['help', 'verbose'] + +config_files = [ + # Linux users will prefer this + "~/.noserc", + # Windows users will prefer this + "~/nose.cfg" + ] + +# plaforms on which the exe check defaults to off +# Windows and IronPython +exe_allowed_platforms = ('win32', 'cli') + +filterwarnings("always", category=DeprecationWarning, + module=r'(.*\.)?nose\.config') + +class NoSuchOptionError(Exception): + def __init__(self, name): + Exception.__init__(self, name) + self.name = name + + +class ConfigError(Exception): + pass + + +class ConfiguredDefaultsOptionParser(object): + """ + Handler for options from commandline and config files. + """ + def __init__(self, parser, config_section, error=None, file_error=None): + self._parser = parser + self._config_section = config_section + if error is None: + error = self._parser.error + self._error = error + if file_error is None: + file_error = lambda msg, **kw: error(msg) + self._file_error = file_error + + def _configTuples(self, cfg, filename): + config = [] + if self._config_section in cfg.sections(): + for name, value in cfg.items(self._config_section): + config.append((name, value, filename)) + return config + + def _readFromFilenames(self, filenames): + config = [] + for filename in filenames: + cfg = ConfigParser.RawConfigParser() + try: + cfg.read(filename) + except ConfigParser.Error, exc: + raise ConfigError("Error reading config file %r: %s" % + (filename, str(exc))) + config.extend(self._configTuples(cfg, filename)) + return config + + def _readFromFileObject(self, fh): + cfg = ConfigParser.RawConfigParser() + try: + filename = fh.name + except AttributeError: + filename = '' + try: + cfg.readfp(fh) + except ConfigParser.Error, exc: + raise ConfigError("Error reading config file %r: %s" % + (filename, str(exc))) + return self._configTuples(cfg, filename) + + def _readConfiguration(self, config_files): + try: + config_files.readline + except AttributeError: + filename_or_filenames = config_files + if isinstance(filename_or_filenames, basestring): + filenames = [filename_or_filenames] + else: + filenames = filename_or_filenames + config = self._readFromFilenames(filenames) + else: + fh = config_files + config = self._readFromFileObject(fh) + return config + + def _processConfigValue(self, name, value, values, parser): + opt_str = '--' + name + option = parser.get_option(opt_str) + if option is None: + raise NoSuchOptionError(name) + else: + option.process(opt_str, value, values, parser) + + def _applyConfigurationToValues(self, parser, config, values): + for name, value, filename in config: + if name in option_blacklist: + continue + try: + self._processConfigValue(name, value, values, parser) + except NoSuchOptionError, exc: + self._file_error( + "Error reading config file %r: " + "no such option %r" % (filename, exc.name), + name=name, filename=filename) + except optparse.OptionValueError, exc: + msg = str(exc).replace('--' + name, repr(name), 1) + self._file_error("Error reading config file %r: " + "%s" % (filename, msg), + name=name, filename=filename) + + def parseArgsAndConfigFiles(self, args, config_files): + values = self._parser.get_default_values() + try: + config = self._readConfiguration(config_files) + except ConfigError, exc: + self._error(str(exc)) + else: + try: + self._applyConfigurationToValues(self._parser, config, values) + except ConfigError, exc: + self._error(str(exc)) + return self._parser.parse_args(args, values) + + +class Config(object): + """nose configuration. + + Instances of Config are used throughout nose to configure + behavior, including plugin lists. Here are the default values for + all config keys:: + + self.env = env = kw.pop('env', {}) + self.args = () + self.testMatch = re.compile(r'(?:^|[\\b_\\.%s-])[Tt]est' % os.sep) + self.addPaths = not env.get('NOSE_NOPATH', False) + self.configSection = 'nosetests' + self.debug = env.get('NOSE_DEBUG') + self.debugLog = env.get('NOSE_DEBUG_LOG') + self.exclude = None + self.getTestCaseNamesCompat = False + self.includeExe = env.get('NOSE_INCLUDE_EXE', + sys.platform in exe_allowed_platforms) + self.ignoreFiles = (re.compile(r'^\.'), + re.compile(r'^_'), + re.compile(r'^setup\.py$') + ) + self.include = None + self.loggingConfig = None + self.logStream = sys.stderr + self.options = NoOptions() + self.parser = None + self.plugins = NoPlugins() + self.srcDirs = ('lib', 'src') + self.runOnInit = True + self.stopOnError = env.get('NOSE_STOP', False) + self.stream = sys.stderr + self.testNames = () + self.verbosity = int(env.get('NOSE_VERBOSE', 1)) + self.where = () + self.py3where = () + self.workingDir = None + """ + + def __init__(self, **kw): + self.env = env = kw.pop('env', {}) + self.args = () + self.testMatchPat = env.get('NOSE_TESTMATCH', + r'(?:^|[\b_\.%s-])[Tt]est' % os.sep) + self.testMatch = re.compile(self.testMatchPat) + self.addPaths = not env.get('NOSE_NOPATH', False) + self.configSection = 'nosetests' + self.debug = env.get('NOSE_DEBUG') + self.debugLog = env.get('NOSE_DEBUG_LOG') + self.exclude = None + self.getTestCaseNamesCompat = False + self.includeExe = env.get('NOSE_INCLUDE_EXE', + sys.platform in exe_allowed_platforms) + self.ignoreFilesDefaultStrings = [r'^\.', + r'^_', + r'^setup\.py$', + ] + self.ignoreFiles = map(re.compile, self.ignoreFilesDefaultStrings) + self.include = None + self.loggingConfig = None + self.logStream = sys.stderr + self.options = NoOptions() + self.parser = None + self.plugins = NoPlugins() + self.srcDirs = ('lib', 'src') + self.runOnInit = True + self.stopOnError = env.get('NOSE_STOP', False) + self.stream = sys.stderr + self.testNames = [] + self.verbosity = int(env.get('NOSE_VERBOSE', 1)) + self.where = () + self.py3where = () + self.workingDir = os.getcwd() + self.traverseNamespace = False + self.firstPackageWins = False + self.parserClass = OptionParser + self.worker = False + + self._default = self.__dict__.copy() + self.update(kw) + self._orig = self.__dict__.copy() + + def __getstate__(self): + state = self.__dict__.copy() + del state['stream'] + del state['_orig'] + del state['_default'] + del state['env'] + del state['logStream'] + # FIXME remove plugins, have only plugin manager class + state['plugins'] = self.plugins.__class__ + return state + + def __setstate__(self, state): + plugincls = state.pop('plugins') + self.update(state) + self.worker = True + # FIXME won't work for static plugin lists + self.plugins = plugincls() + self.plugins.loadPlugins() + # needed so .can_configure gets set appropriately + dummy_parser = self.parserClass() + self.plugins.addOptions(dummy_parser, {}) + self.plugins.configure(self.options, self) + + def __repr__(self): + d = self.__dict__.copy() + # don't expose env, could include sensitive info + d['env'] = {} + keys = [ k for k in d.keys() + if not k.startswith('_') ] + keys.sort() + return "Config(%s)" % ', '.join([ '%s=%r' % (k, d[k]) + for k in keys ]) + __str__ = __repr__ + + def _parseArgs(self, argv, cfg_files): + def warn_sometimes(msg, name=None, filename=None): + if (hasattr(self.plugins, 'excludedOption') and + self.plugins.excludedOption(name)): + msg = ("Option %r in config file %r ignored: " + "excluded by runtime environment" % + (name, filename)) + warn(msg, RuntimeWarning) + else: + raise ConfigError(msg) + parser = ConfiguredDefaultsOptionParser( + self.getParser(), self.configSection, file_error=warn_sometimes) + return parser.parseArgsAndConfigFiles(argv[1:], cfg_files) + + def configure(self, argv=None, doc=None): + """Configure the nose running environment. Execute configure before + collecting tests with nose.TestCollector to enable output capture and + other features. + """ + env = self.env + if argv is None: + argv = sys.argv + + cfg_files = getattr(self, 'files', []) + options, args = self._parseArgs(argv, cfg_files) + # If -c --config has been specified on command line, + # load those config files and reparse + if getattr(options, 'files', []): + options, args = self._parseArgs(argv, options.files) + + self.options = options + if args: + self.testNames = args + if options.testNames is not None: + self.testNames.extend(tolist(options.testNames)) + + if options.py3where is not None: + if sys.version_info >= (3,): + options.where = options.py3where + + # `where` is an append action, so it can't have a default value + # in the parser, or that default will always be in the list + if not options.where: + options.where = env.get('NOSE_WHERE', None) + + # include and exclude also + if not options.ignoreFiles: + options.ignoreFiles = env.get('NOSE_IGNORE_FILES', []) + if not options.include: + options.include = env.get('NOSE_INCLUDE', []) + if not options.exclude: + options.exclude = env.get('NOSE_EXCLUDE', []) + + self.addPaths = options.addPaths + self.stopOnError = options.stopOnError + self.verbosity = options.verbosity + self.includeExe = options.includeExe + self.traverseNamespace = options.traverseNamespace + self.debug = options.debug + self.debugLog = options.debugLog + self.loggingConfig = options.loggingConfig + self.firstPackageWins = options.firstPackageWins + self.configureLogging() + + if not options.byteCompile: + sys.dont_write_bytecode = True + + if options.where is not None: + self.configureWhere(options.where) + + if options.testMatch: + self.testMatch = re.compile(options.testMatch) + + if options.ignoreFiles: + self.ignoreFiles = map(re.compile, tolist(options.ignoreFiles)) + log.info("Ignoring files matching %s", options.ignoreFiles) + else: + log.info("Ignoring files matching %s", self.ignoreFilesDefaultStrings) + + if options.include: + self.include = map(re.compile, tolist(options.include)) + log.info("Including tests matching %s", options.include) + + if options.exclude: + self.exclude = map(re.compile, tolist(options.exclude)) + log.info("Excluding tests matching %s", options.exclude) + + # When listing plugins we don't want to run them + if not options.showPlugins: + self.plugins.configure(options, self) + self.plugins.begin() + + def configureLogging(self): + """Configure logging for nose, or optionally other packages. Any logger + name may be set with the debug option, and that logger will be set to + debug level and be assigned the same handler as the nose loggers, unless + it already has a handler. + """ + if self.loggingConfig: + from logging.config import fileConfig + fileConfig(self.loggingConfig) + return + + format = logging.Formatter('%(name)s: %(levelname)s: %(message)s') + if self.debugLog: + handler = logging.FileHandler(self.debugLog) + else: + handler = logging.StreamHandler(self.logStream) + handler.setFormatter(format) + + logger = logging.getLogger('nose') + logger.propagate = 0 + + # only add our default handler if there isn't already one there + # this avoids annoying duplicate log messages. + found = False + if self.debugLog: + debugLogAbsPath = os.path.abspath(self.debugLog) + for h in logger.handlers: + if type(h) == logging.FileHandler and \ + h.baseFilename == debugLogAbsPath: + found = True + else: + for h in logger.handlers: + if type(h) == logging.StreamHandler and \ + h.stream == self.logStream: + found = True + if not found: + logger.addHandler(handler) + + # default level + lvl = logging.WARNING + if self.verbosity >= 5: + lvl = 0 + elif self.verbosity >= 4: + lvl = logging.DEBUG + elif self.verbosity >= 3: + lvl = logging.INFO + logger.setLevel(lvl) + + # individual overrides + if self.debug: + # no blanks + debug_loggers = [ name for name in self.debug.split(',') + if name ] + for logger_name in debug_loggers: + l = logging.getLogger(logger_name) + l.setLevel(logging.DEBUG) + if not l.handlers and not logger_name.startswith('nose'): + l.addHandler(handler) + + def configureWhere(self, where): + """Configure the working directory or directories for the test run. + """ + from nose.importer import add_path + self.workingDir = None + where = tolist(where) + warned = False + for path in where: + if not self.workingDir: + abs_path = absdir(path) + if abs_path is None: + raise ValueError("Working directory '%s' not found, or " + "not a directory" % path) + log.info("Set working dir to %s", abs_path) + self.workingDir = abs_path + if self.addPaths and \ + os.path.exists(os.path.join(abs_path, '__init__.py')): + log.info("Working directory %s is a package; " + "adding to sys.path" % abs_path) + add_path(abs_path) + continue + if not warned: + warn("Use of multiple -w arguments is deprecated and " + "support may be removed in a future release. You can " + "get the same behavior by passing directories without " + "the -w argument on the command line, or by using the " + "--tests argument in a configuration file.", + DeprecationWarning) + warned = True + self.testNames.append(path) + + def default(self): + """Reset all config values to defaults. + """ + self.__dict__.update(self._default) + + def getParser(self, doc=None): + """Get the command line option parser. + """ + if self.parser: + return self.parser + env = self.env + parser = self.parserClass(doc) + parser.add_option( + "-V","--version", action="store_true", + dest="version", default=False, + help="Output nose version and exit") + parser.add_option( + "-p", "--plugins", action="store_true", + dest="showPlugins", default=False, + help="Output list of available plugins and exit. Combine with " + "higher verbosity for greater detail") + parser.add_option( + "-v", "--verbose", + action="count", dest="verbosity", + default=self.verbosity, + help="Be more verbose. [NOSE_VERBOSE]") + parser.add_option( + "--verbosity", action="store", dest="verbosity", + metavar='VERBOSITY', + type="int", help="Set verbosity; --verbosity=2 is " + "the same as -v") + parser.add_option( + "-q", "--quiet", action="store_const", const=0, dest="verbosity", + help="Be less verbose") + parser.add_option( + "-c", "--config", action="append", dest="files", + metavar="FILES", + help="Load configuration from config file(s). May be specified " + "multiple times; in that case, all config files will be " + "loaded and combined") + parser.add_option( + "-w", "--where", action="append", dest="where", + metavar="WHERE", + help="Look for tests in this directory. " + "May be specified multiple times. The first directory passed " + "will be used as the working directory, in place of the current " + "working directory, which is the default. Others will be added " + "to the list of tests to execute. [NOSE_WHERE]" + ) + parser.add_option( + "--py3where", action="append", dest="py3where", + metavar="PY3WHERE", + help="Look for tests in this directory under Python 3.x. " + "Functions the same as 'where', but only applies if running under " + "Python 3.x or above. Note that, if present under 3.x, this " + "option completely replaces any directories specified with " + "'where', so the 'where' option becomes ineffective. " + "[NOSE_PY3WHERE]" + ) + parser.add_option( + "-m", "--match", "--testmatch", action="store", + dest="testMatch", metavar="REGEX", + help="Files, directories, function names, and class names " + "that match this regular expression are considered tests. " + "Default: %s [NOSE_TESTMATCH]" % self.testMatchPat, + default=self.testMatchPat) + parser.add_option( + "--tests", action="store", dest="testNames", default=None, + metavar='NAMES', + help="Run these tests (comma-separated list). This argument is " + "useful mainly from configuration files; on the command line, " + "just pass the tests to run as additional arguments with no " + "switch.") + parser.add_option( + "-l", "--debug", action="store", + dest="debug", default=self.debug, + help="Activate debug logging for one or more systems. " + "Available debug loggers: nose, nose.importer, " + "nose.inspector, nose.plugins, nose.result and " + "nose.selector. Separate multiple names with a comma.") + parser.add_option( + "--debug-log", dest="debugLog", action="store", + default=self.debugLog, metavar="FILE", + help="Log debug messages to this file " + "(default: sys.stderr)") + parser.add_option( + "--logging-config", "--log-config", + dest="loggingConfig", action="store", + default=self.loggingConfig, metavar="FILE", + help="Load logging config from this file -- bypasses all other" + " logging config settings.") + parser.add_option( + "-I", "--ignore-files", action="append", dest="ignoreFiles", + metavar="REGEX", + help="Completely ignore any file that matches this regular " + "expression. Takes precedence over any other settings or " + "plugins. " + "Specifying this option will replace the default setting. " + "Specify this option multiple times " + "to add more regular expressions [NOSE_IGNORE_FILES]") + parser.add_option( + "-e", "--exclude", action="append", dest="exclude", + metavar="REGEX", + help="Don't run tests that match regular " + "expression [NOSE_EXCLUDE]") + parser.add_option( + "-i", "--include", action="append", dest="include", + metavar="REGEX", + help="This regular expression will be applied to files, " + "directories, function names, and class names for a chance " + "to include additional tests that do not match TESTMATCH. " + "Specify this option multiple times " + "to add more regular expressions [NOSE_INCLUDE]") + parser.add_option( + "-x", "--stop", action="store_true", dest="stopOnError", + default=self.stopOnError, + help="Stop running tests after the first error or failure") + parser.add_option( + "-P", "--no-path-adjustment", action="store_false", + dest="addPaths", + default=self.addPaths, + help="Don't make any changes to sys.path when " + "loading tests [NOSE_NOPATH]") + parser.add_option( + "--exe", action="store_true", dest="includeExe", + default=self.includeExe, + help="Look for tests in python modules that are " + "executable. Normal behavior is to exclude executable " + "modules, since they may not be import-safe " + "[NOSE_INCLUDE_EXE]") + parser.add_option( + "--noexe", action="store_false", dest="includeExe", + help="DO NOT look for tests in python modules that are " + "executable. (The default on the windows platform is to " + "do so.)") + parser.add_option( + "--traverse-namespace", action="store_true", + default=self.traverseNamespace, dest="traverseNamespace", + help="Traverse through all path entries of a namespace package") + parser.add_option( + "--first-package-wins", "--first-pkg-wins", "--1st-pkg-wins", + action="store_true", default=False, dest="firstPackageWins", + help="nose's importer will normally evict a package from sys." + "modules if it sees a package with the same name in a different " + "location. Set this option to disable that behavior.") + parser.add_option( + "--no-byte-compile", + action="store_false", default=True, dest="byteCompile", + help="Prevent nose from byte-compiling the source into .pyc files " + "while nose is scanning for and running tests.") + + self.plugins.loadPlugins() + self.pluginOpts(parser) + + self.parser = parser + return parser + + def help(self, doc=None): + """Return the generated help message + """ + return self.getParser(doc).format_help() + + def pluginOpts(self, parser): + self.plugins.addOptions(parser, self.env) + + def reset(self): + self.__dict__.update(self._orig) + + def todict(self): + return self.__dict__.copy() + + def update(self, d): + self.__dict__.update(d) + + +class NoOptions(object): + """Options container that returns None for all options. + """ + def __getstate__(self): + return {} + + def __setstate__(self, state): + pass + + def __getnewargs__(self): + return () + + def __nonzero__(self): + return False + + +def user_config_files(): + """Return path to any existing user config files + """ + return filter(os.path.exists, + map(os.path.expanduser, config_files)) + + +def all_config_files(): + """Return path to any existing user config files, plus any setup.cfg + in the current working directory. + """ + user = user_config_files() + if os.path.exists('setup.cfg'): + return user + ['setup.cfg'] + return user + + +# used when parsing config files +def flag(val): + """Does the value look like an on/off flag?""" + if val == 1: + return True + elif val == 0: + return False + val = str(val) + if len(val) > 5: + return False + return val.upper() in ('1', '0', 'F', 'T', 'TRUE', 'FALSE', 'ON', 'OFF') + + +def _bool(val): + return str(val).upper() in ('1', 'T', 'TRUE', 'ON') diff --git a/env/lib/python2.7/site-packages/nose/config.pyc b/env/lib/python2.7/site-packages/nose/config.pyc new file mode 100644 index 0000000..dbfd034 Binary files /dev/null and b/env/lib/python2.7/site-packages/nose/config.pyc differ diff --git a/env/lib/python2.7/site-packages/nose/core.py b/env/lib/python2.7/site-packages/nose/core.py new file mode 100644 index 0000000..49e7939 --- /dev/null +++ b/env/lib/python2.7/site-packages/nose/core.py @@ -0,0 +1,341 @@ +"""Implements nose test program and collector. +""" +from __future__ import generators + +import logging +import os +import sys +import time +import unittest + +from nose.config import Config, all_config_files +from nose.loader import defaultTestLoader +from nose.plugins.manager import PluginManager, DefaultPluginManager, \ + RestrictedPluginManager +from nose.result import TextTestResult +from nose.suite import FinalizingSuiteWrapper +from nose.util import isclass, tolist + + +log = logging.getLogger('nose.core') +compat_24 = sys.version_info >= (2, 4) + +__all__ = ['TestProgram', 'main', 'run', 'run_exit', 'runmodule', 'collector', + 'TextTestRunner'] + + +class TextTestRunner(unittest.TextTestRunner): + """Test runner that uses nose's TextTestResult to enable errorClasses, + as well as providing hooks for plugins to override or replace the test + output stream, results, and the test case itself. + """ + def __init__(self, stream=sys.stderr, descriptions=1, verbosity=1, + config=None): + if config is None: + config = Config() + self.config = config + unittest.TextTestRunner.__init__(self, stream, descriptions, verbosity) + + + def _makeResult(self): + return TextTestResult(self.stream, + self.descriptions, + self.verbosity, + self.config) + + def run(self, test): + """Overrides to provide plugin hooks and defer all output to + the test result class. + """ + wrapper = self.config.plugins.prepareTest(test) + if wrapper is not None: + test = wrapper + + # plugins can decorate or capture the output stream + wrapped = self.config.plugins.setOutputStream(self.stream) + if wrapped is not None: + self.stream = wrapped + + result = self._makeResult() + start = time.time() + try: + test(result) + except KeyboardInterrupt: + pass + stop = time.time() + result.printErrors() + result.printSummary(start, stop) + self.config.plugins.finalize(result) + return result + + +class TestProgram(unittest.TestProgram): + """Collect and run tests, returning success or failure. + + The arguments to TestProgram() are the same as to + :func:`main()` and :func:`run()`: + + * module: All tests are in this module (default: None) + * defaultTest: Tests to load (default: '.') + * argv: Command line arguments (default: None; sys.argv is read) + * testRunner: Test runner instance (default: None) + * testLoader: Test loader instance (default: None) + * env: Environment; ignored if config is provided (default: None; + os.environ is read) + * config: :class:`nose.config.Config` instance (default: None) + * suite: Suite or list of tests to run (default: None). Passing a + suite or lists of tests will bypass all test discovery and + loading. *ALSO NOTE* that if you pass a unittest.TestSuite + instance as the suite, context fixtures at the class, module and + package level will not be used, and many plugin hooks will not + be called. If you want normal nose behavior, either pass a list + of tests, or a fully-configured :class:`nose.suite.ContextSuite`. + * exit: Exit after running tests and printing report (default: True) + * plugins: List of plugins to use; ignored if config is provided + (default: load plugins with DefaultPluginManager) + * addplugins: List of **extra** plugins to use. Pass a list of plugin + instances in this argument to make custom plugins available while + still using the DefaultPluginManager. + """ + verbosity = 1 + + def __init__(self, module=None, defaultTest='.', argv=None, + testRunner=None, testLoader=None, env=None, config=None, + suite=None, exit=True, plugins=None, addplugins=None): + if env is None: + env = os.environ + if config is None: + config = self.makeConfig(env, plugins) + if addplugins: + config.plugins.addPlugins(extraplugins=addplugins) + self.config = config + self.suite = suite + self.exit = exit + extra_args = {} + version = sys.version_info[0:2] + if version >= (2,7) and version != (3,0): + extra_args['exit'] = exit + unittest.TestProgram.__init__( + self, module=module, defaultTest=defaultTest, + argv=argv, testRunner=testRunner, testLoader=testLoader, + **extra_args) + + def getAllConfigFiles(self, env=None): + env = env or {} + if env.get('NOSE_IGNORE_CONFIG_FILES', False): + return [] + else: + return all_config_files() + + def makeConfig(self, env, plugins=None): + """Load a Config, pre-filled with user config files if any are + found. + """ + cfg_files = self.getAllConfigFiles(env) + if plugins: + manager = PluginManager(plugins=plugins) + else: + manager = DefaultPluginManager() + return Config( + env=env, files=cfg_files, plugins=manager) + + def parseArgs(self, argv): + """Parse argv and env and configure running environment. + """ + self.config.configure(argv, doc=self.usage()) + log.debug("configured %s", self.config) + + # quick outs: version, plugins (optparse would have already + # caught and exited on help) + if self.config.options.version: + from nose import __version__ + sys.stdout = sys.__stdout__ + print "%s version %s" % (os.path.basename(sys.argv[0]), __version__) + sys.exit(0) + + if self.config.options.showPlugins: + self.showPlugins() + sys.exit(0) + + if self.testLoader is None: + self.testLoader = defaultTestLoader(config=self.config) + elif isclass(self.testLoader): + self.testLoader = self.testLoader(config=self.config) + plug_loader = self.config.plugins.prepareTestLoader(self.testLoader) + if plug_loader is not None: + self.testLoader = plug_loader + log.debug("test loader is %s", self.testLoader) + + # FIXME if self.module is a string, add it to self.testNames? not sure + + if self.config.testNames: + self.testNames = self.config.testNames + else: + self.testNames = tolist(self.defaultTest) + log.debug('defaultTest %s', self.defaultTest) + log.debug('Test names are %s', self.testNames) + if self.config.workingDir is not None: + os.chdir(self.config.workingDir) + self.createTests() + + def createTests(self): + """Create the tests to run. If a self.suite + is set, then that suite will be used. Otherwise, tests will be + loaded from the given test names (self.testNames) using the + test loader. + """ + log.debug("createTests called with %s", self.suite) + if self.suite is not None: + # We were given an explicit suite to run. Make sure it's + # loaded and wrapped correctly. + self.test = self.testLoader.suiteClass(self.suite) + else: + self.test = self.testLoader.loadTestsFromNames(self.testNames) + + def runTests(self): + """Run Tests. Returns true on success, false on failure, and sets + self.success to the same value. + """ + log.debug("runTests called") + if self.testRunner is None: + self.testRunner = TextTestRunner(stream=self.config.stream, + verbosity=self.config.verbosity, + config=self.config) + plug_runner = self.config.plugins.prepareTestRunner(self.testRunner) + if plug_runner is not None: + self.testRunner = plug_runner + result = self.testRunner.run(self.test) + self.success = result.wasSuccessful() + if self.exit: + sys.exit(not self.success) + return self.success + + def showPlugins(self): + """Print list of available plugins. + """ + import textwrap + + class DummyParser: + def __init__(self): + self.options = [] + def add_option(self, *arg, **kw): + self.options.append((arg, kw.pop('help', ''))) + + v = self.config.verbosity + self.config.plugins.sort() + for p in self.config.plugins: + print "Plugin %s" % p.name + if v >= 2: + print " score: %s" % p.score + print '\n'.join(textwrap.wrap(p.help().strip(), + initial_indent=' ', + subsequent_indent=' ')) + if v >= 3: + parser = DummyParser() + p.addOptions(parser) + if len(parser.options): + print + print " Options:" + for opts, help in parser.options: + print ' %s' % (', '.join(opts)) + if help: + print '\n'.join( + textwrap.wrap(help.strip(), + initial_indent=' ', + subsequent_indent=' ')) + print + + def usage(cls): + import nose + try: + ld = nose.__loader__ + text = ld.get_data(os.path.join( + os.path.dirname(__file__), 'usage.txt')) + except AttributeError: + f = open(os.path.join( + os.path.dirname(__file__), 'usage.txt'), 'r') + try: + text = f.read() + finally: + f.close() + # Ensure that we return str, not bytes. + if not isinstance(text, str): + text = text.decode('utf-8') + return text + usage = classmethod(usage) + +# backwards compatibility +run_exit = main = TestProgram + + +def run(*arg, **kw): + """Collect and run tests, returning success or failure. + + The arguments to `run()` are the same as to `main()`: + + * module: All tests are in this module (default: None) + * defaultTest: Tests to load (default: '.') + * argv: Command line arguments (default: None; sys.argv is read) + * testRunner: Test runner instance (default: None) + * testLoader: Test loader instance (default: None) + * env: Environment; ignored if config is provided (default: None; + os.environ is read) + * config: :class:`nose.config.Config` instance (default: None) + * suite: Suite or list of tests to run (default: None). Passing a + suite or lists of tests will bypass all test discovery and + loading. *ALSO NOTE* that if you pass a unittest.TestSuite + instance as the suite, context fixtures at the class, module and + package level will not be used, and many plugin hooks will not + be called. If you want normal nose behavior, either pass a list + of tests, or a fully-configured :class:`nose.suite.ContextSuite`. + * plugins: List of plugins to use; ignored if config is provided + (default: load plugins with DefaultPluginManager) + * addplugins: List of **extra** plugins to use. Pass a list of plugin + instances in this argument to make custom plugins available while + still using the DefaultPluginManager. + + With the exception that the ``exit`` argument is always set + to False. + """ + kw['exit'] = False + return TestProgram(*arg, **kw).success + + +def runmodule(name='__main__', **kw): + """Collect and run tests in a single module only. Defaults to running + tests in __main__. Additional arguments to TestProgram may be passed + as keyword arguments. + """ + main(defaultTest=name, **kw) + + +def collector(): + """TestSuite replacement entry point. Use anywhere you might use a + unittest.TestSuite. The collector will, by default, load options from + all config files and execute loader.loadTestsFromNames() on the + configured testNames, or '.' if no testNames are configured. + """ + # plugins that implement any of these methods are disabled, since + # we don't control the test runner and won't be able to run them + # finalize() is also not called, but plugins that use it aren't disabled, + # because capture needs it. + setuptools_incompat = ('report', 'prepareTest', + 'prepareTestLoader', 'prepareTestRunner', + 'setOutputStream') + + plugins = RestrictedPluginManager(exclude=setuptools_incompat) + conf = Config(files=all_config_files(), + plugins=plugins) + conf.configure(argv=['collector']) + loader = defaultTestLoader(conf) + + if conf.testNames: + suite = loader.loadTestsFromNames(conf.testNames) + else: + suite = loader.loadTestsFromNames(('.',)) + return FinalizingSuiteWrapper(suite, plugins.finalize) + + + +if __name__ == '__main__': + main() diff --git a/env/lib/python2.7/site-packages/nose/core.pyc b/env/lib/python2.7/site-packages/nose/core.pyc new file mode 100644 index 0000000..0489913 Binary files /dev/null and b/env/lib/python2.7/site-packages/nose/core.pyc differ diff --git a/env/lib/python2.7/site-packages/nose/exc.py b/env/lib/python2.7/site-packages/nose/exc.py new file mode 100644 index 0000000..8b780db --- /dev/null +++ b/env/lib/python2.7/site-packages/nose/exc.py @@ -0,0 +1,9 @@ +"""Exceptions for marking tests as skipped or deprecated. + +This module exists to provide backwards compatibility with previous +versions of nose where skipped and deprecated tests were core +functionality, rather than being provided by plugins. It may be +removed in a future release. +""" +from nose.plugins.skip import SkipTest +from nose.plugins.deprecated import DeprecatedTest diff --git a/env/lib/python2.7/site-packages/nose/exc.pyc b/env/lib/python2.7/site-packages/nose/exc.pyc new file mode 100644 index 0000000..bbf7d09 Binary files /dev/null and b/env/lib/python2.7/site-packages/nose/exc.pyc differ diff --git a/env/lib/python2.7/site-packages/nose/ext/__init__.py b/env/lib/python2.7/site-packages/nose/ext/__init__.py new file mode 100644 index 0000000..5fd1516 --- /dev/null +++ b/env/lib/python2.7/site-packages/nose/ext/__init__.py @@ -0,0 +1,3 @@ +""" +External or vendor files +""" diff --git a/env/lib/python2.7/site-packages/nose/ext/__init__.pyc b/env/lib/python2.7/site-packages/nose/ext/__init__.pyc new file mode 100644 index 0000000..c97b717 Binary files /dev/null and b/env/lib/python2.7/site-packages/nose/ext/__init__.pyc differ diff --git a/env/lib/python2.7/site-packages/nose/ext/dtcompat.py b/env/lib/python2.7/site-packages/nose/ext/dtcompat.py new file mode 100644 index 0000000..332cf08 --- /dev/null +++ b/env/lib/python2.7/site-packages/nose/ext/dtcompat.py @@ -0,0 +1,2272 @@ +# Module doctest. +# Released to the public domain 16-Jan-2001, by Tim Peters (tim@python.org). +# Major enhancements and refactoring by: +# Jim Fulton +# Edward Loper + +# Provided as-is; use at your own risk; no warranty; no promises; enjoy! +# +# Modified for inclusion in nose to provide support for DocFileTest in +# python 2.3: +# +# - all doctests removed from module (they fail under 2.3 and 2.5) +# - now handles the $py.class extension when ran under Jython + +r"""Module doctest -- a framework for running examples in docstrings. + +In simplest use, end each module M to be tested with: + +def _test(): + import doctest + doctest.testmod() + +if __name__ == "__main__": + _test() + +Then running the module as a script will cause the examples in the +docstrings to get executed and verified: + +python M.py + +This won't display anything unless an example fails, in which case the +failing example(s) and the cause(s) of the failure(s) are printed to stdout +(why not stderr? because stderr is a lame hack <0.2 wink>), and the final +line of output is "Test failed.". + +Run it with the -v switch instead: + +python M.py -v + +and a detailed report of all examples tried is printed to stdout, along +with assorted summaries at the end. + +You can force verbose mode by passing "verbose=True" to testmod, or prohibit +it by passing "verbose=False". In either of those cases, sys.argv is not +examined by testmod. + +There are a variety of other ways to run doctests, including integration +with the unittest framework, and support for running non-Python text +files containing doctests. There are also many ways to override parts +of doctest's default behaviors. See the Library Reference Manual for +details. +""" + +__docformat__ = 'reStructuredText en' + +__all__ = [ + # 0, Option Flags + 'register_optionflag', + 'DONT_ACCEPT_TRUE_FOR_1', + 'DONT_ACCEPT_BLANKLINE', + 'NORMALIZE_WHITESPACE', + 'ELLIPSIS', + 'IGNORE_EXCEPTION_DETAIL', + 'COMPARISON_FLAGS', + 'REPORT_UDIFF', + 'REPORT_CDIFF', + 'REPORT_NDIFF', + 'REPORT_ONLY_FIRST_FAILURE', + 'REPORTING_FLAGS', + # 1. Utility Functions + 'is_private', + # 2. Example & DocTest + 'Example', + 'DocTest', + # 3. Doctest Parser + 'DocTestParser', + # 4. Doctest Finder + 'DocTestFinder', + # 5. Doctest Runner + 'DocTestRunner', + 'OutputChecker', + 'DocTestFailure', + 'UnexpectedException', + 'DebugRunner', + # 6. Test Functions + 'testmod', + 'testfile', + 'run_docstring_examples', + # 7. Tester + 'Tester', + # 8. Unittest Support + 'DocTestSuite', + 'DocFileSuite', + 'set_unittest_reportflags', + # 9. Debugging Support + 'script_from_examples', + 'testsource', + 'debug_src', + 'debug', +] + +import __future__ + +import sys, traceback, inspect, linecache, os, re +import unittest, difflib, pdb, tempfile +import warnings +from StringIO import StringIO + +# Don't whine about the deprecated is_private function in this +# module's tests. +warnings.filterwarnings("ignore", "is_private", DeprecationWarning, + __name__, 0) + +# There are 4 basic classes: +# - Example: a pair, plus an intra-docstring line number. +# - DocTest: a collection of examples, parsed from a docstring, plus +# info about where the docstring came from (name, filename, lineno). +# - DocTestFinder: extracts DocTests from a given object's docstring and +# its contained objects' docstrings. +# - DocTestRunner: runs DocTest cases, and accumulates statistics. +# +# So the basic picture is: +# +# list of: +# +------+ +---------+ +-------+ +# |object| --DocTestFinder-> | DocTest | --DocTestRunner-> |results| +# +------+ +---------+ +-------+ +# | Example | +# | ... | +# | Example | +# +---------+ + +# Option constants. + +OPTIONFLAGS_BY_NAME = {} +def register_optionflag(name): + # Create a new flag unless `name` is already known. + return OPTIONFLAGS_BY_NAME.setdefault(name, 1 << len(OPTIONFLAGS_BY_NAME)) + +DONT_ACCEPT_TRUE_FOR_1 = register_optionflag('DONT_ACCEPT_TRUE_FOR_1') +DONT_ACCEPT_BLANKLINE = register_optionflag('DONT_ACCEPT_BLANKLINE') +NORMALIZE_WHITESPACE = register_optionflag('NORMALIZE_WHITESPACE') +ELLIPSIS = register_optionflag('ELLIPSIS') +IGNORE_EXCEPTION_DETAIL = register_optionflag('IGNORE_EXCEPTION_DETAIL') + +COMPARISON_FLAGS = (DONT_ACCEPT_TRUE_FOR_1 | + DONT_ACCEPT_BLANKLINE | + NORMALIZE_WHITESPACE | + ELLIPSIS | + IGNORE_EXCEPTION_DETAIL) + +REPORT_UDIFF = register_optionflag('REPORT_UDIFF') +REPORT_CDIFF = register_optionflag('REPORT_CDIFF') +REPORT_NDIFF = register_optionflag('REPORT_NDIFF') +REPORT_ONLY_FIRST_FAILURE = register_optionflag('REPORT_ONLY_FIRST_FAILURE') + +REPORTING_FLAGS = (REPORT_UDIFF | + REPORT_CDIFF | + REPORT_NDIFF | + REPORT_ONLY_FIRST_FAILURE) + +# Special string markers for use in `want` strings: +BLANKLINE_MARKER = '' +ELLIPSIS_MARKER = '...' + +###################################################################### +## Table of Contents +###################################################################### +# 1. Utility Functions +# 2. Example & DocTest -- store test cases +# 3. DocTest Parser -- extracts examples from strings +# 4. DocTest Finder -- extracts test cases from objects +# 5. DocTest Runner -- runs test cases +# 6. Test Functions -- convenient wrappers for testing +# 7. Tester Class -- for backwards compatibility +# 8. Unittest Support +# 9. Debugging Support +# 10. Example Usage + +###################################################################### +## 1. Utility Functions +###################################################################### + +def is_private(prefix, base): + """prefix, base -> true iff name prefix + "." + base is "private". + + Prefix may be an empty string, and base does not contain a period. + Prefix is ignored (although functions you write conforming to this + protocol may make use of it). + Return true iff base begins with an (at least one) underscore, but + does not both begin and end with (at least) two underscores. + """ + warnings.warn("is_private is deprecated; it wasn't useful; " + "examine DocTestFinder.find() lists instead", + DeprecationWarning, stacklevel=2) + return base[:1] == "_" and not base[:2] == "__" == base[-2:] + +def _extract_future_flags(globs): + """ + Return the compiler-flags associated with the future features that + have been imported into the given namespace (globs). + """ + flags = 0 + for fname in __future__.all_feature_names: + feature = globs.get(fname, None) + if feature is getattr(__future__, fname): + flags |= feature.compiler_flag + return flags + +def _normalize_module(module, depth=2): + """ + Return the module specified by `module`. In particular: + - If `module` is a module, then return module. + - If `module` is a string, then import and return the + module with that name. + - If `module` is None, then return the calling module. + The calling module is assumed to be the module of + the stack frame at the given depth in the call stack. + """ + if inspect.ismodule(module): + return module + elif isinstance(module, (str, unicode)): + return __import__(module, globals(), locals(), ["*"]) + elif module is None: + return sys.modules[sys._getframe(depth).f_globals['__name__']] + else: + raise TypeError("Expected a module, string, or None") + +def _indent(s, indent=4): + """ + Add the given number of space characters to the beginning every + non-blank line in `s`, and return the result. + """ + # This regexp matches the start of non-blank lines: + return re.sub('(?m)^(?!$)', indent*' ', s) + +def _exception_traceback(exc_info): + """ + Return a string containing a traceback message for the given + exc_info tuple (as returned by sys.exc_info()). + """ + # Get a traceback message. + excout = StringIO() + exc_type, exc_val, exc_tb = exc_info + traceback.print_exception(exc_type, exc_val, exc_tb, file=excout) + return excout.getvalue() + +# Override some StringIO methods. +class _SpoofOut(StringIO): + def getvalue(self): + result = StringIO.getvalue(self) + # If anything at all was written, make sure there's a trailing + # newline. There's no way for the expected output to indicate + # that a trailing newline is missing. + if result and not result.endswith("\n"): + result += "\n" + # Prevent softspace from screwing up the next test case, in + # case they used print with a trailing comma in an example. + if hasattr(self, "softspace"): + del self.softspace + return result + + def truncate(self, size=None): + StringIO.truncate(self, size) + if hasattr(self, "softspace"): + del self.softspace + +# Worst-case linear-time ellipsis matching. +def _ellipsis_match(want, got): + if ELLIPSIS_MARKER not in want: + return want == got + + # Find "the real" strings. + ws = want.split(ELLIPSIS_MARKER) + assert len(ws) >= 2 + + # Deal with exact matches possibly needed at one or both ends. + startpos, endpos = 0, len(got) + w = ws[0] + if w: # starts with exact match + if got.startswith(w): + startpos = len(w) + del ws[0] + else: + return False + w = ws[-1] + if w: # ends with exact match + if got.endswith(w): + endpos -= len(w) + del ws[-1] + else: + return False + + if startpos > endpos: + # Exact end matches required more characters than we have, as in + # _ellipsis_match('aa...aa', 'aaa') + return False + + # For the rest, we only need to find the leftmost non-overlapping + # match for each piece. If there's no overall match that way alone, + # there's no overall match period. + for w in ws: + # w may be '' at times, if there are consecutive ellipses, or + # due to an ellipsis at the start or end of `want`. That's OK. + # Search for an empty string succeeds, and doesn't change startpos. + startpos = got.find(w, startpos, endpos) + if startpos < 0: + return False + startpos += len(w) + + return True + +def _comment_line(line): + "Return a commented form of the given line" + line = line.rstrip() + if line: + return '# '+line + else: + return '#' + +class _OutputRedirectingPdb(pdb.Pdb): + """ + A specialized version of the python debugger that redirects stdout + to a given stream when interacting with the user. Stdout is *not* + redirected when traced code is executed. + """ + def __init__(self, out): + self.__out = out + pdb.Pdb.__init__(self) + + def trace_dispatch(self, *args): + # Redirect stdout to the given stream. + save_stdout = sys.stdout + sys.stdout = self.__out + # Call Pdb's trace dispatch method. + try: + return pdb.Pdb.trace_dispatch(self, *args) + finally: + sys.stdout = save_stdout + +# [XX] Normalize with respect to os.path.pardir? +def _module_relative_path(module, path): + if not inspect.ismodule(module): + raise TypeError, 'Expected a module: %r' % module + if path.startswith('/'): + raise ValueError, 'Module-relative files may not have absolute paths' + + # Find the base directory for the path. + if hasattr(module, '__file__'): + # A normal module/package + basedir = os.path.split(module.__file__)[0] + elif module.__name__ == '__main__': + # An interactive session. + if len(sys.argv)>0 and sys.argv[0] != '': + basedir = os.path.split(sys.argv[0])[0] + else: + basedir = os.curdir + else: + # A module w/o __file__ (this includes builtins) + raise ValueError("Can't resolve paths relative to the module " + + module + " (it has no __file__)") + + # Combine the base directory and the path. + return os.path.join(basedir, *(path.split('/'))) + +###################################################################### +## 2. Example & DocTest +###################################################################### +## - An "example" is a pair, where "source" is a +## fragment of source code, and "want" is the expected output for +## "source." The Example class also includes information about +## where the example was extracted from. +## +## - A "doctest" is a collection of examples, typically extracted from +## a string (such as an object's docstring). The DocTest class also +## includes information about where the string was extracted from. + +class Example: + """ + A single doctest example, consisting of source code and expected + output. `Example` defines the following attributes: + + - source: A single Python statement, always ending with a newline. + The constructor adds a newline if needed. + + - want: The expected output from running the source code (either + from stdout, or a traceback in case of exception). `want` ends + with a newline unless it's empty, in which case it's an empty + string. The constructor adds a newline if needed. + + - exc_msg: The exception message generated by the example, if + the example is expected to generate an exception; or `None` if + it is not expected to generate an exception. This exception + message is compared against the return value of + `traceback.format_exception_only()`. `exc_msg` ends with a + newline unless it's `None`. The constructor adds a newline + if needed. + + - lineno: The line number within the DocTest string containing + this Example where the Example begins. This line number is + zero-based, with respect to the beginning of the DocTest. + + - indent: The example's indentation in the DocTest string. + I.e., the number of space characters that preceed the + example's first prompt. + + - options: A dictionary mapping from option flags to True or + False, which is used to override default options for this + example. Any option flags not contained in this dictionary + are left at their default value (as specified by the + DocTestRunner's optionflags). By default, no options are set. + """ + def __init__(self, source, want, exc_msg=None, lineno=0, indent=0, + options=None): + # Normalize inputs. + if not source.endswith('\n'): + source += '\n' + if want and not want.endswith('\n'): + want += '\n' + if exc_msg is not None and not exc_msg.endswith('\n'): + exc_msg += '\n' + # Store properties. + self.source = source + self.want = want + self.lineno = lineno + self.indent = indent + if options is None: options = {} + self.options = options + self.exc_msg = exc_msg + +class DocTest: + """ + A collection of doctest examples that should be run in a single + namespace. Each `DocTest` defines the following attributes: + + - examples: the list of examples. + + - globs: The namespace (aka globals) that the examples should + be run in. + + - name: A name identifying the DocTest (typically, the name of + the object whose docstring this DocTest was extracted from). + + - filename: The name of the file that this DocTest was extracted + from, or `None` if the filename is unknown. + + - lineno: The line number within filename where this DocTest + begins, or `None` if the line number is unavailable. This + line number is zero-based, with respect to the beginning of + the file. + + - docstring: The string that the examples were extracted from, + or `None` if the string is unavailable. + """ + def __init__(self, examples, globs, name, filename, lineno, docstring): + """ + Create a new DocTest containing the given examples. The + DocTest's globals are initialized with a copy of `globs`. + """ + assert not isinstance(examples, basestring), \ + "DocTest no longer accepts str; use DocTestParser instead" + self.examples = examples + self.docstring = docstring + self.globs = globs.copy() + self.name = name + self.filename = filename + self.lineno = lineno + + def __repr__(self): + if len(self.examples) == 0: + examples = 'no examples' + elif len(self.examples) == 1: + examples = '1 example' + else: + examples = '%d examples' % len(self.examples) + return ('' % + (self.name, self.filename, self.lineno, examples)) + + + # This lets us sort tests by name: + def __cmp__(self, other): + if not isinstance(other, DocTest): + return -1 + return cmp((self.name, self.filename, self.lineno, id(self)), + (other.name, other.filename, other.lineno, id(other))) + +###################################################################### +## 3. DocTestParser +###################################################################### + +class DocTestParser: + """ + A class used to parse strings containing doctest examples. + """ + # This regular expression is used to find doctest examples in a + # string. It defines three groups: `source` is the source code + # (including leading indentation and prompts); `indent` is the + # indentation of the first (PS1) line of the source code; and + # `want` is the expected output (including leading indentation). + _EXAMPLE_RE = re.compile(r''' + # Source consists of a PS1 line followed by zero or more PS2 lines. + (?P + (?:^(?P [ ]*) >>> .*) # PS1 line + (?:\n [ ]* \.\.\. .*)*) # PS2 lines + \n? + # Want consists of any non-blank lines that do not start with PS1. + (?P (?:(?![ ]*$) # Not a blank line + (?![ ]*>>>) # Not a line starting with PS1 + .*$\n? # But any other line + )*) + ''', re.MULTILINE | re.VERBOSE) + + # A regular expression for handling `want` strings that contain + # expected exceptions. It divides `want` into three pieces: + # - the traceback header line (`hdr`) + # - the traceback stack (`stack`) + # - the exception message (`msg`), as generated by + # traceback.format_exception_only() + # `msg` may have multiple lines. We assume/require that the + # exception message is the first non-indented line starting with a word + # character following the traceback header line. + _EXCEPTION_RE = re.compile(r""" + # Grab the traceback header. Different versions of Python have + # said different things on the first traceback line. + ^(?P Traceback\ \( + (?: most\ recent\ call\ last + | innermost\ last + ) \) : + ) + \s* $ # toss trailing whitespace on the header. + (?P .*?) # don't blink: absorb stuff until... + ^ (?P \w+ .*) # a line *starts* with alphanum. + """, re.VERBOSE | re.MULTILINE | re.DOTALL) + + # A callable returning a true value iff its argument is a blank line + # or contains a single comment. + _IS_BLANK_OR_COMMENT = re.compile(r'^[ ]*(#.*)?$').match + + def parse(self, string, name=''): + """ + Divide the given string into examples and intervening text, + and return them as a list of alternating Examples and strings. + Line numbers for the Examples are 0-based. The optional + argument `name` is a name identifying this string, and is only + used for error messages. + """ + string = string.expandtabs() + # If all lines begin with the same indentation, then strip it. + min_indent = self._min_indent(string) + if min_indent > 0: + string = '\n'.join([l[min_indent:] for l in string.split('\n')]) + + output = [] + charno, lineno = 0, 0 + # Find all doctest examples in the string: + for m in self._EXAMPLE_RE.finditer(string): + # Add the pre-example text to `output`. + output.append(string[charno:m.start()]) + # Update lineno (lines before this example) + lineno += string.count('\n', charno, m.start()) + # Extract info from the regexp match. + (source, options, want, exc_msg) = \ + self._parse_example(m, name, lineno) + # Create an Example, and add it to the list. + if not self._IS_BLANK_OR_COMMENT(source): + output.append( Example(source, want, exc_msg, + lineno=lineno, + indent=min_indent+len(m.group('indent')), + options=options) ) + # Update lineno (lines inside this example) + lineno += string.count('\n', m.start(), m.end()) + # Update charno. + charno = m.end() + # Add any remaining post-example text to `output`. + output.append(string[charno:]) + return output + + def get_doctest(self, string, globs, name, filename, lineno): + """ + Extract all doctest examples from the given string, and + collect them into a `DocTest` object. + + `globs`, `name`, `filename`, and `lineno` are attributes for + the new `DocTest` object. See the documentation for `DocTest` + for more information. + """ + return DocTest(self.get_examples(string, name), globs, + name, filename, lineno, string) + + def get_examples(self, string, name=''): + """ + Extract all doctest examples from the given string, and return + them as a list of `Example` objects. Line numbers are + 0-based, because it's most common in doctests that nothing + interesting appears on the same line as opening triple-quote, + and so the first interesting line is called \"line 1\" then. + + The optional argument `name` is a name identifying this + string, and is only used for error messages. + """ + return [x for x in self.parse(string, name) + if isinstance(x, Example)] + + def _parse_example(self, m, name, lineno): + """ + Given a regular expression match from `_EXAMPLE_RE` (`m`), + return a pair `(source, want)`, where `source` is the matched + example's source code (with prompts and indentation stripped); + and `want` is the example's expected output (with indentation + stripped). + + `name` is the string's name, and `lineno` is the line number + where the example starts; both are used for error messages. + """ + # Get the example's indentation level. + indent = len(m.group('indent')) + + # Divide source into lines; check that they're properly + # indented; and then strip their indentation & prompts. + source_lines = m.group('source').split('\n') + self._check_prompt_blank(source_lines, indent, name, lineno) + self._check_prefix(source_lines[1:], ' '*indent + '.', name, lineno) + source = '\n'.join([sl[indent+4:] for sl in source_lines]) + + # Divide want into lines; check that it's properly indented; and + # then strip the indentation. Spaces before the last newline should + # be preserved, so plain rstrip() isn't good enough. + want = m.group('want') + want_lines = want.split('\n') + if len(want_lines) > 1 and re.match(r' *$', want_lines[-1]): + del want_lines[-1] # forget final newline & spaces after it + self._check_prefix(want_lines, ' '*indent, name, + lineno + len(source_lines)) + want = '\n'.join([wl[indent:] for wl in want_lines]) + + # If `want` contains a traceback message, then extract it. + m = self._EXCEPTION_RE.match(want) + if m: + exc_msg = m.group('msg') + else: + exc_msg = None + + # Extract options from the source. + options = self._find_options(source, name, lineno) + + return source, options, want, exc_msg + + # This regular expression looks for option directives in the + # source code of an example. Option directives are comments + # starting with "doctest:". Warning: this may give false + # positives for string-literals that contain the string + # "#doctest:". Eliminating these false positives would require + # actually parsing the string; but we limit them by ignoring any + # line containing "#doctest:" that is *followed* by a quote mark. + _OPTION_DIRECTIVE_RE = re.compile(r'#\s*doctest:\s*([^\n\'"]*)$', + re.MULTILINE) + + def _find_options(self, source, name, lineno): + """ + Return a dictionary containing option overrides extracted from + option directives in the given source string. + + `name` is the string's name, and `lineno` is the line number + where the example starts; both are used for error messages. + """ + options = {} + # (note: with the current regexp, this will match at most once:) + for m in self._OPTION_DIRECTIVE_RE.finditer(source): + option_strings = m.group(1).replace(',', ' ').split() + for option in option_strings: + if (option[0] not in '+-' or + option[1:] not in OPTIONFLAGS_BY_NAME): + raise ValueError('line %r of the doctest for %s ' + 'has an invalid option: %r' % + (lineno+1, name, option)) + flag = OPTIONFLAGS_BY_NAME[option[1:]] + options[flag] = (option[0] == '+') + if options and self._IS_BLANK_OR_COMMENT(source): + raise ValueError('line %r of the doctest for %s has an option ' + 'directive on a line with no example: %r' % + (lineno, name, source)) + return options + + # This regular expression finds the indentation of every non-blank + # line in a string. + _INDENT_RE = re.compile('^([ ]*)(?=\S)', re.MULTILINE) + + def _min_indent(self, s): + "Return the minimum indentation of any non-blank line in `s`" + indents = [len(indent) for indent in self._INDENT_RE.findall(s)] + if len(indents) > 0: + return min(indents) + else: + return 0 + + def _check_prompt_blank(self, lines, indent, name, lineno): + """ + Given the lines of a source string (including prompts and + leading indentation), check to make sure that every prompt is + followed by a space character. If any line is not followed by + a space character, then raise ValueError. + """ + for i, line in enumerate(lines): + if len(line) >= indent+4 and line[indent+3] != ' ': + raise ValueError('line %r of the docstring for %s ' + 'lacks blank after %s: %r' % + (lineno+i+1, name, + line[indent:indent+3], line)) + + def _check_prefix(self, lines, prefix, name, lineno): + """ + Check that every line in the given list starts with the given + prefix; if any line does not, then raise a ValueError. + """ + for i, line in enumerate(lines): + if line and not line.startswith(prefix): + raise ValueError('line %r of the docstring for %s has ' + 'inconsistent leading whitespace: %r' % + (lineno+i+1, name, line)) + + +###################################################################### +## 4. DocTest Finder +###################################################################### + +class DocTestFinder: + """ + A class used to extract the DocTests that are relevant to a given + object, from its docstring and the docstrings of its contained + objects. Doctests can currently be extracted from the following + object types: modules, functions, classes, methods, staticmethods, + classmethods, and properties. + """ + + def __init__(self, verbose=False, parser=DocTestParser(), + recurse=True, _namefilter=None, exclude_empty=True): + """ + Create a new doctest finder. + + The optional argument `parser` specifies a class or + function that should be used to create new DocTest objects (or + objects that implement the same interface as DocTest). The + signature for this factory function should match the signature + of the DocTest constructor. + + If the optional argument `recurse` is false, then `find` will + only examine the given object, and not any contained objects. + + If the optional argument `exclude_empty` is false, then `find` + will include tests for objects with empty docstrings. + """ + self._parser = parser + self._verbose = verbose + self._recurse = recurse + self._exclude_empty = exclude_empty + # _namefilter is undocumented, and exists only for temporary backward- + # compatibility support of testmod's deprecated isprivate mess. + self._namefilter = _namefilter + + def find(self, obj, name=None, module=None, globs=None, + extraglobs=None): + """ + Return a list of the DocTests that are defined by the given + object's docstring, or by any of its contained objects' + docstrings. + + The optional parameter `module` is the module that contains + the given object. If the module is not specified or is None, then + the test finder will attempt to automatically determine the + correct module. The object's module is used: + + - As a default namespace, if `globs` is not specified. + - To prevent the DocTestFinder from extracting DocTests + from objects that are imported from other modules. + - To find the name of the file containing the object. + - To help find the line number of the object within its + file. + + Contained objects whose module does not match `module` are ignored. + + If `module` is False, no attempt to find the module will be made. + This is obscure, of use mostly in tests: if `module` is False, or + is None but cannot be found automatically, then all objects are + considered to belong to the (non-existent) module, so all contained + objects will (recursively) be searched for doctests. + + The globals for each DocTest is formed by combining `globs` + and `extraglobs` (bindings in `extraglobs` override bindings + in `globs`). A new copy of the globals dictionary is created + for each DocTest. If `globs` is not specified, then it + defaults to the module's `__dict__`, if specified, or {} + otherwise. If `extraglobs` is not specified, then it defaults + to {}. + + """ + # If name was not specified, then extract it from the object. + if name is None: + name = getattr(obj, '__name__', None) + if name is None: + raise ValueError("DocTestFinder.find: name must be given " + "when obj.__name__ doesn't exist: %r" % + (type(obj),)) + + # Find the module that contains the given object (if obj is + # a module, then module=obj.). Note: this may fail, in which + # case module will be None. + if module is False: + module = None + elif module is None: + module = inspect.getmodule(obj) + + # Read the module's source code. This is used by + # DocTestFinder._find_lineno to find the line number for a + # given object's docstring. + try: + file = inspect.getsourcefile(obj) or inspect.getfile(obj) + source_lines = linecache.getlines(file) + if not source_lines: + source_lines = None + except TypeError: + source_lines = None + + # Initialize globals, and merge in extraglobs. + if globs is None: + if module is None: + globs = {} + else: + globs = module.__dict__.copy() + else: + globs = globs.copy() + if extraglobs is not None: + globs.update(extraglobs) + + # Recursively expore `obj`, extracting DocTests. + tests = [] + self._find(tests, obj, name, module, source_lines, globs, {}) + # Sort the tests by alpha order of names, for consistency in + # verbose-mode output. This was a feature of doctest in Pythons + # <= 2.3 that got lost by accident in 2.4. It was repaired in + # 2.4.4 and 2.5. + tests.sort() + return tests + + def _filter(self, obj, prefix, base): + """ + Return true if the given object should not be examined. + """ + return (self._namefilter is not None and + self._namefilter(prefix, base)) + + def _from_module(self, module, object): + """ + Return true if the given object is defined in the given + module. + """ + if module is None: + return True + elif inspect.isfunction(object): + return module.__dict__ is object.func_globals + elif inspect.isclass(object): + # Some jython classes don't set __module__ + return module.__name__ == getattr(object, '__module__', None) + elif inspect.getmodule(object) is not None: + return module is inspect.getmodule(object) + elif hasattr(object, '__module__'): + return module.__name__ == object.__module__ + elif isinstance(object, property): + return True # [XX] no way not be sure. + else: + raise ValueError("object must be a class or function") + + def _find(self, tests, obj, name, module, source_lines, globs, seen): + """ + Find tests for the given object and any contained objects, and + add them to `tests`. + """ + if self._verbose: + print 'Finding tests in %s' % name + + # If we've already processed this object, then ignore it. + if id(obj) in seen: + return + seen[id(obj)] = 1 + + # Find a test for this object, and add it to the list of tests. + test = self._get_test(obj, name, module, globs, source_lines) + if test is not None: + tests.append(test) + + # Look for tests in a module's contained objects. + if inspect.ismodule(obj) and self._recurse: + for valname, val in obj.__dict__.items(): + # Check if this contained object should be ignored. + if self._filter(val, name, valname): + continue + valname = '%s.%s' % (name, valname) + # Recurse to functions & classes. + if ((inspect.isfunction(val) or inspect.isclass(val)) and + self._from_module(module, val)): + self._find(tests, val, valname, module, source_lines, + globs, seen) + + # Look for tests in a module's __test__ dictionary. + if inspect.ismodule(obj) and self._recurse: + for valname, val in getattr(obj, '__test__', {}).items(): + if not isinstance(valname, basestring): + raise ValueError("DocTestFinder.find: __test__ keys " + "must be strings: %r" % + (type(valname),)) + if not (inspect.isfunction(val) or inspect.isclass(val) or + inspect.ismethod(val) or inspect.ismodule(val) or + isinstance(val, basestring)): + raise ValueError("DocTestFinder.find: __test__ values " + "must be strings, functions, methods, " + "classes, or modules: %r" % + (type(val),)) + valname = '%s.__test__.%s' % (name, valname) + self._find(tests, val, valname, module, source_lines, + globs, seen) + + # Look for tests in a class's contained objects. + if inspect.isclass(obj) and self._recurse: + for valname, val in obj.__dict__.items(): + # Check if this contained object should be ignored. + if self._filter(val, name, valname): + continue + # Special handling for staticmethod/classmethod. + if isinstance(val, staticmethod): + val = getattr(obj, valname) + if isinstance(val, classmethod): + val = getattr(obj, valname).im_func + + # Recurse to methods, properties, and nested classes. + if ((inspect.isfunction(val) or inspect.isclass(val) or + isinstance(val, property)) and + self._from_module(module, val)): + valname = '%s.%s' % (name, valname) + self._find(tests, val, valname, module, source_lines, + globs, seen) + + def _get_test(self, obj, name, module, globs, source_lines): + """ + Return a DocTest for the given object, if it defines a docstring; + otherwise, return None. + """ + # Extract the object's docstring. If it doesn't have one, + # then return None (no test for this object). + if isinstance(obj, basestring): + docstring = obj + else: + try: + if obj.__doc__ is None: + docstring = '' + else: + docstring = obj.__doc__ + if not isinstance(docstring, basestring): + docstring = str(docstring) + except (TypeError, AttributeError): + docstring = '' + + # Find the docstring's location in the file. + lineno = self._find_lineno(obj, source_lines) + + # Don't bother if the docstring is empty. + if self._exclude_empty and not docstring: + return None + + # Return a DocTest for this object. + if module is None: + filename = None + else: + filename = getattr(module, '__file__', module.__name__) + if filename[-4:] in (".pyc", ".pyo"): + filename = filename[:-1] + elif sys.platform.startswith('java') and \ + filename.endswith('$py.class'): + filename = '%s.py' % filename[:-9] + return self._parser.get_doctest(docstring, globs, name, + filename, lineno) + + def _find_lineno(self, obj, source_lines): + """ + Return a line number of the given object's docstring. Note: + this method assumes that the object has a docstring. + """ + lineno = None + + # Find the line number for modules. + if inspect.ismodule(obj): + lineno = 0 + + # Find the line number for classes. + # Note: this could be fooled if a class is defined multiple + # times in a single file. + if inspect.isclass(obj): + if source_lines is None: + return None + pat = re.compile(r'^\s*class\s*%s\b' % + getattr(obj, '__name__', '-')) + for i, line in enumerate(source_lines): + if pat.match(line): + lineno = i + break + + # Find the line number for functions & methods. + if inspect.ismethod(obj): obj = obj.im_func + if inspect.isfunction(obj): obj = obj.func_code + if inspect.istraceback(obj): obj = obj.tb_frame + if inspect.isframe(obj): obj = obj.f_code + if inspect.iscode(obj): + lineno = getattr(obj, 'co_firstlineno', None)-1 + + # Find the line number where the docstring starts. Assume + # that it's the first line that begins with a quote mark. + # Note: this could be fooled by a multiline function + # signature, where a continuation line begins with a quote + # mark. + if lineno is not None: + if source_lines is None: + return lineno+1 + pat = re.compile('(^|.*:)\s*\w*("|\')') + for lineno in range(lineno, len(source_lines)): + if pat.match(source_lines[lineno]): + return lineno + + # We couldn't find the line number. + return None + +###################################################################### +## 5. DocTest Runner +###################################################################### + +class DocTestRunner: + # This divider string is used to separate failure messages, and to + # separate sections of the summary. + DIVIDER = "*" * 70 + + def __init__(self, checker=None, verbose=None, optionflags=0): + """ + Create a new test runner. + + Optional keyword arg `checker` is the `OutputChecker` that + should be used to compare the expected outputs and actual + outputs of doctest examples. + + Optional keyword arg 'verbose' prints lots of stuff if true, + only failures if false; by default, it's true iff '-v' is in + sys.argv. + + Optional argument `optionflags` can be used to control how the + test runner compares expected output to actual output, and how + it displays failures. See the documentation for `testmod` for + more information. + """ + self._checker = checker or OutputChecker() + if verbose is None: + verbose = '-v' in sys.argv + self._verbose = verbose + self.optionflags = optionflags + self.original_optionflags = optionflags + + # Keep track of the examples we've run. + self.tries = 0 + self.failures = 0 + self._name2ft = {} + + # Create a fake output target for capturing doctest output. + self._fakeout = _SpoofOut() + + #///////////////////////////////////////////////////////////////// + # Reporting methods + #///////////////////////////////////////////////////////////////// + + def report_start(self, out, test, example): + """ + Report that the test runner is about to process the given + example. (Only displays a message if verbose=True) + """ + if self._verbose: + if example.want: + out('Trying:\n' + _indent(example.source) + + 'Expecting:\n' + _indent(example.want)) + else: + out('Trying:\n' + _indent(example.source) + + 'Expecting nothing\n') + + def report_success(self, out, test, example, got): + """ + Report that the given example ran successfully. (Only + displays a message if verbose=True) + """ + if self._verbose: + out("ok\n") + + def report_failure(self, out, test, example, got): + """ + Report that the given example failed. + """ + out(self._failure_header(test, example) + + self._checker.output_difference(example, got, self.optionflags)) + + def report_unexpected_exception(self, out, test, example, exc_info): + """ + Report that the given example raised an unexpected exception. + """ + out(self._failure_header(test, example) + + 'Exception raised:\n' + _indent(_exception_traceback(exc_info))) + + def _failure_header(self, test, example): + out = [self.DIVIDER] + if test.filename: + if test.lineno is not None and example.lineno is not None: + lineno = test.lineno + example.lineno + 1 + else: + lineno = '?' + out.append('File "%s", line %s, in %s' % + (test.filename, lineno, test.name)) + else: + out.append('Line %s, in %s' % (example.lineno+1, test.name)) + out.append('Failed example:') + source = example.source + out.append(_indent(source)) + return '\n'.join(out) + + #///////////////////////////////////////////////////////////////// + # DocTest Running + #///////////////////////////////////////////////////////////////// + + def __run(self, test, compileflags, out): + """ + Run the examples in `test`. Write the outcome of each example + with one of the `DocTestRunner.report_*` methods, using the + writer function `out`. `compileflags` is the set of compiler + flags that should be used to execute examples. Return a tuple + `(f, t)`, where `t` is the number of examples tried, and `f` + is the number of examples that failed. The examples are run + in the namespace `test.globs`. + """ + # Keep track of the number of failures and tries. + failures = tries = 0 + + # Save the option flags (since option directives can be used + # to modify them). + original_optionflags = self.optionflags + + SUCCESS, FAILURE, BOOM = range(3) # `outcome` state + + check = self._checker.check_output + + # Process each example. + for examplenum, example in enumerate(test.examples): + + # If REPORT_ONLY_FIRST_FAILURE is set, then supress + # reporting after the first failure. + quiet = (self.optionflags & REPORT_ONLY_FIRST_FAILURE and + failures > 0) + + # Merge in the example's options. + self.optionflags = original_optionflags + if example.options: + for (optionflag, val) in example.options.items(): + if val: + self.optionflags |= optionflag + else: + self.optionflags &= ~optionflag + + # Record that we started this example. + tries += 1 + if not quiet: + self.report_start(out, test, example) + + # Use a special filename for compile(), so we can retrieve + # the source code during interactive debugging (see + # __patched_linecache_getlines). + filename = '' % (test.name, examplenum) + + # Run the example in the given context (globs), and record + # any exception that gets raised. (But don't intercept + # keyboard interrupts.) + try: + # Don't blink! This is where the user's code gets run. + exec compile(example.source, filename, "single", + compileflags, 1) in test.globs + self.debugger.set_continue() # ==== Example Finished ==== + exception = None + except KeyboardInterrupt: + raise + except: + exception = sys.exc_info() + self.debugger.set_continue() # ==== Example Finished ==== + + got = self._fakeout.getvalue() # the actual output + self._fakeout.truncate(0) + outcome = FAILURE # guilty until proved innocent or insane + + # If the example executed without raising any exceptions, + # verify its output. + if exception is None: + if check(example.want, got, self.optionflags): + outcome = SUCCESS + + # The example raised an exception: check if it was expected. + else: + exc_info = sys.exc_info() + exc_msg = traceback.format_exception_only(*exc_info[:2])[-1] + if not quiet: + got += _exception_traceback(exc_info) + + # If `example.exc_msg` is None, then we weren't expecting + # an exception. + if example.exc_msg is None: + outcome = BOOM + + # We expected an exception: see whether it matches. + elif check(example.exc_msg, exc_msg, self.optionflags): + outcome = SUCCESS + + # Another chance if they didn't care about the detail. + elif self.optionflags & IGNORE_EXCEPTION_DETAIL: + m1 = re.match(r'[^:]*:', example.exc_msg) + m2 = re.match(r'[^:]*:', exc_msg) + if m1 and m2 and check(m1.group(0), m2.group(0), + self.optionflags): + outcome = SUCCESS + + # Report the outcome. + if outcome is SUCCESS: + if not quiet: + self.report_success(out, test, example, got) + elif outcome is FAILURE: + if not quiet: + self.report_failure(out, test, example, got) + failures += 1 + elif outcome is BOOM: + if not quiet: + self.report_unexpected_exception(out, test, example, + exc_info) + failures += 1 + else: + assert False, ("unknown outcome", outcome) + + # Restore the option flags (in case they were modified) + self.optionflags = original_optionflags + + # Record and return the number of failures and tries. + self.__record_outcome(test, failures, tries) + return failures, tries + + def __record_outcome(self, test, f, t): + """ + Record the fact that the given DocTest (`test`) generated `f` + failures out of `t` tried examples. + """ + f2, t2 = self._name2ft.get(test.name, (0,0)) + self._name2ft[test.name] = (f+f2, t+t2) + self.failures += f + self.tries += t + + __LINECACHE_FILENAME_RE = re.compile(r'[\w\.]+)' + r'\[(?P\d+)\]>$') + def __patched_linecache_getlines(self, filename): + m = self.__LINECACHE_FILENAME_RE.match(filename) + if m and m.group('name') == self.test.name: + example = self.test.examples[int(m.group('examplenum'))] + return example.source.splitlines(True) + else: + return self.save_linecache_getlines(filename) + + def run(self, test, compileflags=None, out=None, clear_globs=True): + """ + Run the examples in `test`, and display the results using the + writer function `out`. + + The examples are run in the namespace `test.globs`. If + `clear_globs` is true (the default), then this namespace will + be cleared after the test runs, to help with garbage + collection. If you would like to examine the namespace after + the test completes, then use `clear_globs=False`. + + `compileflags` gives the set of flags that should be used by + the Python compiler when running the examples. If not + specified, then it will default to the set of future-import + flags that apply to `globs`. + + The output of each example is checked using + `DocTestRunner.check_output`, and the results are formatted by + the `DocTestRunner.report_*` methods. + """ + self.test = test + + if compileflags is None: + compileflags = _extract_future_flags(test.globs) + + save_stdout = sys.stdout + if out is None: + out = save_stdout.write + sys.stdout = self._fakeout + + # Patch pdb.set_trace to restore sys.stdout during interactive + # debugging (so it's not still redirected to self._fakeout). + # Note that the interactive output will go to *our* + # save_stdout, even if that's not the real sys.stdout; this + # allows us to write test cases for the set_trace behavior. + save_set_trace = pdb.set_trace + self.debugger = _OutputRedirectingPdb(save_stdout) + self.debugger.reset() + pdb.set_trace = self.debugger.set_trace + + # Patch linecache.getlines, so we can see the example's source + # when we're inside the debugger. + self.save_linecache_getlines = linecache.getlines + linecache.getlines = self.__patched_linecache_getlines + + try: + return self.__run(test, compileflags, out) + finally: + sys.stdout = save_stdout + pdb.set_trace = save_set_trace + linecache.getlines = self.save_linecache_getlines + if clear_globs: + test.globs.clear() + + #///////////////////////////////////////////////////////////////// + # Summarization + #///////////////////////////////////////////////////////////////// + def summarize(self, verbose=None): + """ + Print a summary of all the test cases that have been run by + this DocTestRunner, and return a tuple `(f, t)`, where `f` is + the total number of failed examples, and `t` is the total + number of tried examples. + + The optional `verbose` argument controls how detailed the + summary is. If the verbosity is not specified, then the + DocTestRunner's verbosity is used. + """ + if verbose is None: + verbose = self._verbose + notests = [] + passed = [] + failed = [] + totalt = totalf = 0 + for x in self._name2ft.items(): + name, (f, t) = x + assert f <= t + totalt += t + totalf += f + if t == 0: + notests.append(name) + elif f == 0: + passed.append( (name, t) ) + else: + failed.append(x) + if verbose: + if notests: + print len(notests), "items had no tests:" + notests.sort() + for thing in notests: + print " ", thing + if passed: + print len(passed), "items passed all tests:" + passed.sort() + for thing, count in passed: + print " %3d tests in %s" % (count, thing) + if failed: + print self.DIVIDER + print len(failed), "items had failures:" + failed.sort() + for thing, (f, t) in failed: + print " %3d of %3d in %s" % (f, t, thing) + if verbose: + print totalt, "tests in", len(self._name2ft), "items." + print totalt - totalf, "passed and", totalf, "failed." + if totalf: + print "***Test Failed***", totalf, "failures." + elif verbose: + print "Test passed." + return totalf, totalt + + #///////////////////////////////////////////////////////////////// + # Backward compatibility cruft to maintain doctest.master. + #///////////////////////////////////////////////////////////////// + def merge(self, other): + d = self._name2ft + for name, (f, t) in other._name2ft.items(): + if name in d: + print "*** DocTestRunner.merge: '" + name + "' in both" \ + " testers; summing outcomes." + f2, t2 = d[name] + f = f + f2 + t = t + t2 + d[name] = f, t + +class OutputChecker: + """ + A class used to check the whether the actual output from a doctest + example matches the expected output. `OutputChecker` defines two + methods: `check_output`, which compares a given pair of outputs, + and returns true if they match; and `output_difference`, which + returns a string describing the differences between two outputs. + """ + def check_output(self, want, got, optionflags): + """ + Return True iff the actual output from an example (`got`) + matches the expected output (`want`). These strings are + always considered to match if they are identical; but + depending on what option flags the test runner is using, + several non-exact match types are also possible. See the + documentation for `TestRunner` for more information about + option flags. + """ + # Handle the common case first, for efficiency: + # if they're string-identical, always return true. + if got == want: + return True + + # The values True and False replaced 1 and 0 as the return + # value for boolean comparisons in Python 2.3. + if not (optionflags & DONT_ACCEPT_TRUE_FOR_1): + if (got,want) == ("True\n", "1\n"): + return True + if (got,want) == ("False\n", "0\n"): + return True + + # can be used as a special sequence to signify a + # blank line, unless the DONT_ACCEPT_BLANKLINE flag is used. + if not (optionflags & DONT_ACCEPT_BLANKLINE): + # Replace in want with a blank line. + want = re.sub('(?m)^%s\s*?$' % re.escape(BLANKLINE_MARKER), + '', want) + # If a line in got contains only spaces, then remove the + # spaces. + got = re.sub('(?m)^\s*?$', '', got) + if got == want: + return True + + # This flag causes doctest to ignore any differences in the + # contents of whitespace strings. Note that this can be used + # in conjunction with the ELLIPSIS flag. + if optionflags & NORMALIZE_WHITESPACE: + got = ' '.join(got.split()) + want = ' '.join(want.split()) + if got == want: + return True + + # The ELLIPSIS flag says to let the sequence "..." in `want` + # match any substring in `got`. + if optionflags & ELLIPSIS: + if _ellipsis_match(want, got): + return True + + # We didn't find any match; return false. + return False + + # Should we do a fancy diff? + def _do_a_fancy_diff(self, want, got, optionflags): + # Not unless they asked for a fancy diff. + if not optionflags & (REPORT_UDIFF | + REPORT_CDIFF | + REPORT_NDIFF): + return False + + # If expected output uses ellipsis, a meaningful fancy diff is + # too hard ... or maybe not. In two real-life failures Tim saw, + # a diff was a major help anyway, so this is commented out. + # [todo] _ellipsis_match() knows which pieces do and don't match, + # and could be the basis for a kick-ass diff in this case. + ##if optionflags & ELLIPSIS and ELLIPSIS_MARKER in want: + ## return False + + # ndiff does intraline difference marking, so can be useful even + # for 1-line differences. + if optionflags & REPORT_NDIFF: + return True + + # The other diff types need at least a few lines to be helpful. + return want.count('\n') > 2 and got.count('\n') > 2 + + def output_difference(self, example, got, optionflags): + """ + Return a string describing the differences between the + expected output for a given example (`example`) and the actual + output (`got`). `optionflags` is the set of option flags used + to compare `want` and `got`. + """ + want = example.want + # If s are being used, then replace blank lines + # with in the actual output string. + if not (optionflags & DONT_ACCEPT_BLANKLINE): + got = re.sub('(?m)^[ ]*(?=\n)', BLANKLINE_MARKER, got) + + # Check if we should use diff. + if self._do_a_fancy_diff(want, got, optionflags): + # Split want & got into lines. + want_lines = want.splitlines(True) # True == keep line ends + got_lines = got.splitlines(True) + # Use difflib to find their differences. + if optionflags & REPORT_UDIFF: + diff = difflib.unified_diff(want_lines, got_lines, n=2) + diff = list(diff)[2:] # strip the diff header + kind = 'unified diff with -expected +actual' + elif optionflags & REPORT_CDIFF: + diff = difflib.context_diff(want_lines, got_lines, n=2) + diff = list(diff)[2:] # strip the diff header + kind = 'context diff with expected followed by actual' + elif optionflags & REPORT_NDIFF: + engine = difflib.Differ(charjunk=difflib.IS_CHARACTER_JUNK) + diff = list(engine.compare(want_lines, got_lines)) + kind = 'ndiff with -expected +actual' + else: + assert 0, 'Bad diff option' + # Remove trailing whitespace on diff output. + diff = [line.rstrip() + '\n' for line in diff] + return 'Differences (%s):\n' % kind + _indent(''.join(diff)) + + # If we're not using diff, then simply list the expected + # output followed by the actual output. + if want and got: + return 'Expected:\n%sGot:\n%s' % (_indent(want), _indent(got)) + elif want: + return 'Expected:\n%sGot nothing\n' % _indent(want) + elif got: + return 'Expected nothing\nGot:\n%s' % _indent(got) + else: + return 'Expected nothing\nGot nothing\n' + +class DocTestFailure(Exception): + """A DocTest example has failed in debugging mode. + + The exception instance has variables: + + - test: the DocTest object being run + + - excample: the Example object that failed + + - got: the actual output + """ + def __init__(self, test, example, got): + self.test = test + self.example = example + self.got = got + + def __str__(self): + return str(self.test) + +class UnexpectedException(Exception): + """A DocTest example has encountered an unexpected exception + + The exception instance has variables: + + - test: the DocTest object being run + + - excample: the Example object that failed + + - exc_info: the exception info + """ + def __init__(self, test, example, exc_info): + self.test = test + self.example = example + self.exc_info = exc_info + + def __str__(self): + return str(self.test) + +class DebugRunner(DocTestRunner): + + def run(self, test, compileflags=None, out=None, clear_globs=True): + r = DocTestRunner.run(self, test, compileflags, out, False) + if clear_globs: + test.globs.clear() + return r + + def report_unexpected_exception(self, out, test, example, exc_info): + raise UnexpectedException(test, example, exc_info) + + def report_failure(self, out, test, example, got): + raise DocTestFailure(test, example, got) + +###################################################################### +## 6. Test Functions +###################################################################### +# These should be backwards compatible. + +# For backward compatibility, a global instance of a DocTestRunner +# class, updated by testmod. +master = None + +def testmod(m=None, name=None, globs=None, verbose=None, isprivate=None, + report=True, optionflags=0, extraglobs=None, + raise_on_error=False, exclude_empty=False): + """m=None, name=None, globs=None, verbose=None, isprivate=None, + report=True, optionflags=0, extraglobs=None, raise_on_error=False, + exclude_empty=False + + Test examples in docstrings in functions and classes reachable + from module m (or the current module if m is not supplied), starting + with m.__doc__. Unless isprivate is specified, private names + are not skipped. + + Also test examples reachable from dict m.__test__ if it exists and is + not None. m.__test__ maps names to functions, classes and strings; + function and class docstrings are tested even if the name is private; + strings are tested directly, as if they were docstrings. + + Return (#failures, #tests). + + See doctest.__doc__ for an overview. + + Optional keyword arg "name" gives the name of the module; by default + use m.__name__. + + Optional keyword arg "globs" gives a dict to be used as the globals + when executing examples; by default, use m.__dict__. A copy of this + dict is actually used for each docstring, so that each docstring's + examples start with a clean slate. + + Optional keyword arg "extraglobs" gives a dictionary that should be + merged into the globals that are used to execute examples. By + default, no extra globals are used. This is new in 2.4. + + Optional keyword arg "verbose" prints lots of stuff if true, prints + only failures if false; by default, it's true iff "-v" is in sys.argv. + + Optional keyword arg "report" prints a summary at the end when true, + else prints nothing at the end. In verbose mode, the summary is + detailed, else very brief (in fact, empty if all tests passed). + + Optional keyword arg "optionflags" or's together module constants, + and defaults to 0. This is new in 2.3. Possible values (see the + docs for details): + + DONT_ACCEPT_TRUE_FOR_1 + DONT_ACCEPT_BLANKLINE + NORMALIZE_WHITESPACE + ELLIPSIS + IGNORE_EXCEPTION_DETAIL + REPORT_UDIFF + REPORT_CDIFF + REPORT_NDIFF + REPORT_ONLY_FIRST_FAILURE + + Optional keyword arg "raise_on_error" raises an exception on the + first unexpected exception or failure. This allows failures to be + post-mortem debugged. + + Deprecated in Python 2.4: + Optional keyword arg "isprivate" specifies a function used to + determine whether a name is private. The default function is + treat all functions as public. Optionally, "isprivate" can be + set to doctest.is_private to skip over functions marked as private + using the underscore naming convention; see its docs for details. + + Advanced tomfoolery: testmod runs methods of a local instance of + class doctest.Tester, then merges the results into (or creates) + global Tester instance doctest.master. Methods of doctest.master + can be called directly too, if you want to do something unusual. + Passing report=0 to testmod is especially useful then, to delay + displaying a summary. Invoke doctest.master.summarize(verbose) + when you're done fiddling. + """ + global master + + if isprivate is not None: + warnings.warn("the isprivate argument is deprecated; " + "examine DocTestFinder.find() lists instead", + DeprecationWarning) + + # If no module was given, then use __main__. + if m is None: + # DWA - m will still be None if this wasn't invoked from the command + # line, in which case the following TypeError is about as good an error + # as we should expect + m = sys.modules.get('__main__') + + # Check that we were actually given a module. + if not inspect.ismodule(m): + raise TypeError("testmod: module required; %r" % (m,)) + + # If no name was given, then use the module's name. + if name is None: + name = m.__name__ + + # Find, parse, and run all tests in the given module. + finder = DocTestFinder(_namefilter=isprivate, exclude_empty=exclude_empty) + + if raise_on_error: + runner = DebugRunner(verbose=verbose, optionflags=optionflags) + else: + runner = DocTestRunner(verbose=verbose, optionflags=optionflags) + + for test in finder.find(m, name, globs=globs, extraglobs=extraglobs): + runner.run(test) + + if report: + runner.summarize() + + if master is None: + master = runner + else: + master.merge(runner) + + return runner.failures, runner.tries + +def testfile(filename, module_relative=True, name=None, package=None, + globs=None, verbose=None, report=True, optionflags=0, + extraglobs=None, raise_on_error=False, parser=DocTestParser()): + """ + Test examples in the given file. Return (#failures, #tests). + + Optional keyword arg "module_relative" specifies how filenames + should be interpreted: + + - If "module_relative" is True (the default), then "filename" + specifies a module-relative path. By default, this path is + relative to the calling module's directory; but if the + "package" argument is specified, then it is relative to that + package. To ensure os-independence, "filename" should use + "/" characters to separate path segments, and should not + be an absolute path (i.e., it may not begin with "/"). + + - If "module_relative" is False, then "filename" specifies an + os-specific path. The path may be absolute or relative (to + the current working directory). + + Optional keyword arg "name" gives the name of the test; by default + use the file's basename. + + Optional keyword argument "package" is a Python package or the + name of a Python package whose directory should be used as the + base directory for a module relative filename. If no package is + specified, then the calling module's directory is used as the base + directory for module relative filenames. It is an error to + specify "package" if "module_relative" is False. + + Optional keyword arg "globs" gives a dict to be used as the globals + when executing examples; by default, use {}. A copy of this dict + is actually used for each docstring, so that each docstring's + examples start with a clean slate. + + Optional keyword arg "extraglobs" gives a dictionary that should be + merged into the globals that are used to execute examples. By + default, no extra globals are used. + + Optional keyword arg "verbose" prints lots of stuff if true, prints + only failures if false; by default, it's true iff "-v" is in sys.argv. + + Optional keyword arg "report" prints a summary at the end when true, + else prints nothing at the end. In verbose mode, the summary is + detailed, else very brief (in fact, empty if all tests passed). + + Optional keyword arg "optionflags" or's together module constants, + and defaults to 0. Possible values (see the docs for details): + + DONT_ACCEPT_TRUE_FOR_1 + DONT_ACCEPT_BLANKLINE + NORMALIZE_WHITESPACE + ELLIPSIS + IGNORE_EXCEPTION_DETAIL + REPORT_UDIFF + REPORT_CDIFF + REPORT_NDIFF + REPORT_ONLY_FIRST_FAILURE + + Optional keyword arg "raise_on_error" raises an exception on the + first unexpected exception or failure. This allows failures to be + post-mortem debugged. + + Optional keyword arg "parser" specifies a DocTestParser (or + subclass) that should be used to extract tests from the files. + + Advanced tomfoolery: testmod runs methods of a local instance of + class doctest.Tester, then merges the results into (or creates) + global Tester instance doctest.master. Methods of doctest.master + can be called directly too, if you want to do something unusual. + Passing report=0 to testmod is especially useful then, to delay + displaying a summary. Invoke doctest.master.summarize(verbose) + when you're done fiddling. + """ + global master + + if package and not module_relative: + raise ValueError("Package may only be specified for module-" + "relative paths.") + + # Relativize the path + if module_relative: + package = _normalize_module(package) + filename = _module_relative_path(package, filename) + + # If no name was given, then use the file's name. + if name is None: + name = os.path.basename(filename) + + # Assemble the globals. + if globs is None: + globs = {} + else: + globs = globs.copy() + if extraglobs is not None: + globs.update(extraglobs) + + if raise_on_error: + runner = DebugRunner(verbose=verbose, optionflags=optionflags) + else: + runner = DocTestRunner(verbose=verbose, optionflags=optionflags) + + # Read the file, convert it to a test, and run it. + s = open(filename).read() + test = parser.get_doctest(s, globs, name, filename, 0) + runner.run(test) + + if report: + runner.summarize() + + if master is None: + master = runner + else: + master.merge(runner) + + return runner.failures, runner.tries + +def run_docstring_examples(f, globs, verbose=False, name="NoName", + compileflags=None, optionflags=0): + """ + Test examples in the given object's docstring (`f`), using `globs` + as globals. Optional argument `name` is used in failure messages. + If the optional argument `verbose` is true, then generate output + even if there are no failures. + + `compileflags` gives the set of flags that should be used by the + Python compiler when running the examples. If not specified, then + it will default to the set of future-import flags that apply to + `globs`. + + Optional keyword arg `optionflags` specifies options for the + testing and output. See the documentation for `testmod` for more + information. + """ + # Find, parse, and run all tests in the given module. + finder = DocTestFinder(verbose=verbose, recurse=False) + runner = DocTestRunner(verbose=verbose, optionflags=optionflags) + for test in finder.find(f, name, globs=globs): + runner.run(test, compileflags=compileflags) + +###################################################################### +## 7. Tester +###################################################################### +# This is provided only for backwards compatibility. It's not +# actually used in any way. + +class Tester: + def __init__(self, mod=None, globs=None, verbose=None, + isprivate=None, optionflags=0): + + warnings.warn("class Tester is deprecated; " + "use class doctest.DocTestRunner instead", + DeprecationWarning, stacklevel=2) + if mod is None and globs is None: + raise TypeError("Tester.__init__: must specify mod or globs") + if mod is not None and not inspect.ismodule(mod): + raise TypeError("Tester.__init__: mod must be a module; %r" % + (mod,)) + if globs is None: + globs = mod.__dict__ + self.globs = globs + + self.verbose = verbose + self.isprivate = isprivate + self.optionflags = optionflags + self.testfinder = DocTestFinder(_namefilter=isprivate) + self.testrunner = DocTestRunner(verbose=verbose, + optionflags=optionflags) + + def runstring(self, s, name): + test = DocTestParser().get_doctest(s, self.globs, name, None, None) + if self.verbose: + print "Running string", name + (f,t) = self.testrunner.run(test) + if self.verbose: + print f, "of", t, "examples failed in string", name + return (f,t) + + def rundoc(self, object, name=None, module=None): + f = t = 0 + tests = self.testfinder.find(object, name, module=module, + globs=self.globs) + for test in tests: + (f2, t2) = self.testrunner.run(test) + (f,t) = (f+f2, t+t2) + return (f,t) + + def rundict(self, d, name, module=None): + import new + m = new.module(name) + m.__dict__.update(d) + if module is None: + module = False + return self.rundoc(m, name, module) + + def run__test__(self, d, name): + import new + m = new.module(name) + m.__test__ = d + return self.rundoc(m, name) + + def summarize(self, verbose=None): + return self.testrunner.summarize(verbose) + + def merge(self, other): + self.testrunner.merge(other.testrunner) + +###################################################################### +## 8. Unittest Support +###################################################################### + +_unittest_reportflags = 0 + +def set_unittest_reportflags(flags): + global _unittest_reportflags + + if (flags & REPORTING_FLAGS) != flags: + raise ValueError("Only reporting flags allowed", flags) + old = _unittest_reportflags + _unittest_reportflags = flags + return old + + +class DocTestCase(unittest.TestCase): + + def __init__(self, test, optionflags=0, setUp=None, tearDown=None, + checker=None): + + unittest.TestCase.__init__(self) + self._dt_optionflags = optionflags + self._dt_checker = checker + self._dt_test = test + self._dt_setUp = setUp + self._dt_tearDown = tearDown + + def setUp(self): + test = self._dt_test + + if self._dt_setUp is not None: + self._dt_setUp(test) + + def tearDown(self): + test = self._dt_test + + if self._dt_tearDown is not None: + self._dt_tearDown(test) + + test.globs.clear() + + def runTest(self): + test = self._dt_test + old = sys.stdout + new = StringIO() + optionflags = self._dt_optionflags + + if not (optionflags & REPORTING_FLAGS): + # The option flags don't include any reporting flags, + # so add the default reporting flags + optionflags |= _unittest_reportflags + + runner = DocTestRunner(optionflags=optionflags, + checker=self._dt_checker, verbose=False) + + try: + runner.DIVIDER = "-"*70 + failures, tries = runner.run( + test, out=new.write, clear_globs=False) + finally: + sys.stdout = old + + if failures: + raise self.failureException(self.format_failure(new.getvalue())) + + def format_failure(self, err): + test = self._dt_test + if test.lineno is None: + lineno = 'unknown line number' + else: + lineno = '%s' % test.lineno + lname = '.'.join(test.name.split('.')[-1:]) + return ('Failed doctest test for %s\n' + ' File "%s", line %s, in %s\n\n%s' + % (test.name, test.filename, lineno, lname, err) + ) + + def debug(self): + self.setUp() + runner = DebugRunner(optionflags=self._dt_optionflags, + checker=self._dt_checker, verbose=False) + runner.run(self._dt_test) + self.tearDown() + + def id(self): + return self._dt_test.name + + def __repr__(self): + name = self._dt_test.name.split('.') + return "%s (%s)" % (name[-1], '.'.join(name[:-1])) + + __str__ = __repr__ + + def shortDescription(self): + return "Doctest: " + self._dt_test.name + +def DocTestSuite(module=None, globs=None, extraglobs=None, test_finder=None, + **options): + """ + Convert doctest tests for a module to a unittest test suite. + + This converts each documentation string in a module that + contains doctest tests to a unittest test case. If any of the + tests in a doc string fail, then the test case fails. An exception + is raised showing the name of the file containing the test and a + (sometimes approximate) line number. + + The `module` argument provides the module to be tested. The argument + can be either a module or a module name. + + If no argument is given, the calling module is used. + + A number of options may be provided as keyword arguments: + + setUp + A set-up function. This is called before running the + tests in each file. The setUp function will be passed a DocTest + object. The setUp function can access the test globals as the + globs attribute of the test passed. + + tearDown + A tear-down function. This is called after running the + tests in each file. The tearDown function will be passed a DocTest + object. The tearDown function can access the test globals as the + globs attribute of the test passed. + + globs + A dictionary containing initial global variables for the tests. + + optionflags + A set of doctest option flags expressed as an integer. + """ + + if test_finder is None: + test_finder = DocTestFinder() + + module = _normalize_module(module) + tests = test_finder.find(module, globs=globs, extraglobs=extraglobs) + if globs is None: + globs = module.__dict__ + if not tests: + # Why do we want to do this? Because it reveals a bug that might + # otherwise be hidden. + raise ValueError(module, "has no tests") + + tests.sort() + suite = unittest.TestSuite() + for test in tests: + if len(test.examples) == 0: + continue + if not test.filename: + filename = module.__file__ + if filename[-4:] in (".pyc", ".pyo"): + filename = filename[:-1] + elif sys.platform.startswith('java') and \ + filename.endswith('$py.class'): + filename = '%s.py' % filename[:-9] + test.filename = filename + suite.addTest(DocTestCase(test, **options)) + + return suite + +class DocFileCase(DocTestCase): + + def id(self): + return '_'.join(self._dt_test.name.split('.')) + + def __repr__(self): + return self._dt_test.filename + __str__ = __repr__ + + def format_failure(self, err): + return ('Failed doctest test for %s\n File "%s", line 0\n\n%s' + % (self._dt_test.name, self._dt_test.filename, err) + ) + +def DocFileTest(path, module_relative=True, package=None, + globs=None, parser=DocTestParser(), **options): + if globs is None: + globs = {} + + if package and not module_relative: + raise ValueError("Package may only be specified for module-" + "relative paths.") + + # Relativize the path. + if module_relative: + package = _normalize_module(package) + path = _module_relative_path(package, path) + + # Find the file and read it. + name = os.path.basename(path) + doc = open(path).read() + + # Convert it to a test, and wrap it in a DocFileCase. + test = parser.get_doctest(doc, globs, name, path, 0) + return DocFileCase(test, **options) + +def DocFileSuite(*paths, **kw): + """A unittest suite for one or more doctest files. + + The path to each doctest file is given as a string; the + interpretation of that string depends on the keyword argument + "module_relative". + + A number of options may be provided as keyword arguments: + + module_relative + If "module_relative" is True, then the given file paths are + interpreted as os-independent module-relative paths. By + default, these paths are relative to the calling module's + directory; but if the "package" argument is specified, then + they are relative to that package. To ensure os-independence, + "filename" should use "/" characters to separate path + segments, and may not be an absolute path (i.e., it may not + begin with "/"). + + If "module_relative" is False, then the given file paths are + interpreted as os-specific paths. These paths may be absolute + or relative (to the current working directory). + + package + A Python package or the name of a Python package whose directory + should be used as the base directory for module relative paths. + If "package" is not specified, then the calling module's + directory is used as the base directory for module relative + filenames. It is an error to specify "package" if + "module_relative" is False. + + setUp + A set-up function. This is called before running the + tests in each file. The setUp function will be passed a DocTest + object. The setUp function can access the test globals as the + globs attribute of the test passed. + + tearDown + A tear-down function. This is called after running the + tests in each file. The tearDown function will be passed a DocTest + object. The tearDown function can access the test globals as the + globs attribute of the test passed. + + globs + A dictionary containing initial global variables for the tests. + + optionflags + A set of doctest option flags expressed as an integer. + + parser + A DocTestParser (or subclass) that should be used to extract + tests from the files. + """ + suite = unittest.TestSuite() + + # We do this here so that _normalize_module is called at the right + # level. If it were called in DocFileTest, then this function + # would be the caller and we might guess the package incorrectly. + if kw.get('module_relative', True): + kw['package'] = _normalize_module(kw.get('package')) + + for path in paths: + suite.addTest(DocFileTest(path, **kw)) + + return suite + +###################################################################### +## 9. Debugging Support +###################################################################### + +def script_from_examples(s): + output = [] + for piece in DocTestParser().parse(s): + if isinstance(piece, Example): + # Add the example's source code (strip trailing NL) + output.append(piece.source[:-1]) + # Add the expected output: + want = piece.want + if want: + output.append('# Expected:') + output += ['## '+l for l in want.split('\n')[:-1]] + else: + # Add non-example text. + output += [_comment_line(l) + for l in piece.split('\n')[:-1]] + + # Trim junk on both ends. + while output and output[-1] == '#': + output.pop() + while output and output[0] == '#': + output.pop(0) + # Combine the output, and return it. + # Add a courtesy newline to prevent exec from choking (see bug #1172785) + return '\n'.join(output) + '\n' + +def testsource(module, name): + """Extract the test sources from a doctest docstring as a script. + + Provide the module (or dotted name of the module) containing the + test to be debugged and the name (within the module) of the object + with the doc string with tests to be debugged. + """ + module = _normalize_module(module) + tests = DocTestFinder().find(module) + test = [t for t in tests if t.name == name] + if not test: + raise ValueError(name, "not found in tests") + test = test[0] + testsrc = script_from_examples(test.docstring) + return testsrc + +def debug_src(src, pm=False, globs=None): + """Debug a single doctest docstring, in argument `src`'""" + testsrc = script_from_examples(src) + debug_script(testsrc, pm, globs) + +def debug_script(src, pm=False, globs=None): + "Debug a test script. `src` is the script, as a string." + import pdb + + # Note that tempfile.NameTemporaryFile() cannot be used. As the + # docs say, a file so created cannot be opened by name a second time + # on modern Windows boxes, and execfile() needs to open it. + srcfilename = tempfile.mktemp(".py", "doctestdebug") + f = open(srcfilename, 'w') + f.write(src) + f.close() + + try: + if globs: + globs = globs.copy() + else: + globs = {} + + if pm: + try: + execfile(srcfilename, globs, globs) + except: + print sys.exc_info()[1] + pdb.post_mortem(sys.exc_info()[2]) + else: + # Note that %r is vital here. '%s' instead can, e.g., cause + # backslashes to get treated as metacharacters on Windows. + pdb.run("execfile(%r)" % srcfilename, globs, globs) + + finally: + os.remove(srcfilename) + +def debug(module, name, pm=False): + """Debug a single doctest docstring. + + Provide the module (or dotted name of the module) containing the + test to be debugged and the name (within the module) of the object + with the docstring with tests to be debugged. + """ + module = _normalize_module(module) + testsrc = testsource(module, name) + debug_script(testsrc, pm, module.__dict__) + + +__test__ = {} diff --git a/env/lib/python2.7/site-packages/nose/ext/dtcompat.pyc b/env/lib/python2.7/site-packages/nose/ext/dtcompat.pyc new file mode 100644 index 0000000..951593b Binary files /dev/null and b/env/lib/python2.7/site-packages/nose/ext/dtcompat.pyc differ diff --git a/env/lib/python2.7/site-packages/nose/failure.py b/env/lib/python2.7/site-packages/nose/failure.py new file mode 100644 index 0000000..c5fabfd --- /dev/null +++ b/env/lib/python2.7/site-packages/nose/failure.py @@ -0,0 +1,42 @@ +import logging +import unittest +from traceback import format_tb +from nose.pyversion import is_base_exception + +log = logging.getLogger(__name__) + + +__all__ = ['Failure'] + + +class Failure(unittest.TestCase): + """Unloadable or unexecutable test. + + A Failure case is placed in a test suite to indicate the presence of a + test that could not be loaded or executed. A common example is a test + module that fails to import. + + """ + __test__ = False # do not collect + def __init__(self, exc_class, exc_val, tb=None, address=None): + log.debug("A failure! %s %s %s", exc_class, exc_val, format_tb(tb)) + self.exc_class = exc_class + self.exc_val = exc_val + self.tb = tb + self._address = address + unittest.TestCase.__init__(self) + + def __str__(self): + return "Failure: %s (%s)" % ( + getattr(self.exc_class, '__name__', self.exc_class), self.exc_val) + + def address(self): + return self._address + + def runTest(self): + if self.tb is not None: + if is_base_exception(self.exc_val): + raise self.exc_val, None, self.tb + raise self.exc_class, self.exc_val, self.tb + else: + raise self.exc_class(self.exc_val) diff --git a/env/lib/python2.7/site-packages/nose/failure.pyc b/env/lib/python2.7/site-packages/nose/failure.pyc new file mode 100644 index 0000000..7c82441 Binary files /dev/null and b/env/lib/python2.7/site-packages/nose/failure.pyc differ diff --git a/env/lib/python2.7/site-packages/nose/importer.py b/env/lib/python2.7/site-packages/nose/importer.py new file mode 100644 index 0000000..e677658 --- /dev/null +++ b/env/lib/python2.7/site-packages/nose/importer.py @@ -0,0 +1,167 @@ +"""Implements an importer that looks only in specific path (ignoring +sys.path), and uses a per-path cache in addition to sys.modules. This is +necessary because test modules in different directories frequently have the +same names, which means that the first loaded would mask the rest when using +the builtin importer. +""" +import logging +import os +import sys +from nose.config import Config + +from imp import find_module, load_module, acquire_lock, release_lock + +log = logging.getLogger(__name__) + +try: + _samefile = os.path.samefile +except AttributeError: + def _samefile(src, dst): + return (os.path.normcase(os.path.realpath(src)) == + os.path.normcase(os.path.realpath(dst))) + + +class Importer(object): + """An importer class that does only path-specific imports. That + is, the given module is not searched for on sys.path, but only at + the path or in the directory specified. + """ + def __init__(self, config=None): + if config is None: + config = Config() + self.config = config + + def importFromPath(self, path, fqname): + """Import a dotted-name package whose tail is at path. In other words, + given foo.bar and path/to/foo/bar.py, import foo from path/to/foo then + bar from path/to/foo/bar, returning bar. + """ + # find the base dir of the package + path_parts = os.path.normpath(os.path.abspath(path)).split(os.sep) + name_parts = fqname.split('.') + if path_parts[-1] == '__init__.py': + path_parts.pop() + path_parts = path_parts[:-(len(name_parts))] + dir_path = os.sep.join(path_parts) + # then import fqname starting from that dir + return self.importFromDir(dir_path, fqname) + + def importFromDir(self, dir, fqname): + """Import a module *only* from path, ignoring sys.path and + reloading if the version in sys.modules is not the one we want. + """ + dir = os.path.normpath(os.path.abspath(dir)) + log.debug("Import %s from %s", fqname, dir) + + # FIXME reimplement local per-dir cache? + + # special case for __main__ + if fqname == '__main__': + return sys.modules[fqname] + + if self.config.addPaths: + add_path(dir, self.config) + + path = [dir] + parts = fqname.split('.') + part_fqname = '' + mod = parent = fh = None + + for part in parts: + if part_fqname == '': + part_fqname = part + else: + part_fqname = "%s.%s" % (part_fqname, part) + try: + acquire_lock() + log.debug("find module part %s (%s) in %s", + part, part_fqname, path) + fh, filename, desc = find_module(part, path) + old = sys.modules.get(part_fqname) + if old is not None: + # test modules frequently have name overlap; make sure + # we get a fresh copy of anything we are trying to load + # from a new path + log.debug("sys.modules has %s as %s", part_fqname, old) + if (self.sameModule(old, filename) + or (self.config.firstPackageWins and + getattr(old, '__path__', None))): + mod = old + else: + del sys.modules[part_fqname] + mod = load_module(part_fqname, fh, filename, desc) + else: + mod = load_module(part_fqname, fh, filename, desc) + finally: + if fh: + fh.close() + release_lock() + if parent: + setattr(parent, part, mod) + if hasattr(mod, '__path__'): + path = mod.__path__ + parent = mod + return mod + + def _dirname_if_file(self, filename): + # We only take the dirname if we have a path to a non-dir, + # because taking the dirname of a symlink to a directory does not + # give the actual directory parent. + if os.path.isdir(filename): + return filename + else: + return os.path.dirname(filename) + + def sameModule(self, mod, filename): + mod_paths = [] + if hasattr(mod, '__path__'): + for path in mod.__path__: + mod_paths.append(self._dirname_if_file(path)) + elif hasattr(mod, '__file__'): + mod_paths.append(self._dirname_if_file(mod.__file__)) + else: + # builtin or other module-like object that + # doesn't have __file__; must be new + return False + new_path = self._dirname_if_file(filename) + for mod_path in mod_paths: + log.debug( + "module already loaded? mod: %s new: %s", + mod_path, new_path) + if _samefile(mod_path, new_path): + return True + return False + + +def add_path(path, config=None): + """Ensure that the path, or the root of the current package (if + path is in a package), is in sys.path. + """ + + # FIXME add any src-looking dirs seen too... need to get config for that + + log.debug('Add path %s' % path) + if not path: + return [] + added = [] + parent = os.path.dirname(path) + if (parent + and os.path.exists(os.path.join(path, '__init__.py'))): + added.extend(add_path(parent, config)) + elif not path in sys.path: + log.debug("insert %s into sys.path", path) + sys.path.insert(0, path) + added.append(path) + if config and config.srcDirs: + for dirname in config.srcDirs: + dirpath = os.path.join(path, dirname) + if os.path.isdir(dirpath): + sys.path.insert(0, dirpath) + added.append(dirpath) + return added + + +def remove_path(path): + log.debug('Remove path %s' % path) + if path in sys.path: + sys.path.remove(path) diff --git a/env/lib/python2.7/site-packages/nose/importer.pyc b/env/lib/python2.7/site-packages/nose/importer.pyc new file mode 100644 index 0000000..6328f86 Binary files /dev/null and b/env/lib/python2.7/site-packages/nose/importer.pyc differ diff --git a/env/lib/python2.7/site-packages/nose/inspector.py b/env/lib/python2.7/site-packages/nose/inspector.py new file mode 100644 index 0000000..a6c4a3e --- /dev/null +++ b/env/lib/python2.7/site-packages/nose/inspector.py @@ -0,0 +1,207 @@ +"""Simple traceback introspection. Used to add additional information to +AssertionErrors in tests, so that failure messages may be more informative. +""" +import inspect +import logging +import re +import sys +import textwrap +import tokenize + +try: + from cStringIO import StringIO +except ImportError: + from StringIO import StringIO + +log = logging.getLogger(__name__) + +def inspect_traceback(tb): + """Inspect a traceback and its frame, returning source for the expression + where the exception was raised, with simple variable replacement performed + and the line on which the exception was raised marked with '>>' + """ + log.debug('inspect traceback %s', tb) + + # we only want the innermost frame, where the exception was raised + while tb.tb_next: + tb = tb.tb_next + + frame = tb.tb_frame + lines, exc_line = tbsource(tb) + + # figure out the set of lines to grab. + inspect_lines, mark_line = find_inspectable_lines(lines, exc_line) + src = StringIO(textwrap.dedent(''.join(inspect_lines))) + exp = Expander(frame.f_locals, frame.f_globals) + + while inspect_lines: + try: + for tok in tokenize.generate_tokens(src.readline): + exp(*tok) + except tokenize.TokenError, e: + # this can happen if our inspectable region happens to butt up + # against the end of a construct like a docstring with the closing + # """ on separate line + log.debug("Tokenizer error: %s", e) + inspect_lines.pop(0) + mark_line -= 1 + src = StringIO(textwrap.dedent(''.join(inspect_lines))) + exp = Expander(frame.f_locals, frame.f_globals) + continue + break + padded = [] + if exp.expanded_source: + exp_lines = exp.expanded_source.split('\n') + ep = 0 + for line in exp_lines: + if ep == mark_line: + padded.append('>> ' + line) + else: + padded.append(' ' + line) + ep += 1 + return '\n'.join(padded) + + +def tbsource(tb, context=6): + """Get source from a traceback object. + + A tuple of two things is returned: a list of lines of context from + the source code, and the index of the current line within that list. + The optional second argument specifies the number of lines of context + to return, which are centered around the current line. + + .. Note :: + This is adapted from inspect.py in the python 2.4 standard library, + since a bug in the 2.3 version of inspect prevents it from correctly + locating source lines in a traceback frame. + """ + + lineno = tb.tb_lineno + frame = tb.tb_frame + + if context > 0: + start = lineno - 1 - context//2 + log.debug("lineno: %s start: %s", lineno, start) + + try: + lines, dummy = inspect.findsource(frame) + except IOError: + lines, index = [''], 0 + else: + all_lines = lines + start = max(start, 1) + start = max(0, min(start, len(lines) - context)) + lines = lines[start:start+context] + index = lineno - 1 - start + + # python 2.5 compat: if previous line ends in a continuation, + # decrement start by 1 to match 2.4 behavior + if sys.version_info >= (2, 5) and index > 0: + while lines[index-1].strip().endswith('\\'): + start -= 1 + lines = all_lines[start:start+context] + else: + lines, index = [''], 0 + log.debug("tbsource lines '''%s''' around index %s", lines, index) + return (lines, index) + + +def find_inspectable_lines(lines, pos): + """Find lines in home that are inspectable. + + Walk back from the err line up to 3 lines, but don't walk back over + changes in indent level. + + Walk forward up to 3 lines, counting \ separated lines as 1. Don't walk + over changes in indent level (unless part of an extended line) + """ + cnt = re.compile(r'\\[\s\n]*$') + df = re.compile(r':[\s\n]*$') + ind = re.compile(r'^(\s*)') + toinspect = [] + home = lines[pos] + home_indent = ind.match(home).groups()[0] + + before = lines[max(pos-3, 0):pos] + before.reverse() + after = lines[pos+1:min(pos+4, len(lines))] + + for line in before: + if ind.match(line).groups()[0] == home_indent: + toinspect.append(line) + else: + break + toinspect.reverse() + toinspect.append(home) + home_pos = len(toinspect)-1 + continued = cnt.search(home) + for line in after: + if ((continued or ind.match(line).groups()[0] == home_indent) + and not df.search(line)): + toinspect.append(line) + continued = cnt.search(line) + else: + break + log.debug("Inspecting lines '''%s''' around %s", toinspect, home_pos) + return toinspect, home_pos + + +class Expander: + """Simple expression expander. Uses tokenize to find the names and + expands any that can be looked up in the frame. + """ + def __init__(self, locals, globals): + self.locals = locals + self.globals = globals + self.lpos = None + self.expanded_source = '' + + def __call__(self, ttype, tok, start, end, line): + # TODO + # deal with unicode properly + + # TODO + # Dealing with instance members + # always keep the last thing seen + # if the current token is a dot, + # get ready to getattr(lastthing, this thing) on the + # next call. + + if self.lpos is not None: + if start[1] >= self.lpos: + self.expanded_source += ' ' * (start[1]-self.lpos) + elif start[1] < self.lpos: + # newline, indent correctly + self.expanded_source += ' ' * start[1] + self.lpos = end[1] + + if ttype == tokenize.INDENT: + pass + elif ttype == tokenize.NAME: + # Clean this junk up + try: + val = self.locals[tok] + if callable(val): + val = tok + else: + val = repr(val) + except KeyError: + try: + val = self.globals[tok] + if callable(val): + val = tok + else: + val = repr(val) + + except KeyError: + val = tok + # FIXME... not sure how to handle things like funcs, classes + # FIXME this is broken for some unicode strings + self.expanded_source += val + else: + self.expanded_source += tok + # if this is the end of the line and the line ends with + # \, then tack a \ and newline onto the output + # print line[end[1]:] + if re.match(r'\s+\\\n', line[end[1]:]): + self.expanded_source += ' \\\n' diff --git a/env/lib/python2.7/site-packages/nose/inspector.pyc b/env/lib/python2.7/site-packages/nose/inspector.pyc new file mode 100644 index 0000000..53f5cdb Binary files /dev/null and b/env/lib/python2.7/site-packages/nose/inspector.pyc differ diff --git a/env/lib/python2.7/site-packages/nose/loader.py b/env/lib/python2.7/site-packages/nose/loader.py new file mode 100644 index 0000000..3744e54 --- /dev/null +++ b/env/lib/python2.7/site-packages/nose/loader.py @@ -0,0 +1,623 @@ +""" +Test Loader +----------- + +nose's test loader implements the same basic functionality as its +superclass, unittest.TestLoader, but extends it by more liberal +interpretations of what may be a test and how a test may be named. +""" +from __future__ import generators + +import logging +import os +import sys +import unittest +import types +from inspect import isfunction +from nose.pyversion import unbound_method, ismethod +from nose.case import FunctionTestCase, MethodTestCase +from nose.failure import Failure +from nose.config import Config +from nose.importer import Importer, add_path, remove_path +from nose.selector import defaultSelector, TestAddress +from nose.util import func_lineno, getpackage, isclass, isgenerator, \ + ispackage, regex_last_key, resolve_name, transplant_func, \ + transplant_class, test_address +from nose.suite import ContextSuiteFactory, ContextList, LazySuite +from nose.pyversion import sort_list, cmp_to_key + + +log = logging.getLogger(__name__) +#log.setLevel(logging.DEBUG) + +# for efficiency and easier mocking +op_normpath = os.path.normpath +op_abspath = os.path.abspath +op_join = os.path.join +op_isdir = os.path.isdir +op_isfile = os.path.isfile + + +__all__ = ['TestLoader', 'defaultTestLoader'] + + +class TestLoader(unittest.TestLoader): + """Test loader that extends unittest.TestLoader to: + + * Load tests from test-like functions and classes that are not + unittest.TestCase subclasses + * Find and load test modules in a directory + * Support tests that are generators + * Support easy extensions of or changes to that behavior through plugins + """ + config = None + importer = None + workingDir = None + selector = None + suiteClass = None + + def __init__(self, config=None, importer=None, workingDir=None, + selector=None): + """Initialize a test loader. + + Parameters (all optional): + + * config: provide a `nose.config.Config`_ or other config class + instance; if not provided a `nose.config.Config`_ with + default values is used. + * importer: provide an importer instance that implements + `importFromPath`. If not provided, a + `nose.importer.Importer`_ is used. + * workingDir: the directory to which file and module names are + relative. If not provided, assumed to be the current working + directory. + * selector: a selector class or instance. If a class is + provided, it will be instantiated with one argument, the + current config. If not provided, a `nose.selector.Selector`_ + is used. + """ + if config is None: + config = Config() + if importer is None: + importer = Importer(config=config) + if workingDir is None: + workingDir = config.workingDir + if selector is None: + selector = defaultSelector(config) + elif isclass(selector): + selector = selector(config) + self.config = config + self.importer = importer + self.workingDir = op_normpath(op_abspath(workingDir)) + self.selector = selector + if config.addPaths: + add_path(workingDir, config) + self.suiteClass = ContextSuiteFactory(config=config) + + self._visitedPaths = set([]) + + unittest.TestLoader.__init__(self) + + def getTestCaseNames(self, testCaseClass): + """Override to select with selector, unless + config.getTestCaseNamesCompat is True + """ + if self.config.getTestCaseNamesCompat: + return unittest.TestLoader.getTestCaseNames(self, testCaseClass) + + def wanted(attr, cls=testCaseClass, sel=self.selector): + item = getattr(cls, attr, None) + if isfunction(item): + item = unbound_method(cls, item) + elif not ismethod(item): + return False + return sel.wantMethod(item) + + cases = filter(wanted, dir(testCaseClass)) + + # add runTest if nothing else picked + if not cases and hasattr(testCaseClass, 'runTest'): + cases = ['runTest'] + if self.sortTestMethodsUsing: + sort_list(cases, cmp_to_key(self.sortTestMethodsUsing)) + return cases + + def _haveVisited(self, path): + # For cases where path is None, we always pretend we haven't visited + # them. + if path is None: + return False + + return path in self._visitedPaths + + def _addVisitedPath(self, path): + if path is not None: + self._visitedPaths.add(path) + + def loadTestsFromDir(self, path): + """Load tests from the directory at path. This is a generator + -- each suite of tests from a module or other file is yielded + and is expected to be executed before the next file is + examined. + """ + log.debug("load from dir %s", path) + plugins = self.config.plugins + plugins.beforeDirectory(path) + if self.config.addPaths: + paths_added = add_path(path, self.config) + + entries = os.listdir(path) + sort_list(entries, regex_last_key(self.config.testMatch)) + for entry in entries: + # this hard-coded initial-dot test will be removed: + # http://code.google.com/p/python-nose/issues/detail?id=82 + if entry.startswith('.'): + continue + entry_path = op_abspath(op_join(path, entry)) + is_file = op_isfile(entry_path) + wanted = False + if is_file: + is_dir = False + wanted = self.selector.wantFile(entry_path) + else: + is_dir = op_isdir(entry_path) + if is_dir: + # this hard-coded initial-underscore test will be removed: + # http://code.google.com/p/python-nose/issues/detail?id=82 + if entry.startswith('_'): + continue + wanted = self.selector.wantDirectory(entry_path) + is_package = ispackage(entry_path) + + # Python 3.3 now implements PEP 420: Implicit Namespace Packages. + # As a result, it's now possible that parent paths that have a + # segment with the same basename as our package ends up + # in module.__path__. So we have to keep track of what we've + # visited, and not-revisit them again. + if wanted and not self._haveVisited(entry_path): + self._addVisitedPath(entry_path) + if is_file: + plugins.beforeContext() + if entry.endswith('.py'): + yield self.loadTestsFromName( + entry_path, discovered=True) + else: + yield self.loadTestsFromFile(entry_path) + plugins.afterContext() + elif is_package: + # Load the entry as a package: given the full path, + # loadTestsFromName() will figure it out + yield self.loadTestsFromName( + entry_path, discovered=True) + else: + # Another test dir in this one: recurse lazily + yield self.suiteClass( + lambda: self.loadTestsFromDir(entry_path)) + tests = [] + for test in plugins.loadTestsFromDir(path): + tests.append(test) + # TODO: is this try/except needed? + try: + if tests: + yield self.suiteClass(tests) + except (KeyboardInterrupt, SystemExit): + raise + except: + yield self.suiteClass([Failure(*sys.exc_info())]) + + # pop paths + if self.config.addPaths: + for p in paths_added: + remove_path(p) + plugins.afterDirectory(path) + + def loadTestsFromFile(self, filename): + """Load tests from a non-module file. Default is to raise a + ValueError; plugins may implement `loadTestsFromFile` to + provide a list of tests loaded from the file. + """ + log.debug("Load from non-module file %s", filename) + try: + tests = [test for test in + self.config.plugins.loadTestsFromFile(filename)] + if tests: + # Plugins can yield False to indicate that they were + # unable to load tests from a file, but it was not an + # error -- the file just had no tests to load. + tests = filter(None, tests) + return self.suiteClass(tests) + else: + # Nothing was able to even try to load from this file + open(filename, 'r').close() # trigger os error + raise ValueError("Unable to load tests from file %s" + % filename) + except (KeyboardInterrupt, SystemExit): + raise + except: + exc = sys.exc_info() + return self.suiteClass( + [Failure(exc[0], exc[1], exc[2], + address=(filename, None, None))]) + + def loadTestsFromGenerator(self, generator, module): + """Lazy-load tests from a generator function. The generator function + may yield either: + + * a callable, or + * a function name resolvable within the same module + """ + def generate(g=generator, m=module): + try: + for test in g(): + test_func, arg = self.parseGeneratedTest(test) + if not callable(test_func): + test_func = getattr(m, test_func) + yield FunctionTestCase(test_func, arg=arg, descriptor=g) + except KeyboardInterrupt: + raise + except: + exc = sys.exc_info() + yield Failure(exc[0], exc[1], exc[2], + address=test_address(generator)) + return self.suiteClass(generate, context=generator, can_split=False) + + def loadTestsFromGeneratorMethod(self, generator, cls): + """Lazy-load tests from a generator method. + + This is more complicated than loading from a generator function, + since a generator method may yield: + + * a function + * a bound or unbound method, or + * a method name + """ + # convert the unbound generator method + # into a bound method so it can be called below + if hasattr(generator, 'im_class'): + cls = generator.im_class + inst = cls() + method = generator.__name__ + generator = getattr(inst, method) + + def generate(g=generator, c=cls): + try: + for test in g(): + test_func, arg = self.parseGeneratedTest(test) + if not callable(test_func): + test_func = unbound_method(c, getattr(c, test_func)) + if ismethod(test_func): + yield MethodTestCase(test_func, arg=arg, descriptor=g) + elif callable(test_func): + # In this case we're forcing the 'MethodTestCase' + # to run the inline function as its test call, + # but using the generator method as the 'method of + # record' (so no need to pass it as the descriptor) + yield MethodTestCase(g, test=test_func, arg=arg) + else: + yield Failure( + TypeError, + "%s is not a callable or method" % test_func) + except KeyboardInterrupt: + raise + except: + exc = sys.exc_info() + yield Failure(exc[0], exc[1], exc[2], + address=test_address(generator)) + return self.suiteClass(generate, context=generator, can_split=False) + + def loadTestsFromModule(self, module, path=None, discovered=False): + """Load all tests from module and return a suite containing + them. If the module has been discovered and is not test-like, + the suite will be empty by default, though plugins may add + their own tests. + """ + log.debug("Load from module %s", module) + tests = [] + test_classes = [] + test_funcs = [] + # For *discovered* modules, we only load tests when the module looks + # testlike. For modules we've been directed to load, we always + # look for tests. (discovered is set to True by loadTestsFromDir) + if not discovered or self.selector.wantModule(module): + for item in dir(module): + test = getattr(module, item, None) + # print "Check %s (%s) in %s" % (item, test, module.__name__) + if isclass(test): + if self.selector.wantClass(test): + test_classes.append(test) + elif isfunction(test) and self.selector.wantFunction(test): + test_funcs.append(test) + sort_list(test_classes, lambda x: x.__name__) + sort_list(test_funcs, func_lineno) + tests = map(lambda t: self.makeTest(t, parent=module), + test_classes + test_funcs) + + # Now, descend into packages + # FIXME can or should this be lazy? + # is this syntax 2.2 compatible? + module_paths = getattr(module, '__path__', []) + + if path: + path = os.path.normcase(os.path.realpath(path)) + + for module_path in module_paths: + log.debug("Load tests from module path %s?", module_path) + log.debug("path: %s os.path.realpath(%s): %s", + path, os.path.normcase(module_path), + os.path.realpath(os.path.normcase(module_path))) + if (self.config.traverseNamespace or not path) or \ + os.path.realpath( + os.path.normcase(module_path)).startswith(path): + # Egg files can be on sys.path, so make sure the path is a + # directory before trying to load from it. + if os.path.isdir(module_path): + tests.extend(self.loadTestsFromDir(module_path)) + + for test in self.config.plugins.loadTestsFromModule(module, path): + tests.append(test) + + return self.suiteClass(ContextList(tests, context=module)) + + def loadTestsFromName(self, name, module=None, discovered=False): + """Load tests from the entity with the given name. + + The name may indicate a file, directory, module, or any object + within a module. See `nose.util.split_test_name` for details on + test name parsing. + """ + # FIXME refactor this method into little bites? + log.debug("load from %s (%s)", name, module) + + suite = self.suiteClass + + # give plugins first crack + plug_tests = self.config.plugins.loadTestsFromName(name, module) + if plug_tests: + return suite(plug_tests) + + addr = TestAddress(name, workingDir=self.workingDir) + if module: + # Two cases: + # name is class.foo + # The addr will be incorrect, since it thinks class.foo is + # a dotted module name. It's actually a dotted attribute + # name. In this case we want to use the full submitted + # name as the name to load from the module. + # name is module:class.foo + # The addr will be correct. The part we want is the part after + # the :, which is in addr.call. + if addr.call: + name = addr.call + parent, obj = self.resolve(name, module) + if (isclass(parent) + and getattr(parent, '__module__', None) != module.__name__ + and not isinstance(obj, Failure)): + parent = transplant_class(parent, module.__name__) + obj = getattr(parent, obj.__name__) + log.debug("parent %s obj %s module %s", parent, obj, module) + if isinstance(obj, Failure): + return suite([obj]) + else: + return suite(ContextList([self.makeTest(obj, parent)], + context=parent)) + else: + if addr.module: + try: + if addr.filename is None: + module = resolve_name(addr.module) + else: + self.config.plugins.beforeImport( + addr.filename, addr.module) + # FIXME: to support module.name names, + # do what resolve-name does and keep trying to + # import, popping tail of module into addr.call, + # until we either get an import or run out of + # module parts + try: + module = self.importer.importFromPath( + addr.filename, addr.module) + finally: + self.config.plugins.afterImport( + addr.filename, addr.module) + except (KeyboardInterrupt, SystemExit): + raise + except: + exc = sys.exc_info() + return suite([Failure(exc[0], exc[1], exc[2], + address=addr.totuple())]) + if addr.call: + return self.loadTestsFromName(addr.call, module) + else: + return self.loadTestsFromModule( + module, addr.filename, + discovered=discovered) + elif addr.filename: + path = addr.filename + if addr.call: + package = getpackage(path) + if package is None: + return suite([ + Failure(ValueError, + "Can't find callable %s in file %s: " + "file is not a python module" % + (addr.call, path), + address=addr.totuple())]) + return self.loadTestsFromName(addr.call, module=package) + else: + if op_isdir(path): + # In this case we *can* be lazy since we know + # that each module in the dir will be fully + # loaded before its tests are executed; we + # also know that we're not going to be asked + # to load from . and ./some_module.py *as part + # of this named test load* + return LazySuite( + lambda: self.loadTestsFromDir(path)) + elif op_isfile(path): + return self.loadTestsFromFile(path) + else: + return suite([ + Failure(OSError, "No such file %s" % path, + address=addr.totuple())]) + else: + # just a function? what to do? I think it can only be + # handled when module is not None + return suite([ + Failure(ValueError, "Unresolvable test name %s" % name, + address=addr.totuple())]) + + def loadTestsFromNames(self, names, module=None): + """Load tests from all names, returning a suite containing all + tests. + """ + plug_res = self.config.plugins.loadTestsFromNames(names, module) + if plug_res: + suite, names = plug_res + if suite: + return self.suiteClass([ + self.suiteClass(suite), + unittest.TestLoader.loadTestsFromNames(self, names, module) + ]) + return unittest.TestLoader.loadTestsFromNames(self, names, module) + + def loadTestsFromTestCase(self, testCaseClass): + """Load tests from a unittest.TestCase subclass. + """ + cases = [] + plugins = self.config.plugins + for case in plugins.loadTestsFromTestCase(testCaseClass): + cases.append(case) + # For efficiency in the most common case, just call and return from + # super. This avoids having to extract cases and rebuild a context + # suite when there are no plugin-contributed cases. + if not cases: + return super(TestLoader, self).loadTestsFromTestCase(testCaseClass) + cases.extend( + [case for case in + super(TestLoader, self).loadTestsFromTestCase(testCaseClass)]) + return self.suiteClass(cases) + + def loadTestsFromTestClass(self, cls): + """Load tests from a test class that is *not* a unittest.TestCase + subclass. + + In this case, we can't depend on the class's `__init__` taking method + name arguments, so we have to compose a MethodTestCase for each + method in the class that looks testlike. + """ + def wanted(attr, cls=cls, sel=self.selector): + item = getattr(cls, attr, None) + if isfunction(item): + item = unbound_method(cls, item) + elif not ismethod(item): + return False + return sel.wantMethod(item) + cases = [self.makeTest(getattr(cls, case), cls) + for case in filter(wanted, dir(cls))] + for test in self.config.plugins.loadTestsFromTestClass(cls): + cases.append(test) + return self.suiteClass(ContextList(cases, context=cls)) + + def makeTest(self, obj, parent=None): + try: + return self._makeTest(obj, parent) + except (KeyboardInterrupt, SystemExit): + raise + except: + exc = sys.exc_info() + try: + addr = test_address(obj) + except KeyboardInterrupt: + raise + except: + addr = None + return Failure(exc[0], exc[1], exc[2], address=addr) + + def _makeTest(self, obj, parent=None): + """Given a test object and its parent, return a test case + or test suite. + """ + plug_tests = [] + try: + addr = test_address(obj) + except KeyboardInterrupt: + raise + except: + addr = None + for test in self.config.plugins.makeTest(obj, parent): + plug_tests.append(test) + # TODO: is this try/except needed? + try: + if plug_tests: + return self.suiteClass(plug_tests) + except (KeyboardInterrupt, SystemExit): + raise + except: + exc = sys.exc_info() + return Failure(exc[0], exc[1], exc[2], address=addr) + + if isfunction(obj) and parent and not isinstance(parent, types.ModuleType): + # This is a Python 3.x 'unbound method'. Wrap it with its + # associated class.. + obj = unbound_method(parent, obj) + + if isinstance(obj, unittest.TestCase): + return obj + elif isclass(obj): + if parent and obj.__module__ != parent.__name__: + obj = transplant_class(obj, parent.__name__) + if issubclass(obj, unittest.TestCase): + return self.loadTestsFromTestCase(obj) + else: + return self.loadTestsFromTestClass(obj) + elif ismethod(obj): + if parent is None: + parent = obj.__class__ + if issubclass(parent, unittest.TestCase): + return parent(obj.__name__) + else: + if isgenerator(obj): + return self.loadTestsFromGeneratorMethod(obj, parent) + else: + return MethodTestCase(obj) + elif isfunction(obj): + if parent and obj.__module__ != parent.__name__: + obj = transplant_func(obj, parent.__name__) + if isgenerator(obj): + return self.loadTestsFromGenerator(obj, parent) + else: + return FunctionTestCase(obj) + else: + return Failure(TypeError, + "Can't make a test from %s" % obj, + address=addr) + + def resolve(self, name, module): + """Resolve name within module + """ + obj = module + parts = name.split('.') + for part in parts: + parent, obj = obj, getattr(obj, part, None) + if obj is None: + # no such test + obj = Failure(ValueError, "No such test %s" % name) + return parent, obj + + def parseGeneratedTest(self, test): + """Given the yield value of a test generator, return a func and args. + + This is used in the two loadTestsFromGenerator* methods. + + """ + if not isinstance(test, tuple): # yield test + test_func, arg = (test, tuple()) + elif len(test) == 1: # yield (test,) + test_func, arg = (test[0], tuple()) + else: # yield test, foo, bar, ... + assert len(test) > 1 # sanity check + test_func, arg = (test[0], test[1:]) + return test_func, arg + +defaultTestLoader = TestLoader + diff --git a/env/lib/python2.7/site-packages/nose/loader.pyc b/env/lib/python2.7/site-packages/nose/loader.pyc new file mode 100644 index 0000000..e7eca8d Binary files /dev/null and b/env/lib/python2.7/site-packages/nose/loader.pyc differ diff --git a/env/lib/python2.7/site-packages/nose/plugins/__init__.py b/env/lib/python2.7/site-packages/nose/plugins/__init__.py new file mode 100644 index 0000000..08ee8f3 --- /dev/null +++ b/env/lib/python2.7/site-packages/nose/plugins/__init__.py @@ -0,0 +1,190 @@ +""" +Writing Plugins +--------------- + +nose supports plugins for test collection, selection, observation and +reporting. There are two basic rules for plugins: + +* Plugin classes should subclass :class:`nose.plugins.Plugin`. + +* Plugins may implement any of the methods described in the class + :doc:`IPluginInterface ` in nose.plugins.base. Please note that + this class is for documentary purposes only; plugins may not subclass + IPluginInterface. + +Hello World +=========== + +Here's a basic plugin. It doesn't do much so read on for more ideas or dive +into the :doc:`IPluginInterface ` to see all available hooks. + +.. code-block:: python + + import logging + import os + + from nose.plugins import Plugin + + log = logging.getLogger('nose.plugins.helloworld') + + class HelloWorld(Plugin): + name = 'helloworld' + + def options(self, parser, env=os.environ): + super(HelloWorld, self).options(parser, env=env) + + def configure(self, options, conf): + super(HelloWorld, self).configure(options, conf) + if not self.enabled: + return + + def finalize(self, result): + log.info('Hello pluginized world!') + +Registering +=========== + +.. Note:: + Important note: the following applies only to the default + plugin manager. Other plugin managers may use different means to + locate and load plugins. + +For nose to find a plugin, it must be part of a package that uses +setuptools_, and the plugin must be included in the entry points defined +in the setup.py for the package: + +.. code-block:: python + + setup(name='Some plugin', + # ... + entry_points = { + 'nose.plugins.0.10': [ + 'someplugin = someplugin:SomePlugin' + ] + }, + # ... + ) + +Once the package is installed with install or develop, nose will be able +to load the plugin. + +.. _setuptools: http://peak.telecommunity.com/DevCenter/setuptools + +Registering a plugin without setuptools +======================================= + +It is currently possible to register a plugin programmatically by +creating a custom nose runner like this : + +.. code-block:: python + + import nose + from yourplugin import YourPlugin + + if __name__ == '__main__': + nose.main(addplugins=[YourPlugin()]) + +Defining options +================ + +All plugins must implement the methods ``options(self, parser, env)`` +and ``configure(self, options, conf)``. Subclasses of nose.plugins.Plugin +that want the standard options should call the superclass methods. + +nose uses optparse.OptionParser from the standard library to parse +arguments. A plugin's ``options()`` method receives a parser +instance. It's good form for a plugin to use that instance only to add +additional arguments that take only long arguments (--like-this). Most +of nose's built-in arguments get their default value from an environment +variable. + +A plugin's ``configure()`` method receives the parsed ``OptionParser`` options +object, as well as the current config object. Plugins should configure their +behavior based on the user-selected settings, and may raise exceptions +if the configured behavior is nonsensical. + +Logging +======= + +nose uses the logging classes from the standard library. To enable users +to view debug messages easily, plugins should use ``logging.getLogger()`` to +acquire a logger in the ``nose.plugins`` namespace. + +Recipes +======= + +* Writing a plugin that monitors or controls test result output + + Implement any or all of ``addError``, ``addFailure``, etc., to monitor test + results. If you also want to monitor output, implement + ``setOutputStream`` and keep a reference to the output stream. If you + want to prevent the builtin ``TextTestResult`` output, implement + ``setOutputSteam`` and *return a dummy stream*. The default output will go + to the dummy stream, while you send your desired output to the real stream. + + Example: `examples/html_plugin/htmlplug.py`_ + +* Writing a plugin that handles exceptions + + Subclass :doc:`ErrorClassPlugin `. + + Examples: :doc:`nose.plugins.deprecated `, + :doc:`nose.plugins.skip ` + +* Writing a plugin that adds detail to error reports + + Implement ``formatError`` and/or ``formatFailure``. The error tuple + you return (error class, error message, traceback) will replace the + original error tuple. + + Examples: :doc:`nose.plugins.capture `, + :doc:`nose.plugins.failuredetail ` + +* Writing a plugin that loads tests from files other than python modules + + Implement ``wantFile`` and ``loadTestsFromFile``. In ``wantFile``, + return True for files that you want to examine for tests. In + ``loadTestsFromFile``, for those files, return an iterable + containing TestCases (or yield them as you find them; + ``loadTestsFromFile`` may also be a generator). + + Example: :doc:`nose.plugins.doctests ` + +* Writing a plugin that prints a report + + Implement ``begin`` if you need to perform setup before testing + begins. Implement ``report`` and output your report to the provided stream. + + Examples: :doc:`nose.plugins.cover `, :doc:`nose.plugins.prof ` + +* Writing a plugin that selects or rejects tests + + Implement any or all ``want*`` methods. Return False to reject the test + candidate, True to accept it -- which means that the test candidate + will pass through the rest of the system, so you must be prepared to + load tests from it if tests can't be loaded by the core loader or + another plugin -- and None if you don't care. + + Examples: :doc:`nose.plugins.attrib `, + :doc:`nose.plugins.doctests `, :doc:`nose.plugins.testid ` + + +More Examples +============= + +See any builtin plugin or example plugin in the examples_ directory in +the nose source distribution. There is a list of third-party plugins +`on jottit`_. + +.. _examples/html_plugin/htmlplug.py: http://python-nose.googlecode.com/svn/trunk/examples/html_plugin/htmlplug.py +.. _examples: http://python-nose.googlecode.com/svn/trunk/examples +.. _on jottit: http://nose-plugins.jottit.com/ + +""" +from nose.plugins.base import Plugin +from nose.plugins.manager import * +from nose.plugins.plugintest import PluginTester + +if __name__ == '__main__': + import doctest + doctest.testmod() diff --git a/env/lib/python2.7/site-packages/nose/plugins/__init__.pyc b/env/lib/python2.7/site-packages/nose/plugins/__init__.pyc new file mode 100644 index 0000000..7ecc48e Binary files /dev/null and b/env/lib/python2.7/site-packages/nose/plugins/__init__.pyc differ diff --git a/env/lib/python2.7/site-packages/nose/plugins/allmodules.py b/env/lib/python2.7/site-packages/nose/plugins/allmodules.py new file mode 100644 index 0000000..1ccd777 --- /dev/null +++ b/env/lib/python2.7/site-packages/nose/plugins/allmodules.py @@ -0,0 +1,45 @@ +"""Use the AllModules plugin by passing ``--all-modules`` or setting the +NOSE_ALL_MODULES environment variable to enable collection and execution of +tests in all python modules. Normal nose behavior is to look for tests only in +modules that match testMatch. + +More information: :doc:`../doc_tests/test_allmodules/test_allmodules` + +.. warning :: + + This plugin can have surprising interactions with plugins that load tests + from what nose normally considers non-test modules, such as + the :doc:`doctest plugin `. This is because any given + object in a module can't be loaded both by a plugin and the normal nose + :class:`test loader `. Also, if you have functions + or classes in non-test modules that look like tests but aren't, you will + likely see errors as nose attempts to run them as tests. + +""" + +import os +from nose.plugins.base import Plugin + +class AllModules(Plugin): + """Collect tests from all python modules. + """ + def options(self, parser, env): + """Register commandline options. + """ + env_opt = 'NOSE_ALL_MODULES' + parser.add_option('--all-modules', + action="store_true", + dest=self.enableOpt, + default=env.get(env_opt), + help="Enable plugin %s: %s [%s]" % + (self.__class__.__name__, self.help(), env_opt)) + + def wantFile(self, file): + """Override to return True for all files ending with .py""" + # always want .py files + if file.endswith('.py'): + return True + + def wantModule(self, module): + """Override return True for all modules""" + return True diff --git a/env/lib/python2.7/site-packages/nose/plugins/allmodules.pyc b/env/lib/python2.7/site-packages/nose/plugins/allmodules.pyc new file mode 100644 index 0000000..e12dbf2 Binary files /dev/null and b/env/lib/python2.7/site-packages/nose/plugins/allmodules.pyc differ diff --git a/env/lib/python2.7/site-packages/nose/plugins/attrib.py b/env/lib/python2.7/site-packages/nose/plugins/attrib.py new file mode 100644 index 0000000..3d4422a --- /dev/null +++ b/env/lib/python2.7/site-packages/nose/plugins/attrib.py @@ -0,0 +1,286 @@ +"""Attribute selector plugin. + +Oftentimes when testing you will want to select tests based on +criteria rather then simply by filename. For example, you might want +to run all tests except for the slow ones. You can do this with the +Attribute selector plugin by setting attributes on your test methods. +Here is an example: + +.. code-block:: python + + def test_big_download(): + import urllib + # commence slowness... + + test_big_download.slow = 1 + +Once you've assigned an attribute ``slow = 1`` you can exclude that +test and all other tests having the slow attribute by running :: + + $ nosetests -a '!slow' + +There is also a decorator available for you that will set attributes. +Here's how to set ``slow=1`` like above with the decorator: + +.. code-block:: python + + from nose.plugins.attrib import attr + @attr('slow') + def test_big_download(): + import urllib + # commence slowness... + +And here's how to set an attribute with a specific value: + +.. code-block:: python + + from nose.plugins.attrib import attr + @attr(speed='slow') + def test_big_download(): + import urllib + # commence slowness... + +This test could be run with :: + + $ nosetests -a speed=slow + +In Python 2.6 and higher, ``@attr`` can be used on a class to set attributes +on all its test methods at once. For example: + +.. code-block:: python + + from nose.plugins.attrib import attr + @attr(speed='slow') + class MyTestCase: + def test_long_integration(self): + pass + def test_end_to_end_something(self): + pass + +Below is a reference to the different syntaxes available. + +Simple syntax +------------- + +Examples of using the ``-a`` and ``--attr`` options: + +* ``nosetests -a status=stable`` + Only runs tests with attribute "status" having value "stable" + +* ``nosetests -a priority=2,status=stable`` + Runs tests having both attributes and values + +* ``nosetests -a priority=2 -a slow`` + Runs tests that match either attribute + +* ``nosetests -a tags=http`` + If a test's ``tags`` attribute was a list and it contained the value + ``http`` then it would be run + +* ``nosetests -a slow`` + Runs tests with the attribute ``slow`` if its value does not equal False + (False, [], "", etc...) + +* ``nosetests -a '!slow'`` + Runs tests that do NOT have the attribute ``slow`` or have a ``slow`` + attribute that is equal to False + **NOTE**: + if your shell (like bash) interprets '!' as a special character make sure to + put single quotes around it. + +Expression Evaluation +--------------------- + +Examples using the ``-A`` and ``--eval-attr`` options: + +* ``nosetests -A "not slow"`` + Evaluates the Python expression "not slow" and runs the test if True + +* ``nosetests -A "(priority > 5) and not slow"`` + Evaluates a complex Python expression and runs the test if True + +""" +import inspect +import logging +import os +import sys +from inspect import isfunction +from nose.plugins.base import Plugin +from nose.util import tolist + +log = logging.getLogger('nose.plugins.attrib') +compat_24 = sys.version_info >= (2, 4) + +def attr(*args, **kwargs): + """Decorator that adds attributes to classes or functions + for use with the Attribute (-a) plugin. + """ + def wrap_ob(ob): + for name in args: + setattr(ob, name, True) + for name, value in kwargs.iteritems(): + setattr(ob, name, value) + return ob + return wrap_ob + +def get_method_attr(method, cls, attr_name, default = False): + """Look up an attribute on a method/ function. + If the attribute isn't found there, looking it up in the + method's class, if any. + """ + Missing = object() + value = getattr(method, attr_name, Missing) + if value is Missing and cls is not None: + value = getattr(cls, attr_name, Missing) + if value is Missing: + return default + return value + + +class ContextHelper: + """Object that can act as context dictionary for eval and looks up + names as attributes on a method/ function and its class. + """ + def __init__(self, method, cls): + self.method = method + self.cls = cls + + def __getitem__(self, name): + return get_method_attr(self.method, self.cls, name) + + +class AttributeSelector(Plugin): + """Selects test cases to be run based on their attributes. + """ + + def __init__(self): + Plugin.__init__(self) + self.attribs = [] + + def options(self, parser, env): + """Register command line options""" + parser.add_option("-a", "--attr", + dest="attr", action="append", + default=env.get('NOSE_ATTR'), + metavar="ATTR", + help="Run only tests that have attributes " + "specified by ATTR [NOSE_ATTR]") + # disable in < 2.4: eval can't take needed args + if compat_24: + parser.add_option("-A", "--eval-attr", + dest="eval_attr", metavar="EXPR", action="append", + default=env.get('NOSE_EVAL_ATTR'), + help="Run only tests for whose attributes " + "the Python expression EXPR evaluates " + "to True [NOSE_EVAL_ATTR]") + + def configure(self, options, config): + """Configure the plugin and system, based on selected options. + + attr and eval_attr may each be lists. + + self.attribs will be a list of lists of tuples. In that list, each + list is a group of attributes, all of which must match for the rule to + match. + """ + self.attribs = [] + + # handle python eval-expression parameter + if compat_24 and options.eval_attr: + eval_attr = tolist(options.eval_attr) + for attr in eval_attr: + # "" + # -> eval(expr) in attribute context must be True + def eval_in_context(expr, obj, cls): + return eval(expr, None, ContextHelper(obj, cls)) + self.attribs.append([(attr, eval_in_context)]) + + # attribute requirements are a comma separated list of + # 'key=value' pairs + if options.attr: + std_attr = tolist(options.attr) + for attr in std_attr: + # all attributes within an attribute group must match + attr_group = [] + for attrib in attr.strip().split(","): + # don't die on trailing comma + if not attrib: + continue + items = attrib.split("=", 1) + if len(items) > 1: + # "name=value" + # -> 'str(obj.name) == value' must be True + key, value = items + else: + key = items[0] + if key[0] == "!": + # "!name" + # 'bool(obj.name)' must be False + key = key[1:] + value = False + else: + # "name" + # -> 'bool(obj.name)' must be True + value = True + attr_group.append((key, value)) + self.attribs.append(attr_group) + if self.attribs: + self.enabled = True + + def validateAttrib(self, method, cls = None): + """Verify whether a method has the required attributes + The method is considered a match if it matches all attributes + for any attribute group. + .""" + # TODO: is there a need for case-sensitive value comparison? + any = False + for group in self.attribs: + match = True + for key, value in group: + attr = get_method_attr(method, cls, key) + if callable(value): + if not value(key, method, cls): + match = False + break + elif value is True: + # value must exist and be True + if not bool(attr): + match = False + break + elif value is False: + # value must not exist or be False + if bool(attr): + match = False + break + elif type(attr) in (list, tuple): + # value must be found in the list attribute + if not str(value).lower() in [str(x).lower() + for x in attr]: + match = False + break + else: + # value must match, convert to string and compare + if (value != attr + and str(value).lower() != str(attr).lower()): + match = False + break + any = any or match + if any: + # not True because we don't want to FORCE the selection of the + # item, only say that it is acceptable + return None + return False + + def wantFunction(self, function): + """Accept the function if its attributes match. + """ + return self.validateAttrib(function) + + def wantMethod(self, method): + """Accept the method if its attributes match. + """ + try: + cls = method.im_class + except AttributeError: + return False + return self.validateAttrib(method, cls) diff --git a/env/lib/python2.7/site-packages/nose/plugins/attrib.pyc b/env/lib/python2.7/site-packages/nose/plugins/attrib.pyc new file mode 100644 index 0000000..fb12e88 Binary files /dev/null and b/env/lib/python2.7/site-packages/nose/plugins/attrib.pyc differ diff --git a/env/lib/python2.7/site-packages/nose/plugins/base.py b/env/lib/python2.7/site-packages/nose/plugins/base.py new file mode 100644 index 0000000..f09beb6 --- /dev/null +++ b/env/lib/python2.7/site-packages/nose/plugins/base.py @@ -0,0 +1,725 @@ +import os +import textwrap +from optparse import OptionConflictError +from warnings import warn +from nose.util import tolist + +class Plugin(object): + """Base class for nose plugins. It's recommended but not *necessary* to + subclass this class to create a plugin, but all plugins *must* implement + `options(self, parser, env)` and `configure(self, options, conf)`, and + must have the attributes `enabled`, `name` and `score`. The `name` + attribute may contain hyphens ('-'). + + Plugins should not be enabled by default. + + Subclassing Plugin (and calling the superclass methods in + __init__, configure, and options, if you override them) will give + your plugin some friendly default behavior: + + * A --with-$name option will be added to the command line interface + to enable the plugin, and a corresponding environment variable + will be used as the default value. The plugin class's docstring + will be used as the help for this option. + * The plugin will not be enabled unless this option is selected by + the user. + """ + can_configure = False + enabled = False + enableOpt = None + name = None + score = 100 + + def __init__(self): + if self.name is None: + self.name = self.__class__.__name__.lower() + if self.enableOpt is None: + self.enableOpt = "enable_plugin_%s" % self.name.replace('-', '_') + + def addOptions(self, parser, env=None): + """Add command-line options for this plugin. + + The base plugin class adds --with-$name by default, used to enable the + plugin. + + .. warning :: Don't implement addOptions unless you want to override + all default option handling behavior, including + warnings for conflicting options. Implement + :meth:`options + ` + instead. + """ + self.add_options(parser, env) + + def add_options(self, parser, env=None): + """Non-camel-case version of func name for backwards compatibility. + + .. warning :: + + DEPRECATED: Do not use this method, + use :meth:`options ` + instead. + + """ + # FIXME raise deprecation warning if wasn't called by wrapper + if env is None: + env = os.environ + try: + self.options(parser, env) + self.can_configure = True + except OptionConflictError, e: + warn("Plugin %s has conflicting option string: %s and will " + "be disabled" % (self, e), RuntimeWarning) + self.enabled = False + self.can_configure = False + + def options(self, parser, env): + """Register commandline options. + + Implement this method for normal options behavior with protection from + OptionConflictErrors. If you override this method and want the default + --with-$name option to be registered, be sure to call super(). + """ + env_opt = 'NOSE_WITH_%s' % self.name.upper() + env_opt = env_opt.replace('-', '_') + parser.add_option("--with-%s" % self.name, + action="store_true", + dest=self.enableOpt, + default=env.get(env_opt), + help="Enable plugin %s: %s [%s]" % + (self.__class__.__name__, self.help(), env_opt)) + + def configure(self, options, conf): + """Configure the plugin and system, based on selected options. + + The base plugin class sets the plugin to enabled if the enable option + for the plugin (self.enableOpt) is true. + """ + if not self.can_configure: + return + self.conf = conf + if hasattr(options, self.enableOpt): + self.enabled = getattr(options, self.enableOpt) + + def help(self): + """Return help for this plugin. This will be output as the help + section of the --with-$name option that enables the plugin. + """ + if self.__class__.__doc__: + # doc sections are often indented; compress the spaces + return textwrap.dedent(self.__class__.__doc__) + return "(no help available)" + + # Compatiblity shim + def tolist(self, val): + warn("Plugin.tolist is deprecated. Use nose.util.tolist instead", + DeprecationWarning) + return tolist(val) + + +class IPluginInterface(object): + """ + IPluginInterface describes the plugin API. Do not subclass or use this + class directly. + """ + def __new__(cls, *arg, **kw): + raise TypeError("IPluginInterface class is for documentation only") + + def addOptions(self, parser, env): + """Called to allow plugin to register command-line options with the + parser. DO NOT return a value from this method unless you want to stop + all other plugins from setting their options. + + .. warning :: + + DEPRECATED -- implement + :meth:`options ` instead. + """ + pass + add_options = addOptions + add_options.deprecated = True + + def addDeprecated(self, test): + """Called when a deprecated test is seen. DO NOT return a value + unless you want to stop other plugins from seeing the deprecated + test. + + .. warning :: DEPRECATED -- check error class in addError instead + """ + pass + addDeprecated.deprecated = True + + def addError(self, test, err): + """Called when a test raises an uncaught exception. DO NOT return a + value unless you want to stop other plugins from seeing that the + test has raised an error. + + :param test: the test case + :type test: :class:`nose.case.Test` + :param err: sys.exc_info() tuple + :type err: 3-tuple + """ + pass + addError.changed = True + + def addFailure(self, test, err): + """Called when a test fails. DO NOT return a value unless you + want to stop other plugins from seeing that the test has failed. + + :param test: the test case + :type test: :class:`nose.case.Test` + :param err: 3-tuple + :type err: sys.exc_info() tuple + """ + pass + addFailure.changed = True + + def addSkip(self, test): + """Called when a test is skipped. DO NOT return a value unless + you want to stop other plugins from seeing the skipped test. + + .. warning:: DEPRECATED -- check error class in addError instead + """ + pass + addSkip.deprecated = True + + def addSuccess(self, test): + """Called when a test passes. DO NOT return a value unless you + want to stop other plugins from seeing the passing test. + + :param test: the test case + :type test: :class:`nose.case.Test` + """ + pass + addSuccess.changed = True + + def afterContext(self): + """Called after a context (generally a module) has been + lazy-loaded, imported, setup, had its tests loaded and + executed, and torn down. + """ + pass + afterContext._new = True + + def afterDirectory(self, path): + """Called after all tests have been loaded from directory at path + and run. + + :param path: the directory that has finished processing + :type path: string + """ + pass + afterDirectory._new = True + + def afterImport(self, filename, module): + """Called after module is imported from filename. afterImport + is called even if the import failed. + + :param filename: The file that was loaded + :type filename: string + :param module: The name of the module + :type module: string + """ + pass + afterImport._new = True + + def afterTest(self, test): + """Called after the test has been run and the result recorded + (after stopTest). + + :param test: the test case + :type test: :class:`nose.case.Test` + """ + pass + afterTest._new = True + + def beforeContext(self): + """Called before a context (generally a module) is + examined. Because the context is not yet loaded, plugins don't + get to know what the context is; so any context operations + should use a stack that is pushed in `beforeContext` and popped + in `afterContext` to ensure they operate symmetrically. + + `beforeContext` and `afterContext` are mainly useful for tracking + and restoring global state around possible changes from within a + context, whatever the context may be. If you need to operate on + contexts themselves, see `startContext` and `stopContext`, which + are passed the context in question, but are called after + it has been loaded (imported in the module case). + """ + pass + beforeContext._new = True + + def beforeDirectory(self, path): + """Called before tests are loaded from directory at path. + + :param path: the directory that is about to be processed + """ + pass + beforeDirectory._new = True + + def beforeImport(self, filename, module): + """Called before module is imported from filename. + + :param filename: The file that will be loaded + :param module: The name of the module found in file + :type module: string + """ + beforeImport._new = True + + def beforeTest(self, test): + """Called before the test is run (before startTest). + + :param test: the test case + :type test: :class:`nose.case.Test` + """ + pass + beforeTest._new = True + + def begin(self): + """Called before any tests are collected or run. Use this to + perform any setup needed before testing begins. + """ + pass + + def configure(self, options, conf): + """Called after the command line has been parsed, with the + parsed options and the config container. Here, implement any + config storage or changes to state or operation that are set + by command line options. + + DO NOT return a value from this method unless you want to + stop all other plugins from being configured. + """ + pass + + def finalize(self, result): + """Called after all report output, including output from all + plugins, has been sent to the stream. Use this to print final + test results or perform final cleanup. Return None to allow + other plugins to continue printing, or any other value to stop + them. + + :param result: test result object + + .. Note:: When tests are run under a test runner other than + :class:`nose.core.TextTestRunner`, such as + via ``python setup.py test``, this method may be called + **before** the default report output is sent. + """ + pass + + def describeTest(self, test): + """Return a test description. + + Called by :meth:`nose.case.Test.shortDescription`. + + :param test: the test case + :type test: :class:`nose.case.Test` + """ + pass + describeTest._new = True + + def formatError(self, test, err): + """Called in result.addError, before plugin.addError. If you + want to replace or modify the error tuple, return a new error + tuple, otherwise return err, the original error tuple. + + :param test: the test case + :type test: :class:`nose.case.Test` + :param err: sys.exc_info() tuple + :type err: 3-tuple + """ + pass + formatError._new = True + formatError.chainable = True + # test arg is not chainable + formatError.static_args = (True, False) + + def formatFailure(self, test, err): + """Called in result.addFailure, before plugin.addFailure. If you + want to replace or modify the error tuple, return a new error + tuple, otherwise return err, the original error tuple. + + :param test: the test case + :type test: :class:`nose.case.Test` + :param err: sys.exc_info() tuple + :type err: 3-tuple + """ + pass + formatFailure._new = True + formatFailure.chainable = True + # test arg is not chainable + formatFailure.static_args = (True, False) + + def handleError(self, test, err): + """Called on addError. To handle the error yourself and prevent normal + error processing, return a true value. + + :param test: the test case + :type test: :class:`nose.case.Test` + :param err: sys.exc_info() tuple + :type err: 3-tuple + """ + pass + handleError._new = True + + def handleFailure(self, test, err): + """Called on addFailure. To handle the failure yourself and + prevent normal failure processing, return a true value. + + :param test: the test case + :type test: :class:`nose.case.Test` + :param err: sys.exc_info() tuple + :type err: 3-tuple + """ + pass + handleFailure._new = True + + def loadTestsFromDir(self, path): + """Return iterable of tests from a directory. May be a + generator. Each item returned must be a runnable + unittest.TestCase (or subclass) instance or suite instance. + Return None if your plugin cannot collect any tests from + directory. + + :param path: The path to the directory. + """ + pass + loadTestsFromDir.generative = True + loadTestsFromDir._new = True + + def loadTestsFromModule(self, module, path=None): + """Return iterable of tests in a module. May be a + generator. Each item returned must be a runnable + unittest.TestCase (or subclass) instance. + Return None if your plugin cannot + collect any tests from module. + + :param module: The module object + :type module: python module + :param path: the path of the module to search, to distinguish from + namespace package modules + + .. note:: + + NEW. The ``path`` parameter will only be passed by nose 0.11 + or above. + """ + pass + loadTestsFromModule.generative = True + + def loadTestsFromName(self, name, module=None, importPath=None): + """Return tests in this file or module. Return None if you are not able + to load any tests, or an iterable if you are. May be a + generator. + + :param name: The test name. May be a file or module name plus a test + callable. Use split_test_name to split into parts. Or it might + be some crazy name of your own devising, in which case, do + whatever you want. + :param module: Module from which the name is to be loaded + :param importPath: Path from which file (must be a python module) was + found + + .. warning:: DEPRECATED: this argument will NOT be passed. + """ + pass + loadTestsFromName.generative = True + + def loadTestsFromNames(self, names, module=None): + """Return a tuple of (tests loaded, remaining names). Return + None if you are not able to load any tests. Multiple plugins + may implement loadTestsFromNames; the remaining name list from + each will be passed to the next as input. + + :param names: List of test names. + :type names: iterable + :param module: Module from which the names are to be loaded + """ + pass + loadTestsFromNames._new = True + loadTestsFromNames.chainable = True + + def loadTestsFromFile(self, filename): + """Return tests in this file. Return None if you are not + interested in loading any tests, or an iterable if you are and + can load some. May be a generator. *If you are interested in + loading tests from the file and encounter no errors, but find + no tests, yield False or return [False].* + + .. Note:: This method replaces loadTestsFromPath from the 0.9 + API. + + :param filename: The full path to the file or directory. + """ + pass + loadTestsFromFile.generative = True + loadTestsFromFile._new = True + + def loadTestsFromPath(self, path): + """ + .. warning:: DEPRECATED -- use loadTestsFromFile instead + """ + pass + loadTestsFromPath.deprecated = True + + def loadTestsFromTestCase(self, cls): + """Return tests in this test case class. Return None if you are + not able to load any tests, or an iterable if you are. May be a + generator. + + :param cls: The test case class. Must be subclass of + :class:`unittest.TestCase`. + """ + pass + loadTestsFromTestCase.generative = True + + def loadTestsFromTestClass(self, cls): + """Return tests in this test class. Class will *not* be a + unittest.TestCase subclass. Return None if you are not able to + load any tests, an iterable if you are. May be a generator. + + :param cls: The test case class. Must be **not** be subclass of + :class:`unittest.TestCase`. + """ + pass + loadTestsFromTestClass._new = True + loadTestsFromTestClass.generative = True + + def makeTest(self, obj, parent): + """Given an object and its parent, return or yield one or more + test cases. Each test must be a unittest.TestCase (or subclass) + instance. This is called before default test loading to allow + plugins to load an alternate test case or cases for an + object. May be a generator. + + :param obj: The object to be made into a test + :param parent: The parent of obj (eg, for a method, the class) + """ + pass + makeTest._new = True + makeTest.generative = True + + def options(self, parser, env): + """Called to allow plugin to register command line + options with the parser. + + DO NOT return a value from this method unless you want to stop + all other plugins from setting their options. + + :param parser: options parser instance + :type parser: :class:`ConfigParser.ConfigParser` + :param env: environment, default is os.environ + """ + pass + options._new = True + + def prepareTest(self, test): + """Called before the test is run by the test runner. Please + note the article *the* in the previous sentence: prepareTest + is called *only once*, and is passed the test case or test + suite that the test runner will execute. It is *not* called + for each individual test case. If you return a non-None value, + that return value will be run as the test. Use this hook to + wrap or decorate the test with another function. If you need + to modify or wrap individual test cases, use `prepareTestCase` + instead. + + :param test: the test case + :type test: :class:`nose.case.Test` + """ + pass + + def prepareTestCase(self, test): + """Prepare or wrap an individual test case. Called before + execution of the test. The test passed here is a + nose.case.Test instance; the case to be executed is in the + test attribute of the passed case. To modify the test to be + run, you should return a callable that takes one argument (the + test result object) -- it is recommended that you *do not* + side-effect the nose.case.Test instance you have been passed. + + Keep in mind that when you replace the test callable you are + replacing the run() method of the test case -- including the + exception handling and result calls, etc. + + :param test: the test case + :type test: :class:`nose.case.Test` + """ + pass + prepareTestCase._new = True + + def prepareTestLoader(self, loader): + """Called before tests are loaded. To replace the test loader, + return a test loader. To allow other plugins to process the + test loader, return None. Only one plugin may replace the test + loader. Only valid when using nose.TestProgram. + + :param loader: :class:`nose.loader.TestLoader` + (or other loader) instance + """ + pass + prepareTestLoader._new = True + + def prepareTestResult(self, result): + """Called before the first test is run. To use a different + test result handler for all tests than the given result, + return a test result handler. NOTE however that this handler + will only be seen by tests, that is, inside of the result + proxy system. The TestRunner and TestProgram -- whether nose's + or other -- will continue to see the original result + handler. For this reason, it is usually better to monkeypatch + the result (for instance, if you want to handle some + exceptions in a unique way). Only one plugin may replace the + result, but many may monkeypatch it. If you want to + monkeypatch and stop other plugins from doing so, monkeypatch + and return the patched result. + + :param result: :class:`nose.result.TextTestResult` + (or other result) instance + """ + pass + prepareTestResult._new = True + + def prepareTestRunner(self, runner): + """Called before tests are run. To replace the test runner, + return a test runner. To allow other plugins to process the + test runner, return None. Only valid when using nose.TestProgram. + + :param runner: :class:`nose.core.TextTestRunner` + (or other runner) instance + """ + pass + prepareTestRunner._new = True + + def report(self, stream): + """Called after all error output has been printed. Print your + plugin's report to the provided stream. Return None to allow + other plugins to print reports, any other value to stop them. + + :param stream: stream object; send your output here + :type stream: file-like object + """ + pass + + def setOutputStream(self, stream): + """Called before test output begins. To direct test output to a + new stream, return a stream object, which must implement a + `write(msg)` method. If you only want to note the stream, not + capture or redirect it, then return None. + + :param stream: stream object; send your output here + :type stream: file-like object + """ + + def startContext(self, context): + """Called before context setup and the running of tests in the + context. Note that tests have already been *loaded* from the + context before this call. + + :param context: the context about to be setup. May be a module or + class, or any other object that contains tests. + """ + pass + startContext._new = True + + def startTest(self, test): + """Called before each test is run. DO NOT return a value unless + you want to stop other plugins from seeing the test start. + + :param test: the test case + :type test: :class:`nose.case.Test` + """ + pass + + def stopContext(self, context): + """Called after the tests in a context have run and the + context has been torn down. + + :param context: the context that has been torn down. May be a module or + class, or any other object that contains tests. + """ + pass + stopContext._new = True + + def stopTest(self, test): + """Called after each test is run. DO NOT return a value unless + you want to stop other plugins from seeing that the test has stopped. + + :param test: the test case + :type test: :class:`nose.case.Test` + """ + pass + + def testName(self, test): + """Return a short test name. Called by `nose.case.Test.__str__`. + + :param test: the test case + :type test: :class:`nose.case.Test` + """ + pass + testName._new = True + + def wantClass(self, cls): + """Return true if you want the main test selector to collect + tests from this class, false if you don't, and None if you don't + care. + + :param cls: The class being examined by the selector + """ + pass + + def wantDirectory(self, dirname): + """Return true if you want test collection to descend into this + directory, false if you do not, and None if you don't care. + + :param dirname: Full path to directory being examined by the selector + """ + pass + + def wantFile(self, file): + """Return true if you want to collect tests from this file, + false if you do not and None if you don't care. + + Change from 0.9: The optional package parameter is no longer passed. + + :param file: Full path to file being examined by the selector + """ + pass + + def wantFunction(self, function): + """Return true to collect this function as a test, false to + prevent it from being collected, and None if you don't care. + + :param function: The function object being examined by the selector + """ + pass + + def wantMethod(self, method): + """Return true to collect this method as a test, false to + prevent it from being collected, and None if you don't care. + + :param method: The method object being examined by the selector + :type method: unbound method + """ + pass + + def wantModule(self, module): + """Return true if you want to collection to descend into this + module, false to prevent the collector from descending into the + module, and None if you don't care. + + :param module: The module object being examined by the selector + :type module: python module + """ + pass + + def wantModuleTests(self, module): + """ + .. warning:: DEPRECATED -- this method will not be called, it has + been folded into wantModule. + """ + pass + wantModuleTests.deprecated = True + diff --git a/env/lib/python2.7/site-packages/nose/plugins/base.pyc b/env/lib/python2.7/site-packages/nose/plugins/base.pyc new file mode 100644 index 0000000..fe4ebb6 Binary files /dev/null and b/env/lib/python2.7/site-packages/nose/plugins/base.pyc differ diff --git a/env/lib/python2.7/site-packages/nose/plugins/builtin.py b/env/lib/python2.7/site-packages/nose/plugins/builtin.py new file mode 100644 index 0000000..4fcc001 --- /dev/null +++ b/env/lib/python2.7/site-packages/nose/plugins/builtin.py @@ -0,0 +1,34 @@ +""" +Lists builtin plugins. +""" +plugins = [] +builtins = ( + ('nose.plugins.attrib', 'AttributeSelector'), + ('nose.plugins.capture', 'Capture'), + ('nose.plugins.logcapture', 'LogCapture'), + ('nose.plugins.cover', 'Coverage'), + ('nose.plugins.debug', 'Pdb'), + ('nose.plugins.deprecated', 'Deprecated'), + ('nose.plugins.doctests', 'Doctest'), + ('nose.plugins.isolate', 'IsolationPlugin'), + ('nose.plugins.failuredetail', 'FailureDetail'), + ('nose.plugins.prof', 'Profile'), + ('nose.plugins.skip', 'Skip'), + ('nose.plugins.testid', 'TestId'), + ('nose.plugins.multiprocess', 'MultiProcess'), + ('nose.plugins.xunit', 'Xunit'), + ('nose.plugins.allmodules', 'AllModules'), + ('nose.plugins.collect', 'CollectOnly'), + ) + +for module, cls in builtins: + try: + plugmod = __import__(module, globals(), locals(), [cls]) + except KeyboardInterrupt: + raise + except: + continue + plug = getattr(plugmod, cls) + plugins.append(plug) + globals()[cls] = plug + diff --git a/env/lib/python2.7/site-packages/nose/plugins/builtin.pyc b/env/lib/python2.7/site-packages/nose/plugins/builtin.pyc new file mode 100644 index 0000000..fef37d4 Binary files /dev/null and b/env/lib/python2.7/site-packages/nose/plugins/builtin.pyc differ diff --git a/env/lib/python2.7/site-packages/nose/plugins/capture.py b/env/lib/python2.7/site-packages/nose/plugins/capture.py new file mode 100644 index 0000000..fa4e5dc --- /dev/null +++ b/env/lib/python2.7/site-packages/nose/plugins/capture.py @@ -0,0 +1,115 @@ +""" +This plugin captures stdout during test execution. If the test fails +or raises an error, the captured output will be appended to the error +or failure output. It is enabled by default but can be disabled with +the options ``-s`` or ``--nocapture``. + +:Options: + ``--nocapture`` + Don't capture stdout (any stdout output will be printed immediately) + +""" +import logging +import os +import sys +from nose.plugins.base import Plugin +from nose.pyversion import exc_to_unicode, force_unicode +from nose.util import ln +from StringIO import StringIO + + +log = logging.getLogger(__name__) + +class Capture(Plugin): + """ + Output capture plugin. Enabled by default. Disable with ``-s`` or + ``--nocapture``. This plugin captures stdout during test execution, + appending any output captured to the error or failure output, + should the test fail or raise an error. + """ + enabled = True + env_opt = 'NOSE_NOCAPTURE' + name = 'capture' + score = 1600 + + def __init__(self): + self.stdout = [] + self._buf = None + + def options(self, parser, env): + """Register commandline options + """ + parser.add_option( + "-s", "--nocapture", action="store_false", + default=not env.get(self.env_opt), dest="capture", + help="Don't capture stdout (any stdout output " + "will be printed immediately) [NOSE_NOCAPTURE]") + + def configure(self, options, conf): + """Configure plugin. Plugin is enabled by default. + """ + self.conf = conf + if not options.capture: + self.enabled = False + + def afterTest(self, test): + """Clear capture buffer. + """ + self.end() + self._buf = None + + def begin(self): + """Replace sys.stdout with capture buffer. + """ + self.start() # get an early handle on sys.stdout + + def beforeTest(self, test): + """Flush capture buffer. + """ + self.start() + + def formatError(self, test, err): + """Add captured output to error report. + """ + test.capturedOutput = output = self.buffer + self._buf = None + if not output: + # Don't return None as that will prevent other + # formatters from formatting and remove earlier formatters + # formats, instead return the err we got + return err + ec, ev, tb = err + return (ec, self.addCaptureToErr(ev, output), tb) + + def formatFailure(self, test, err): + """Add captured output to failure report. + """ + return self.formatError(test, err) + + def addCaptureToErr(self, ev, output): + ev = exc_to_unicode(ev) + output = force_unicode(output) + return u'\n'.join([ev, ln(u'>> begin captured stdout <<'), + output, ln(u'>> end captured stdout <<')]) + + def start(self): + self.stdout.append(sys.stdout) + self._buf = StringIO() + sys.stdout = self._buf + + def end(self): + if self.stdout: + sys.stdout = self.stdout.pop() + + def finalize(self, result): + """Restore stdout. + """ + while self.stdout: + self.end() + + def _get_buffer(self): + if self._buf is not None: + return self._buf.getvalue() + + buffer = property(_get_buffer, None, None, + """Captured stdout output.""") diff --git a/env/lib/python2.7/site-packages/nose/plugins/capture.pyc b/env/lib/python2.7/site-packages/nose/plugins/capture.pyc new file mode 100644 index 0000000..8478330 Binary files /dev/null and b/env/lib/python2.7/site-packages/nose/plugins/capture.pyc differ diff --git a/env/lib/python2.7/site-packages/nose/plugins/collect.py b/env/lib/python2.7/site-packages/nose/plugins/collect.py new file mode 100644 index 0000000..6f9f0fa --- /dev/null +++ b/env/lib/python2.7/site-packages/nose/plugins/collect.py @@ -0,0 +1,94 @@ +""" +This plugin bypasses the actual execution of tests, and instead just collects +test names. Fixtures are also bypassed, so running nosetests with the +collection plugin enabled should be very quick. + +This plugin is useful in combination with the testid plugin (``--with-id``). +Run both together to get an indexed list of all tests, which will enable you to +run individual tests by index number. + +This plugin is also useful for counting tests in a test suite, and making +people watching your demo think all of your tests pass. +""" +from nose.plugins.base import Plugin +from nose.case import Test +import logging +import unittest + +log = logging.getLogger(__name__) + + +class CollectOnly(Plugin): + """ + Collect and output test names only, don't run any tests. + """ + name = "collect-only" + enableOpt = 'collect_only' + + def options(self, parser, env): + """Register commandline options. + """ + parser.add_option('--collect-only', + action='store_true', + dest=self.enableOpt, + default=env.get('NOSE_COLLECT_ONLY'), + help="Enable collect-only: %s [COLLECT_ONLY]" % + (self.help())) + + def prepareTestLoader(self, loader): + """Install collect-only suite class in TestLoader. + """ + # Disable context awareness + log.debug("Preparing test loader") + loader.suiteClass = TestSuiteFactory(self.conf) + + def prepareTestCase(self, test): + """Replace actual test with dummy that always passes. + """ + # Return something that always passes + log.debug("Preparing test case %s", test) + if not isinstance(test, Test): + return + def run(result): + # We need to make these plugin calls because there won't be + # a result proxy, due to using a stripped-down test suite + self.conf.plugins.startTest(test) + result.startTest(test) + self.conf.plugins.addSuccess(test) + result.addSuccess(test) + self.conf.plugins.stopTest(test) + result.stopTest(test) + return run + + +class TestSuiteFactory: + """ + Factory for producing configured test suites. + """ + def __init__(self, conf): + self.conf = conf + + def __call__(self, tests=(), **kw): + return TestSuite(tests, conf=self.conf) + + +class TestSuite(unittest.TestSuite): + """ + Basic test suite that bypasses most proxy and plugin calls, but does + wrap tests in a nose.case.Test so prepareTestCase will be called. + """ + def __init__(self, tests=(), conf=None): + self.conf = conf + # Exec lazy suites: makes discovery depth-first + if callable(tests): + tests = tests() + log.debug("TestSuite(%r)", tests) + unittest.TestSuite.__init__(self, tests) + + def addTest(self, test): + log.debug("Add test %s", test) + if isinstance(test, unittest.TestSuite): + self._tests.append(test) + else: + self._tests.append(Test(test, config=self.conf)) + diff --git a/env/lib/python2.7/site-packages/nose/plugins/collect.pyc b/env/lib/python2.7/site-packages/nose/plugins/collect.pyc new file mode 100644 index 0000000..006f766 Binary files /dev/null and b/env/lib/python2.7/site-packages/nose/plugins/collect.pyc differ diff --git a/env/lib/python2.7/site-packages/nose/plugins/cover.py b/env/lib/python2.7/site-packages/nose/plugins/cover.py new file mode 100644 index 0000000..fbe2e30 --- /dev/null +++ b/env/lib/python2.7/site-packages/nose/plugins/cover.py @@ -0,0 +1,271 @@ +"""If you have Ned Batchelder's coverage_ module installed, you may activate a +coverage report with the ``--with-coverage`` switch or NOSE_WITH_COVERAGE +environment variable. The coverage report will cover any python source module +imported after the start of the test run, excluding modules that match +testMatch. If you want to include those modules too, use the ``--cover-tests`` +switch, or set the NOSE_COVER_TESTS environment variable to a true value. To +restrict the coverage report to modules from a particular package or packages, +use the ``--cover-package`` switch or the NOSE_COVER_PACKAGE environment +variable. + +.. _coverage: http://www.nedbatchelder.com/code/modules/coverage.html +""" +import logging +import re +import sys +import StringIO +from nose.plugins.base import Plugin +from nose.util import src, tolist + +log = logging.getLogger(__name__) + + +class Coverage(Plugin): + """ + Activate a coverage report using Ned Batchelder's coverage module. + """ + coverTests = False + coverPackages = None + coverInstance = None + coverErase = False + coverMinPercentage = None + score = 200 + status = {} + + def options(self, parser, env): + """ + Add options to command line. + """ + super(Coverage, self).options(parser, env) + parser.add_option("--cover-package", action="append", + default=env.get('NOSE_COVER_PACKAGE'), + metavar="PACKAGE", + dest="cover_packages", + help="Restrict coverage output to selected packages " + "[NOSE_COVER_PACKAGE]") + parser.add_option("--cover-erase", action="store_true", + default=env.get('NOSE_COVER_ERASE'), + dest="cover_erase", + help="Erase previously collected coverage " + "statistics before run") + parser.add_option("--cover-tests", action="store_true", + dest="cover_tests", + default=env.get('NOSE_COVER_TESTS'), + help="Include test modules in coverage report " + "[NOSE_COVER_TESTS]") + parser.add_option("--cover-min-percentage", action="store", + dest="cover_min_percentage", + default=env.get('NOSE_COVER_MIN_PERCENTAGE'), + help="Minimum percentage of coverage for tests " + "to pass [NOSE_COVER_MIN_PERCENTAGE]") + parser.add_option("--cover-inclusive", action="store_true", + dest="cover_inclusive", + default=env.get('NOSE_COVER_INCLUSIVE'), + help="Include all python files under working " + "directory in coverage report. Useful for " + "discovering holes in test coverage if not all " + "files are imported by the test suite. " + "[NOSE_COVER_INCLUSIVE]") + parser.add_option("--cover-html", action="store_true", + default=env.get('NOSE_COVER_HTML'), + dest='cover_html', + help="Produce HTML coverage information") + parser.add_option('--cover-html-dir', action='store', + default=env.get('NOSE_COVER_HTML_DIR', 'cover'), + dest='cover_html_dir', + metavar='DIR', + help='Produce HTML coverage information in dir') + parser.add_option("--cover-branches", action="store_true", + default=env.get('NOSE_COVER_BRANCHES'), + dest="cover_branches", + help="Include branch coverage in coverage report " + "[NOSE_COVER_BRANCHES]") + parser.add_option("--cover-xml", action="store_true", + default=env.get('NOSE_COVER_XML'), + dest="cover_xml", + help="Produce XML coverage information") + parser.add_option("--cover-xml-file", action="store", + default=env.get('NOSE_COVER_XML_FILE', 'coverage.xml'), + dest="cover_xml_file", + metavar="FILE", + help="Produce XML coverage information in file") + + def configure(self, options, conf): + """ + Configure plugin. + """ + try: + self.status.pop('active') + except KeyError: + pass + super(Coverage, self).configure(options, conf) + if self.enabled: + try: + import coverage + if not hasattr(coverage, 'coverage'): + raise ImportError("Unable to import coverage module") + except ImportError: + log.error("Coverage not available: " + "unable to import coverage module") + self.enabled = False + return + self.conf = conf + self.coverErase = options.cover_erase + self.coverTests = options.cover_tests + self.coverPackages = [] + if options.cover_packages: + if isinstance(options.cover_packages, (list, tuple)): + cover_packages = options.cover_packages + else: + cover_packages = [options.cover_packages] + for pkgs in [tolist(x) for x in cover_packages]: + self.coverPackages.extend(pkgs) + self.coverInclusive = options.cover_inclusive + if self.coverPackages: + log.info("Coverage report will include only packages: %s", + self.coverPackages) + self.coverHtmlDir = None + if options.cover_html: + self.coverHtmlDir = options.cover_html_dir + log.debug('Will put HTML coverage report in %s', self.coverHtmlDir) + self.coverBranches = options.cover_branches + self.coverXmlFile = None + if options.cover_min_percentage: + self.coverMinPercentage = int(options.cover_min_percentage.rstrip('%')) + if options.cover_xml: + self.coverXmlFile = options.cover_xml_file + log.debug('Will put XML coverage report in %s', self.coverXmlFile) + if self.enabled: + self.status['active'] = True + self.coverInstance = coverage.coverage(auto_data=False, + branch=self.coverBranches, data_suffix=conf.worker, + source=self.coverPackages) + self.coverInstance._warn_no_data = False + self.coverInstance.is_worker = conf.worker + self.coverInstance.exclude('#pragma[: ]+[nN][oO] [cC][oO][vV][eE][rR]') + + log.debug("Coverage begin") + self.skipModules = sys.modules.keys()[:] + if self.coverErase: + log.debug("Clearing previously collected coverage statistics") + self.coverInstance.combine() + self.coverInstance.erase() + + if not self.coverInstance.is_worker: + self.coverInstance.load() + self.coverInstance.start() + + + def beforeTest(self, *args, **kwargs): + """ + Begin recording coverage information. + """ + + if self.coverInstance.is_worker: + self.coverInstance.load() + self.coverInstance.start() + + def afterTest(self, *args, **kwargs): + """ + Stop recording coverage information. + """ + + if self.coverInstance.is_worker: + self.coverInstance.stop() + self.coverInstance.save() + + + def report(self, stream): + """ + Output code coverage report. + """ + log.debug("Coverage report") + self.coverInstance.stop() + self.coverInstance.combine() + self.coverInstance.save() + modules = [module + for name, module in sys.modules.items() + if self.wantModuleCoverage(name, module)] + log.debug("Coverage report will cover modules: %s", modules) + self.coverInstance.report(modules, file=stream) + + import coverage + if self.coverHtmlDir: + log.debug("Generating HTML coverage report") + try: + self.coverInstance.html_report(modules, self.coverHtmlDir) + except coverage.misc.CoverageException, e: + log.warning("Failed to generate HTML report: %s" % str(e)) + + if self.coverXmlFile: + log.debug("Generating XML coverage report") + try: + self.coverInstance.xml_report(modules, self.coverXmlFile) + except coverage.misc.CoverageException, e: + log.warning("Failed to generate XML report: %s" % str(e)) + + # make sure we have minimum required coverage + if self.coverMinPercentage: + f = StringIO.StringIO() + self.coverInstance.report(modules, file=f) + + multiPackageRe = (r'-------\s\w+\s+\d+\s+\d+(?:\s+\d+\s+\d+)?' + r'\s+(\d+)%\s+\d*\s{0,1}$') + singlePackageRe = (r'-------\s[\w./]+\s+\d+\s+\d+(?:\s+\d+\s+\d+)?' + r'\s+(\d+)%(?:\s+[-\d, ]+)\s{0,1}$') + + m = re.search(multiPackageRe, f.getvalue()) + if m is None: + m = re.search(singlePackageRe, f.getvalue()) + + if m: + percentage = int(m.groups()[0]) + if percentage < self.coverMinPercentage: + log.error('TOTAL Coverage did not reach minimum ' + 'required: %d%%' % self.coverMinPercentage) + sys.exit(1) + else: + log.error("No total percentage was found in coverage output, " + "something went wrong.") + + + def wantModuleCoverage(self, name, module): + if not hasattr(module, '__file__'): + log.debug("no coverage of %s: no __file__", name) + return False + module_file = src(module.__file__) + if not module_file or not module_file.endswith('.py'): + log.debug("no coverage of %s: not a python file", name) + return False + if self.coverPackages: + for package in self.coverPackages: + if (re.findall(r'^%s\b' % re.escape(package), name) + and (self.coverTests + or not self.conf.testMatch.search(name))): + log.debug("coverage for %s", name) + return True + if name in self.skipModules: + log.debug("no coverage for %s: loaded before coverage start", + name) + return False + if self.conf.testMatch.search(name) and not self.coverTests: + log.debug("no coverage for %s: is a test", name) + return False + # accept any package that passed the previous tests, unless + # coverPackages is on -- in that case, if we wanted this + # module, we would have already returned True + return not self.coverPackages + + def wantFile(self, file, package=None): + """If inclusive coverage enabled, return true for all source files + in wanted packages. + """ + if self.coverInclusive: + if file.endswith(".py"): + if package and self.coverPackages: + for want in self.coverPackages: + if package.startswith(want): + return True + else: + return True + return None diff --git a/env/lib/python2.7/site-packages/nose/plugins/cover.pyc b/env/lib/python2.7/site-packages/nose/plugins/cover.pyc new file mode 100644 index 0000000..8085a57 Binary files /dev/null and b/env/lib/python2.7/site-packages/nose/plugins/cover.pyc differ diff --git a/env/lib/python2.7/site-packages/nose/plugins/debug.py b/env/lib/python2.7/site-packages/nose/plugins/debug.py new file mode 100644 index 0000000..78243e6 --- /dev/null +++ b/env/lib/python2.7/site-packages/nose/plugins/debug.py @@ -0,0 +1,67 @@ +""" +This plugin provides ``--pdb`` and ``--pdb-failures`` options. The ``--pdb`` +option will drop the test runner into pdb when it encounters an error. To +drop into pdb on failure, use ``--pdb-failures``. +""" + +import pdb +from nose.plugins.base import Plugin + +class Pdb(Plugin): + """ + Provides --pdb and --pdb-failures options that cause the test runner to + drop into pdb if it encounters an error or failure, respectively. + """ + enabled_for_errors = False + enabled_for_failures = False + score = 5 # run last, among builtins + + def options(self, parser, env): + """Register commandline options. + """ + parser.add_option( + "--pdb", action="store_true", dest="debugBoth", + default=env.get('NOSE_PDB', False), + help="Drop into debugger on failures or errors") + parser.add_option( + "--pdb-failures", action="store_true", + dest="debugFailures", + default=env.get('NOSE_PDB_FAILURES', False), + help="Drop into debugger on failures") + parser.add_option( + "--pdb-errors", action="store_true", + dest="debugErrors", + default=env.get('NOSE_PDB_ERRORS', False), + help="Drop into debugger on errors") + + def configure(self, options, conf): + """Configure which kinds of exceptions trigger plugin. + """ + self.conf = conf + self.enabled_for_errors = options.debugErrors or options.debugBoth + self.enabled_for_failures = options.debugFailures or options.debugBoth + self.enabled = self.enabled_for_failures or self.enabled_for_errors + + def addError(self, test, err): + """Enter pdb if configured to debug errors. + """ + if not self.enabled_for_errors: + return + self.debug(err) + + def addFailure(self, test, err): + """Enter pdb if configured to debug failures. + """ + if not self.enabled_for_failures: + return + self.debug(err) + + def debug(self, err): + import sys # FIXME why is this import here? + ec, ev, tb = err + stdout = sys.stdout + sys.stdout = sys.__stdout__ + try: + pdb.post_mortem(tb) + finally: + sys.stdout = stdout diff --git a/env/lib/python2.7/site-packages/nose/plugins/debug.pyc b/env/lib/python2.7/site-packages/nose/plugins/debug.pyc new file mode 100644 index 0000000..384ae6c Binary files /dev/null and b/env/lib/python2.7/site-packages/nose/plugins/debug.pyc differ diff --git a/env/lib/python2.7/site-packages/nose/plugins/deprecated.py b/env/lib/python2.7/site-packages/nose/plugins/deprecated.py new file mode 100644 index 0000000..461a26b --- /dev/null +++ b/env/lib/python2.7/site-packages/nose/plugins/deprecated.py @@ -0,0 +1,45 @@ +""" +This plugin installs a DEPRECATED error class for the :class:`DeprecatedTest` +exception. When :class:`DeprecatedTest` is raised, the exception will be logged +in the deprecated attribute of the result, ``D`` or ``DEPRECATED`` (verbose) +will be output, and the exception will not be counted as an error or failure. +It is enabled by default, but can be turned off by using ``--no-deprecated``. +""" + +from nose.plugins.errorclass import ErrorClass, ErrorClassPlugin + + +class DeprecatedTest(Exception): + """Raise this exception to mark a test as deprecated. + """ + pass + + +class Deprecated(ErrorClassPlugin): + """ + Installs a DEPRECATED error class for the DeprecatedTest exception. Enabled + by default. + """ + enabled = True + deprecated = ErrorClass(DeprecatedTest, + label='DEPRECATED', + isfailure=False) + + def options(self, parser, env): + """Register commandline options. + """ + env_opt = 'NOSE_WITHOUT_DEPRECATED' + parser.add_option('--no-deprecated', action='store_true', + dest='noDeprecated', default=env.get(env_opt, False), + help="Disable special handling of DeprecatedTest " + "exceptions.") + + def configure(self, options, conf): + """Configure plugin. + """ + if not self.can_configure: + return + self.conf = conf + disable = getattr(options, 'noDeprecated', False) + if disable: + self.enabled = False diff --git a/env/lib/python2.7/site-packages/nose/plugins/deprecated.pyc b/env/lib/python2.7/site-packages/nose/plugins/deprecated.pyc new file mode 100644 index 0000000..d5c327a Binary files /dev/null and b/env/lib/python2.7/site-packages/nose/plugins/deprecated.pyc differ diff --git a/env/lib/python2.7/site-packages/nose/plugins/doctests.py b/env/lib/python2.7/site-packages/nose/plugins/doctests.py new file mode 100644 index 0000000..5ef6579 --- /dev/null +++ b/env/lib/python2.7/site-packages/nose/plugins/doctests.py @@ -0,0 +1,455 @@ +"""Use the Doctest plugin with ``--with-doctest`` or the NOSE_WITH_DOCTEST +environment variable to enable collection and execution of :mod:`doctests +`. Because doctests are usually included in the tested package +(instead of being grouped into packages or modules of their own), nose only +looks for them in the non-test packages it discovers in the working directory. + +Doctests may also be placed into files other than python modules, in which +case they can be collected and executed by using the ``--doctest-extension`` +switch or NOSE_DOCTEST_EXTENSION environment variable to indicate which file +extension(s) to load. + +When loading doctests from non-module files, use the ``--doctest-fixtures`` +switch to specify how to find modules containing fixtures for the tests. A +module name will be produced by appending the value of that switch to the base +name of each doctest file loaded. For example, a doctest file "widgets.rst" +with the switch ``--doctest_fixtures=_fixt`` will load fixtures from the module +``widgets_fixt.py``. + +A fixtures module may define any or all of the following functions: + +* setup([module]) or setup_module([module]) + + Called before the test runs. You may raise SkipTest to skip all tests. + +* teardown([module]) or teardown_module([module]) + + Called after the test runs, if setup/setup_module did not raise an + unhandled exception. + +* setup_test(test) + + Called before the test. NOTE: the argument passed is a + doctest.DocTest instance, *not* a unittest.TestCase. + +* teardown_test(test) + + Called after the test, if setup_test did not raise an exception. NOTE: the + argument passed is a doctest.DocTest instance, *not* a unittest.TestCase. + +Doctests are run like any other test, with the exception that output +capture does not work; doctest does its own output capture while running a +test. + +.. note :: + + See :doc:`../doc_tests/test_doctest_fixtures/doctest_fixtures` for + additional documentation and examples. + +""" +from __future__ import generators + +import logging +import os +import sys +import unittest +from inspect import getmodule +from nose.plugins.base import Plugin +from nose.suite import ContextList +from nose.util import anyp, getpackage, test_address, resolve_name, \ + src, tolist, isproperty +try: + from cStringIO import StringIO +except ImportError: + from StringIO import StringIO +import sys +import __builtin__ as builtin_mod + +log = logging.getLogger(__name__) + +try: + import doctest + doctest.DocTestCase + # system version of doctest is acceptable, but needs a monkeypatch +except (ImportError, AttributeError): + # system version is too old + import nose.ext.dtcompat as doctest + + +# +# Doctest and coverage don't get along, so we need to create +# a monkeypatch that will replace the part of doctest that +# interferes with coverage reports. +# +# The monkeypatch is based on this zope patch: +# http://svn.zope.org/Zope3/trunk/src/zope/testing/doctest.py?rev=28679&r1=28703&r2=28705 +# +_orp = doctest._OutputRedirectingPdb + +class NoseOutputRedirectingPdb(_orp): + def __init__(self, out): + self.__debugger_used = False + _orp.__init__(self, out) + + def set_trace(self): + self.__debugger_used = True + _orp.set_trace(self, sys._getframe().f_back) + + def set_continue(self): + # Calling set_continue unconditionally would break unit test + # coverage reporting, as Bdb.set_continue calls sys.settrace(None). + if self.__debugger_used: + _orp.set_continue(self) +doctest._OutputRedirectingPdb = NoseOutputRedirectingPdb + + +class DoctestSuite(unittest.TestSuite): + """ + Doctest suites are parallelizable at the module or file level only, + since they may be attached to objects that are not individually + addressable (like properties). This suite subclass is used when + loading doctests from a module to ensure that behavior. + + This class is used only if the plugin is not fully prepared; + in normal use, the loader's suiteClass is used. + + """ + can_split = False + + def __init__(self, tests=(), context=None, can_split=False): + self.context = context + self.can_split = can_split + unittest.TestSuite.__init__(self, tests=tests) + + def address(self): + return test_address(self.context) + + def __iter__(self): + # 2.3 compat + return iter(self._tests) + + def __str__(self): + return str(self._tests) + + +class Doctest(Plugin): + """ + Activate doctest plugin to find and run doctests in non-test modules. + """ + extension = None + suiteClass = DoctestSuite + + def options(self, parser, env): + """Register commmandline options. + """ + Plugin.options(self, parser, env) + parser.add_option('--doctest-tests', action='store_true', + dest='doctest_tests', + default=env.get('NOSE_DOCTEST_TESTS'), + help="Also look for doctests in test modules. " + "Note that classes, methods and functions should " + "have either doctests or non-doctest tests, " + "not both. [NOSE_DOCTEST_TESTS]") + parser.add_option('--doctest-extension', action="append", + dest="doctestExtension", + metavar="EXT", + help="Also look for doctests in files with " + "this extension [NOSE_DOCTEST_EXTENSION]") + parser.add_option('--doctest-result-variable', + dest='doctest_result_var', + default=env.get('NOSE_DOCTEST_RESULT_VAR'), + metavar="VAR", + help="Change the variable name set to the result of " + "the last interpreter command from the default '_'. " + "Can be used to avoid conflicts with the _() " + "function used for text translation. " + "[NOSE_DOCTEST_RESULT_VAR]") + parser.add_option('--doctest-fixtures', action="store", + dest="doctestFixtures", + metavar="SUFFIX", + help="Find fixtures for a doctest file in module " + "with this name appended to the base name " + "of the doctest file") + parser.add_option('--doctest-options', action="append", + dest="doctestOptions", + metavar="OPTIONS", + help="Specify options to pass to doctest. " + + "Eg. '+ELLIPSIS,+NORMALIZE_WHITESPACE'") + # Set the default as a list, if given in env; otherwise + # an additional value set on the command line will cause + # an error. + env_setting = env.get('NOSE_DOCTEST_EXTENSION') + if env_setting is not None: + parser.set_defaults(doctestExtension=tolist(env_setting)) + + def configure(self, options, config): + """Configure plugin. + """ + Plugin.configure(self, options, config) + self.doctest_result_var = options.doctest_result_var + self.doctest_tests = options.doctest_tests + self.extension = tolist(options.doctestExtension) + self.fixtures = options.doctestFixtures + self.finder = doctest.DocTestFinder() + self.optionflags = 0 + if options.doctestOptions: + flags = ",".join(options.doctestOptions).split(',') + for flag in flags: + if not flag or flag[0] not in '+-': + raise ValueError( + "Must specify doctest options with starting " + + "'+' or '-'. Got %s" % (flag,)) + mode, option_name = flag[0], flag[1:] + option_flag = doctest.OPTIONFLAGS_BY_NAME.get(option_name) + if not option_flag: + raise ValueError("Unknown doctest option %s" % + (option_name,)) + if mode == '+': + self.optionflags |= option_flag + elif mode == '-': + self.optionflags &= ~option_flag + + def prepareTestLoader(self, loader): + """Capture loader's suiteClass. + + This is used to create test suites from doctest files. + + """ + self.suiteClass = loader.suiteClass + + def loadTestsFromModule(self, module): + """Load doctests from the module. + """ + log.debug("loading from %s", module) + if not self.matches(module.__name__): + log.debug("Doctest doesn't want module %s", module) + return + try: + tests = self.finder.find(module) + except AttributeError: + log.exception("Attribute error loading from %s", module) + # nose allows module.__test__ = False; doctest does not and throws + # AttributeError + return + if not tests: + log.debug("No tests found in %s", module) + return + tests.sort() + module_file = src(module.__file__) + # FIXME this breaks the id plugin somehow (tests probably don't + # get wrapped in result proxy or something) + cases = [] + for test in tests: + if not test.examples: + continue + if not test.filename: + test.filename = module_file + cases.append(DocTestCase(test, + optionflags=self.optionflags, + result_var=self.doctest_result_var)) + if cases: + yield self.suiteClass(cases, context=module, can_split=False) + + def loadTestsFromFile(self, filename): + """Load doctests from the file. + + Tests are loaded only if filename's extension matches + configured doctest extension. + + """ + if self.extension and anyp(filename.endswith, self.extension): + name = os.path.basename(filename) + dh = open(filename) + try: + doc = dh.read() + finally: + dh.close() + + fixture_context = None + globs = {'__file__': filename} + if self.fixtures: + base, ext = os.path.splitext(name) + dirname = os.path.dirname(filename) + sys.path.append(dirname) + fixt_mod = base + self.fixtures + try: + fixture_context = __import__( + fixt_mod, globals(), locals(), ["nop"]) + except ImportError, e: + log.debug( + "Could not import %s: %s (%s)", fixt_mod, e, sys.path) + log.debug("Fixture module %s resolved to %s", + fixt_mod, fixture_context) + if hasattr(fixture_context, 'globs'): + globs = fixture_context.globs(globs) + parser = doctest.DocTestParser() + test = parser.get_doctest( + doc, globs=globs, name=name, + filename=filename, lineno=0) + if test.examples: + case = DocFileCase( + test, + optionflags=self.optionflags, + setUp=getattr(fixture_context, 'setup_test', None), + tearDown=getattr(fixture_context, 'teardown_test', None), + result_var=self.doctest_result_var) + if fixture_context: + yield ContextList((case,), context=fixture_context) + else: + yield case + else: + yield False # no tests to load + + def makeTest(self, obj, parent): + """Look for doctests in the given object, which will be a + function, method or class. + """ + name = getattr(obj, '__name__', 'Unnammed %s' % type(obj)) + doctests = self.finder.find(obj, module=getmodule(parent), name=name) + if doctests: + for test in doctests: + if len(test.examples) == 0: + continue + yield DocTestCase(test, obj=obj, optionflags=self.optionflags, + result_var=self.doctest_result_var) + + def matches(self, name): + # FIXME this seems wrong -- nothing is ever going to + # fail this test, since we're given a module NAME not FILE + if name == '__init__.py': + return False + # FIXME don't think we need include/exclude checks here? + return ((self.doctest_tests or not self.conf.testMatch.search(name) + or (self.conf.include + and filter(None, + [inc.search(name) + for inc in self.conf.include]))) + and (not self.conf.exclude + or not filter(None, + [exc.search(name) + for exc in self.conf.exclude]))) + + def wantFile(self, file): + """Override to select all modules and any file ending with + configured doctest extension. + """ + # always want .py files + if file.endswith('.py'): + return True + # also want files that match my extension + if (self.extension + and anyp(file.endswith, self.extension) + and (not self.conf.exclude + or not filter(None, + [exc.search(file) + for exc in self.conf.exclude]))): + return True + return None + + +class DocTestCase(doctest.DocTestCase): + """Overrides DocTestCase to + provide an address() method that returns the correct address for + the doctest case. To provide hints for address(), an obj may also + be passed -- this will be used as the test object for purposes of + determining the test address, if it is provided. + """ + def __init__(self, test, optionflags=0, setUp=None, tearDown=None, + checker=None, obj=None, result_var='_'): + self._result_var = result_var + self._nose_obj = obj + super(DocTestCase, self).__init__( + test, optionflags=optionflags, setUp=setUp, tearDown=tearDown, + checker=checker) + + def address(self): + if self._nose_obj is not None: + return test_address(self._nose_obj) + obj = resolve_name(self._dt_test.name) + + if isproperty(obj): + # properties have no connection to the class they are in + # so we can't just look 'em up, we have to first look up + # the class, then stick the prop on the end + parts = self._dt_test.name.split('.') + class_name = '.'.join(parts[:-1]) + cls = resolve_name(class_name) + base_addr = test_address(cls) + return (base_addr[0], base_addr[1], + '.'.join([base_addr[2], parts[-1]])) + else: + return test_address(obj) + + # doctests loaded via find(obj) omit the module name + # so we need to override id, __repr__ and shortDescription + # bonus: this will squash a 2.3 vs 2.4 incompatiblity + def id(self): + name = self._dt_test.name + filename = self._dt_test.filename + if filename is not None: + pk = getpackage(filename) + if pk is None: + return name + if not name.startswith(pk): + name = "%s.%s" % (pk, name) + return name + + def __repr__(self): + name = self.id() + name = name.split('.') + return "%s (%s)" % (name[-1], '.'.join(name[:-1])) + __str__ = __repr__ + + def shortDescription(self): + return 'Doctest: %s' % self.id() + + def setUp(self): + if self._result_var is not None: + self._old_displayhook = sys.displayhook + sys.displayhook = self._displayhook + super(DocTestCase, self).setUp() + + def _displayhook(self, value): + if value is None: + return + setattr(builtin_mod, self._result_var, value) + print repr(value) + + def tearDown(self): + super(DocTestCase, self).tearDown() + if self._result_var is not None: + sys.displayhook = self._old_displayhook + delattr(builtin_mod, self._result_var) + + +class DocFileCase(doctest.DocFileCase): + """Overrides to provide address() method that returns the correct + address for the doc file case. + """ + def __init__(self, test, optionflags=0, setUp=None, tearDown=None, + checker=None, result_var='_'): + self._result_var = result_var + super(DocFileCase, self).__init__( + test, optionflags=optionflags, setUp=setUp, tearDown=tearDown, + checker=None) + + def address(self): + return (self._dt_test.filename, None, None) + + def setUp(self): + if self._result_var is not None: + self._old_displayhook = sys.displayhook + sys.displayhook = self._displayhook + super(DocFileCase, self).setUp() + + def _displayhook(self, value): + if value is None: + return + setattr(builtin_mod, self._result_var, value) + print repr(value) + + def tearDown(self): + super(DocFileCase, self).tearDown() + if self._result_var is not None: + sys.displayhook = self._old_displayhook + delattr(builtin_mod, self._result_var) diff --git a/env/lib/python2.7/site-packages/nose/plugins/doctests.pyc b/env/lib/python2.7/site-packages/nose/plugins/doctests.pyc new file mode 100644 index 0000000..2e804c1 Binary files /dev/null and b/env/lib/python2.7/site-packages/nose/plugins/doctests.pyc differ diff --git a/env/lib/python2.7/site-packages/nose/plugins/errorclass.py b/env/lib/python2.7/site-packages/nose/plugins/errorclass.py new file mode 100644 index 0000000..d1540e0 --- /dev/null +++ b/env/lib/python2.7/site-packages/nose/plugins/errorclass.py @@ -0,0 +1,210 @@ +""" +ErrorClass Plugins +------------------ + +ErrorClass plugins provide an easy way to add support for custom +handling of particular classes of exceptions. + +An ErrorClass plugin defines one or more ErrorClasses and how each is +handled and reported on. Each error class is stored in a different +attribute on the result, and reported separately. Each error class must +indicate the exceptions that fall under that class, the label to use +for reporting, and whether exceptions of the class should be +considered as failures for the whole test run. + +ErrorClasses use a declarative syntax. Assign an ErrorClass to the +attribute you wish to add to the result object, defining the +exceptions, label and isfailure attributes. For example, to declare an +ErrorClassPlugin that defines TodoErrors (and subclasses of TodoError) +as an error class with the label 'TODO' that is considered a failure, +do this: + + >>> class Todo(Exception): + ... pass + >>> class TodoError(ErrorClassPlugin): + ... todo = ErrorClass(Todo, label='TODO', isfailure=True) + +The MetaErrorClass metaclass translates the ErrorClass declarations +into the tuples used by the error handling and reporting functions in +the result. This is an internal format and subject to change; you +should always use the declarative syntax for attaching ErrorClasses to +an ErrorClass plugin. + + >>> TodoError.errorClasses # doctest: +ELLIPSIS + ((, ('todo', 'TODO', True)),) + +Let's see the plugin in action. First some boilerplate. + + >>> import sys + >>> import unittest + >>> try: + ... # 2.7+ + ... from unittest.runner import _WritelnDecorator + ... except ImportError: + ... from unittest import _WritelnDecorator + ... + >>> buf = _WritelnDecorator(sys.stdout) + +Now define a test case that raises a Todo. + + >>> class TestTodo(unittest.TestCase): + ... def runTest(self): + ... raise Todo("I need to test something") + >>> case = TestTodo() + +Prepare the result using our plugin. Normally this happens during the +course of test execution within nose -- you won't be doing this +yourself. For the purposes of this testing document, I'm stepping +through the internal process of nose so you can see what happens at +each step. + + >>> plugin = TodoError() + >>> from nose.result import _TextTestResult + >>> result = _TextTestResult(stream=buf, descriptions=0, verbosity=2) + >>> plugin.prepareTestResult(result) + +Now run the test. TODO is printed. + + >>> _ = case(result) # doctest: +ELLIPSIS + runTest (....TestTodo) ... TODO: I need to test something + +Errors and failures are empty, but todo has our test: + + >>> result.errors + [] + >>> result.failures + [] + >>> result.todo # doctest: +ELLIPSIS + [(<....TestTodo testMethod=runTest>, '...Todo: I need to test something\\n')] + >>> result.printErrors() # doctest: +ELLIPSIS + + ====================================================================== + TODO: runTest (....TestTodo) + ---------------------------------------------------------------------- + Traceback (most recent call last): + ... + ...Todo: I need to test something + + +Since we defined a Todo as a failure, the run was not successful. + + >>> result.wasSuccessful() + False +""" + +from nose.pyversion import make_instancemethod +from nose.plugins.base import Plugin +from nose.result import TextTestResult +from nose.util import isclass + +class MetaErrorClass(type): + """Metaclass for ErrorClassPlugins that allows error classes to be + set up in a declarative manner. + """ + def __init__(self, name, bases, attr): + errorClasses = [] + for name, detail in attr.items(): + if isinstance(detail, ErrorClass): + attr.pop(name) + for cls in detail: + errorClasses.append( + (cls, (name, detail.label, detail.isfailure))) + super(MetaErrorClass, self).__init__(name, bases, attr) + self.errorClasses = tuple(errorClasses) + + +class ErrorClass(object): + def __init__(self, *errorClasses, **kw): + self.errorClasses = errorClasses + try: + for key in ('label', 'isfailure'): + setattr(self, key, kw.pop(key)) + except KeyError: + raise TypeError("%r is a required named argument for ErrorClass" + % key) + + def __iter__(self): + return iter(self.errorClasses) + + +class ErrorClassPlugin(Plugin): + """ + Base class for ErrorClass plugins. Subclass this class and declare the + exceptions that you wish to handle as attributes of the subclass. + """ + __metaclass__ = MetaErrorClass + score = 1000 + errorClasses = () + + def addError(self, test, err): + err_cls, a, b = err + if not isclass(err_cls): + return + classes = [e[0] for e in self.errorClasses] + if filter(lambda c: issubclass(err_cls, c), classes): + return True + + def prepareTestResult(self, result): + if not hasattr(result, 'errorClasses'): + self.patchResult(result) + for cls, (storage_attr, label, isfail) in self.errorClasses: + if cls not in result.errorClasses: + storage = getattr(result, storage_attr, []) + setattr(result, storage_attr, storage) + result.errorClasses[cls] = (storage, label, isfail) + + def patchResult(self, result): + result.printLabel = print_label_patch(result) + result._orig_addError, result.addError = \ + result.addError, add_error_patch(result) + result._orig_wasSuccessful, result.wasSuccessful = \ + result.wasSuccessful, wassuccessful_patch(result) + if hasattr(result, 'printErrors'): + result._orig_printErrors, result.printErrors = \ + result.printErrors, print_errors_patch(result) + if hasattr(result, 'addSkip'): + result._orig_addSkip, result.addSkip = \ + result.addSkip, add_skip_patch(result) + result.errorClasses = {} + + +def add_error_patch(result): + """Create a new addError method to patch into a result instance + that recognizes the errorClasses attribute and deals with + errorclasses correctly. + """ + return make_instancemethod(TextTestResult.addError, result) + + +def print_errors_patch(result): + """Create a new printErrors method that prints errorClasses items + as well. + """ + return make_instancemethod(TextTestResult.printErrors, result) + + +def print_label_patch(result): + """Create a new printLabel method that prints errorClasses items + as well. + """ + return make_instancemethod(TextTestResult.printLabel, result) + + +def wassuccessful_patch(result): + """Create a new wasSuccessful method that checks errorClasses for + exceptions that were put into other slots than error or failure + but that still count as not success. + """ + return make_instancemethod(TextTestResult.wasSuccessful, result) + + +def add_skip_patch(result): + """Create a new addSkip method to patch into a result instance + that delegates to addError. + """ + return make_instancemethod(TextTestResult.addSkip, result) + + +if __name__ == '__main__': + import doctest + doctest.testmod() diff --git a/env/lib/python2.7/site-packages/nose/plugins/errorclass.pyc b/env/lib/python2.7/site-packages/nose/plugins/errorclass.pyc new file mode 100644 index 0000000..338c986 Binary files /dev/null and b/env/lib/python2.7/site-packages/nose/plugins/errorclass.pyc differ diff --git a/env/lib/python2.7/site-packages/nose/plugins/failuredetail.py b/env/lib/python2.7/site-packages/nose/plugins/failuredetail.py new file mode 100644 index 0000000..6462865 --- /dev/null +++ b/env/lib/python2.7/site-packages/nose/plugins/failuredetail.py @@ -0,0 +1,49 @@ +""" +This plugin provides assert introspection. When the plugin is enabled +and a test failure occurs, the traceback is displayed with extra context +around the line in which the exception was raised. Simple variable +substitution is also performed in the context output to provide more +debugging information. +""" + +from nose.plugins import Plugin +from nose.pyversion import exc_to_unicode, force_unicode +from nose.inspector import inspect_traceback + +class FailureDetail(Plugin): + """ + Plugin that provides extra information in tracebacks of test failures. + """ + score = 1600 # before capture + + def options(self, parser, env): + """Register commmandline options. + """ + parser.add_option( + "-d", "--detailed-errors", "--failure-detail", + action="store_true", + default=env.get('NOSE_DETAILED_ERRORS'), + dest="detailedErrors", help="Add detail to error" + " output by attempting to evaluate failed" + " asserts [NOSE_DETAILED_ERRORS]") + + def configure(self, options, conf): + """Configure plugin. + """ + if not self.can_configure: + return + self.enabled = options.detailedErrors + self.conf = conf + + def formatFailure(self, test, err): + """Add detail from traceback inspection to error message of a failure. + """ + ec, ev, tb = err + tbinfo, str_ev = None, exc_to_unicode(ev) + + if tb: + tbinfo = force_unicode(inspect_traceback(tb)) + str_ev = '\n'.join([str_ev, tbinfo]) + test.tbinfo = tbinfo + return (ec, str_ev, tb) + diff --git a/env/lib/python2.7/site-packages/nose/plugins/failuredetail.pyc b/env/lib/python2.7/site-packages/nose/plugins/failuredetail.pyc new file mode 100644 index 0000000..32810bc Binary files /dev/null and b/env/lib/python2.7/site-packages/nose/plugins/failuredetail.pyc differ diff --git a/env/lib/python2.7/site-packages/nose/plugins/isolate.py b/env/lib/python2.7/site-packages/nose/plugins/isolate.py new file mode 100644 index 0000000..13235df --- /dev/null +++ b/env/lib/python2.7/site-packages/nose/plugins/isolate.py @@ -0,0 +1,103 @@ +"""The isolation plugin resets the contents of sys.modules after running +each test module or package. Use it by setting ``--with-isolation`` or the +NOSE_WITH_ISOLATION environment variable. + +The effects are similar to wrapping the following functions around the +import and execution of each test module:: + + def setup(module): + module._mods = sys.modules.copy() + + def teardown(module): + to_del = [ m for m in sys.modules.keys() if m not in + module._mods ] + for mod in to_del: + del sys.modules[mod] + sys.modules.update(module._mods) + +Isolation works only during lazy loading. In normal use, this is only +during discovery of modules within a directory, where the process of +importing, loading tests and running tests from each module is +encapsulated in a single loadTestsFromName call. This plugin +implements loadTestsFromNames to force the same lazy-loading there, +which allows isolation to work in directed mode as well as discovery, +at the cost of some efficiency: lazy-loading names forces full context +setup and teardown to run for each name, defeating the grouping that +is normally used to ensure that context setup and teardown are run the +fewest possible times for a given set of names. + +.. warning :: + + This plugin should not be used in conjunction with other plugins + that assume that modules, once imported, will stay imported; for + instance, it may cause very odd results when used with the coverage + plugin. + +""" + +import logging +import sys + +from nose.plugins import Plugin + + +log = logging.getLogger('nose.plugins.isolation') + +class IsolationPlugin(Plugin): + """ + Activate the isolation plugin to isolate changes to external + modules to a single test module or package. The isolation plugin + resets the contents of sys.modules after each test module or + package runs to its state before the test. PLEASE NOTE that this + plugin should not be used with the coverage plugin, or in any other case + where module reloading may produce undesirable side-effects. + """ + score = 10 # I want to be last + name = 'isolation' + + def configure(self, options, conf): + """Configure plugin. + """ + Plugin.configure(self, options, conf) + self._mod_stack = [] + + def beforeContext(self): + """Copy sys.modules onto my mod stack + """ + mods = sys.modules.copy() + self._mod_stack.append(mods) + + def afterContext(self): + """Pop my mod stack and restore sys.modules to the state + it was in when mod stack was pushed. + """ + mods = self._mod_stack.pop() + to_del = [ m for m in sys.modules.keys() if m not in mods ] + if to_del: + log.debug('removing sys modules entries: %s', to_del) + for mod in to_del: + del sys.modules[mod] + sys.modules.update(mods) + + def loadTestsFromNames(self, names, module=None): + """Create a lazy suite that calls beforeContext and afterContext + around each name. The side-effect of this is that full context + fixtures will be set up and torn down around each test named. + """ + # Fast path for when we don't care + if not names or len(names) == 1: + return + loader = self.loader + plugins = self.conf.plugins + def lazy(): + for name in names: + plugins.beforeContext() + yield loader.loadTestsFromName(name, module=module) + plugins.afterContext() + return (loader.suiteClass(lazy), []) + + def prepareTestLoader(self, loader): + """Get handle on test loader so we can use it in loadTestsFromNames. + """ + self.loader = loader + diff --git a/env/lib/python2.7/site-packages/nose/plugins/isolate.pyc b/env/lib/python2.7/site-packages/nose/plugins/isolate.pyc new file mode 100644 index 0000000..7cf8362 Binary files /dev/null and b/env/lib/python2.7/site-packages/nose/plugins/isolate.pyc differ diff --git a/env/lib/python2.7/site-packages/nose/plugins/logcapture.py b/env/lib/python2.7/site-packages/nose/plugins/logcapture.py new file mode 100644 index 0000000..4c9a79f --- /dev/null +++ b/env/lib/python2.7/site-packages/nose/plugins/logcapture.py @@ -0,0 +1,245 @@ +""" +This plugin captures logging statements issued during test execution. When an +error or failure occurs, the captured log messages are attached to the running +test in the test.capturedLogging attribute, and displayed with the error failure +output. It is enabled by default but can be turned off with the option +``--nologcapture``. + +You can filter captured logging statements with the ``--logging-filter`` option. +If set, it specifies which logger(s) will be captured; loggers that do not match +will be passed. Example: specifying ``--logging-filter=sqlalchemy,myapp`` +will ensure that only statements logged via sqlalchemy.engine, myapp +or myapp.foo.bar logger will be logged. + +You can remove other installed logging handlers with the +``--logging-clear-handlers`` option. +""" + +import logging +from logging import Handler +import threading + +from nose.plugins.base import Plugin +from nose.util import anyp, ln, safe_str + +try: + from cStringIO import StringIO +except ImportError: + from StringIO import StringIO + +log = logging.getLogger(__name__) + +class FilterSet(object): + def __init__(self, filter_components): + self.inclusive, self.exclusive = self._partition(filter_components) + + # @staticmethod + def _partition(components): + inclusive, exclusive = [], [] + for component in components: + if component.startswith('-'): + exclusive.append(component[1:]) + else: + inclusive.append(component) + return inclusive, exclusive + _partition = staticmethod(_partition) + + def allow(self, record): + """returns whether this record should be printed""" + if not self: + # nothing to filter + return True + return self._allow(record) and not self._deny(record) + + # @staticmethod + def _any_match(matchers, record): + """return the bool of whether `record` starts with + any item in `matchers`""" + def record_matches_key(key): + return record == key or record.startswith(key + '.') + return anyp(bool, map(record_matches_key, matchers)) + _any_match = staticmethod(_any_match) + + def _allow(self, record): + if not self.inclusive: + return True + return self._any_match(self.inclusive, record) + + def _deny(self, record): + if not self.exclusive: + return False + return self._any_match(self.exclusive, record) + + +class MyMemoryHandler(Handler): + def __init__(self, logformat, logdatefmt, filters): + Handler.__init__(self) + fmt = logging.Formatter(logformat, logdatefmt) + self.setFormatter(fmt) + self.filterset = FilterSet(filters) + self.buffer = [] + def emit(self, record): + self.buffer.append(self.format(record)) + def flush(self): + pass # do nothing + def truncate(self): + self.buffer = [] + def filter(self, record): + if self.filterset.allow(record.name): + return Handler.filter(self, record) + def __getstate__(self): + state = self.__dict__.copy() + del state['lock'] + return state + def __setstate__(self, state): + self.__dict__.update(state) + self.lock = threading.RLock() + + +class LogCapture(Plugin): + """ + Log capture plugin. Enabled by default. Disable with --nologcapture. + This plugin captures logging statements issued during test execution, + appending any output captured to the error or failure output, + should the test fail or raise an error. + """ + enabled = True + env_opt = 'NOSE_NOLOGCAPTURE' + name = 'logcapture' + score = 500 + logformat = '%(name)s: %(levelname)s: %(message)s' + logdatefmt = None + clear = False + filters = ['-nose'] + + def options(self, parser, env): + """Register commandline options. + """ + parser.add_option( + "--nologcapture", action="store_false", + default=not env.get(self.env_opt), dest="logcapture", + help="Disable logging capture plugin. " + "Logging configuration will be left intact." + " [NOSE_NOLOGCAPTURE]") + parser.add_option( + "--logging-format", action="store", dest="logcapture_format", + default=env.get('NOSE_LOGFORMAT') or self.logformat, + metavar="FORMAT", + help="Specify custom format to print statements. " + "Uses the same format as used by standard logging handlers." + " [NOSE_LOGFORMAT]") + parser.add_option( + "--logging-datefmt", action="store", dest="logcapture_datefmt", + default=env.get('NOSE_LOGDATEFMT') or self.logdatefmt, + metavar="FORMAT", + help="Specify custom date/time format to print statements. " + "Uses the same format as used by standard logging handlers." + " [NOSE_LOGDATEFMT]") + parser.add_option( + "--logging-filter", action="store", dest="logcapture_filters", + default=env.get('NOSE_LOGFILTER'), + metavar="FILTER", + help="Specify which statements to filter in/out. " + "By default, everything is captured. If the output is too" + " verbose,\nuse this option to filter out needless output.\n" + "Example: filter=foo will capture statements issued ONLY to\n" + " foo or foo.what.ever.sub but not foobar or other logger.\n" + "Specify multiple loggers with comma: filter=foo,bar,baz.\n" + "If any logger name is prefixed with a minus, eg filter=-foo,\n" + "it will be excluded rather than included. Default: " + "exclude logging messages from nose itself (-nose)." + " [NOSE_LOGFILTER]\n") + parser.add_option( + "--logging-clear-handlers", action="store_true", + default=False, dest="logcapture_clear", + help="Clear all other logging handlers") + parser.add_option( + "--logging-level", action="store", + default='NOTSET', dest="logcapture_level", + help="Set the log level to capture") + + def configure(self, options, conf): + """Configure plugin. + """ + self.conf = conf + # Disable if explicitly disabled, or if logging is + # configured via logging config file + if not options.logcapture or conf.loggingConfig: + self.enabled = False + self.logformat = options.logcapture_format + self.logdatefmt = options.logcapture_datefmt + self.clear = options.logcapture_clear + self.loglevel = options.logcapture_level + if options.logcapture_filters: + self.filters = options.logcapture_filters.split(',') + + def setupLoghandler(self): + # setup our handler with root logger + root_logger = logging.getLogger() + if self.clear: + if hasattr(root_logger, "handlers"): + for handler in root_logger.handlers: + root_logger.removeHandler(handler) + for logger in logging.Logger.manager.loggerDict.values(): + if hasattr(logger, "handlers"): + for handler in logger.handlers: + logger.removeHandler(handler) + # make sure there isn't one already + # you can't simply use "if self.handler not in root_logger.handlers" + # since at least in unit tests this doesn't work -- + # LogCapture() is instantiated for each test case while root_logger + # is module global + # so we always add new MyMemoryHandler instance + for handler in root_logger.handlers[:]: + if isinstance(handler, MyMemoryHandler): + root_logger.handlers.remove(handler) + root_logger.addHandler(self.handler) + # to make sure everything gets captured + loglevel = getattr(self, "loglevel", "NOTSET") + root_logger.setLevel(getattr(logging, loglevel)) + + def begin(self): + """Set up logging handler before test run begins. + """ + self.start() + + def start(self): + self.handler = MyMemoryHandler(self.logformat, self.logdatefmt, + self.filters) + self.setupLoghandler() + + def end(self): + pass + + def beforeTest(self, test): + """Clear buffers and handlers before test. + """ + self.setupLoghandler() + + def afterTest(self, test): + """Clear buffers after test. + """ + self.handler.truncate() + + def formatFailure(self, test, err): + """Add captured log messages to failure output. + """ + return self.formatError(test, err) + + def formatError(self, test, err): + """Add captured log messages to error output. + """ + # logic flow copied from Capture.formatError + test.capturedLogging = records = self.formatLogRecords() + if not records: + return err + ec, ev, tb = err + return (ec, self.addCaptureToErr(ev, records), tb) + + def formatLogRecords(self): + return map(safe_str, self.handler.buffer) + + def addCaptureToErr(self, ev, records): + return '\n'.join([safe_str(ev), ln('>> begin captured logging <<')] + \ + records + \ + [ln('>> end captured logging <<')]) diff --git a/env/lib/python2.7/site-packages/nose/plugins/logcapture.pyc b/env/lib/python2.7/site-packages/nose/plugins/logcapture.pyc new file mode 100644 index 0000000..ee43815 Binary files /dev/null and b/env/lib/python2.7/site-packages/nose/plugins/logcapture.pyc differ diff --git a/env/lib/python2.7/site-packages/nose/plugins/manager.py b/env/lib/python2.7/site-packages/nose/plugins/manager.py new file mode 100644 index 0000000..4d2ed22 --- /dev/null +++ b/env/lib/python2.7/site-packages/nose/plugins/manager.py @@ -0,0 +1,460 @@ +""" +Plugin Manager +-------------- + +A plugin manager class is used to load plugins, manage the list of +loaded plugins, and proxy calls to those plugins. + +The plugin managers provided with nose are: + +:class:`PluginManager` + This manager doesn't implement loadPlugins, so it can only work + with a static list of plugins. + +:class:`BuiltinPluginManager` + This manager loads plugins referenced in ``nose.plugins.builtin``. + +:class:`EntryPointPluginManager` + This manager uses setuptools entrypoints to load plugins. + +:class:`ExtraPluginsPluginManager` + This manager loads extra plugins specified with the keyword + `addplugins`. + +:class:`DefaultPluginMananger` + This is the manager class that will be used by default. If + setuptools is installed, it is a subclass of + :class:`EntryPointPluginManager` and :class:`BuiltinPluginManager`; + otherwise, an alias to :class:`BuiltinPluginManager`. + +:class:`RestrictedPluginManager` + This manager is for use in test runs where some plugin calls are + not available, such as runs started with ``python setup.py test``, + where the test runner is the default unittest :class:`TextTestRunner`. It + is a subclass of :class:`DefaultPluginManager`. + +Writing a plugin manager +======================== + +If you want to load plugins via some other means, you can write a +plugin manager and pass an instance of your plugin manager class when +instantiating the :class:`nose.config.Config` instance that you pass to +:class:`TestProgram` (or :func:`main` or :func:`run`). + +To implement your plugin loading scheme, implement ``loadPlugins()``, +and in that method, call ``addPlugin()`` with an instance of each plugin +you wish to make available. Make sure to call +``super(self).loadPlugins()`` as well if have subclassed a manager +other than ``PluginManager``. + +""" +import inspect +import logging +import os +import sys +from itertools import chain as iterchain +from warnings import warn +import nose.config +from nose.failure import Failure +from nose.plugins.base import IPluginInterface +from nose.pyversion import sort_list + +try: + import cPickle as pickle +except: + import pickle +try: + from cStringIO import StringIO +except: + from StringIO import StringIO + + +__all__ = ['DefaultPluginManager', 'PluginManager', 'EntryPointPluginManager', + 'BuiltinPluginManager', 'RestrictedPluginManager'] + +log = logging.getLogger(__name__) + + +class PluginProxy(object): + """Proxy for plugin calls. Essentially a closure bound to the + given call and plugin list. + + The plugin proxy also must be bound to a particular plugin + interface specification, so that it knows what calls are available + and any special handling that is required for each call. + """ + interface = IPluginInterface + def __init__(self, call, plugins): + try: + self.method = getattr(self.interface, call) + except AttributeError: + raise AttributeError("%s is not a valid %s method" + % (call, self.interface.__name__)) + self.call = self.makeCall(call) + self.plugins = [] + for p in plugins: + self.addPlugin(p, call) + + def __call__(self, *arg, **kw): + return self.call(*arg, **kw) + + def addPlugin(self, plugin, call): + """Add plugin to my list of plugins to call, if it has the attribute + I'm bound to. + """ + meth = getattr(plugin, call, None) + if meth is not None: + if call == 'loadTestsFromModule' and \ + len(inspect.getargspec(meth)[0]) == 2: + orig_meth = meth + meth = lambda module, path, **kwargs: orig_meth(module) + self.plugins.append((plugin, meth)) + + def makeCall(self, call): + if call == 'loadTestsFromNames': + # special case -- load tests from names behaves somewhat differently + # from other chainable calls, because plugins return a tuple, only + # part of which can be chained to the next plugin. + return self._loadTestsFromNames + + meth = self.method + if getattr(meth, 'generative', False): + # call all plugins and yield a flattened iterator of their results + return lambda *arg, **kw: list(self.generate(*arg, **kw)) + elif getattr(meth, 'chainable', False): + return self.chain + else: + # return a value from the first plugin that returns non-None + return self.simple + + def chain(self, *arg, **kw): + """Call plugins in a chain, where the result of each plugin call is + sent to the next plugin as input. The final output result is returned. + """ + result = None + # extract the static arguments (if any) from arg so they can + # be passed to each plugin call in the chain + static = [a for (static, a) + in zip(getattr(self.method, 'static_args', []), arg) + if static] + for p, meth in self.plugins: + result = meth(*arg, **kw) + arg = static[:] + arg.append(result) + return result + + def generate(self, *arg, **kw): + """Call all plugins, yielding each item in each non-None result. + """ + for p, meth in self.plugins: + result = None + try: + result = meth(*arg, **kw) + if result is not None: + for r in result: + yield r + except (KeyboardInterrupt, SystemExit): + raise + except: + exc = sys.exc_info() + yield Failure(*exc) + continue + + def simple(self, *arg, **kw): + """Call all plugins, returning the first non-None result. + """ + for p, meth in self.plugins: + result = meth(*arg, **kw) + if result is not None: + return result + + def _loadTestsFromNames(self, names, module=None): + """Chainable but not quite normal. Plugins return a tuple of + (tests, names) after processing the names. The tests are added + to a suite that is accumulated throughout the full call, while + names are input for the next plugin in the chain. + """ + suite = [] + for p, meth in self.plugins: + result = meth(names, module=module) + if result is not None: + suite_part, names = result + if suite_part: + suite.extend(suite_part) + return suite, names + + +class NoPlugins(object): + """Null Plugin manager that has no plugins.""" + interface = IPluginInterface + def __init__(self): + self._plugins = self.plugins = () + + def __iter__(self): + return () + + def _doNothing(self, *args, **kwds): + pass + + def _emptyIterator(self, *args, **kwds): + return () + + def __getattr__(self, call): + method = getattr(self.interface, call) + if getattr(method, "generative", False): + return self._emptyIterator + else: + return self._doNothing + + def addPlugin(self, plug): + raise NotImplementedError() + + def addPlugins(self, plugins): + raise NotImplementedError() + + def configure(self, options, config): + pass + + def loadPlugins(self): + pass + + def sort(self): + pass + + +class PluginManager(object): + """Base class for plugin managers. PluginManager is intended to be + used only with a static list of plugins. The loadPlugins() implementation + only reloads plugins from _extraplugins to prevent those from being + overridden by a subclass. + + The basic functionality of a plugin manager is to proxy all unknown + attributes through a ``PluginProxy`` to a list of plugins. + + Note that the list of plugins *may not* be changed after the first plugin + call. + """ + proxyClass = PluginProxy + + def __init__(self, plugins=(), proxyClass=None): + self._plugins = [] + self._extraplugins = () + self._proxies = {} + if plugins: + self.addPlugins(plugins) + if proxyClass is not None: + self.proxyClass = proxyClass + + def __getattr__(self, call): + try: + return self._proxies[call] + except KeyError: + proxy = self.proxyClass(call, self._plugins) + self._proxies[call] = proxy + return proxy + + def __iter__(self): + return iter(self.plugins) + + def addPlugin(self, plug): + # allow, for instance, plugins loaded via entry points to + # supplant builtin plugins. + new_name = getattr(plug, 'name', object()) + self._plugins[:] = [p for p in self._plugins + if getattr(p, 'name', None) != new_name] + self._plugins.append(plug) + + def addPlugins(self, plugins=(), extraplugins=()): + """extraplugins are maintained in a separate list and + re-added by loadPlugins() to prevent their being overwritten + by plugins added by a subclass of PluginManager + """ + self._extraplugins = extraplugins + for plug in iterchain(plugins, extraplugins): + self.addPlugin(plug) + + def configure(self, options, config): + """Configure the set of plugins with the given options + and config instance. After configuration, disabled plugins + are removed from the plugins list. + """ + log.debug("Configuring plugins") + self.config = config + cfg = PluginProxy('configure', self._plugins) + cfg(options, config) + enabled = [plug for plug in self._plugins if plug.enabled] + self.plugins = enabled + self.sort() + log.debug("Plugins enabled: %s", enabled) + + def loadPlugins(self): + for plug in self._extraplugins: + self.addPlugin(plug) + + def sort(self): + return sort_list(self._plugins, lambda x: getattr(x, 'score', 1), reverse=True) + + def _get_plugins(self): + return self._plugins + + def _set_plugins(self, plugins): + self._plugins = [] + self.addPlugins(plugins) + + plugins = property(_get_plugins, _set_plugins, None, + """Access the list of plugins managed by + this plugin manager""") + + +class ZeroNinePlugin: + """Proxy for 0.9 plugins, adapts 0.10 calls to 0.9 standard. + """ + def __init__(self, plugin): + self.plugin = plugin + + def options(self, parser, env=os.environ): + self.plugin.add_options(parser, env) + + def addError(self, test, err): + if not hasattr(self.plugin, 'addError'): + return + # switch off to addSkip, addDeprecated if those types + from nose.exc import SkipTest, DeprecatedTest + ec, ev, tb = err + if issubclass(ec, SkipTest): + if not hasattr(self.plugin, 'addSkip'): + return + return self.plugin.addSkip(test.test) + elif issubclass(ec, DeprecatedTest): + if not hasattr(self.plugin, 'addDeprecated'): + return + return self.plugin.addDeprecated(test.test) + # add capt + capt = test.capturedOutput + return self.plugin.addError(test.test, err, capt) + + def loadTestsFromFile(self, filename): + if hasattr(self.plugin, 'loadTestsFromPath'): + return self.plugin.loadTestsFromPath(filename) + + def addFailure(self, test, err): + if not hasattr(self.plugin, 'addFailure'): + return + # add capt and tbinfo + capt = test.capturedOutput + tbinfo = test.tbinfo + return self.plugin.addFailure(test.test, err, capt, tbinfo) + + def addSuccess(self, test): + if not hasattr(self.plugin, 'addSuccess'): + return + capt = test.capturedOutput + self.plugin.addSuccess(test.test, capt) + + def startTest(self, test): + if not hasattr(self.plugin, 'startTest'): + return + return self.plugin.startTest(test.test) + + def stopTest(self, test): + if not hasattr(self.plugin, 'stopTest'): + return + return self.plugin.stopTest(test.test) + + def __getattr__(self, val): + return getattr(self.plugin, val) + + +class EntryPointPluginManager(PluginManager): + """Plugin manager that loads plugins from the `nose.plugins` and + `nose.plugins.0.10` entry points. + """ + entry_points = (('nose.plugins.0.10', None), + ('nose.plugins', ZeroNinePlugin)) + + def loadPlugins(self): + """Load plugins by iterating the `nose.plugins` entry point. + """ + from pkg_resources import iter_entry_points + loaded = {} + for entry_point, adapt in self.entry_points: + for ep in iter_entry_points(entry_point): + if ep.name in loaded: + continue + loaded[ep.name] = True + log.debug('%s load plugin %s', self.__class__.__name__, ep) + try: + plugcls = ep.load() + except KeyboardInterrupt: + raise + except Exception, e: + # never want a plugin load to kill the test run + # but we can't log here because the logger is not yet + # configured + warn("Unable to load plugin %s: %s" % (ep, e), + RuntimeWarning) + continue + if adapt: + plug = adapt(plugcls()) + else: + plug = plugcls() + self.addPlugin(plug) + super(EntryPointPluginManager, self).loadPlugins() + + +class BuiltinPluginManager(PluginManager): + """Plugin manager that loads plugins from the list in + `nose.plugins.builtin`. + """ + def loadPlugins(self): + """Load plugins in nose.plugins.builtin + """ + from nose.plugins import builtin + for plug in builtin.plugins: + self.addPlugin(plug()) + super(BuiltinPluginManager, self).loadPlugins() + +try: + import pkg_resources + class DefaultPluginManager(EntryPointPluginManager, BuiltinPluginManager): + pass + +except ImportError: + class DefaultPluginManager(BuiltinPluginManager): + pass + +class RestrictedPluginManager(DefaultPluginManager): + """Plugin manager that restricts the plugin list to those not + excluded by a list of exclude methods. Any plugin that implements + an excluded method will be removed from the manager's plugin list + after plugins are loaded. + """ + def __init__(self, plugins=(), exclude=(), load=True): + DefaultPluginManager.__init__(self, plugins) + self.load = load + self.exclude = exclude + self.excluded = [] + self._excludedOpts = None + + def excludedOption(self, name): + if self._excludedOpts is None: + from optparse import OptionParser + self._excludedOpts = OptionParser(add_help_option=False) + for plugin in self.excluded: + plugin.options(self._excludedOpts, env={}) + return self._excludedOpts.get_option('--' + name) + + def loadPlugins(self): + if self.load: + DefaultPluginManager.loadPlugins(self) + allow = [] + for plugin in self.plugins: + ok = True + for method in self.exclude: + if hasattr(plugin, method): + ok = False + self.excluded.append(plugin) + break + if ok: + allow.append(plugin) + self.plugins = allow diff --git a/env/lib/python2.7/site-packages/nose/plugins/manager.pyc b/env/lib/python2.7/site-packages/nose/plugins/manager.pyc new file mode 100644 index 0000000..b3e141e Binary files /dev/null and b/env/lib/python2.7/site-packages/nose/plugins/manager.pyc differ diff --git a/env/lib/python2.7/site-packages/nose/plugins/multiprocess.py b/env/lib/python2.7/site-packages/nose/plugins/multiprocess.py new file mode 100644 index 0000000..2cae744 --- /dev/null +++ b/env/lib/python2.7/site-packages/nose/plugins/multiprocess.py @@ -0,0 +1,835 @@ +""" +Overview +======== + +The multiprocess plugin enables you to distribute your test run among a set of +worker processes that run tests in parallel. This can speed up CPU-bound test +runs (as long as the number of work processeses is around the number of +processors or cores available), but is mainly useful for IO-bound tests that +spend most of their time waiting for data to arrive from someplace else. + +.. note :: + + See :doc:`../doc_tests/test_multiprocess/multiprocess` for + additional documentation and examples. Use of this plugin on python + 2.5 or earlier requires the multiprocessing_ module, also available + from PyPI. + +.. _multiprocessing : http://code.google.com/p/python-multiprocessing/ + +How tests are distributed +========================= + +The ideal case would be to dispatch each test to a worker process +separately. This ideal is not attainable in all cases, however, because many +test suites depend on context (class, module or package) fixtures. + +The plugin can't know (unless you tell it -- see below!) if a context fixture +can be called many times concurrently (is re-entrant), or if it can be shared +among tests running in different processes. Therefore, if a context has +fixtures, the default behavior is to dispatch the entire suite to a worker as +a unit. + +Controlling distribution +^^^^^^^^^^^^^^^^^^^^^^^^ + +There are two context-level variables that you can use to control this default +behavior. + +If a context's fixtures are re-entrant, set ``_multiprocess_can_split_ = True`` +in the context, and the plugin will dispatch tests in suites bound to that +context as if the context had no fixtures. This means that the fixtures will +execute concurrently and multiple times, typically once per test. + +If a context's fixtures can be shared by tests running in different processes +-- such as a package-level fixture that starts an external http server or +initializes a shared database -- then set ``_multiprocess_shared_ = True`` in +the context. These fixtures will then execute in the primary nose process, and +tests in those contexts will be individually dispatched to run in parallel. + +How results are collected and reported +====================================== + +As each test or suite executes in a worker process, results (failures, errors, +and specially handled exceptions like SkipTest) are collected in that +process. When the worker process finishes, it returns results to the main +nose process. There, any progress output is printed (dots!), and the +results from the test run are combined into a consolidated result +set. When results have been received for all dispatched tests, or all +workers have died, the result summary is output as normal. + +Beware! +======= + +Not all test suites will benefit from, or even operate correctly using, this +plugin. For example, CPU-bound tests will run more slowly if you don't have +multiple processors. There are also some differences in plugin +interactions and behaviors due to the way in which tests are dispatched and +loaded. In general, test loading under this plugin operates as if it were +always in directed mode instead of discovered mode. For instance, doctests +in test modules will always be found when using this plugin with the doctest +plugin. + +But the biggest issue you will face is probably concurrency. Unless you +have kept your tests as religiously pure unit tests, with no side-effects, no +ordering issues, and no external dependencies, chances are you will experience +odd, intermittent and unexplainable failures and errors when using this +plugin. This doesn't necessarily mean the plugin is broken; it may mean that +your test suite is not safe for concurrency. + +New Features in 1.1.0 +===================== + +* functions generated by test generators are now added to the worker queue + making them multi-threaded. +* fixed timeout functionality, now functions will be terminated with a + TimedOutException exception when they exceed their execution time. The + worker processes are not terminated. +* added ``--process-restartworker`` option to restart workers once they are + done, this helps control memory usage. Sometimes memory leaks can accumulate + making long runs very difficult. +* added global _instantiate_plugins to configure which plugins are started + on the worker processes. + +""" + +import logging +import os +import sys +import time +import traceback +import unittest +import pickle +import signal +import nose.case +from nose.core import TextTestRunner +from nose import failure +from nose import loader +from nose.plugins.base import Plugin +from nose.pyversion import bytes_ +from nose.result import TextTestResult +from nose.suite import ContextSuite +from nose.util import test_address +try: + # 2.7+ + from unittest.runner import _WritelnDecorator +except ImportError: + from unittest import _WritelnDecorator +from Queue import Empty +from warnings import warn +try: + from cStringIO import StringIO +except ImportError: + import StringIO + +# this is a list of plugin classes that will be checked for and created inside +# each worker process +_instantiate_plugins = None + +log = logging.getLogger(__name__) + +Process = Queue = Pool = Event = Value = Array = None + +# have to inherit KeyboardInterrupt to it will interrupt process properly +class TimedOutException(KeyboardInterrupt): + def __init__(self, value = "Timed Out"): + self.value = value + def __str__(self): + return repr(self.value) + +def _import_mp(): + global Process, Queue, Pool, Event, Value, Array + try: + from multiprocessing import Manager, Process + #prevent the server process created in the manager which holds Python + #objects and allows other processes to manipulate them using proxies + #to interrupt on SIGINT (keyboardinterrupt) so that the communication + #channel between subprocesses and main process is still usable after + #ctrl+C is received in the main process. + old=signal.signal(signal.SIGINT, signal.SIG_IGN) + m = Manager() + #reset it back so main process will receive a KeyboardInterrupt + #exception on ctrl+c + signal.signal(signal.SIGINT, old) + Queue, Pool, Event, Value, Array = ( + m.Queue, m.Pool, m.Event, m.Value, m.Array + ) + except ImportError: + warn("multiprocessing module is not available, multiprocess plugin " + "cannot be used", RuntimeWarning) + + +class TestLet: + def __init__(self, case): + try: + self._id = case.id() + except AttributeError: + pass + self._short_description = case.shortDescription() + self._str = str(case) + + def id(self): + return self._id + + def shortDescription(self): + return self._short_description + + def __str__(self): + return self._str + +class MultiProcess(Plugin): + """ + Run tests in multiple processes. Requires processing module. + """ + score = 1000 + status = {} + + def options(self, parser, env): + """ + Register command-line options. + """ + parser.add_option("--processes", action="store", + default=env.get('NOSE_PROCESSES', 0), + dest="multiprocess_workers", + metavar="NUM", + help="Spread test run among this many processes. " + "Set a number equal to the number of processors " + "or cores in your machine for best results. " + "Pass a negative number to have the number of " + "processes automatically set to the number of " + "cores. Passing 0 means to disable parallel " + "testing. Default is 0 unless NOSE_PROCESSES is " + "set. " + "[NOSE_PROCESSES]") + parser.add_option("--process-timeout", action="store", + default=env.get('NOSE_PROCESS_TIMEOUT', 10), + dest="multiprocess_timeout", + metavar="SECONDS", + help="Set timeout for return of results from each " + "test runner process. Default is 10. " + "[NOSE_PROCESS_TIMEOUT]") + parser.add_option("--process-restartworker", action="store_true", + default=env.get('NOSE_PROCESS_RESTARTWORKER', False), + dest="multiprocess_restartworker", + help="If set, will restart each worker process once" + " their tests are done, this helps control memory " + "leaks from killing the system. " + "[NOSE_PROCESS_RESTARTWORKER]") + + def configure(self, options, config): + """ + Configure plugin. + """ + try: + self.status.pop('active') + except KeyError: + pass + if not hasattr(options, 'multiprocess_workers'): + self.enabled = False + return + # don't start inside of a worker process + if config.worker: + return + self.config = config + try: + workers = int(options.multiprocess_workers) + except (TypeError, ValueError): + workers = 0 + if workers: + _import_mp() + if Process is None: + self.enabled = False + return + # Negative number of workers will cause multiprocessing to hang. + # Set the number of workers to the CPU count to avoid this. + if workers < 0: + try: + import multiprocessing + workers = multiprocessing.cpu_count() + except NotImplementedError: + self.enabled = False + return + self.enabled = True + self.config.multiprocess_workers = workers + t = float(options.multiprocess_timeout) + self.config.multiprocess_timeout = t + r = int(options.multiprocess_restartworker) + self.config.multiprocess_restartworker = r + self.status['active'] = True + + def prepareTestLoader(self, loader): + """Remember loader class so MultiProcessTestRunner can instantiate + the right loader. + """ + self.loaderClass = loader.__class__ + + def prepareTestRunner(self, runner): + """Replace test runner with MultiProcessTestRunner. + """ + # replace with our runner class + return MultiProcessTestRunner(stream=runner.stream, + verbosity=self.config.verbosity, + config=self.config, + loaderClass=self.loaderClass) + +def signalhandler(sig, frame): + raise TimedOutException() + +class MultiProcessTestRunner(TextTestRunner): + waitkilltime = 5.0 # max time to wait to terminate a process that does not + # respond to SIGILL + def __init__(self, **kw): + self.loaderClass = kw.pop('loaderClass', loader.defaultTestLoader) + super(MultiProcessTestRunner, self).__init__(**kw) + + def collect(self, test, testQueue, tasks, to_teardown, result): + # dispatch and collect results + # put indexes only on queue because tests aren't picklable + for case in self.nextBatch(test): + log.debug("Next batch %s (%s)", case, type(case)) + if (isinstance(case, nose.case.Test) and + isinstance(case.test, failure.Failure)): + log.debug("Case is a Failure") + case(result) # run here to capture the failure + continue + # handle shared fixtures + if isinstance(case, ContextSuite) and case.context is failure.Failure: + log.debug("Case is a Failure") + case(result) # run here to capture the failure + continue + elif isinstance(case, ContextSuite) and self.sharedFixtures(case): + log.debug("%s has shared fixtures", case) + try: + case.setUp() + except (KeyboardInterrupt, SystemExit): + raise + except: + log.debug("%s setup failed", sys.exc_info()) + result.addError(case, sys.exc_info()) + else: + to_teardown.append(case) + if case.factory: + ancestors=case.factory.context.get(case, []) + for an in ancestors[:2]: + #log.debug('reset ancestor %s', an) + if getattr(an, '_multiprocess_shared_', False): + an._multiprocess_can_split_=True + #an._multiprocess_shared_=False + self.collect(case, testQueue, tasks, to_teardown, result) + + else: + test_addr = self.addtask(testQueue,tasks,case) + log.debug("Queued test %s (%s) to %s", + len(tasks), test_addr, testQueue) + + def startProcess(self, iworker, testQueue, resultQueue, shouldStop, result): + currentaddr = Value('c',bytes_('')) + currentstart = Value('d',time.time()) + keyboardCaught = Event() + p = Process(target=runner, + args=(iworker, testQueue, + resultQueue, + currentaddr, + currentstart, + keyboardCaught, + shouldStop, + self.loaderClass, + result.__class__, + pickle.dumps(self.config))) + p.currentaddr = currentaddr + p.currentstart = currentstart + p.keyboardCaught = keyboardCaught + old = signal.signal(signal.SIGILL, signalhandler) + p.start() + signal.signal(signal.SIGILL, old) + return p + + def run(self, test): + """ + Execute the test (which may be a test suite). If the test is a suite, + distribute it out among as many processes as have been configured, at + as fine a level as is possible given the context fixtures defined in + the suite or any sub-suites. + + """ + log.debug("%s.run(%s) (%s)", self, test, os.getpid()) + wrapper = self.config.plugins.prepareTest(test) + if wrapper is not None: + test = wrapper + + # plugins can decorate or capture the output stream + wrapped = self.config.plugins.setOutputStream(self.stream) + if wrapped is not None: + self.stream = wrapped + + testQueue = Queue() + resultQueue = Queue() + tasks = [] + completed = [] + workers = [] + to_teardown = [] + shouldStop = Event() + + result = self._makeResult() + start = time.time() + + self.collect(test, testQueue, tasks, to_teardown, result) + + log.debug("Starting %s workers", self.config.multiprocess_workers) + for i in range(self.config.multiprocess_workers): + p = self.startProcess(i, testQueue, resultQueue, shouldStop, result) + workers.append(p) + log.debug("Started worker process %s", i+1) + + total_tasks = len(tasks) + # need to keep track of the next time to check for timeouts in case + # more than one process times out at the same time. + nexttimeout=self.config.multiprocess_timeout + thrownError = None + + try: + while tasks: + log.debug("Waiting for results (%s/%s tasks), next timeout=%.3fs", + len(completed), total_tasks,nexttimeout) + try: + iworker, addr, newtask_addrs, batch_result = resultQueue.get( + timeout=nexttimeout) + log.debug('Results received for worker %d, %s, new tasks: %d', + iworker,addr,len(newtask_addrs)) + try: + try: + tasks.remove(addr) + except ValueError: + log.warn('worker %s failed to remove from tasks: %s', + iworker,addr) + total_tasks += len(newtask_addrs) + tasks.extend(newtask_addrs) + except KeyError: + log.debug("Got result for unknown task? %s", addr) + log.debug("current: %s",str(list(tasks)[0])) + else: + completed.append([addr,batch_result]) + self.consolidate(result, batch_result) + if (self.config.stopOnError + and not result.wasSuccessful()): + # set the stop condition + shouldStop.set() + break + if self.config.multiprocess_restartworker: + log.debug('joining worker %s',iworker) + # wait for working, but not that important if worker + # cannot be joined in fact, for workers that add to + # testQueue, they will not terminate until all their + # items are read + workers[iworker].join(timeout=1) + if not shouldStop.is_set() and not testQueue.empty(): + log.debug('starting new process on worker %s',iworker) + workers[iworker] = self.startProcess(iworker, testQueue, resultQueue, shouldStop, result) + except Empty: + log.debug("Timed out with %s tasks pending " + "(empty testQueue=%r): %s", + len(tasks),testQueue.empty(),str(tasks)) + any_alive = False + for iworker, w in enumerate(workers): + if w.is_alive(): + worker_addr = bytes_(w.currentaddr.value,'ascii') + timeprocessing = time.time() - w.currentstart.value + if ( len(worker_addr) == 0 + and timeprocessing > self.config.multiprocess_timeout-0.1): + log.debug('worker %d has finished its work item, ' + 'but is not exiting? do we wait for it?', + iworker) + else: + any_alive = True + if (len(worker_addr) > 0 + and timeprocessing > self.config.multiprocess_timeout-0.1): + log.debug('timed out worker %s: %s', + iworker,worker_addr) + w.currentaddr.value = bytes_('') + # If the process is in C++ code, sending a SIGILL + # might not send a python KeybordInterrupt exception + # therefore, send multiple signals until an + # exception is caught. If this takes too long, then + # terminate the process + w.keyboardCaught.clear() + startkilltime = time.time() + while not w.keyboardCaught.is_set() and w.is_alive(): + if time.time()-startkilltime > self.waitkilltime: + # have to terminate... + log.error("terminating worker %s",iworker) + w.terminate() + # there is a small probability that the + # terminated process might send a result, + # which has to be specially handled or + # else processes might get orphaned. + workers[iworker] = w = self.startProcess(iworker, testQueue, resultQueue, shouldStop, result) + break + os.kill(w.pid, signal.SIGILL) + time.sleep(0.1) + if not any_alive and testQueue.empty(): + log.debug("All workers dead") + break + nexttimeout=self.config.multiprocess_timeout + for w in workers: + if w.is_alive() and len(w.currentaddr.value) > 0: + timeprocessing = time.time()-w.currentstart.value + if timeprocessing <= self.config.multiprocess_timeout: + nexttimeout = min(nexttimeout, + self.config.multiprocess_timeout-timeprocessing) + log.debug("Completed %s tasks (%s remain)", len(completed), len(tasks)) + + except (KeyboardInterrupt, SystemExit), e: + log.info('parent received ctrl-c when waiting for test results') + thrownError = e + #resultQueue.get(False) + + result.addError(test, sys.exc_info()) + + try: + for case in to_teardown: + log.debug("Tearing down shared fixtures for %s", case) + try: + case.tearDown() + except (KeyboardInterrupt, SystemExit): + raise + except: + result.addError(case, sys.exc_info()) + + stop = time.time() + + # first write since can freeze on shutting down processes + result.printErrors() + result.printSummary(start, stop) + self.config.plugins.finalize(result) + + if thrownError is None: + log.debug("Tell all workers to stop") + for w in workers: + if w.is_alive(): + testQueue.put('STOP', block=False) + + # wait for the workers to end + for iworker,worker in enumerate(workers): + if worker.is_alive(): + log.debug('joining worker %s',iworker) + worker.join() + if worker.is_alive(): + log.debug('failed to join worker %s',iworker) + except (KeyboardInterrupt, SystemExit): + log.info('parent received ctrl-c when shutting down: stop all processes') + for worker in workers: + if worker.is_alive(): + worker.terminate() + + if thrownError: raise thrownError + else: raise + + return result + + def addtask(testQueue,tasks,case): + arg = None + if isinstance(case,nose.case.Test) and hasattr(case.test,'arg'): + # this removes the top level descriptor and allows real function + # name to be returned + case.test.descriptor = None + arg = case.test.arg + test_addr = MultiProcessTestRunner.address(case) + testQueue.put((test_addr,arg), block=False) + if arg is not None: + test_addr += str(arg) + if tasks is not None: + tasks.append(test_addr) + return test_addr + addtask = staticmethod(addtask) + + def address(case): + if hasattr(case, 'address'): + file, mod, call = case.address() + elif hasattr(case, 'context'): + file, mod, call = test_address(case.context) + else: + raise Exception("Unable to convert %s to address" % case) + parts = [] + if file is None: + if mod is None: + raise Exception("Unaddressable case %s" % case) + else: + parts.append(mod) + else: + # strip __init__.py(c) from end of file part + # if present, having it there confuses loader + dirname, basename = os.path.split(file) + if basename.startswith('__init__'): + file = dirname + parts.append(file) + if call is not None: + parts.append(call) + return ':'.join(map(str, parts)) + address = staticmethod(address) + + def nextBatch(self, test): + # allows tests or suites to mark themselves as not safe + # for multiprocess execution + if hasattr(test, 'context'): + if not getattr(test.context, '_multiprocess_', True): + return + + if ((isinstance(test, ContextSuite) + and test.hasFixtures(self.checkCanSplit)) + or not getattr(test, 'can_split', True) + or not isinstance(test, unittest.TestSuite)): + # regular test case, or a suite with context fixtures + + # special case: when run like nosetests path/to/module.py + # the top-level suite has only one item, and it shares + # the same context as that item. In that case, we want the + # item, not the top-level suite + if isinstance(test, ContextSuite): + contained = list(test) + if (len(contained) == 1 + and getattr(contained[0], + 'context', None) == test.context): + test = contained[0] + yield test + else: + # Suite is without fixtures at this level; but it may have + # fixtures at any deeper level, so we need to examine it all + # the way down to the case level + for case in test: + for batch in self.nextBatch(case): + yield batch + + def checkCanSplit(context, fixt): + """ + Callback that we use to check whether the fixtures found in a + context or ancestor are ones we care about. + + Contexts can tell us that their fixtures are reentrant by setting + _multiprocess_can_split_. So if we see that, we return False to + disregard those fixtures. + """ + if not fixt: + return False + if getattr(context, '_multiprocess_can_split_', False): + return False + return True + checkCanSplit = staticmethod(checkCanSplit) + + def sharedFixtures(self, case): + context = getattr(case, 'context', None) + if not context: + return False + return getattr(context, '_multiprocess_shared_', False) + + def consolidate(self, result, batch_result): + log.debug("batch result is %s" , batch_result) + try: + output, testsRun, failures, errors, errorClasses = batch_result + except ValueError: + log.debug("result in unexpected format %s", batch_result) + failure.Failure(*sys.exc_info())(result) + return + self.stream.write(output) + result.testsRun += testsRun + result.failures.extend(failures) + result.errors.extend(errors) + for key, (storage, label, isfail) in errorClasses.items(): + if key not in result.errorClasses: + # Ordinarily storage is result attribute + # but it's only processed through the errorClasses + # dict, so it's ok to fake it here + result.errorClasses[key] = ([], label, isfail) + mystorage, _junk, _junk = result.errorClasses[key] + mystorage.extend(storage) + log.debug("Ran %s tests (total: %s)", testsRun, result.testsRun) + + +def runner(ix, testQueue, resultQueue, currentaddr, currentstart, + keyboardCaught, shouldStop, loaderClass, resultClass, config): + try: + try: + return __runner(ix, testQueue, resultQueue, currentaddr, currentstart, + keyboardCaught, shouldStop, loaderClass, resultClass, config) + except KeyboardInterrupt: + log.debug('Worker %s keyboard interrupt, stopping',ix) + except Empty: + log.debug("Worker %s timed out waiting for tasks", ix) + +def __runner(ix, testQueue, resultQueue, currentaddr, currentstart, + keyboardCaught, shouldStop, loaderClass, resultClass, config): + + config = pickle.loads(config) + dummy_parser = config.parserClass() + if _instantiate_plugins is not None: + for pluginclass in _instantiate_plugins: + plugin = pluginclass() + plugin.addOptions(dummy_parser,{}) + config.plugins.addPlugin(plugin) + config.plugins.configure(config.options,config) + config.plugins.begin() + log.debug("Worker %s executing, pid=%d", ix,os.getpid()) + loader = loaderClass(config=config) + loader.suiteClass.suiteClass = NoSharedFixtureContextSuite + + def get(): + return testQueue.get(timeout=config.multiprocess_timeout) + + def makeResult(): + stream = _WritelnDecorator(StringIO()) + result = resultClass(stream, descriptions=1, + verbosity=config.verbosity, + config=config) + plug_result = config.plugins.prepareTestResult(result) + if plug_result: + return plug_result + return result + + def batch(result): + failures = [(TestLet(c), err) for c, err in result.failures] + errors = [(TestLet(c), err) for c, err in result.errors] + errorClasses = {} + for key, (storage, label, isfail) in result.errorClasses.items(): + errorClasses[key] = ([(TestLet(c), err) for c, err in storage], + label, isfail) + return ( + result.stream.getvalue(), + result.testsRun, + failures, + errors, + errorClasses) + for test_addr, arg in iter(get, 'STOP'): + if shouldStop.is_set(): + log.exception('Worker %d STOPPED',ix) + break + result = makeResult() + test = loader.loadTestsFromNames([test_addr]) + test.testQueue = testQueue + test.tasks = [] + test.arg = arg + log.debug("Worker %s Test is %s (%s)", ix, test_addr, test) + try: + if arg is not None: + test_addr = test_addr + str(arg) + currentaddr.value = bytes_(test_addr) + currentstart.value = time.time() + test(result) + currentaddr.value = bytes_('') + resultQueue.put((ix, test_addr, test.tasks, batch(result))) + except KeyboardInterrupt, e: #TimedOutException: + timeout = isinstance(e, TimedOutException) + if timeout: + keyboardCaught.set() + if len(currentaddr.value): + if timeout: + msg = 'Worker %s timed out, failing current test %s' + else: + msg = 'Worker %s keyboard interrupt, failing current test %s' + log.exception(msg,ix,test_addr) + currentaddr.value = bytes_('') + failure.Failure(*sys.exc_info())(result) + resultQueue.put((ix, test_addr, test.tasks, batch(result))) + else: + if timeout: + msg = 'Worker %s test %s timed out' + else: + msg = 'Worker %s test %s keyboard interrupt' + log.debug(msg,ix,test_addr) + resultQueue.put((ix, test_addr, test.tasks, batch(result))) + if not timeout: + raise + except SystemExit: + currentaddr.value = bytes_('') + log.exception('Worker %s system exit',ix) + raise + except: + currentaddr.value = bytes_('') + log.exception("Worker %s error running test or returning " + "results",ix) + failure.Failure(*sys.exc_info())(result) + resultQueue.put((ix, test_addr, test.tasks, batch(result))) + if config.multiprocess_restartworker: + break + log.debug("Worker %s ending", ix) + + +class NoSharedFixtureContextSuite(ContextSuite): + """ + Context suite that never fires shared fixtures. + + When a context sets _multiprocess_shared_, fixtures in that context + are executed by the main process. Using this suite class prevents them + from executing in the runner process as well. + + """ + testQueue = None + tasks = None + arg = None + def setupContext(self, context): + if getattr(context, '_multiprocess_shared_', False): + return + super(NoSharedFixtureContextSuite, self).setupContext(context) + + def teardownContext(self, context): + if getattr(context, '_multiprocess_shared_', False): + return + super(NoSharedFixtureContextSuite, self).teardownContext(context) + def run(self, result): + """Run tests in suite inside of suite fixtures. + """ + # proxy the result for myself + log.debug("suite %s (%s) run called, tests: %s", + id(self), self, self._tests) + if self.resultProxy: + result, orig = self.resultProxy(result, self), result + else: + result, orig = result, result + try: + #log.debug('setUp for %s', id(self)); + self.setUp() + except KeyboardInterrupt: + raise + except: + self.error_context = 'setup' + result.addError(self, self._exc_info()) + return + try: + for test in self._tests: + if (isinstance(test,nose.case.Test) + and self.arg is not None): + test.test.arg = self.arg + else: + test.arg = self.arg + test.testQueue = self.testQueue + test.tasks = self.tasks + if result.shouldStop: + log.debug("stopping") + break + # each nose.case.Test will create its own result proxy + # so the cases need the original result, to avoid proxy + # chains + #log.debug('running test %s in suite %s', test, self); + try: + test(orig) + except KeyboardInterrupt, e: + timeout = isinstance(e, TimedOutException) + if timeout: + msg = 'Timeout when running test %s in suite %s' + else: + msg = 'KeyboardInterrupt when running test %s in suite %s' + log.debug(msg, test, self) + err = (TimedOutException,TimedOutException(str(test)), + sys.exc_info()[2]) + test.config.plugins.addError(test,err) + orig.addError(test,err) + if not timeout: + raise + finally: + self.has_run = True + try: + #log.debug('tearDown for %s', id(self)); + self.tearDown() + except KeyboardInterrupt: + raise + except: + self.error_context = 'teardown' + result.addError(self, self._exc_info()) diff --git a/env/lib/python2.7/site-packages/nose/plugins/multiprocess.pyc b/env/lib/python2.7/site-packages/nose/plugins/multiprocess.pyc new file mode 100644 index 0000000..5f28fc2 Binary files /dev/null and b/env/lib/python2.7/site-packages/nose/plugins/multiprocess.pyc differ diff --git a/env/lib/python2.7/site-packages/nose/plugins/plugintest.py b/env/lib/python2.7/site-packages/nose/plugins/plugintest.py new file mode 100644 index 0000000..76d0d2c --- /dev/null +++ b/env/lib/python2.7/site-packages/nose/plugins/plugintest.py @@ -0,0 +1,416 @@ +""" +Testing Plugins +=============== + +The plugin interface is well-tested enough to safely unit test your +use of its hooks with some level of confidence. However, there is also +a mixin for unittest.TestCase called PluginTester that's designed to +test plugins in their native runtime environment. + +Here's a simple example with a do-nothing plugin and a composed suite. + + >>> import unittest + >>> from nose.plugins import Plugin, PluginTester + >>> class FooPlugin(Plugin): + ... pass + >>> class TestPluginFoo(PluginTester, unittest.TestCase): + ... activate = '--with-foo' + ... plugins = [FooPlugin()] + ... def test_foo(self): + ... for line in self.output: + ... # i.e. check for patterns + ... pass + ... + ... # or check for a line containing ... + ... assert "ValueError" in self.output + ... def makeSuite(self): + ... class TC(unittest.TestCase): + ... def runTest(self): + ... raise ValueError("I hate foo") + ... return [TC('runTest')] + ... + >>> res = unittest.TestResult() + >>> case = TestPluginFoo('test_foo') + >>> _ = case(res) + >>> res.errors + [] + >>> res.failures + [] + >>> res.wasSuccessful() + True + >>> res.testsRun + 1 + +And here is a more complex example of testing a plugin that has extra +arguments and reads environment variables. + + >>> import unittest, os + >>> from nose.plugins import Plugin, PluginTester + >>> class FancyOutputter(Plugin): + ... name = "fancy" + ... def configure(self, options, conf): + ... Plugin.configure(self, options, conf) + ... if not self.enabled: + ... return + ... self.fanciness = 1 + ... if options.more_fancy: + ... self.fanciness = 2 + ... if 'EVEN_FANCIER' in self.env: + ... self.fanciness = 3 + ... + ... def options(self, parser, env=os.environ): + ... self.env = env + ... parser.add_option('--more-fancy', action='store_true') + ... Plugin.options(self, parser, env=env) + ... + ... def report(self, stream): + ... stream.write("FANCY " * self.fanciness) + ... + >>> class TestFancyOutputter(PluginTester, unittest.TestCase): + ... activate = '--with-fancy' # enables the plugin + ... plugins = [FancyOutputter()] + ... args = ['--more-fancy'] + ... env = {'EVEN_FANCIER': '1'} + ... + ... def test_fancy_output(self): + ... assert "FANCY FANCY FANCY" in self.output, ( + ... "got: %s" % self.output) + ... def makeSuite(self): + ... class TC(unittest.TestCase): + ... def runTest(self): + ... raise ValueError("I hate fancy stuff") + ... return [TC('runTest')] + ... + >>> res = unittest.TestResult() + >>> case = TestFancyOutputter('test_fancy_output') + >>> _ = case(res) + >>> res.errors + [] + >>> res.failures + [] + >>> res.wasSuccessful() + True + >>> res.testsRun + 1 + +""" + +import re +import sys +from warnings import warn + +try: + from cStringIO import StringIO +except ImportError: + from StringIO import StringIO + +__all__ = ['PluginTester', 'run'] + +from os import getpid +class MultiProcessFile(object): + """ + helper for testing multiprocessing + + multiprocessing poses a problem for doctests, since the strategy + of replacing sys.stdout/stderr with file-like objects then + inspecting the results won't work: the child processes will + write to the objects, but the data will not be reflected + in the parent doctest-ing process. + + The solution is to create file-like objects which will interact with + multiprocessing in a more desirable way. + + All processes can write to this object, but only the creator can read. + This allows the testing system to see a unified picture of I/O. + """ + def __init__(self): + # per advice at: + # http://docs.python.org/library/multiprocessing.html#all-platforms + self.__master = getpid() + self.__queue = Manager().Queue() + self.__buffer = StringIO() + self.softspace = 0 + + def buffer(self): + if getpid() != self.__master: + return + + from Queue import Empty + from collections import defaultdict + cache = defaultdict(str) + while True: + try: + pid, data = self.__queue.get_nowait() + except Empty: + break + if pid == (): + #show parent output after children + #this is what users see, usually + pid = ( 1e100, ) # googol! + cache[pid] += data + for pid in sorted(cache): + #self.__buffer.write( '%s wrote: %r\n' % (pid, cache[pid]) ) #DEBUG + self.__buffer.write( cache[pid] ) + def write(self, data): + # note that these pids are in the form of current_process()._identity + # rather than OS pids + from multiprocessing import current_process + pid = current_process()._identity + self.__queue.put((pid, data)) + def __iter__(self): + "getattr doesn't work for iter()" + self.buffer() + return self.__buffer + def seek(self, offset, whence=0): + self.buffer() + return self.__buffer.seek(offset, whence) + def getvalue(self): + self.buffer() + return self.__buffer.getvalue() + def __getattr__(self, attr): + return getattr(self.__buffer, attr) + +try: + from multiprocessing import Manager + Buffer = MultiProcessFile +except ImportError: + Buffer = StringIO + +class PluginTester(object): + """A mixin for testing nose plugins in their runtime environment. + + Subclass this and mix in unittest.TestCase to run integration/functional + tests on your plugin. When setUp() is called, the stub test suite is + executed with your plugin so that during an actual test you can inspect the + artifacts of how your plugin interacted with the stub test suite. + + - activate + + - the argument to send nosetests to activate the plugin + + - suitepath + + - if set, this is the path of the suite to test. Otherwise, you + will need to use the hook, makeSuite() + + - plugins + + - the list of plugins to make available during the run. Note + that this does not mean these plugins will be *enabled* during + the run -- only the plugins enabled by the activate argument + or other settings in argv or env will be enabled. + + - args + + - a list of arguments to add to the nosetests command, in addition to + the activate argument + + - env + + - optional dict of environment variables to send nosetests + + """ + activate = None + suitepath = None + args = None + env = {} + argv = None + plugins = [] + ignoreFiles = None + + def makeSuite(self): + """returns a suite object of tests to run (unittest.TestSuite()) + + If self.suitepath is None, this must be implemented. The returned suite + object will be executed with all plugins activated. It may return + None. + + Here is an example of a basic suite object you can return :: + + >>> import unittest + >>> class SomeTest(unittest.TestCase): + ... def runTest(self): + ... raise ValueError("Now do something, plugin!") + ... + >>> unittest.TestSuite([SomeTest()]) # doctest: +ELLIPSIS + ]> + + """ + raise NotImplementedError + + def _execPlugin(self): + """execute the plugin on the internal test suite. + """ + from nose.config import Config + from nose.core import TestProgram + from nose.plugins.manager import PluginManager + + suite = None + stream = Buffer() + conf = Config(env=self.env, + stream=stream, + plugins=PluginManager(plugins=self.plugins)) + if self.ignoreFiles is not None: + conf.ignoreFiles = self.ignoreFiles + if not self.suitepath: + suite = self.makeSuite() + + self.nose = TestProgram(argv=self.argv, config=conf, suite=suite, + exit=False) + self.output = AccessDecorator(stream) + + def setUp(self): + """runs nosetests with the specified test suite, all plugins + activated. + """ + self.argv = ['nosetests', self.activate] + if self.args: + self.argv.extend(self.args) + if self.suitepath: + self.argv.append(self.suitepath) + + self._execPlugin() + + +class AccessDecorator(object): + stream = None + _buf = None + def __init__(self, stream): + self.stream = stream + stream.seek(0) + self._buf = stream.read() + stream.seek(0) + def __contains__(self, val): + return val in self._buf + def __iter__(self): + return iter(self.stream) + def __str__(self): + return self._buf + + +def blankline_separated_blocks(text): + "a bunch of === characters is also considered a blank line" + block = [] + for line in text.splitlines(True): + block.append(line) + line = line.strip() + if not line or line.startswith('===') and not line.strip('='): + yield "".join(block) + block = [] + if block: + yield "".join(block) + + +def remove_stack_traces(out): + # this regexp taken from Python 2.5's doctest + traceback_re = re.compile(r""" + # Grab the traceback header. Different versions of Python have + # said different things on the first traceback line. + ^(?P Traceback\ \( + (?: most\ recent\ call\ last + | innermost\ last + ) \) : + ) + \s* $ # toss trailing whitespace on the header. + (?P .*?) # don't blink: absorb stuff until... + ^(?=\w) # a line *starts* with alphanum. + .*?(?P \w+ ) # exception name + (?P [:\n] .*) # the rest + """, re.VERBOSE | re.MULTILINE | re.DOTALL) + blocks = [] + for block in blankline_separated_blocks(out): + blocks.append(traceback_re.sub(r"\g\n...\n\g\g", block)) + return "".join(blocks) + + +def simplify_warnings(out): + warn_re = re.compile(r""" + # Cut the file and line no, up to the warning name + ^.*:\d+:\s + (?P\w+): \s+ # warning category + (?P.+) $ \n? # warning message + ^ .* $ # stack frame + """, re.VERBOSE | re.MULTILINE) + return warn_re.sub(r"\g: \g", out) + + +def remove_timings(out): + return re.sub( + r"Ran (\d+ tests?) in [0-9.]+s", r"Ran \1 in ...s", out) + + +def munge_nose_output_for_doctest(out): + """Modify nose output to make it easy to use in doctests.""" + out = remove_stack_traces(out) + out = simplify_warnings(out) + out = remove_timings(out) + return out.strip() + + +def run(*arg, **kw): + """ + Specialized version of nose.run for use inside of doctests that + test test runs. + + This version of run() prints the result output to stdout. Before + printing, the output is processed by replacing the timing + information with an ellipsis (...), removing traceback stacks, and + removing trailing whitespace. + + Use this version of run wherever you are writing a doctest that + tests nose (or unittest) test result output. + + Note: do not use doctest: +ELLIPSIS when testing nose output, + since ellipses ("test_foo ... ok") in your expected test runner + output may match multiple lines of output, causing spurious test + passes! + """ + from nose import run + from nose.config import Config + from nose.plugins.manager import PluginManager + + buffer = Buffer() + if 'config' not in kw: + plugins = kw.pop('plugins', []) + if isinstance(plugins, list): + plugins = PluginManager(plugins=plugins) + env = kw.pop('env', {}) + kw['config'] = Config(env=env, plugins=plugins) + if 'argv' not in kw: + kw['argv'] = ['nosetests', '-v'] + kw['config'].stream = buffer + + # Set up buffering so that all output goes to our buffer, + # or warn user if deprecated behavior is active. If this is not + # done, prints and warnings will either be out of place or + # disappear. + stderr = sys.stderr + stdout = sys.stdout + if kw.pop('buffer_all', False): + sys.stdout = sys.stderr = buffer + restore = True + else: + restore = False + warn("The behavior of nose.plugins.plugintest.run() will change in " + "the next release of nose. The current behavior does not " + "correctly account for output to stdout and stderr. To enable " + "correct behavior, use run_buffered() instead, or pass " + "the keyword argument buffer_all=True to run().", + DeprecationWarning, stacklevel=2) + try: + run(*arg, **kw) + finally: + if restore: + sys.stderr = stderr + sys.stdout = stdout + out = buffer.getvalue() + print munge_nose_output_for_doctest(out) + + +def run_buffered(*arg, **kw): + kw['buffer_all'] = True + run(*arg, **kw) + +if __name__ == '__main__': + import doctest + doctest.testmod() diff --git a/env/lib/python2.7/site-packages/nose/plugins/plugintest.pyc b/env/lib/python2.7/site-packages/nose/plugins/plugintest.pyc new file mode 100644 index 0000000..9c94a81 Binary files /dev/null and b/env/lib/python2.7/site-packages/nose/plugins/plugintest.pyc differ diff --git a/env/lib/python2.7/site-packages/nose/plugins/prof.py b/env/lib/python2.7/site-packages/nose/plugins/prof.py new file mode 100644 index 0000000..4d304a9 --- /dev/null +++ b/env/lib/python2.7/site-packages/nose/plugins/prof.py @@ -0,0 +1,154 @@ +"""This plugin will run tests using the hotshot profiler, which is part +of the standard library. To turn it on, use the ``--with-profile`` option +or set the NOSE_WITH_PROFILE environment variable. Profiler output can be +controlled with the ``--profile-sort`` and ``--profile-restrict`` options, +and the profiler output file may be changed with ``--profile-stats-file``. + +See the `hotshot documentation`_ in the standard library documentation for +more details on the various output options. + +.. _hotshot documentation: http://docs.python.org/library/hotshot.html +""" + +try: + import hotshot + from hotshot import stats +except ImportError: + hotshot, stats = None, None +import logging +import os +import sys +import tempfile +from nose.plugins.base import Plugin +from nose.util import tolist + +log = logging.getLogger('nose.plugins') + +class Profile(Plugin): + """ + Use this plugin to run tests using the hotshot profiler. + """ + pfile = None + clean_stats_file = False + def options(self, parser, env): + """Register commandline options. + """ + if not self.available(): + return + Plugin.options(self, parser, env) + parser.add_option('--profile-sort', action='store', dest='profile_sort', + default=env.get('NOSE_PROFILE_SORT', 'cumulative'), + metavar="SORT", + help="Set sort order for profiler output") + parser.add_option('--profile-stats-file', action='store', + dest='profile_stats_file', + metavar="FILE", + default=env.get('NOSE_PROFILE_STATS_FILE'), + help='Profiler stats file; default is a new ' + 'temp file on each run') + parser.add_option('--profile-restrict', action='append', + dest='profile_restrict', + metavar="RESTRICT", + default=env.get('NOSE_PROFILE_RESTRICT'), + help="Restrict profiler output. See help for " + "pstats.Stats for details") + + def available(cls): + return hotshot is not None + available = classmethod(available) + + def begin(self): + """Create profile stats file and load profiler. + """ + if not self.available(): + return + self._create_pfile() + self.prof = hotshot.Profile(self.pfile) + + def configure(self, options, conf): + """Configure plugin. + """ + if not self.available(): + self.enabled = False + return + Plugin.configure(self, options, conf) + self.conf = conf + if options.profile_stats_file: + self.pfile = options.profile_stats_file + self.clean_stats_file = False + else: + self.pfile = None + self.clean_stats_file = True + self.fileno = None + self.sort = options.profile_sort + self.restrict = tolist(options.profile_restrict) + + def prepareTest(self, test): + """Wrap entire test run in :func:`prof.runcall`. + """ + if not self.available(): + return + log.debug('preparing test %s' % test) + def run_and_profile(result, prof=self.prof, test=test): + self._create_pfile() + prof.runcall(test, result) + return run_and_profile + + def report(self, stream): + """Output profiler report. + """ + log.debug('printing profiler report') + self.prof.close() + prof_stats = stats.load(self.pfile) + prof_stats.sort_stats(self.sort) + + # 2.5 has completely different stream handling from 2.4 and earlier. + # Before 2.5, stats objects have no stream attribute; in 2.5 and later + # a reference sys.stdout is stored before we can tweak it. + compat_25 = hasattr(prof_stats, 'stream') + if compat_25: + tmp = prof_stats.stream + prof_stats.stream = stream + else: + tmp = sys.stdout + sys.stdout = stream + try: + if self.restrict: + log.debug('setting profiler restriction to %s', self.restrict) + prof_stats.print_stats(*self.restrict) + else: + prof_stats.print_stats() + finally: + if compat_25: + prof_stats.stream = tmp + else: + sys.stdout = tmp + + def finalize(self, result): + """Clean up stats file, if configured to do so. + """ + if not self.available(): + return + try: + self.prof.close() + except AttributeError: + # TODO: is this trying to catch just the case where not + # hasattr(self.prof, "close")? If so, the function call should be + # moved out of the try: suite. + pass + if self.clean_stats_file: + if self.fileno: + try: + os.close(self.fileno) + except OSError: + pass + try: + os.unlink(self.pfile) + except OSError: + pass + return None + + def _create_pfile(self): + if not self.pfile: + self.fileno, self.pfile = tempfile.mkstemp() + self.clean_stats_file = True diff --git a/env/lib/python2.7/site-packages/nose/plugins/prof.pyc b/env/lib/python2.7/site-packages/nose/plugins/prof.pyc new file mode 100644 index 0000000..d31f09f Binary files /dev/null and b/env/lib/python2.7/site-packages/nose/plugins/prof.pyc differ diff --git a/env/lib/python2.7/site-packages/nose/plugins/skip.py b/env/lib/python2.7/site-packages/nose/plugins/skip.py new file mode 100644 index 0000000..9d1ac8f --- /dev/null +++ b/env/lib/python2.7/site-packages/nose/plugins/skip.py @@ -0,0 +1,63 @@ +""" +This plugin installs a SKIP error class for the SkipTest exception. +When SkipTest is raised, the exception will be logged in the skipped +attribute of the result, 'S' or 'SKIP' (verbose) will be output, and +the exception will not be counted as an error or failure. This plugin +is enabled by default but may be disabled with the ``--no-skip`` option. +""" + +from nose.plugins.errorclass import ErrorClass, ErrorClassPlugin + + +# on SkipTest: +# - unittest SkipTest is first preference, but it's only available +# for >= 2.7 +# - unittest2 SkipTest is second preference for older pythons. This +# mirrors logic for choosing SkipTest exception in testtools +# - if none of the above, provide custom class +try: + from unittest.case import SkipTest +except ImportError: + try: + from unittest2.case import SkipTest + except ImportError: + class SkipTest(Exception): + """Raise this exception to mark a test as skipped. + """ + pass + + +class Skip(ErrorClassPlugin): + """ + Plugin that installs a SKIP error class for the SkipTest + exception. When SkipTest is raised, the exception will be logged + in the skipped attribute of the result, 'S' or 'SKIP' (verbose) + will be output, and the exception will not be counted as an error + or failure. + """ + enabled = True + skipped = ErrorClass(SkipTest, + label='SKIP', + isfailure=False) + + def options(self, parser, env): + """ + Add my options to command line. + """ + env_opt = 'NOSE_WITHOUT_SKIP' + parser.add_option('--no-skip', action='store_true', + dest='noSkip', default=env.get(env_opt, False), + help="Disable special handling of SkipTest " + "exceptions.") + + def configure(self, options, conf): + """ + Configure plugin. Skip plugin is enabled by default. + """ + if not self.can_configure: + return + self.conf = conf + disable = getattr(options, 'noSkip', False) + if disable: + self.enabled = False + diff --git a/env/lib/python2.7/site-packages/nose/plugins/skip.pyc b/env/lib/python2.7/site-packages/nose/plugins/skip.pyc new file mode 100644 index 0000000..b5ddff3 Binary files /dev/null and b/env/lib/python2.7/site-packages/nose/plugins/skip.pyc differ diff --git a/env/lib/python2.7/site-packages/nose/plugins/testid.py b/env/lib/python2.7/site-packages/nose/plugins/testid.py new file mode 100644 index 0000000..ae8119b --- /dev/null +++ b/env/lib/python2.7/site-packages/nose/plugins/testid.py @@ -0,0 +1,311 @@ +""" +This plugin adds a test id (like #1) to each test name output. After +you've run once to generate test ids, you can re-run individual +tests by activating the plugin and passing the ids (with or +without the # prefix) instead of test names. + +For example, if your normal test run looks like:: + + % nosetests -v + tests.test_a ... ok + tests.test_b ... ok + tests.test_c ... ok + +When adding ``--with-id`` you'll see:: + + % nosetests -v --with-id + #1 tests.test_a ... ok + #2 tests.test_b ... ok + #3 tests.test_c ... ok + +Then you can re-run individual tests by supplying just an id number:: + + % nosetests -v --with-id 2 + #2 tests.test_b ... ok + +You can also pass multiple id numbers:: + + % nosetests -v --with-id 2 3 + #2 tests.test_b ... ok + #3 tests.test_c ... ok + +Since most shells consider '#' a special character, you can leave it out when +specifying a test id. + +Note that when run without the -v switch, no special output is displayed, but +the ids file is still written. + +Looping over failed tests +------------------------- + +This plugin also adds a mode that will direct the test runner to record +failed tests. Subsequent test runs will then run only the tests that failed +last time. Activate this mode with the ``--failed`` switch:: + + % nosetests -v --failed + #1 test.test_a ... ok + #2 test.test_b ... ERROR + #3 test.test_c ... FAILED + #4 test.test_d ... ok + +On the second run, only tests #2 and #3 will run:: + + % nosetests -v --failed + #2 test.test_b ... ERROR + #3 test.test_c ... FAILED + +As you correct errors and tests pass, they'll drop out of subsequent runs. + +First:: + + % nosetests -v --failed + #2 test.test_b ... ok + #3 test.test_c ... FAILED + +Second:: + + % nosetests -v --failed + #3 test.test_c ... FAILED + +When all tests pass, the full set will run on the next invocation. + +First:: + + % nosetests -v --failed + #3 test.test_c ... ok + +Second:: + + % nosetests -v --failed + #1 test.test_a ... ok + #2 test.test_b ... ok + #3 test.test_c ... ok + #4 test.test_d ... ok + +.. note :: + + If you expect to use ``--failed`` regularly, it's a good idea to always run + using the ``--with-id`` option. This will ensure that an id file is always + created, allowing you to add ``--failed`` to the command line as soon as + you have failing tests. Otherwise, your first run using ``--failed`` will + (perhaps surprisingly) run *all* tests, because there won't be an id file + containing the record of failed tests from your previous run. + +""" +__test__ = False + +import logging +import os +from nose.plugins import Plugin +from nose.util import src, set + +try: + from cPickle import dump, load +except ImportError: + from pickle import dump, load + +log = logging.getLogger(__name__) + + +class TestId(Plugin): + """ + Activate to add a test id (like #1) to each test name output. Activate + with --failed to rerun failing tests only. + """ + name = 'id' + idfile = None + collecting = True + loopOnFailed = False + + def options(self, parser, env): + """Register commandline options. + """ + Plugin.options(self, parser, env) + parser.add_option('--id-file', action='store', dest='testIdFile', + default='.noseids', metavar="FILE", + help="Store test ids found in test runs in this " + "file. Default is the file .noseids in the " + "working directory.") + parser.add_option('--failed', action='store_true', + dest='failed', default=False, + help="Run the tests that failed in the last " + "test run.") + + def configure(self, options, conf): + """Configure plugin. + """ + Plugin.configure(self, options, conf) + if options.failed: + self.enabled = True + self.loopOnFailed = True + log.debug("Looping on failed tests") + self.idfile = os.path.expanduser(options.testIdFile) + if not os.path.isabs(self.idfile): + self.idfile = os.path.join(conf.workingDir, self.idfile) + self.id = 1 + # Ids and tests are mirror images: ids are {id: test address} and + # tests are {test address: id} + self.ids = {} + self.tests = {} + self.failed = [] + self.source_names = [] + # used to track ids seen when tests is filled from + # loaded ids file + self._seen = {} + self._write_hashes = conf.verbosity >= 2 + + def finalize(self, result): + """Save new ids file, if needed. + """ + if result.wasSuccessful(): + self.failed = [] + if self.collecting: + ids = dict(list(zip(list(self.tests.values()), list(self.tests.keys())))) + else: + ids = self.ids + fh = open(self.idfile, 'wb') + dump({'ids': ids, + 'failed': self.failed, + 'source_names': self.source_names}, fh) + fh.close() + log.debug('Saved test ids: %s, failed %s to %s', + ids, self.failed, self.idfile) + + def loadTestsFromNames(self, names, module=None): + """Translate ids in the list of requested names into their + test addresses, if they are found in my dict of tests. + """ + log.debug('ltfn %s %s', names, module) + try: + fh = open(self.idfile, 'rb') + data = load(fh) + if 'ids' in data: + self.ids = data['ids'] + self.failed = data['failed'] + self.source_names = data['source_names'] + else: + # old ids field + self.ids = data + self.failed = [] + self.source_names = names + if self.ids: + self.id = max(self.ids) + 1 + self.tests = dict(list(zip(list(self.ids.values()), list(self.ids.keys())))) + else: + self.id = 1 + log.debug( + 'Loaded test ids %s tests %s failed %s sources %s from %s', + self.ids, self.tests, self.failed, self.source_names, + self.idfile) + fh.close() + except ValueError, e: + # load() may throw a ValueError when reading the ids file, if it + # was generated with a newer version of Python than we are currently + # running. + log.debug('Error loading %s : %s', self.idfile, str(e)) + except IOError: + log.debug('IO error reading %s', self.idfile) + + if self.loopOnFailed and self.failed: + self.collecting = False + names = self.failed + self.failed = [] + # I don't load any tests myself, only translate names like '#2' + # into the associated test addresses + translated = [] + new_source = [] + really_new = [] + for name in names: + trans = self.tr(name) + if trans != name: + translated.append(trans) + else: + new_source.append(name) + # names that are not ids and that are not in the current + # list of source names go into the list for next time + if new_source: + new_set = set(new_source) + old_set = set(self.source_names) + log.debug("old: %s new: %s", old_set, new_set) + really_new = [s for s in new_source + if not s in old_set] + if really_new: + # remember new sources + self.source_names.extend(really_new) + if not translated: + # new set of source names, no translations + # means "run the requested tests" + names = new_source + else: + # no new names to translate and add to id set + self.collecting = False + log.debug("translated: %s new sources %s names %s", + translated, really_new, names) + return (None, translated + really_new or names) + + def makeName(self, addr): + log.debug("Make name %s", addr) + filename, module, call = addr + if filename is not None: + head = src(filename) + else: + head = module + if call is not None: + return "%s:%s" % (head, call) + return head + + def setOutputStream(self, stream): + """Get handle on output stream so the plugin can print id #s + """ + self.stream = stream + + def startTest(self, test): + """Maybe output an id # before the test name. + + Example output:: + + #1 test.test ... ok + #2 test.test_two ... ok + + """ + adr = test.address() + log.debug('start test %s (%s)', adr, adr in self.tests) + if adr in self.tests: + if adr in self._seen: + self.write(' ') + else: + self.write('#%s ' % self.tests[adr]) + self._seen[adr] = 1 + return + self.tests[adr] = self.id + self.write('#%s ' % self.id) + self.id += 1 + + def afterTest(self, test): + # None means test never ran, False means failed/err + if test.passed is False: + try: + key = str(self.tests[test.address()]) + except KeyError: + # never saw this test -- startTest didn't run + pass + else: + if key not in self.failed: + self.failed.append(key) + + def tr(self, name): + log.debug("tr '%s'", name) + try: + key = int(name.replace('#', '')) + except ValueError: + return name + log.debug("Got key %s", key) + # I'm running tests mapped from the ids file, + # not collecting new ones + if key in self.ids: + return self.makeName(self.ids[key]) + return name + + def write(self, output): + if self._write_hashes: + self.stream.write(output) diff --git a/env/lib/python2.7/site-packages/nose/plugins/testid.pyc b/env/lib/python2.7/site-packages/nose/plugins/testid.pyc new file mode 100644 index 0000000..15cfee5 Binary files /dev/null and b/env/lib/python2.7/site-packages/nose/plugins/testid.pyc differ diff --git a/env/lib/python2.7/site-packages/nose/plugins/xunit.py b/env/lib/python2.7/site-packages/nose/plugins/xunit.py new file mode 100644 index 0000000..90b52f5 --- /dev/null +++ b/env/lib/python2.7/site-packages/nose/plugins/xunit.py @@ -0,0 +1,341 @@ +"""This plugin provides test results in the standard XUnit XML format. + +It's designed for the `Jenkins`_ (previously Hudson) continuous build +system, but will probably work for anything else that understands an +XUnit-formatted XML representation of test results. + +Add this shell command to your builder :: + + nosetests --with-xunit + +And by default a file named nosetests.xml will be written to the +working directory. + +In a Jenkins builder, tick the box named "Publish JUnit test result report" +under the Post-build Actions and enter this value for Test report XMLs:: + + **/nosetests.xml + +If you need to change the name or location of the file, you can set the +``--xunit-file`` option. + +If you need to change the name of the test suite, you can set the +``--xunit-testsuite-name`` option. + +Here is an abbreviated version of what an XML test report might look like:: + + + + + + Traceback (most recent call last): + ... + TypeError: oops, wrong type + + + + +.. _Jenkins: http://jenkins-ci.org/ + +""" +import codecs +import doctest +import os +import sys +import traceback +import re +import inspect +from StringIO import StringIO +from time import time +from xml.sax import saxutils + +from nose.plugins.base import Plugin +from nose.exc import SkipTest +from nose.pyversion import force_unicode, format_exception + +# Invalid XML characters, control characters 0-31 sans \t, \n and \r +CONTROL_CHARACTERS = re.compile(r"[\000-\010\013\014\016-\037]") + +TEST_ID = re.compile(r'^(.*?)(\(.*\))$') + +def xml_safe(value): + """Replaces invalid XML characters with '?'.""" + return CONTROL_CHARACTERS.sub('?', value) + +def escape_cdata(cdata): + """Escape a string for an XML CDATA section.""" + return xml_safe(cdata).replace(']]>', ']]>]]>>> nice_classname(Exception()) # doctest: +ELLIPSIS + '...Exception' + >>> nice_classname(Exception) # doctest: +ELLIPSIS + '...Exception' + + """ + if inspect.isclass(obj): + cls_name = obj.__name__ + else: + cls_name = obj.__class__.__name__ + mod = inspect.getmodule(obj) + if mod: + name = mod.__name__ + # jython + if name.startswith('org.python.core.'): + name = name[len('org.python.core.'):] + return "%s.%s" % (name, cls_name) + else: + return cls_name + +def exc_message(exc_info): + """Return the exception's message.""" + exc = exc_info[1] + if exc is None: + # str exception + result = exc_info[0] + else: + try: + result = str(exc) + except UnicodeEncodeError: + try: + result = unicode(exc) + except UnicodeError: + # Fallback to args as neither str nor + # unicode(Exception(u'\xe6')) work in Python < 2.6 + result = exc.args[0] + result = force_unicode(result, 'UTF-8') + return xml_safe(result) + +class Tee(object): + def __init__(self, encoding, *args): + self._encoding = encoding + self._streams = args + + def write(self, data): + data = force_unicode(data, self._encoding) + for s in self._streams: + s.write(data) + + def writelines(self, lines): + for line in lines: + self.write(line) + + def flush(self): + for s in self._streams: + s.flush() + + def isatty(self): + return False + + +class Xunit(Plugin): + """This plugin provides test results in the standard XUnit XML format.""" + name = 'xunit' + score = 1500 + encoding = 'UTF-8' + error_report_file = None + + def __init__(self): + super(Xunit, self).__init__() + self._capture_stack = [] + self._currentStdout = None + self._currentStderr = None + + def _timeTaken(self): + if hasattr(self, '_timer'): + taken = time() - self._timer + else: + # test died before it ran (probably error in setup()) + # or success/failure added before test started probably + # due to custom TestResult munging + taken = 0.0 + return taken + + def _quoteattr(self, attr): + """Escape an XML attribute. Value can be unicode.""" + attr = xml_safe(attr) + return saxutils.quoteattr(attr) + + def options(self, parser, env): + """Sets additional command line options.""" + Plugin.options(self, parser, env) + parser.add_option( + '--xunit-file', action='store', + dest='xunit_file', metavar="FILE", + default=env.get('NOSE_XUNIT_FILE', 'nosetests.xml'), + help=("Path to xml file to store the xunit report in. " + "Default is nosetests.xml in the working directory " + "[NOSE_XUNIT_FILE]")) + + parser.add_option( + '--xunit-testsuite-name', action='store', + dest='xunit_testsuite_name', metavar="PACKAGE", + default=env.get('NOSE_XUNIT_TESTSUITE_NAME', 'nosetests'), + help=("Name of the testsuite in the xunit xml, generated by plugin. " + "Default test suite name is nosetests.")) + + def configure(self, options, config): + """Configures the xunit plugin.""" + Plugin.configure(self, options, config) + self.config = config + if self.enabled: + self.stats = {'errors': 0, + 'failures': 0, + 'passes': 0, + 'skipped': 0 + } + self.errorlist = [] + self.error_report_file_name = os.path.realpath(options.xunit_file) + self.xunit_testsuite_name = options.xunit_testsuite_name + + def report(self, stream): + """Writes an Xunit-formatted XML file + + The file includes a report of test errors and failures. + + """ + self.error_report_file = codecs.open(self.error_report_file_name, 'w', + self.encoding, 'replace') + self.stats['encoding'] = self.encoding + self.stats['testsuite_name'] = self.xunit_testsuite_name + self.stats['total'] = (self.stats['errors'] + self.stats['failures'] + + self.stats['passes'] + self.stats['skipped']) + self.error_report_file.write( + u'' + u'' % self.stats) + self.error_report_file.write(u''.join([force_unicode(e, self.encoding) + for e in self.errorlist])) + self.error_report_file.write(u'') + self.error_report_file.close() + if self.config.verbosity > 1: + stream.writeln("-" * 70) + stream.writeln("XML: %s" % self.error_report_file.name) + + def _startCapture(self): + self._capture_stack.append((sys.stdout, sys.stderr)) + self._currentStdout = StringIO() + self._currentStderr = StringIO() + sys.stdout = Tee(self.encoding, self._currentStdout, sys.stdout) + sys.stderr = Tee(self.encoding, self._currentStderr, sys.stderr) + + def startContext(self, context): + self._startCapture() + + def stopContext(self, context): + self._endCapture() + + def beforeTest(self, test): + """Initializes a timer before starting a test.""" + self._timer = time() + self._startCapture() + + def _endCapture(self): + if self._capture_stack: + sys.stdout, sys.stderr = self._capture_stack.pop() + + def afterTest(self, test): + self._endCapture() + self._currentStdout = None + self._currentStderr = None + + def finalize(self, test): + while self._capture_stack: + self._endCapture() + + def _getCapturedStdout(self): + if self._currentStdout: + value = self._currentStdout.getvalue() + if value: + return '' % escape_cdata( + value) + return '' + + def _getCapturedStderr(self): + if self._currentStderr: + value = self._currentStderr.getvalue() + if value: + return '' % escape_cdata( + value) + return '' + + def addError(self, test, err, capt=None): + """Add error output to Xunit report. + """ + taken = self._timeTaken() + + if issubclass(err[0], SkipTest): + type = 'skipped' + self.stats['skipped'] += 1 + else: + type = 'error' + self.stats['errors'] += 1 + + tb = format_exception(err, self.encoding) + id = test.id() + + self.errorlist.append( + u'' + u'<%(type)s type=%(errtype)s message=%(message)s>' + u'%(systemout)s%(systemerr)s' % + {'cls': self._quoteattr(id_split(id)[0]), + 'name': self._quoteattr(id_split(id)[-1]), + 'taken': taken, + 'type': type, + 'errtype': self._quoteattr(nice_classname(err[0])), + 'message': self._quoteattr(exc_message(err)), + 'tb': escape_cdata(tb), + 'systemout': self._getCapturedStdout(), + 'systemerr': self._getCapturedStderr(), + }) + + def addFailure(self, test, err, capt=None, tb_info=None): + """Add failure output to Xunit report. + """ + taken = self._timeTaken() + tb = format_exception(err, self.encoding) + self.stats['failures'] += 1 + id = test.id() + + self.errorlist.append( + u'' + u'' + u'%(systemout)s%(systemerr)s' % + {'cls': self._quoteattr(id_split(id)[0]), + 'name': self._quoteattr(id_split(id)[-1]), + 'taken': taken, + 'errtype': self._quoteattr(nice_classname(err[0])), + 'message': self._quoteattr(exc_message(err)), + 'tb': escape_cdata(tb), + 'systemout': self._getCapturedStdout(), + 'systemerr': self._getCapturedStderr(), + }) + + def addSuccess(self, test, capt=None): + """Add success output to Xunit report. + """ + taken = self._timeTaken() + self.stats['passes'] += 1 + id = test.id() + self.errorlist.append( + '%(systemout)s%(systemerr)s' % + {'cls': self._quoteattr(id_split(id)[0]), + 'name': self._quoteattr(id_split(id)[-1]), + 'taken': taken, + 'systemout': self._getCapturedStdout(), + 'systemerr': self._getCapturedStderr(), + }) diff --git a/env/lib/python2.7/site-packages/nose/plugins/xunit.pyc b/env/lib/python2.7/site-packages/nose/plugins/xunit.pyc new file mode 100644 index 0000000..8515f3c Binary files /dev/null and b/env/lib/python2.7/site-packages/nose/plugins/xunit.pyc differ diff --git a/env/lib/python2.7/site-packages/nose/proxy.py b/env/lib/python2.7/site-packages/nose/proxy.py new file mode 100644 index 0000000..c2676cb --- /dev/null +++ b/env/lib/python2.7/site-packages/nose/proxy.py @@ -0,0 +1,188 @@ +""" +Result Proxy +------------ + +The result proxy wraps the result instance given to each test. It +performs two functions: enabling extended error/failure reporting +and calling plugins. + +As each result event is fired, plugins are called with the same event; +however, plugins are called with the nose.case.Test instance that +wraps the actual test. So when a test fails and calls +result.addFailure(self, err), the result proxy calls +addFailure(self.test, err) for each plugin. This allows plugins to +have a single stable interface for all test types, and also to +manipulate the test object itself by setting the `test` attribute of +the nose.case.Test that they receive. +""" +import logging +from nose.config import Config + + +log = logging.getLogger(__name__) + + +def proxied_attribute(local_attr, proxied_attr, doc): + """Create a property that proxies attribute ``proxied_attr`` through + the local attribute ``local_attr``. + """ + def fget(self): + return getattr(getattr(self, local_attr), proxied_attr) + def fset(self, value): + setattr(getattr(self, local_attr), proxied_attr, value) + def fdel(self): + delattr(getattr(self, local_attr), proxied_attr) + return property(fget, fset, fdel, doc) + + +class ResultProxyFactory(object): + """Factory for result proxies. Generates a ResultProxy bound to each test + and the result passed to the test. + """ + def __init__(self, config=None): + if config is None: + config = Config() + self.config = config + self.__prepared = False + self.__result = None + + def __call__(self, result, test): + """Return a ResultProxy for the current test. + + On first call, plugins are given a chance to replace the + result used for the remaining tests. If a plugin returns a + value from prepareTestResult, that object will be used as the + result for all tests. + """ + if not self.__prepared: + self.__prepared = True + plug_result = self.config.plugins.prepareTestResult(result) + if plug_result is not None: + self.__result = result = plug_result + if self.__result is not None: + result = self.__result + return ResultProxy(result, test, config=self.config) + + +class ResultProxy(object): + """Proxy to TestResults (or other results handler). + + One ResultProxy is created for each nose.case.Test. The result + proxy calls plugins with the nose.case.Test instance (instead of + the wrapped test case) as each result call is made. Finally, the + real result method is called, also with the nose.case.Test + instance as the test parameter. + + """ + def __init__(self, result, test, config=None): + if config is None: + config = Config() + self.config = config + self.plugins = config.plugins + self.result = result + self.test = test + + def __repr__(self): + return repr(self.result) + + def _prepareErr(self, err): + if not isinstance(err[1], Exception) and isinstance(err[0], type): + # Turn value back into an Exception (required in Python 3.x). + # Plugins do all sorts of crazy things with exception values. + # Convert it to a custom subclass of Exception with the same + # name as the actual exception to make it print correctly. + value = type(err[0].__name__, (Exception,), {})(err[1]) + err = (err[0], value, err[2]) + return err + + def assertMyTest(self, test): + # The test I was called with must be my .test or my + # .test's .test. or my .test.test's .case + + case = getattr(self.test, 'test', None) + assert (test is self.test + or test is case + or test is getattr(case, '_nose_case', None)), ( + "ResultProxy for %r (%s) was called with test %r (%s)" + % (self.test, id(self.test), test, id(test))) + + def afterTest(self, test): + self.assertMyTest(test) + self.plugins.afterTest(self.test) + if hasattr(self.result, "afterTest"): + self.result.afterTest(self.test) + + def beforeTest(self, test): + self.assertMyTest(test) + self.plugins.beforeTest(self.test) + if hasattr(self.result, "beforeTest"): + self.result.beforeTest(self.test) + + def addError(self, test, err): + self.assertMyTest(test) + plugins = self.plugins + plugin_handled = plugins.handleError(self.test, err) + if plugin_handled: + return + # test.passed is set in result, to account for error classes + formatted = plugins.formatError(self.test, err) + if formatted is not None: + err = formatted + plugins.addError(self.test, err) + self.result.addError(self.test, self._prepareErr(err)) + if not self.result.wasSuccessful() and self.config.stopOnError: + self.shouldStop = True + + def addFailure(self, test, err): + self.assertMyTest(test) + plugins = self.plugins + plugin_handled = plugins.handleFailure(self.test, err) + if plugin_handled: + return + self.test.passed = False + formatted = plugins.formatFailure(self.test, err) + if formatted is not None: + err = formatted + plugins.addFailure(self.test, err) + self.result.addFailure(self.test, self._prepareErr(err)) + if self.config.stopOnError: + self.shouldStop = True + + def addSkip(self, test, reason): + # 2.7 compat shim + from nose.plugins.skip import SkipTest + self.assertMyTest(test) + plugins = self.plugins + if not isinstance(reason, Exception): + # for Python 3.2+ + reason = Exception(reason) + plugins.addError(self.test, (SkipTest, reason, None)) + self.result.addSkip(self.test, reason) + + def addSuccess(self, test): + self.assertMyTest(test) + self.plugins.addSuccess(self.test) + self.result.addSuccess(self.test) + + def startTest(self, test): + self.assertMyTest(test) + self.plugins.startTest(self.test) + self.result.startTest(self.test) + + def stop(self): + self.result.stop() + + def stopTest(self, test): + self.assertMyTest(test) + self.plugins.stopTest(self.test) + self.result.stopTest(self.test) + + # proxied attributes + shouldStop = proxied_attribute('result', 'shouldStop', + """Should the test run stop?""") + errors = proxied_attribute('result', 'errors', + """Tests that raised an exception""") + failures = proxied_attribute('result', 'failures', + """Tests that failed""") + testsRun = proxied_attribute('result', 'testsRun', + """Number of tests run""") diff --git a/env/lib/python2.7/site-packages/nose/proxy.pyc b/env/lib/python2.7/site-packages/nose/proxy.pyc new file mode 100644 index 0000000..25d7238 Binary files /dev/null and b/env/lib/python2.7/site-packages/nose/proxy.pyc differ diff --git a/env/lib/python2.7/site-packages/nose/pyversion.py b/env/lib/python2.7/site-packages/nose/pyversion.py new file mode 100644 index 0000000..091238d --- /dev/null +++ b/env/lib/python2.7/site-packages/nose/pyversion.py @@ -0,0 +1,215 @@ +""" +This module contains fixups for using nose under different versions of Python. +""" +import sys +import os +import traceback +import types +import inspect +import nose.util + +__all__ = ['make_instancemethod', 'cmp_to_key', 'sort_list', 'ClassType', + 'TypeType', 'UNICODE_STRINGS', 'unbound_method', 'ismethod', + 'bytes_', 'is_base_exception', 'force_unicode', 'exc_to_unicode', + 'format_exception'] + +# In Python 3.x, all strings are unicode (the call to 'unicode()' in the 2.x +# source will be replaced with 'str()' when running 2to3, so this test will +# then become true) +UNICODE_STRINGS = (type(unicode()) == type(str())) + +if sys.version_info[:2] < (3, 0): + def force_unicode(s, encoding='UTF-8'): + try: + s = unicode(s) + except UnicodeDecodeError: + s = str(s).decode(encoding, 'replace') + + return s +else: + def force_unicode(s, encoding='UTF-8'): + return str(s) + +# new.instancemethod() is obsolete for new-style classes (Python 3.x) +# We need to use descriptor methods instead. +try: + import new + def make_instancemethod(function, instance): + return new.instancemethod(function.im_func, instance, + instance.__class__) +except ImportError: + def make_instancemethod(function, instance): + return function.__get__(instance, instance.__class__) + +# To be forward-compatible, we do all list sorts using keys instead of cmp +# functions. However, part of the unittest.TestLoader API involves a +# user-provideable cmp function, so we need some way to convert that. +def cmp_to_key(mycmp): + 'Convert a cmp= function into a key= function' + class Key(object): + def __init__(self, obj): + self.obj = obj + def __lt__(self, other): + return mycmp(self.obj, other.obj) < 0 + def __gt__(self, other): + return mycmp(self.obj, other.obj) > 0 + def __eq__(self, other): + return mycmp(self.obj, other.obj) == 0 + return Key + +# Python 2.3 also does not support list-sorting by key, so we need to convert +# keys to cmp functions if we're running on old Python.. +if sys.version_info < (2, 4): + def sort_list(l, key, reverse=False): + if reverse: + return l.sort(lambda a, b: cmp(key(b), key(a))) + else: + return l.sort(lambda a, b: cmp(key(a), key(b))) +else: + def sort_list(l, key, reverse=False): + return l.sort(key=key, reverse=reverse) + +# In Python 3.x, all objects are "new style" objects descended from 'type', and +# thus types.ClassType and types.TypeType don't exist anymore. For +# compatibility, we make sure they still work. +if hasattr(types, 'ClassType'): + ClassType = types.ClassType + TypeType = types.TypeType +else: + ClassType = type + TypeType = type + +# The following emulates the behavior (we need) of an 'unbound method' under +# Python 3.x (namely, the ability to have a class associated with a function +# definition so that things can do stuff based on its associated class) +class UnboundMethod: + def __init__(self, cls, func): + # Make sure we have all the same attributes as the original function, + # so that the AttributeSelector plugin will work correctly... + self.__dict__ = func.__dict__.copy() + self._func = func + self.__self__ = UnboundSelf(cls) + if sys.version_info < (3, 0): + self.im_class = cls + self.__doc__ = getattr(func, '__doc__', None) + + def address(self): + cls = self.__self__.cls + modname = cls.__module__ + module = sys.modules[modname] + filename = getattr(module, '__file__', None) + if filename is not None: + filename = os.path.abspath(filename) + return (nose.util.src(filename), modname, "%s.%s" % (cls.__name__, + self._func.__name__)) + + def __call__(self, *args, **kwargs): + return self._func(*args, **kwargs) + + def __getattr__(self, attr): + return getattr(self._func, attr) + + def __repr__(self): + return '' % (self.__self__.cls.__name__, + self._func.__name__) + +class UnboundSelf: + def __init__(self, cls): + self.cls = cls + + # We have to do this hackery because Python won't let us override the + # __class__ attribute... + def __getattribute__(self, attr): + if attr == '__class__': + return self.cls + else: + return object.__getattribute__(self, attr) + +def unbound_method(cls, func): + if inspect.ismethod(func): + return func + if not inspect.isfunction(func): + raise TypeError('%s is not a function' % (repr(func),)) + return UnboundMethod(cls, func) + +def ismethod(obj): + return inspect.ismethod(obj) or isinstance(obj, UnboundMethod) + + +# Make a pseudo-bytes function that can be called without the encoding arg: +if sys.version_info >= (3, 0): + def bytes_(s, encoding='utf8'): + if isinstance(s, bytes): + return s + return bytes(s, encoding) +else: + def bytes_(s, encoding=None): + return str(s) + + +if sys.version_info[:2] >= (2, 6): + def isgenerator(o): + if isinstance(o, UnboundMethod): + o = o._func + return inspect.isgeneratorfunction(o) or inspect.isgenerator(o) +else: + try: + from compiler.consts import CO_GENERATOR + except ImportError: + # IronPython doesn't have a complier module + CO_GENERATOR=0x20 + + def isgenerator(func): + try: + return func.func_code.co_flags & CO_GENERATOR != 0 + except AttributeError: + return False + +# Make a function to help check if an exception is derived from BaseException. +# In Python 2.4, we just use Exception instead. +if sys.version_info[:2] < (2, 5): + def is_base_exception(exc): + return isinstance(exc, Exception) +else: + def is_base_exception(exc): + return isinstance(exc, BaseException) + +if sys.version_info[:2] < (3, 0): + def exc_to_unicode(ev, encoding='utf-8'): + if is_base_exception(ev): + if not hasattr(ev, '__unicode__'): + # 2.5- + if not hasattr(ev, 'message'): + # 2.4 + msg = len(ev.args) and ev.args[0] or '' + else: + msg = ev.message + msg = force_unicode(msg, encoding=encoding) + clsname = force_unicode(ev.__class__.__name__, + encoding=encoding) + ev = u'%s: %s' % (clsname, msg) + elif not isinstance(ev, unicode): + ev = repr(ev) + + return force_unicode(ev, encoding=encoding) +else: + def exc_to_unicode(ev, encoding='utf-8'): + return str(ev) + +def format_exception(exc_info, encoding='UTF-8'): + ec, ev, tb = exc_info + + # Our exception object may have been turned into a string, and Python 3's + # traceback.format_exception() doesn't take kindly to that (it expects an + # actual exception object). So we work around it, by doing the work + # ourselves if ev is not an exception object. + if not is_base_exception(ev): + tb_data = force_unicode( + ''.join(traceback.format_tb(tb)), + encoding) + ev = exc_to_unicode(ev) + return tb_data + ev + else: + return force_unicode( + ''.join(traceback.format_exception(*exc_info)), + encoding) diff --git a/env/lib/python2.7/site-packages/nose/pyversion.pyc b/env/lib/python2.7/site-packages/nose/pyversion.pyc new file mode 100644 index 0000000..a208cbc Binary files /dev/null and b/env/lib/python2.7/site-packages/nose/pyversion.pyc differ diff --git a/env/lib/python2.7/site-packages/nose/result.py b/env/lib/python2.7/site-packages/nose/result.py new file mode 100644 index 0000000..f974a14 --- /dev/null +++ b/env/lib/python2.7/site-packages/nose/result.py @@ -0,0 +1,200 @@ +""" +Test Result +----------- + +Provides a TextTestResult that extends unittest's _TextTestResult to +provide support for error classes (such as the builtin skip and +deprecated classes), and hooks for plugins to take over or extend +reporting. +""" + +import logging +try: + # 2.7+ + from unittest.runner import _TextTestResult +except ImportError: + from unittest import _TextTestResult +from nose.config import Config +from nose.util import isclass, ln as _ln # backwards compat + +log = logging.getLogger('nose.result') + + +def _exception_detail(exc): + # this is what stdlib module traceback does + try: + return str(exc) + except: + return '' % type(exc).__name__ + + +class TextTestResult(_TextTestResult): + """Text test result that extends unittest's default test result + support for a configurable set of errorClasses (eg, Skip, + Deprecated, TODO) that extend the errors/failures/success triad. + """ + def __init__(self, stream, descriptions, verbosity, config=None, + errorClasses=None): + if errorClasses is None: + errorClasses = {} + self.errorClasses = errorClasses + if config is None: + config = Config() + self.config = config + _TextTestResult.__init__(self, stream, descriptions, verbosity) + + def addSkip(self, test, reason): + # 2.7 skip compat + from nose.plugins.skip import SkipTest + if SkipTest in self.errorClasses: + storage, label, isfail = self.errorClasses[SkipTest] + storage.append((test, reason)) + self.printLabel(label, (SkipTest, reason, None)) + + def addError(self, test, err): + """Overrides normal addError to add support for + errorClasses. If the exception is a registered class, the + error will be added to the list for that class, not errors. + """ + ec, ev, tb = err + try: + exc_info = self._exc_info_to_string(err, test) + except TypeError: + # 2.3 compat + exc_info = self._exc_info_to_string(err) + for cls, (storage, label, isfail) in self.errorClasses.items(): + #if 'Skip' in cls.__name__ or 'Skip' in ec.__name__: + # from nose.tools import set_trace + # set_trace() + if isclass(ec) and issubclass(ec, cls): + if isfail: + test.passed = False + storage.append((test, exc_info)) + self.printLabel(label, err) + return + self.errors.append((test, exc_info)) + test.passed = False + self.printLabel('ERROR') + + # override to bypass changes in 2.7 + def getDescription(self, test): + if self.descriptions: + return test.shortDescription() or str(test) + else: + return str(test) + + def printLabel(self, label, err=None): + # Might get patched into a streamless result + stream = getattr(self, 'stream', None) + if stream is not None: + if self.showAll: + message = [label] + if err: + detail = _exception_detail(err[1]) + if detail: + message.append(detail) + stream.writeln(": ".join(message)) + elif self.dots: + stream.write(label[:1]) + + def printErrors(self): + """Overrides to print all errorClasses errors as well. + """ + _TextTestResult.printErrors(self) + for cls in self.errorClasses.keys(): + storage, label, isfail = self.errorClasses[cls] + if isfail: + self.printErrorList(label, storage) + # Might get patched into a result with no config + if hasattr(self, 'config'): + self.config.plugins.report(self.stream) + + def printSummary(self, start, stop): + """Called by the test runner to print the final summary of test + run results. + """ + write = self.stream.write + writeln = self.stream.writeln + taken = float(stop - start) + run = self.testsRun + plural = run != 1 and "s" or "" + + writeln(self.separator2) + writeln("Ran %s test%s in %.3fs" % (run, plural, taken)) + writeln() + + summary = {} + eckeys = self.errorClasses.keys() + for cls in eckeys: + storage, label, isfail = self.errorClasses[cls] + count = len(storage) + if not count: + continue + summary[label] = count + if len(self.failures): + summary['failures'] = len(self.failures) + if len(self.errors): + summary['errors'] = len(self.errors) + + if not self.wasSuccessful(): + write("FAILED") + else: + write("OK") + items = summary.items() + if items: + items.sort() + write(" (") + write(", ".join(["%s=%s" % (label, count) for + label, count in items])) + writeln(")") + else: + writeln() + + def wasSuccessful(self): + """Overrides to check that there are no errors in errorClasses + lists that are marked as errors and should cause a run to + fail. + """ + if self.errors or self.failures: + return False + for cls in self.errorClasses.keys(): + storage, label, isfail = self.errorClasses[cls] + if not isfail: + continue + if storage: + return False + return True + + def _addError(self, test, err): + try: + exc_info = self._exc_info_to_string(err, test) + except TypeError: + # 2.3: does not take test arg + exc_info = self._exc_info_to_string(err) + self.errors.append((test, exc_info)) + if self.showAll: + self.stream.write('ERROR') + elif self.dots: + self.stream.write('E') + + def _exc_info_to_string(self, err, test=None): + # 2.7 skip compat + from nose.plugins.skip import SkipTest + if isclass(err[0]) and issubclass(err[0], SkipTest): + return str(err[1]) + # 2.3/2.4 -- 2.4 passes test, 2.3 does not + try: + return _TextTestResult._exc_info_to_string(self, err, test) + except TypeError: + # 2.3: does not take test arg + return _TextTestResult._exc_info_to_string(self, err) + + +def ln(*arg, **kw): + from warnings import warn + warn("ln() has moved to nose.util from nose.result and will be removed " + "from nose.result in a future release. Please update your imports ", + DeprecationWarning) + return _ln(*arg, **kw) + + diff --git a/env/lib/python2.7/site-packages/nose/result.pyc b/env/lib/python2.7/site-packages/nose/result.pyc new file mode 100644 index 0000000..49955bc Binary files /dev/null and b/env/lib/python2.7/site-packages/nose/result.pyc differ diff --git a/env/lib/python2.7/site-packages/nose/selector.py b/env/lib/python2.7/site-packages/nose/selector.py new file mode 100644 index 0000000..b63f7af --- /dev/null +++ b/env/lib/python2.7/site-packages/nose/selector.py @@ -0,0 +1,251 @@ +""" +Test Selection +-------------- + +Test selection is handled by a Selector. The test loader calls the +appropriate selector method for each object it encounters that it +thinks may be a test. +""" +import logging +import os +import unittest +from nose.config import Config +from nose.util import split_test_name, src, getfilename, getpackage, ispackage, is_executable + +log = logging.getLogger(__name__) + +__all__ = ['Selector', 'defaultSelector', 'TestAddress'] + + +# for efficiency and easier mocking +op_join = os.path.join +op_basename = os.path.basename +op_exists = os.path.exists +op_splitext = os.path.splitext +op_isabs = os.path.isabs +op_abspath = os.path.abspath + + +class Selector(object): + """Core test selector. Examines test candidates and determines whether, + given the specified configuration, the test candidate should be selected + as a test. + """ + def __init__(self, config): + if config is None: + config = Config() + self.configure(config) + + def configure(self, config): + self.config = config + self.exclude = config.exclude + self.ignoreFiles = config.ignoreFiles + self.include = config.include + self.plugins = config.plugins + self.match = config.testMatch + + def matches(self, name): + """Does the name match my requirements? + + To match, a name must match config.testMatch OR config.include + and it must not match config.exclude + """ + return ((self.match.search(name) + or (self.include and + filter(None, + [inc.search(name) for inc in self.include]))) + and ((not self.exclude) + or not filter(None, + [exc.search(name) for exc in self.exclude]) + )) + + def wantClass(self, cls): + """Is the class a wanted test class? + + A class must be a unittest.TestCase subclass, or match test name + requirements. Classes that start with _ are always excluded. + """ + declared = getattr(cls, '__test__', None) + if declared is not None: + wanted = declared + else: + wanted = (not cls.__name__.startswith('_') + and (issubclass(cls, unittest.TestCase) + or self.matches(cls.__name__))) + + plug_wants = self.plugins.wantClass(cls) + if plug_wants is not None: + log.debug("Plugin setting selection of %s to %s", cls, plug_wants) + wanted = plug_wants + log.debug("wantClass %s? %s", cls, wanted) + return wanted + + def wantDirectory(self, dirname): + """Is the directory a wanted test directory? + + All package directories match, so long as they do not match exclude. + All other directories must match test requirements. + """ + tail = op_basename(dirname) + if ispackage(dirname): + wanted = (not self.exclude + or not filter(None, + [exc.search(tail) for exc in self.exclude] + )) + else: + wanted = (self.matches(tail) + or (self.config.srcDirs + and tail in self.config.srcDirs)) + plug_wants = self.plugins.wantDirectory(dirname) + if plug_wants is not None: + log.debug("Plugin setting selection of %s to %s", + dirname, plug_wants) + wanted = plug_wants + log.debug("wantDirectory %s? %s", dirname, wanted) + return wanted + + def wantFile(self, file): + """Is the file a wanted test file? + + The file must be a python source file and match testMatch or + include, and not match exclude. Files that match ignore are *never* + wanted, regardless of plugin, testMatch, include or exclude settings. + """ + # never, ever load files that match anything in ignore + # (.* _* and *setup*.py by default) + base = op_basename(file) + ignore_matches = [ ignore_this for ignore_this in self.ignoreFiles + if ignore_this.search(base) ] + if ignore_matches: + log.debug('%s matches ignoreFiles pattern; skipped', + base) + return False + if not self.config.includeExe and is_executable(file): + log.info('%s is executable; skipped', file) + return False + dummy, ext = op_splitext(base) + pysrc = ext == '.py' + + wanted = pysrc and self.matches(base) + plug_wants = self.plugins.wantFile(file) + if plug_wants is not None: + log.debug("plugin setting want %s to %s", file, plug_wants) + wanted = plug_wants + log.debug("wantFile %s? %s", file, wanted) + return wanted + + def wantFunction(self, function): + """Is the function a test function? + """ + try: + if hasattr(function, 'compat_func_name'): + funcname = function.compat_func_name + else: + funcname = function.__name__ + except AttributeError: + # not a function + return False + declared = getattr(function, '__test__', None) + if declared is not None: + wanted = declared + else: + wanted = not funcname.startswith('_') and self.matches(funcname) + plug_wants = self.plugins.wantFunction(function) + if plug_wants is not None: + wanted = plug_wants + log.debug("wantFunction %s? %s", function, wanted) + return wanted + + def wantMethod(self, method): + """Is the method a test method? + """ + try: + method_name = method.__name__ + except AttributeError: + # not a method + return False + if method_name.startswith('_'): + # never collect 'private' methods + return False + declared = getattr(method, '__test__', None) + if declared is not None: + wanted = declared + else: + wanted = self.matches(method_name) + plug_wants = self.plugins.wantMethod(method) + if plug_wants is not None: + wanted = plug_wants + log.debug("wantMethod %s? %s", method, wanted) + return wanted + + def wantModule(self, module): + """Is the module a test module? + + The tail of the module name must match test requirements. One exception: + we always want __main__. + """ + declared = getattr(module, '__test__', None) + if declared is not None: + wanted = declared + else: + wanted = self.matches(module.__name__.split('.')[-1]) \ + or module.__name__ == '__main__' + plug_wants = self.plugins.wantModule(module) + if plug_wants is not None: + wanted = plug_wants + log.debug("wantModule %s? %s", module, wanted) + return wanted + +defaultSelector = Selector + + +class TestAddress(object): + """A test address represents a user's request to run a particular + test. The user may specify a filename or module (or neither), + and/or a callable (a class, function, or method). The naming + format for test addresses is: + + filename_or_module:callable + + Filenames that are not absolute will be made absolute relative to + the working dir. + + The filename or module part will be considered a module name if it + doesn't look like a file, that is, if it doesn't exist on the file + system and it doesn't contain any directory separators and it + doesn't end in .py. + + Callables may be a class name, function name, method name, or + class.method specification. + """ + def __init__(self, name, workingDir=None): + if workingDir is None: + workingDir = os.getcwd() + self.name = name + self.workingDir = workingDir + self.filename, self.module, self.call = split_test_name(name) + log.debug('Test name %s resolved to file %s, module %s, call %s', + name, self.filename, self.module, self.call) + if self.filename is None: + if self.module is not None: + self.filename = getfilename(self.module, self.workingDir) + if self.filename: + self.filename = src(self.filename) + if not op_isabs(self.filename): + self.filename = op_abspath(op_join(workingDir, + self.filename)) + if self.module is None: + self.module = getpackage(self.filename) + log.debug( + 'Final resolution of test name %s: file %s module %s call %s', + name, self.filename, self.module, self.call) + + def totuple(self): + return (self.filename, self.module, self.call) + + def __str__(self): + return self.name + + def __repr__(self): + return "%s: (%s, %s, %s)" % (self.name, self.filename, + self.module, self.call) diff --git a/env/lib/python2.7/site-packages/nose/selector.pyc b/env/lib/python2.7/site-packages/nose/selector.pyc new file mode 100644 index 0000000..daa3a27 Binary files /dev/null and b/env/lib/python2.7/site-packages/nose/selector.pyc differ diff --git a/env/lib/python2.7/site-packages/nose/sphinx/__init__.py b/env/lib/python2.7/site-packages/nose/sphinx/__init__.py new file mode 100644 index 0000000..2ae2839 --- /dev/null +++ b/env/lib/python2.7/site-packages/nose/sphinx/__init__.py @@ -0,0 +1 @@ +pass diff --git a/env/lib/python2.7/site-packages/nose/sphinx/__init__.pyc b/env/lib/python2.7/site-packages/nose/sphinx/__init__.pyc new file mode 100644 index 0000000..79361b7 Binary files /dev/null and b/env/lib/python2.7/site-packages/nose/sphinx/__init__.pyc differ diff --git a/env/lib/python2.7/site-packages/nose/sphinx/pluginopts.py b/env/lib/python2.7/site-packages/nose/sphinx/pluginopts.py new file mode 100644 index 0000000..d2b284a --- /dev/null +++ b/env/lib/python2.7/site-packages/nose/sphinx/pluginopts.py @@ -0,0 +1,189 @@ +""" +Adds a sphinx directive that can be used to automatically document a plugin. + +this:: + + .. autoplugin :: nose.plugins.foo + :plugin: Pluggy + +produces:: + + .. automodule :: nose.plugins.foo + + Options + ------- + + .. cmdoption :: --foo=BAR, --fooble=BAR + + Do the foo thing to the new thing. + + Plugin + ------ + + .. autoclass :: nose.plugins.foo.Pluggy + :members: + + Source + ------ + + .. include :: path/to/nose/plugins/foo.py + :literal: + +""" +import os +try: + from docutils import nodes, utils + from docutils.statemachine import ViewList + from docutils.parsers.rst import directives +except ImportError: + pass # won't run anyway + +from nose.util import resolve_name +from nose.plugins.base import Plugin +from nose.plugins.manager import BuiltinPluginManager +from nose.config import Config +from nose.core import TestProgram +from inspect import isclass + + +def autoplugin_directive(dirname, arguments, options, content, lineno, + content_offset, block_text, state, state_machine): + mod_name = arguments[0] + mod = resolve_name(mod_name) + plug_name = options.get('plugin', None) + if plug_name: + obj = getattr(mod, plug_name) + else: + for entry in dir(mod): + obj = getattr(mod, entry) + if isclass(obj) and issubclass(obj, Plugin) and obj is not Plugin: + plug_name = '%s.%s' % (mod_name, entry) + break + + # mod docstring + rst = ViewList() + rst.append('.. automodule :: %s\n' % mod_name, '') + rst.append('', '') + + # options + rst.append('Options', '') + rst.append('-------', '') + rst.append('', '') + + plug = obj() + opts = OptBucket() + plug.options(opts, {}) + for opt in opts: + rst.append(opt.options(), '') + rst.append(' \n', '') + rst.append(' ' + opt.help + '\n', '') + rst.append('\n', '') + + # plugin class + rst.append('Plugin', '') + rst.append('------', '') + rst.append('', '') + + rst.append('.. autoclass :: %s\n' % plug_name, '') + rst.append(' :members:\n', '') + rst.append(' :show-inheritance:\n', '') + rst.append('', '') + + # source + rst.append('Source', '') + rst.append('------', '') + rst.append( + '.. include :: %s\n' % utils.relative_path( + state_machine.document['source'], + os.path.abspath(mod.__file__.replace('.pyc', '.py'))), + '') + rst.append(' :literal:\n', '') + rst.append('', '') + + node = nodes.section() + node.document = state.document + surrounding_title_styles = state.memo.title_styles + surrounding_section_level = state.memo.section_level + state.memo.title_styles = [] + state.memo.section_level = 0 + state.nested_parse(rst, 0, node, match_titles=1) + state.memo.title_styles = surrounding_title_styles + state.memo.section_level = surrounding_section_level + + return node.children + + +def autohelp_directive(dirname, arguments, options, content, lineno, + content_offset, block_text, state, state_machine): + """produces rst from nose help""" + config = Config(parserClass=OptBucket, + plugins=BuiltinPluginManager()) + parser = config.getParser(TestProgram.usage()) + rst = ViewList() + for line in parser.format_help().split('\n'): + rst.append(line, '') + + rst.append('Options', '') + rst.append('-------', '') + rst.append('', '') + for opt in parser: + rst.append(opt.options(), '') + rst.append(' \n', '') + rst.append(' ' + opt.help + '\n', '') + rst.append('\n', '') + node = nodes.section() + node.document = state.document + surrounding_title_styles = state.memo.title_styles + surrounding_section_level = state.memo.section_level + state.memo.title_styles = [] + state.memo.section_level = 0 + state.nested_parse(rst, 0, node, match_titles=1) + state.memo.title_styles = surrounding_title_styles + state.memo.section_level = surrounding_section_level + + return node.children + + +class OptBucket(object): + def __init__(self, doc=None, prog='nosetests'): + self.opts = [] + self.doc = doc + self.prog = prog + + def __iter__(self): + return iter(self.opts) + + def format_help(self): + return self.doc.replace('%prog', self.prog).replace(':\n', '::\n') + + def add_option(self, *arg, **kw): + self.opts.append(Opt(*arg, **kw)) + + +class Opt(object): + def __init__(self, *arg, **kw): + self.opts = arg + self.action = kw.pop('action', None) + self.default = kw.pop('default', None) + self.metavar = kw.pop('metavar', None) + self.help = kw.pop('help', None) + + def options(self): + buf = [] + for optstring in self.opts: + desc = optstring + if self.action not in ('store_true', 'store_false'): + desc += '=%s' % self.meta(optstring) + buf.append(desc) + return '.. cmdoption :: ' + ', '.join(buf) + + def meta(self, optstring): + # FIXME optparser default metavar? + return self.metavar or 'DEFAULT' + + +def setup(app): + app.add_directive('autoplugin', + autoplugin_directive, 1, (1, 0, 1), + plugin=directives.unchanged) + app.add_directive('autohelp', autohelp_directive, 0, (0, 0, 1)) diff --git a/env/lib/python2.7/site-packages/nose/sphinx/pluginopts.pyc b/env/lib/python2.7/site-packages/nose/sphinx/pluginopts.pyc new file mode 100644 index 0000000..8a9e6f6 Binary files /dev/null and b/env/lib/python2.7/site-packages/nose/sphinx/pluginopts.pyc differ diff --git a/env/lib/python2.7/site-packages/nose/suite.py b/env/lib/python2.7/site-packages/nose/suite.py new file mode 100644 index 0000000..a831105 --- /dev/null +++ b/env/lib/python2.7/site-packages/nose/suite.py @@ -0,0 +1,609 @@ +""" +Test Suites +----------- + +Provides a LazySuite, which is a suite whose test list is a generator +function, and ContextSuite,which can run fixtures (setup/teardown +functions or methods) for the context that contains its tests. + +""" +from __future__ import generators + +import logging +import sys +import unittest +from nose.case import Test +from nose.config import Config +from nose.proxy import ResultProxyFactory +from nose.util import isclass, resolve_name, try_run + +if sys.platform == 'cli': + if sys.version_info[:2] < (2, 6): + import clr + clr.AddReference("IronPython") + from IronPython.Runtime.Exceptions import StringException + else: + class StringException(Exception): + pass + +log = logging.getLogger(__name__) +#log.setLevel(logging.DEBUG) + +# Singleton for default value -- see ContextSuite.__init__ below +_def = object() + + +def _strclass(cls): + return "%s.%s" % (cls.__module__, cls.__name__) + +class MixedContextError(Exception): + """Error raised when a context suite sees tests from more than + one context. + """ + pass + + +class LazySuite(unittest.TestSuite): + """A suite that may use a generator as its list of tests + """ + def __init__(self, tests=()): + """Initialize the suite. tests may be an iterable or a generator + """ + super(LazySuite, self).__init__() + self._set_tests(tests) + + def __iter__(self): + return iter(self._tests) + + def __repr__(self): + return "<%s tests=generator (%s)>" % ( + _strclass(self.__class__), id(self)) + + def __hash__(self): + return object.__hash__(self) + + __str__ = __repr__ + + def addTest(self, test): + self._precache.append(test) + + # added to bypass run changes in 2.7's unittest + def run(self, result): + for test in self._tests: + if result.shouldStop: + break + test(result) + return result + + def __nonzero__(self): + log.debug("tests in %s?", id(self)) + if self._precache: + return True + if self.test_generator is None: + return False + try: + test = self.test_generator.next() + if test is not None: + self._precache.append(test) + return True + except StopIteration: + pass + return False + + def _get_tests(self): + log.debug("precache is %s", self._precache) + for test in self._precache: + yield test + if self.test_generator is None: + return + for test in self.test_generator: + yield test + + def _set_tests(self, tests): + self._precache = [] + is_suite = isinstance(tests, unittest.TestSuite) + if callable(tests) and not is_suite: + self.test_generator = tests() + elif is_suite: + # Suites need special treatment: they must be called like + # tests for their setup/teardown to run (if any) + self.addTests([tests]) + self.test_generator = None + else: + self.addTests(tests) + self.test_generator = None + + _tests = property(_get_tests, _set_tests, None, + "Access the tests in this suite. Access is through a " + "generator, so iteration may not be repeatable.") + + +class ContextSuite(LazySuite): + """A suite with context. + + A ContextSuite executes fixtures (setup and teardown functions or + methods) for the context containing its tests. + + The context may be explicitly passed. If it is not, a context (or + nested set of contexts) will be constructed by examining the tests + in the suite. + """ + failureException = unittest.TestCase.failureException + was_setup = False + was_torndown = False + classSetup = ('setup_class', 'setup_all', 'setupClass', 'setupAll', + 'setUpClass', 'setUpAll') + classTeardown = ('teardown_class', 'teardown_all', 'teardownClass', + 'teardownAll', 'tearDownClass', 'tearDownAll') + moduleSetup = ('setup_module', 'setupModule', 'setUpModule', 'setup', + 'setUp') + moduleTeardown = ('teardown_module', 'teardownModule', 'tearDownModule', + 'teardown', 'tearDown') + packageSetup = ('setup_package', 'setupPackage', 'setUpPackage') + packageTeardown = ('teardown_package', 'teardownPackage', + 'tearDownPackage') + + def __init__(self, tests=(), context=None, factory=None, + config=None, resultProxy=None, can_split=True): + log.debug("Context suite for %s (%s) (%s)", tests, context, id(self)) + self.context = context + self.factory = factory + if config is None: + config = Config() + self.config = config + self.resultProxy = resultProxy + self.has_run = False + self.can_split = can_split + self.error_context = None + super(ContextSuite, self).__init__(tests) + + def __repr__(self): + return "<%s context=%s>" % ( + _strclass(self.__class__), + getattr(self.context, '__name__', self.context)) + __str__ = __repr__ + + def id(self): + if self.error_context: + return '%s:%s' % (repr(self), self.error_context) + else: + return repr(self) + + def __hash__(self): + return object.__hash__(self) + + # 2.3 compat -- force 2.4 call sequence + def __call__(self, *arg, **kw): + return self.run(*arg, **kw) + + def exc_info(self): + """Hook for replacing error tuple output + """ + return sys.exc_info() + + def _exc_info(self): + """Bottleneck to fix up IronPython string exceptions + """ + e = self.exc_info() + if sys.platform == 'cli': + if isinstance(e[0], StringException): + # IronPython throws these StringExceptions, but + # traceback checks type(etype) == str. Make a real + # string here. + e = (str(e[0]), e[1], e[2]) + + return e + + def run(self, result): + """Run tests in suite inside of suite fixtures. + """ + # proxy the result for myself + log.debug("suite %s (%s) run called, tests: %s", id(self), self, self._tests) + #import pdb + #pdb.set_trace() + if self.resultProxy: + result, orig = self.resultProxy(result, self), result + else: + result, orig = result, result + try: + self.setUp() + except KeyboardInterrupt: + raise + except: + self.error_context = 'setup' + result.addError(self, self._exc_info()) + return + try: + for test in self._tests: + if result.shouldStop: + log.debug("stopping") + break + # each nose.case.Test will create its own result proxy + # so the cases need the original result, to avoid proxy + # chains + test(orig) + finally: + self.has_run = True + try: + self.tearDown() + except KeyboardInterrupt: + raise + except: + self.error_context = 'teardown' + result.addError(self, self._exc_info()) + + def hasFixtures(self, ctx_callback=None): + context = self.context + if context is None: + return False + if self.implementsAnyFixture(context, ctx_callback=ctx_callback): + return True + # My context doesn't have any, but its ancestors might + factory = self.factory + if factory: + ancestors = factory.context.get(self, []) + for ancestor in ancestors: + if self.implementsAnyFixture( + ancestor, ctx_callback=ctx_callback): + return True + return False + + def implementsAnyFixture(self, context, ctx_callback): + if isclass(context): + names = self.classSetup + self.classTeardown + else: + names = self.moduleSetup + self.moduleTeardown + if hasattr(context, '__path__'): + names += self.packageSetup + self.packageTeardown + # If my context has any fixture attribute, I have fixtures + fixt = False + for m in names: + if hasattr(context, m): + fixt = True + break + if ctx_callback is None: + return fixt + return ctx_callback(context, fixt) + + def setUp(self): + log.debug("suite %s setUp called, tests: %s", id(self), self._tests) + if not self: + # I have no tests + log.debug("suite %s has no tests", id(self)) + return + if self.was_setup: + log.debug("suite %s already set up", id(self)) + return + context = self.context + if context is None: + return + # before running my own context's setup, I need to + # ask the factory if my context's contexts' setups have been run + factory = self.factory + if factory: + # get a copy, since we'll be destroying it as we go + ancestors = factory.context.get(self, [])[:] + while ancestors: + ancestor = ancestors.pop() + log.debug("ancestor %s may need setup", ancestor) + if ancestor in factory.was_setup: + continue + log.debug("ancestor %s does need setup", ancestor) + self.setupContext(ancestor) + if not context in factory.was_setup: + self.setupContext(context) + else: + self.setupContext(context) + self.was_setup = True + log.debug("completed suite setup") + + def setupContext(self, context): + self.config.plugins.startContext(context) + log.debug("%s setup context %s", self, context) + if self.factory: + if context in self.factory.was_setup: + return + # note that I ran the setup for this context, so that I'll run + # the teardown in my teardown + self.factory.was_setup[context] = self + if isclass(context): + names = self.classSetup + else: + names = self.moduleSetup + if hasattr(context, '__path__'): + names = self.packageSetup + names + try_run(context, names) + + def shortDescription(self): + if self.context is None: + return "test suite" + return "test suite for %s" % self.context + + def tearDown(self): + log.debug('context teardown') + if not self.was_setup or self.was_torndown: + log.debug( + "No reason to teardown (was_setup? %s was_torndown? %s)" + % (self.was_setup, self.was_torndown)) + return + self.was_torndown = True + context = self.context + if context is None: + log.debug("No context to tear down") + return + + # for each ancestor... if the ancestor was setup + # and I did the setup, I can do teardown + factory = self.factory + if factory: + ancestors = factory.context.get(self, []) + [context] + for ancestor in ancestors: + log.debug('ancestor %s may need teardown', ancestor) + if not ancestor in factory.was_setup: + log.debug('ancestor %s was not setup', ancestor) + continue + if ancestor in factory.was_torndown: + log.debug('ancestor %s already torn down', ancestor) + continue + setup = factory.was_setup[ancestor] + log.debug("%s setup ancestor %s", setup, ancestor) + if setup is self: + self.teardownContext(ancestor) + else: + self.teardownContext(context) + + def teardownContext(self, context): + log.debug("%s teardown context %s", self, context) + if self.factory: + if context in self.factory.was_torndown: + return + self.factory.was_torndown[context] = self + if isclass(context): + names = self.classTeardown + else: + names = self.moduleTeardown + if hasattr(context, '__path__'): + names = self.packageTeardown + names + try_run(context, names) + self.config.plugins.stopContext(context) + + # FIXME the wrapping has to move to the factory? + def _get_wrapped_tests(self): + for test in self._get_tests(): + if isinstance(test, Test) or isinstance(test, unittest.TestSuite): + yield test + else: + yield Test(test, + config=self.config, + resultProxy=self.resultProxy) + + _tests = property(_get_wrapped_tests, LazySuite._set_tests, None, + "Access the tests in this suite. Tests are returned " + "inside of a context wrapper.") + + +class ContextSuiteFactory(object): + """Factory for ContextSuites. Called with a collection of tests, + the factory decides on a hierarchy of contexts by introspecting + the collection or the tests themselves to find the objects + containing the test objects. It always returns one suite, but that + suite may consist of a hierarchy of nested suites. + """ + suiteClass = ContextSuite + def __init__(self, config=None, suiteClass=None, resultProxy=_def): + if config is None: + config = Config() + self.config = config + if suiteClass is not None: + self.suiteClass = suiteClass + # Using a singleton to represent default instead of None allows + # passing resultProxy=None to turn proxying off. + if resultProxy is _def: + resultProxy = ResultProxyFactory(config=config) + self.resultProxy = resultProxy + self.suites = {} + self.context = {} + self.was_setup = {} + self.was_torndown = {} + + def __call__(self, tests, **kw): + """Return ``ContextSuite`` for tests. ``tests`` may either + be a callable (in which case the resulting ContextSuite will + have no parent context and be evaluated lazily) or an + iterable. In that case the tests will wrapped in + nose.case.Test, be examined and the context of each found and a + suite of suites returned, organized into a stack with the + outermost suites belonging to the outermost contexts. + """ + log.debug("Create suite for %s", tests) + context = kw.pop('context', getattr(tests, 'context', None)) + log.debug("tests %s context %s", tests, context) + if context is None: + tests = self.wrapTests(tests) + try: + context = self.findContext(tests) + except MixedContextError: + return self.makeSuite(self.mixedSuites(tests), None, **kw) + return self.makeSuite(tests, context, **kw) + + def ancestry(self, context): + """Return the ancestry of the context (that is, all of the + packages and modules containing the context), in order of + descent with the outermost ancestor last. + This method is a generator. + """ + log.debug("get ancestry %s", context) + if context is None: + return + # Methods include reference to module they are defined in, we + # don't want that, instead want the module the class is in now + # (classes are re-ancestored elsewhere). + if hasattr(context, 'im_class'): + context = context.im_class + elif hasattr(context, '__self__'): + context = context.__self__.__class__ + if hasattr(context, '__module__'): + ancestors = context.__module__.split('.') + elif hasattr(context, '__name__'): + ancestors = context.__name__.split('.')[:-1] + else: + raise TypeError("%s has no ancestors?" % context) + while ancestors: + log.debug(" %s ancestors %s", context, ancestors) + yield resolve_name('.'.join(ancestors)) + ancestors.pop() + + def findContext(self, tests): + if callable(tests) or isinstance(tests, unittest.TestSuite): + return None + context = None + for test in tests: + # Don't look at suites for contexts, only tests + ctx = getattr(test, 'context', None) + if ctx is None: + continue + if context is None: + context = ctx + elif context != ctx: + raise MixedContextError( + "Tests with different contexts in same suite! %s != %s" + % (context, ctx)) + return context + + def makeSuite(self, tests, context, **kw): + suite = self.suiteClass( + tests, context=context, config=self.config, factory=self, + resultProxy=self.resultProxy, **kw) + if context is not None: + self.suites.setdefault(context, []).append(suite) + self.context.setdefault(suite, []).append(context) + log.debug("suite %s has context %s", suite, + getattr(context, '__name__', None)) + for ancestor in self.ancestry(context): + self.suites.setdefault(ancestor, []).append(suite) + self.context[suite].append(ancestor) + log.debug("suite %s has ancestor %s", suite, ancestor.__name__) + return suite + + def mixedSuites(self, tests): + """The complex case where there are tests that don't all share + the same context. Groups tests into suites with common ancestors, + according to the following (essentially tail-recursive) procedure: + + Starting with the context of the first test, if it is not + None, look for tests in the remaining tests that share that + ancestor. If any are found, group into a suite with that + ancestor as the context, and replace the current suite with + that suite. Continue this process for each ancestor of the + first test, until all ancestors have been processed. At this + point if any tests remain, recurse with those tests as the + input, returning a list of the common suite (which may be the + suite or test we started with, if no common tests were found) + plus the results of recursion. + """ + if not tests: + return [] + head = tests.pop(0) + if not tests: + return [head] # short circuit when none are left to combine + suite = head # the common ancestry suite, so far + tail = tests[:] + context = getattr(head, 'context', None) + if context is not None: + ancestors = [context] + [a for a in self.ancestry(context)] + for ancestor in ancestors: + common = [suite] # tests with ancestor in common, so far + remain = [] # tests that remain to be processed + for test in tail: + found_common = False + test_ctx = getattr(test, 'context', None) + if test_ctx is None: + remain.append(test) + continue + if test_ctx is ancestor: + common.append(test) + continue + for test_ancestor in self.ancestry(test_ctx): + if test_ancestor is ancestor: + common.append(test) + found_common = True + break + if not found_common: + remain.append(test) + if common: + suite = self.makeSuite(common, ancestor) + tail = self.mixedSuites(remain) + return [suite] + tail + + def wrapTests(self, tests): + log.debug("wrap %s", tests) + if callable(tests) or isinstance(tests, unittest.TestSuite): + log.debug("I won't wrap") + return tests + wrapped = [] + for test in tests: + log.debug("wrapping %s", test) + if isinstance(test, Test) or isinstance(test, unittest.TestSuite): + wrapped.append(test) + elif isinstance(test, ContextList): + wrapped.append(self.makeSuite(test, context=test.context)) + else: + wrapped.append( + Test(test, config=self.config, resultProxy=self.resultProxy) + ) + return wrapped + + +class ContextList(object): + """Not quite a suite -- a group of tests in a context. This is used + to hint the ContextSuiteFactory about what context the tests + belong to, in cases where it may be ambiguous or missing. + """ + def __init__(self, tests, context=None): + self.tests = tests + self.context = context + + def __iter__(self): + return iter(self.tests) + + +class FinalizingSuiteWrapper(unittest.TestSuite): + """Wraps suite and calls final function after suite has + executed. Used to call final functions in cases (like running in + the standard test runner) where test running is not under nose's + control. + """ + def __init__(self, suite, finalize): + super(FinalizingSuiteWrapper, self).__init__() + self.suite = suite + self.finalize = finalize + + def __call__(self, *arg, **kw): + return self.run(*arg, **kw) + + # 2.7 compat + def __iter__(self): + return iter(self.suite) + + def run(self, *arg, **kw): + try: + return self.suite(*arg, **kw) + finally: + self.finalize(*arg, **kw) + + +# backwards compat -- sort of +class TestDir: + def __init__(*arg, **kw): + raise NotImplementedError( + "TestDir is not usable with nose 0.10. The class is present " + "in nose.suite for backwards compatibility purposes but it " + "may not be used.") + + +class TestModule: + def __init__(*arg, **kw): + raise NotImplementedError( + "TestModule is not usable with nose 0.10. The class is present " + "in nose.suite for backwards compatibility purposes but it " + "may not be used.") diff --git a/env/lib/python2.7/site-packages/nose/suite.pyc b/env/lib/python2.7/site-packages/nose/suite.pyc new file mode 100644 index 0000000..b92c921 Binary files /dev/null and b/env/lib/python2.7/site-packages/nose/suite.pyc differ diff --git a/env/lib/python2.7/site-packages/nose/tools/__init__.py b/env/lib/python2.7/site-packages/nose/tools/__init__.py new file mode 100644 index 0000000..74dab16 --- /dev/null +++ b/env/lib/python2.7/site-packages/nose/tools/__init__.py @@ -0,0 +1,15 @@ +""" +Tools for testing +----------------- + +nose.tools provides a few convenience functions to make writing tests +easier. You don't have to use them; nothing in the rest of nose depends +on any of these methods. + +""" +from nose.tools.nontrivial import * +from nose.tools.nontrivial import __all__ as nontrivial_all +from nose.tools.trivial import * +from nose.tools.trivial import __all__ as trivial_all + +__all__ = trivial_all + nontrivial_all diff --git a/env/lib/python2.7/site-packages/nose/tools/__init__.pyc b/env/lib/python2.7/site-packages/nose/tools/__init__.pyc new file mode 100644 index 0000000..a2246d0 Binary files /dev/null and b/env/lib/python2.7/site-packages/nose/tools/__init__.pyc differ diff --git a/env/lib/python2.7/site-packages/nose/tools/nontrivial.py b/env/lib/python2.7/site-packages/nose/tools/nontrivial.py new file mode 100644 index 0000000..2839732 --- /dev/null +++ b/env/lib/python2.7/site-packages/nose/tools/nontrivial.py @@ -0,0 +1,151 @@ +"""Tools not exempt from being descended into in tracebacks""" + +import time + + +__all__ = ['make_decorator', 'raises', 'set_trace', 'timed', 'with_setup', + 'TimeExpired', 'istest', 'nottest'] + + +class TimeExpired(AssertionError): + pass + + +def make_decorator(func): + """ + Wraps a test decorator so as to properly replicate metadata + of the decorated function, including nose's additional stuff + (namely, setup and teardown). + """ + def decorate(newfunc): + if hasattr(func, 'compat_func_name'): + name = func.compat_func_name + else: + name = func.__name__ + newfunc.__dict__ = func.__dict__ + newfunc.__doc__ = func.__doc__ + newfunc.__module__ = func.__module__ + if not hasattr(newfunc, 'compat_co_firstlineno'): + newfunc.compat_co_firstlineno = func.func_code.co_firstlineno + try: + newfunc.__name__ = name + except TypeError: + # can't set func name in 2.3 + newfunc.compat_func_name = name + return newfunc + return decorate + + +def raises(*exceptions): + """Test must raise one of expected exceptions to pass. + + Example use:: + + @raises(TypeError, ValueError) + def test_raises_type_error(): + raise TypeError("This test passes") + + @raises(Exception) + def test_that_fails_by_passing(): + pass + + If you want to test many assertions about exceptions in a single test, + you may want to use `assert_raises` instead. + """ + valid = ' or '.join([e.__name__ for e in exceptions]) + def decorate(func): + name = func.__name__ + def newfunc(*arg, **kw): + try: + func(*arg, **kw) + except exceptions: + pass + except: + raise + else: + message = "%s() did not raise %s" % (name, valid) + raise AssertionError(message) + newfunc = make_decorator(func)(newfunc) + return newfunc + return decorate + + +def set_trace(): + """Call pdb.set_trace in the calling frame, first restoring + sys.stdout to the real output stream. Note that sys.stdout is NOT + reset to whatever it was before the call once pdb is done! + """ + import pdb + import sys + stdout = sys.stdout + sys.stdout = sys.__stdout__ + pdb.Pdb().set_trace(sys._getframe().f_back) + + +def timed(limit): + """Test must finish within specified time limit to pass. + + Example use:: + + @timed(.1) + def test_that_fails(): + time.sleep(.2) + """ + def decorate(func): + def newfunc(*arg, **kw): + start = time.time() + result = func(*arg, **kw) + end = time.time() + if end - start > limit: + raise TimeExpired("Time limit (%s) exceeded" % limit) + return result + newfunc = make_decorator(func)(newfunc) + return newfunc + return decorate + + +def with_setup(setup=None, teardown=None): + """Decorator to add setup and/or teardown methods to a test function:: + + @with_setup(setup, teardown) + def test_something(): + " ... " + + Note that `with_setup` is useful *only* for test functions, not for test + methods or inside of TestCase subclasses. + """ + def decorate(func, setup=setup, teardown=teardown): + if setup: + if hasattr(func, 'setup'): + _old_s = func.setup + def _s(): + setup() + _old_s() + func.setup = _s + else: + func.setup = setup + if teardown: + if hasattr(func, 'teardown'): + _old_t = func.teardown + def _t(): + _old_t() + teardown() + func.teardown = _t + else: + func.teardown = teardown + return func + return decorate + + +def istest(func): + """Decorator to mark a function or method as a test + """ + func.__test__ = True + return func + + +def nottest(func): + """Decorator to mark a function or method as *not* a test + """ + func.__test__ = False + return func diff --git a/env/lib/python2.7/site-packages/nose/tools/nontrivial.pyc b/env/lib/python2.7/site-packages/nose/tools/nontrivial.pyc new file mode 100644 index 0000000..9d5fba1 Binary files /dev/null and b/env/lib/python2.7/site-packages/nose/tools/nontrivial.pyc differ diff --git a/env/lib/python2.7/site-packages/nose/tools/trivial.py b/env/lib/python2.7/site-packages/nose/tools/trivial.py new file mode 100644 index 0000000..cf83efe --- /dev/null +++ b/env/lib/python2.7/site-packages/nose/tools/trivial.py @@ -0,0 +1,54 @@ +"""Tools so trivial that tracebacks should not descend into them + +We define the ``__unittest`` symbol in their module namespace so unittest will +skip them when printing tracebacks, just as it does for their corresponding +methods in ``unittest`` proper. + +""" +import re +import unittest + + +__all__ = ['ok_', 'eq_'] + +# Use the same flag as unittest itself to prevent descent into these functions: +__unittest = 1 + + +def ok_(expr, msg=None): + """Shorthand for assert. Saves 3 whole characters! + """ + if not expr: + raise AssertionError(msg) + + +def eq_(a, b, msg=None): + """Shorthand for 'assert a == b, "%r != %r" % (a, b) + """ + if not a == b: + raise AssertionError(msg or "%r != %r" % (a, b)) + + +# +# Expose assert* from unittest.TestCase +# - give them pep8 style names +# +caps = re.compile('([A-Z])') + +def pep8(name): + return caps.sub(lambda m: '_' + m.groups()[0].lower(), name) + +class Dummy(unittest.TestCase): + def nop(): + pass +_t = Dummy('nop') + +for at in [ at for at in dir(_t) + if at.startswith('assert') and not '_' in at ]: + pepd = pep8(at) + vars()[pepd] = getattr(_t, at) + __all__.append(pepd) + +del Dummy +del _t +del pep8 diff --git a/env/lib/python2.7/site-packages/nose/tools/trivial.pyc b/env/lib/python2.7/site-packages/nose/tools/trivial.pyc new file mode 100644 index 0000000..4febc0d Binary files /dev/null and b/env/lib/python2.7/site-packages/nose/tools/trivial.pyc differ diff --git a/env/lib/python2.7/site-packages/nose/twistedtools.py b/env/lib/python2.7/site-packages/nose/twistedtools.py new file mode 100644 index 0000000..8d9c6ff --- /dev/null +++ b/env/lib/python2.7/site-packages/nose/twistedtools.py @@ -0,0 +1,173 @@ +""" +Twisted integration +------------------- + +This module provides a very simple way to integrate your tests with the +Twisted_ event loop. + +You must import this module *before* importing anything from Twisted itself! + +Example:: + + from nose.twistedtools import reactor, deferred + + @deferred() + def test_resolve(): + return reactor.resolve("www.python.org") + +Or, more realistically:: + + @deferred(timeout=5.0) + def test_resolve(): + d = reactor.resolve("www.python.org") + def check_ip(ip): + assert ip == "67.15.36.43" + d.addCallback(check_ip) + return d + +.. _Twisted: http://twistedmatrix.com/trac/ +""" + +import sys +from Queue import Queue, Empty +from nose.tools import make_decorator, TimeExpired + +__all__ = [ + 'threaded_reactor', 'reactor', 'deferred', 'TimeExpired', + 'stop_reactor' +] + +_twisted_thread = None + +def threaded_reactor(): + """ + Start the Twisted reactor in a separate thread, if not already done. + Returns the reactor. + The thread will automatically be destroyed when all the tests are done. + """ + global _twisted_thread + try: + from twisted.internet import reactor + except ImportError: + return None, None + if not _twisted_thread: + from twisted.python import threadable + from threading import Thread + _twisted_thread = Thread(target=lambda: reactor.run( \ + installSignalHandlers=False)) + _twisted_thread.setDaemon(True) + _twisted_thread.start() + return reactor, _twisted_thread + +# Export global reactor variable, as Twisted does +reactor, reactor_thread = threaded_reactor() + + +def stop_reactor(): + """Stop the reactor and join the reactor thread until it stops. + Call this function in teardown at the module or package level to + reset the twisted system after your tests. You *must* do this if + you mix tests using these tools and tests using twisted.trial. + """ + global _twisted_thread + + def stop_reactor(): + '''Helper for calling stop from withing the thread.''' + reactor.stop() + + reactor.callFromThread(stop_reactor) + reactor_thread.join() + for p in reactor.getDelayedCalls(): + if p.active(): + p.cancel() + _twisted_thread = None + + +def deferred(timeout=None): + """ + By wrapping a test function with this decorator, you can return a + twisted Deferred and the test will wait for the deferred to be triggered. + The whole test function will run inside the Twisted event loop. + + The optional timeout parameter specifies the maximum duration of the test. + The difference with timed() is that timed() will still wait for the test + to end, while deferred() will stop the test when its timeout has expired. + The latter is more desireable when dealing with network tests, because + the result may actually never arrive. + + If the callback is triggered, the test has passed. + If the errback is triggered or the timeout expires, the test has failed. + + Example:: + + @deferred(timeout=5.0) + def test_resolve(): + return reactor.resolve("www.python.org") + + Attention! If you combine this decorator with other decorators (like + "raises"), deferred() must be called *first*! + + In other words, this is good:: + + @raises(DNSLookupError) + @deferred() + def test_error(): + return reactor.resolve("xxxjhjhj.biz") + + and this is bad:: + + @deferred() + @raises(DNSLookupError) + def test_error(): + return reactor.resolve("xxxjhjhj.biz") + """ + reactor, reactor_thread = threaded_reactor() + if reactor is None: + raise ImportError("twisted is not available or could not be imported") + # Check for common syntax mistake + # (otherwise, tests can be silently ignored + # if one writes "@deferred" instead of "@deferred()") + try: + timeout is None or timeout + 0 + except TypeError: + raise TypeError("'timeout' argument must be a number or None") + + def decorate(func): + def wrapper(*args, **kargs): + q = Queue() + def callback(value): + q.put(None) + def errback(failure): + # Retrieve and save full exception info + try: + failure.raiseException() + except: + q.put(sys.exc_info()) + def g(): + try: + d = func(*args, **kargs) + try: + d.addCallbacks(callback, errback) + # Check for a common mistake and display a nice error + # message + except AttributeError: + raise TypeError("you must return a twisted Deferred " + "from your test case!") + # Catch exceptions raised in the test body (from the + # Twisted thread) + except: + q.put(sys.exc_info()) + reactor.callFromThread(g) + try: + error = q.get(timeout=timeout) + except Empty: + raise TimeExpired("timeout expired before end of test (%f s.)" + % timeout) + # Re-raise all exceptions + if error is not None: + exc_type, exc_value, tb = error + raise exc_type, exc_value, tb + wrapper = make_decorator(func)(wrapper) + return wrapper + return decorate + diff --git a/env/lib/python2.7/site-packages/nose/twistedtools.pyc b/env/lib/python2.7/site-packages/nose/twistedtools.pyc new file mode 100644 index 0000000..a39022c Binary files /dev/null and b/env/lib/python2.7/site-packages/nose/twistedtools.pyc differ diff --git a/env/lib/python2.7/site-packages/nose/usage.txt b/env/lib/python2.7/site-packages/nose/usage.txt new file mode 100644 index 0000000..bc96894 --- /dev/null +++ b/env/lib/python2.7/site-packages/nose/usage.txt @@ -0,0 +1,115 @@ +nose collects tests automatically from python source files, +directories and packages found in its working directory (which +defaults to the current working directory). Any python source file, +directory or package that matches the testMatch regular expression +(by default: `(?:^|[\b_\.-])[Tt]est)` will be collected as a test (or +source for collection of tests). In addition, all other packages +found in the working directory will be examined for python source files +or directories that match testMatch. Package discovery descends all +the way down the tree, so package.tests and package.sub.tests and +package.sub.sub2.tests will all be collected. + +Within a test directory or package, any python source file matching +testMatch will be examined for test cases. Within a test module, +functions and classes whose names match testMatch and TestCase +subclasses with any name will be loaded and executed as tests. Tests +may use the assert keyword or raise AssertionErrors to indicate test +failure. TestCase subclasses may do the same or use the various +TestCase methods available. + +**It is important to note that the default behavior of nose is to +not include tests from files which are executable.** To include +tests from such files, remove their executable bit or use +the --exe flag (see 'Options' section below). + +Selecting Tests +--------------- + +To specify which tests to run, pass test names on the command line: + + %prog only_test_this.py + +Test names specified may be file or module names, and may optionally +indicate the test case to run by separating the module or file name +from the test case name with a colon. Filenames may be relative or +absolute. Examples: + + %prog test.module + %prog another.test:TestCase.test_method + %prog a.test:TestCase + %prog /path/to/test/file.py:test_function + +You may also change the working directory where nose looks for tests +by using the -w switch: + + %prog -w /path/to/tests + +Note, however, that support for multiple -w arguments is now deprecated +and will be removed in a future release. As of nose 0.10, you can get +the same behavior by specifying the target directories *without* +the -w switch: + + %prog /path/to/tests /another/path/to/tests + +Further customization of test selection and loading is possible +through the use of plugins. + +Test result output is identical to that of unittest, except for +the additional features (error classes, and plugin-supplied +features such as output capture and assert introspection) detailed +in the options below. + +Configuration +------------- + +In addition to passing command-line options, you may also put +configuration options in your project's *setup.cfg* file, or a .noserc +or nose.cfg file in your home directory. In any of these standard +ini-style config files, you put your nosetests configuration in a +``[nosetests]`` section. Options are the same as on the command line, +with the -- prefix removed. For options that are simple switches, you +must supply a value: + + [nosetests] + verbosity=3 + with-doctest=1 + +All configuration files that are found will be loaded and their +options combined. You can override the standard config file loading +with the ``-c`` option. + +Using Plugins +------------- + +There are numerous nose plugins available via easy_install and +elsewhere. To use a plugin, just install it. The plugin will add +command line options to nosetests. To verify that the plugin is installed, +run: + + nosetests --plugins + +You can add -v or -vv to that command to show more information +about each plugin. + +If you are running nose.main() or nose.run() from a script, you +can specify a list of plugins to use by passing a list of plugins +with the plugins keyword argument. + +0.9 plugins +----------- + +nose 1.0 can use SOME plugins that were written for nose 0.9. The +default plugin manager inserts a compatibility wrapper around 0.9 +plugins that adapts the changed plugin api calls. However, plugins +that access nose internals are likely to fail, especially if they +attempt to access test case or test suite classes. For example, +plugins that try to determine if a test passed to startTest is an +individual test or a suite will fail, partly because suites are no +longer passed to startTest and partly because it's likely that the +plugin is trying to find out if the test is an instance of a class +that no longer exists. + +0.10 and 0.11 plugins +--------------------- + +All plugins written for nose 0.10 and 0.11 should work with nose 1.0. diff --git a/env/lib/python2.7/site-packages/nose/util.py b/env/lib/python2.7/site-packages/nose/util.py new file mode 100644 index 0000000..bfe1658 --- /dev/null +++ b/env/lib/python2.7/site-packages/nose/util.py @@ -0,0 +1,668 @@ +"""Utility functions and classes used by nose internally. +""" +import inspect +import itertools +import logging +import stat +import os +import re +import sys +import types +import unittest +from nose.pyversion import ClassType, TypeType, isgenerator, ismethod + + +log = logging.getLogger('nose') + +ident_re = re.compile(r'^[A-Za-z_][A-Za-z0-9_.]*$') +class_types = (ClassType, TypeType) +skip_pattern = r"(?:\.svn)|(?:[^.]+\.py[co])|(?:.*~)|(?:.*\$py\.class)|(?:__pycache__)" + +try: + set() + set = set # make from nose.util import set happy +except NameError: + try: + from sets import Set as set + except ImportError: + pass + + +def ls_tree(dir_path="", + skip_pattern=skip_pattern, + indent="|-- ", branch_indent="| ", + last_indent="`-- ", last_branch_indent=" "): + # TODO: empty directories look like non-directory files + return "\n".join(_ls_tree_lines(dir_path, skip_pattern, + indent, branch_indent, + last_indent, last_branch_indent)) + + +def _ls_tree_lines(dir_path, skip_pattern, + indent, branch_indent, last_indent, last_branch_indent): + if dir_path == "": + dir_path = os.getcwd() + + lines = [] + + names = os.listdir(dir_path) + names.sort() + dirs, nondirs = [], [] + for name in names: + if re.match(skip_pattern, name): + continue + if os.path.isdir(os.path.join(dir_path, name)): + dirs.append(name) + else: + nondirs.append(name) + + # list non-directories first + entries = list(itertools.chain([(name, False) for name in nondirs], + [(name, True) for name in dirs])) + def ls_entry(name, is_dir, ind, branch_ind): + if not is_dir: + yield ind + name + else: + path = os.path.join(dir_path, name) + if not os.path.islink(path): + yield ind + name + subtree = _ls_tree_lines(path, skip_pattern, + indent, branch_indent, + last_indent, last_branch_indent) + for x in subtree: + yield branch_ind + x + for name, is_dir in entries[:-1]: + for line in ls_entry(name, is_dir, indent, branch_indent): + yield line + if entries: + name, is_dir = entries[-1] + for line in ls_entry(name, is_dir, last_indent, last_branch_indent): + yield line + + +def absdir(path): + """Return absolute, normalized path to directory, if it exists; None + otherwise. + """ + if not os.path.isabs(path): + path = os.path.normpath(os.path.abspath(os.path.join(os.getcwd(), + path))) + if path is None or not os.path.isdir(path): + return None + return path + + +def absfile(path, where=None): + """Return absolute, normalized path to file (optionally in directory + where), or None if the file can't be found either in where or the current + working directory. + """ + orig = path + if where is None: + where = os.getcwd() + if isinstance(where, list) or isinstance(where, tuple): + for maybe_path in where: + maybe_abs = absfile(path, maybe_path) + if maybe_abs is not None: + return maybe_abs + return None + if not os.path.isabs(path): + path = os.path.normpath(os.path.abspath(os.path.join(where, path))) + if path is None or not os.path.exists(path): + if where != os.getcwd(): + # try the cwd instead + path = os.path.normpath(os.path.abspath(os.path.join(os.getcwd(), + orig))) + if path is None or not os.path.exists(path): + return None + if os.path.isdir(path): + # might want an __init__.py from pacakge + init = os.path.join(path,'__init__.py') + if os.path.isfile(init): + return init + elif os.path.isfile(path): + return path + return None + + +def anyp(predicate, iterable): + for item in iterable: + if predicate(item): + return True + return False + + +def file_like(name): + """A name is file-like if it is a path that exists, or it has a + directory part, or it ends in .py, or it isn't a legal python + identifier. + """ + return (os.path.exists(name) + or os.path.dirname(name) + or name.endswith('.py') + or not ident_re.match(os.path.splitext(name)[0])) + + +def func_lineno(func): + """Get the line number of a function. First looks for + compat_co_firstlineno, then func_code.co_first_lineno. + """ + try: + return func.compat_co_firstlineno + except AttributeError: + try: + return func.func_code.co_firstlineno + except AttributeError: + return -1 + + +def isclass(obj): + """Is obj a class? Inspect's isclass is too liberal and returns True + for objects that can't be subclasses of anything. + """ + obj_type = type(obj) + return obj_type in class_types or issubclass(obj_type, type) + + +# backwards compat (issue #64) +is_generator = isgenerator + + +def ispackage(path): + """ + Is this path a package directory? + + >>> ispackage('nose') + True + >>> ispackage('unit_tests') + False + >>> ispackage('nose/plugins') + True + >>> ispackage('nose/loader.py') + False + """ + if os.path.isdir(path): + # at least the end of the path must be a legal python identifier + # and __init__.py[co] must exist + end = os.path.basename(path) + if ident_re.match(end): + for init in ('__init__.py', '__init__.pyc', '__init__.pyo'): + if os.path.isfile(os.path.join(path, init)): + return True + if sys.platform.startswith('java') and \ + os.path.isfile(os.path.join(path, '__init__$py.class')): + return True + return False + + +def isproperty(obj): + """ + Is this a property? + + >>> class Foo: + ... def got(self): + ... return 2 + ... def get(self): + ... return 1 + ... get = property(get) + + >>> isproperty(Foo.got) + False + >>> isproperty(Foo.get) + True + """ + return type(obj) == property + + +def getfilename(package, relativeTo=None): + """Find the python source file for a package, relative to a + particular directory (defaults to current working directory if not + given). + """ + if relativeTo is None: + relativeTo = os.getcwd() + path = os.path.join(relativeTo, os.sep.join(package.split('.'))) + if os.path.exists(path + '/__init__.py'): + return path + filename = path + '.py' + if os.path.exists(filename): + return filename + return None + + +def getpackage(filename): + """ + Find the full dotted package name for a given python source file + name. Returns None if the file is not a python source file. + + >>> getpackage('foo.py') + 'foo' + >>> getpackage('biff/baf.py') + 'baf' + >>> getpackage('nose/util.py') + 'nose.util' + + Works for directories too. + + >>> getpackage('nose') + 'nose' + >>> getpackage('nose/plugins') + 'nose.plugins' + + And __init__ files stuck onto directories + + >>> getpackage('nose/plugins/__init__.py') + 'nose.plugins' + + Absolute paths also work. + + >>> path = os.path.abspath(os.path.join('nose', 'plugins')) + >>> getpackage(path) + 'nose.plugins' + """ + src_file = src(filename) + if (os.path.isdir(src_file) or not src_file.endswith('.py')) and not ispackage(src_file): + return None + base, ext = os.path.splitext(os.path.basename(src_file)) + if base == '__init__': + mod_parts = [] + else: + mod_parts = [base] + path, part = os.path.split(os.path.split(src_file)[0]) + while part: + if ispackage(os.path.join(path, part)): + mod_parts.append(part) + else: + break + path, part = os.path.split(path) + mod_parts.reverse() + return '.'.join(mod_parts) + + +def ln(label): + """Draw a 70-char-wide divider, with label in the middle. + + >>> ln('hello there') + '---------------------------- hello there -----------------------------' + """ + label_len = len(label) + 2 + chunk = (70 - label_len) // 2 + out = '%s %s %s' % ('-' * chunk, label, '-' * chunk) + pad = 70 - len(out) + if pad > 0: + out = out + ('-' * pad) + return out + + +def resolve_name(name, module=None): + """Resolve a dotted name to a module and its parts. This is stolen + wholesale from unittest.TestLoader.loadTestByName. + + >>> resolve_name('nose.util') #doctest: +ELLIPSIS + + >>> resolve_name('nose.util.resolve_name') #doctest: +ELLIPSIS + + """ + parts = name.split('.') + parts_copy = parts[:] + if module is None: + while parts_copy: + try: + log.debug("__import__ %s", name) + module = __import__('.'.join(parts_copy)) + break + except ImportError: + del parts_copy[-1] + if not parts_copy: + raise + parts = parts[1:] + obj = module + log.debug("resolve: %s, %s, %s, %s", parts, name, obj, module) + for part in parts: + obj = getattr(obj, part) + return obj + + +def split_test_name(test): + """Split a test name into a 3-tuple containing file, module, and callable + names, any of which (but not all) may be blank. + + Test names are in the form: + + file_or_module:callable + + Either side of the : may be dotted. To change the splitting behavior, you + can alter nose.util.split_test_re. + """ + norm = os.path.normpath + file_or_mod = test + fn = None + if not ':' in test: + # only a file or mod part + if file_like(test): + return (norm(test), None, None) + else: + return (None, test, None) + + # could be path|mod:callable, or a : in the file path someplace + head, tail = os.path.split(test) + if not head: + # this is a case like 'foo:bar' -- generally a module + # name followed by a callable, but also may be a windows + # drive letter followed by a path + try: + file_or_mod, fn = test.split(':') + if file_like(fn): + # must be a funny path + file_or_mod, fn = test, None + except ValueError: + # more than one : in the test + # this is a case like c:\some\path.py:a_test + parts = test.split(':') + if len(parts[0]) == 1: + file_or_mod, fn = ':'.join(parts[:-1]), parts[-1] + else: + # nonsense like foo:bar:baz + raise ValueError("Test name '%s' could not be parsed. Please " + "format test names as path:callable or " + "module:callable." % (test,)) + elif not tail: + # this is a case like 'foo:bar/' + # : must be part of the file path, so ignore it + file_or_mod = test + else: + if ':' in tail: + file_part, fn = tail.split(':') + else: + file_part = tail + file_or_mod = os.sep.join([head, file_part]) + if file_or_mod: + if file_like(file_or_mod): + return (norm(file_or_mod), None, fn) + else: + return (None, file_or_mod, fn) + else: + return (None, None, fn) +split_test_name.__test__ = False # do not collect + + +def test_address(test): + """Find the test address for a test, which may be a module, filename, + class, method or function. + """ + if hasattr(test, "address"): + return test.address() + # type-based polymorphism sucks in general, but I believe is + # appropriate here + t = type(test) + file = module = call = None + if t == types.ModuleType: + file = getattr(test, '__file__', None) + module = getattr(test, '__name__', None) + return (src(file), module, call) + if t == types.FunctionType or issubclass(t, type) or t == types.ClassType: + module = getattr(test, '__module__', None) + if module is not None: + m = sys.modules[module] + file = getattr(m, '__file__', None) + if file is not None: + file = os.path.abspath(file) + call = getattr(test, '__name__', None) + return (src(file), module, call) + if t == types.MethodType: + cls_adr = test_address(test.im_class) + return (src(cls_adr[0]), cls_adr[1], + "%s.%s" % (cls_adr[2], test.__name__)) + # handle unittest.TestCase instances + if isinstance(test, unittest.TestCase): + if (hasattr(test, '_FunctionTestCase__testFunc') # pre 2.7 + or hasattr(test, '_testFunc')): # 2.7 + # unittest FunctionTestCase + try: + return test_address(test._FunctionTestCase__testFunc) + except AttributeError: + return test_address(test._testFunc) + # regular unittest.TestCase + cls_adr = test_address(test.__class__) + # 2.5 compat: __testMethodName changed to _testMethodName + try: + method_name = test._TestCase__testMethodName + except AttributeError: + method_name = test._testMethodName + return (src(cls_adr[0]), cls_adr[1], + "%s.%s" % (cls_adr[2], method_name)) + if (hasattr(test, '__class__') and + test.__class__.__module__ not in ('__builtin__', 'builtins')): + return test_address(test.__class__) + raise TypeError("I don't know what %s is (%s)" % (test, t)) +test_address.__test__ = False # do not collect + + +def try_run(obj, names): + """Given a list of possible method names, try to run them with the + provided object. Keep going until something works. Used to run + setup/teardown methods for module, package, and function tests. + """ + for name in names: + func = getattr(obj, name, None) + if func is not None: + if type(obj) == types.ModuleType: + # py.test compatibility + if isinstance(func, types.FunctionType): + args, varargs, varkw, defaults = \ + inspect.getargspec(func) + else: + # Not a function. If it's callable, call it anyway + if hasattr(func, '__call__') and not inspect.ismethod(func): + func = func.__call__ + try: + args, varargs, varkw, defaults = \ + inspect.getargspec(func) + args.pop(0) # pop the self off + except TypeError: + raise TypeError("Attribute %s of %r is not a python " + "function. Only functions or callables" + " may be used as fixtures." % + (name, obj)) + if len(args): + log.debug("call fixture %s.%s(%s)", obj, name, obj) + return func(obj) + log.debug("call fixture %s.%s", obj, name) + return func() + + +def src(filename): + """Find the python source file for a .pyc, .pyo or $py.class file on + jython. Returns the filename provided if it is not a python source + file. + """ + if filename is None: + return filename + if sys.platform.startswith('java') and filename.endswith('$py.class'): + return '.'.join((filename[:-9], 'py')) + base, ext = os.path.splitext(filename) + if ext in ('.pyc', '.pyo', '.py'): + return '.'.join((base, 'py')) + return filename + + +def regex_last_key(regex): + """Sort key function factory that puts items that match a + regular expression last. + + >>> from nose.config import Config + >>> from nose.pyversion import sort_list + >>> c = Config() + >>> regex = c.testMatch + >>> entries = ['.', '..', 'a_test', 'src', 'lib', 'test', 'foo.py'] + >>> sort_list(entries, regex_last_key(regex)) + >>> entries + ['.', '..', 'foo.py', 'lib', 'src', 'a_test', 'test'] + """ + def k(obj): + if regex.search(obj): + return (1, obj) + return (0, obj) + return k + + +def tolist(val): + """Convert a value that may be a list or a (possibly comma-separated) + string into a list. The exception: None is returned as None, not [None]. + + >>> tolist(["one", "two"]) + ['one', 'two'] + >>> tolist("hello") + ['hello'] + >>> tolist("separate,values, with, commas, spaces , are ,ok") + ['separate', 'values', 'with', 'commas', 'spaces', 'are', 'ok'] + """ + if val is None: + return None + try: + # might already be a list + val.extend([]) + return val + except AttributeError: + pass + # might be a string + try: + return re.split(r'\s*,\s*', val) + except TypeError: + # who knows... + return list(val) + + +class odict(dict): + """Simple ordered dict implementation, based on: + + http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/107747 + """ + def __init__(self, *arg, **kw): + self._keys = [] + super(odict, self).__init__(*arg, **kw) + + def __delitem__(self, key): + super(odict, self).__delitem__(key) + self._keys.remove(key) + + def __setitem__(self, key, item): + super(odict, self).__setitem__(key, item) + if key not in self._keys: + self._keys.append(key) + + def __str__(self): + return "{%s}" % ', '.join(["%r: %r" % (k, v) for k, v in self.items()]) + + def clear(self): + super(odict, self).clear() + self._keys = [] + + def copy(self): + d = super(odict, self).copy() + d._keys = self._keys[:] + return d + + def items(self): + return zip(self._keys, self.values()) + + def keys(self): + return self._keys[:] + + def setdefault(self, key, failobj=None): + item = super(odict, self).setdefault(key, failobj) + if key not in self._keys: + self._keys.append(key) + return item + + def update(self, dict): + super(odict, self).update(dict) + for key in dict.keys(): + if key not in self._keys: + self._keys.append(key) + + def values(self): + return map(self.get, self._keys) + + +def transplant_func(func, module): + """ + Make a function imported from module A appear as if it is located + in module B. + + >>> from pprint import pprint + >>> pprint.__module__ + 'pprint' + >>> pp = transplant_func(pprint, __name__) + >>> pp.__module__ + 'nose.util' + + The original function is not modified. + + >>> pprint.__module__ + 'pprint' + + Calling the transplanted function calls the original. + + >>> pp([1, 2]) + [1, 2] + >>> pprint([1,2]) + [1, 2] + + """ + from nose.tools import make_decorator + if isgenerator(func): + def newfunc(*arg, **kw): + for v in func(*arg, **kw): + yield v + else: + def newfunc(*arg, **kw): + return func(*arg, **kw) + + newfunc = make_decorator(func)(newfunc) + newfunc.__module__ = module + return newfunc + + +def transplant_class(cls, module): + """ + Make a class appear to reside in `module`, rather than the module in which + it is actually defined. + + >>> from nose.failure import Failure + >>> Failure.__module__ + 'nose.failure' + >>> Nf = transplant_class(Failure, __name__) + >>> Nf.__module__ + 'nose.util' + >>> Nf.__name__ + 'Failure' + + """ + class C(cls): + pass + C.__module__ = module + C.__name__ = cls.__name__ + return C + + +def safe_str(val, encoding='utf-8'): + try: + return str(val) + except UnicodeEncodeError: + if isinstance(val, Exception): + return ' '.join([safe_str(arg, encoding) + for arg in val]) + return unicode(val).encode(encoding) + + +def is_executable(file): + if not os.path.exists(file): + return False + st = os.stat(file) + return bool(st.st_mode & (stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)) + + +if __name__ == '__main__': + import doctest + doctest.testmod() diff --git a/env/lib/python2.7/site-packages/nose/util.pyc b/env/lib/python2.7/site-packages/nose/util.pyc new file mode 100644 index 0000000..bf1a8d2 Binary files /dev/null and b/env/lib/python2.7/site-packages/nose/util.pyc differ diff --git a/env/lib/python2.7/site-packages/werkzeug/__init__.py b/env/lib/python2.7/site-packages/werkzeug/__init__.py index aea845a..44540f6 100644 --- a/env/lib/python2.7/site-packages/werkzeug/__init__.py +++ b/env/lib/python2.7/site-packages/werkzeug/__init__.py @@ -20,7 +20,7 @@ from werkzeug._compat import iteritems # the version. Usually set automatically by a script. -__version__ = '0.10.1' +__version__ = '0.10.4' # This import magic raises concerns quite often which is why the implementation diff --git a/env/lib/python2.7/site-packages/werkzeug/__init__.pyc b/env/lib/python2.7/site-packages/werkzeug/__init__.pyc index d0f7816..093c075 100644 Binary files a/env/lib/python2.7/site-packages/werkzeug/__init__.pyc and b/env/lib/python2.7/site-packages/werkzeug/__init__.pyc differ diff --git a/env/lib/python2.7/site-packages/werkzeug/_compat.pyc b/env/lib/python2.7/site-packages/werkzeug/_compat.pyc index 6cef8d8..12967c2 100644 Binary files a/env/lib/python2.7/site-packages/werkzeug/_compat.pyc and b/env/lib/python2.7/site-packages/werkzeug/_compat.pyc differ diff --git a/env/lib/python2.7/site-packages/werkzeug/_internal.pyc b/env/lib/python2.7/site-packages/werkzeug/_internal.pyc index eb51d14..bb85ee9 100644 Binary files a/env/lib/python2.7/site-packages/werkzeug/_internal.pyc and b/env/lib/python2.7/site-packages/werkzeug/_internal.pyc differ diff --git a/env/lib/python2.7/site-packages/werkzeug/_reloader.py b/env/lib/python2.7/site-packages/werkzeug/_reloader.py index 83716f1..629c012 100644 --- a/env/lib/python2.7/site-packages/werkzeug/_reloader.py +++ b/env/lib/python2.7/site-packages/werkzeug/_reloader.py @@ -14,61 +14,22 @@ def _iter_module_files(): loaded files from modules, all files in folders of already loaded modules as well as all files reachable through a package. """ - found = set() - entered = set() - - def _verify_file(filename): - if not filename: - return - filename = os.path.abspath(filename) - old = None - while not os.path.isfile(filename): - old = filename - filename = os.path.dirname(filename) - if filename == old: - break - else: - if filename[-4:] in ('.pyc', '.pyo'): - filename = filename[:-1] - if filename not in found: - found.add(filename) - return filename - - def _recursive_walk(path_entry): - if path_entry in entered: - return - entered.add(path_entry) - try: - for filename in os.listdir(path_entry): - path = os.path.join(path_entry, filename) - if os.path.isdir(path): - for filename in _recursive_walk(path): - yield filename - else: - if not filename.endswith(('.py', '.pyc', '.pyo')): - continue - filename = _verify_file(path) - if filename: - yield filename - except OSError: - pass - # The list call is necessary on Python 3 in case the module # dictionary modifies during iteration. - for path_entry in list(sys.path): - for filename in _recursive_walk(os.path.abspath(path_entry)): - yield filename - for module in list(sys.modules.values()): if module is None: continue - filename = _verify_file(getattr(module, '__file__', None)) + filename = getattr(module, '__file__', None) if filename: - yield filename - for filename in _recursive_walk(os.path.dirname(filename)): - yield filename - for package_path in getattr(module, '__path__', ()): - for filename in _recursive_walk(os.path.abspath(package_path)): + old = None + while not os.path.isfile(filename): + old = filename + filename = os.path.dirname(filename) + if filename == old: + break + else: + if filename[-4:] in ('.pyc', '.pyo'): + filename = filename[:-1] yield filename @@ -76,14 +37,14 @@ def _find_observable_paths(extra_files=None): """Finds all paths that should be observed.""" rv = set(os.path.abspath(x) for x in sys.path) for filename in extra_files or (): - rv.append(os.path.dirname(os.path.abspath(filename))) + rv.add(os.path.dirname(os.path.abspath(filename))) for module in list(sys.modules.values()): fn = getattr(module, '__file__', None) if fn is None: continue fn = os.path.abspath(fn) rv.add(os.path.dirname(fn)) - return rv + return _find_common_roots(rv) def _find_common_roots(paths): @@ -219,8 +180,7 @@ def run(self): while not self.should_reload: to_delete = set(watches) - paths = _find_common_roots( - _find_observable_paths(self.extra_files)) + paths = _find_observable_paths(self.extra_files) for path in paths: if path not in watches: try: diff --git a/env/lib/python2.7/site-packages/werkzeug/_reloader.pyc b/env/lib/python2.7/site-packages/werkzeug/_reloader.pyc index ecfcff4..1b3c303 100644 Binary files a/env/lib/python2.7/site-packages/werkzeug/_reloader.pyc and b/env/lib/python2.7/site-packages/werkzeug/_reloader.pyc differ diff --git a/env/lib/python2.7/site-packages/werkzeug/contrib/__init__.pyc b/env/lib/python2.7/site-packages/werkzeug/contrib/__init__.pyc index ef534c1..48fa492 100644 Binary files a/env/lib/python2.7/site-packages/werkzeug/contrib/__init__.pyc and b/env/lib/python2.7/site-packages/werkzeug/contrib/__init__.pyc differ diff --git a/env/lib/python2.7/site-packages/werkzeug/contrib/atom.pyc b/env/lib/python2.7/site-packages/werkzeug/contrib/atom.pyc index ec8643e..2f2f835 100644 Binary files a/env/lib/python2.7/site-packages/werkzeug/contrib/atom.pyc and b/env/lib/python2.7/site-packages/werkzeug/contrib/atom.pyc differ diff --git a/env/lib/python2.7/site-packages/werkzeug/contrib/cache.pyc b/env/lib/python2.7/site-packages/werkzeug/contrib/cache.pyc index ae9199c..348c34d 100644 Binary files a/env/lib/python2.7/site-packages/werkzeug/contrib/cache.pyc and b/env/lib/python2.7/site-packages/werkzeug/contrib/cache.pyc differ diff --git a/env/lib/python2.7/site-packages/werkzeug/contrib/fixers.pyc b/env/lib/python2.7/site-packages/werkzeug/contrib/fixers.pyc index 45aab98..dd359cc 100644 Binary files a/env/lib/python2.7/site-packages/werkzeug/contrib/fixers.pyc and b/env/lib/python2.7/site-packages/werkzeug/contrib/fixers.pyc differ diff --git a/env/lib/python2.7/site-packages/werkzeug/contrib/iterio.pyc b/env/lib/python2.7/site-packages/werkzeug/contrib/iterio.pyc index e0a0af3..086a997 100644 Binary files a/env/lib/python2.7/site-packages/werkzeug/contrib/iterio.pyc and b/env/lib/python2.7/site-packages/werkzeug/contrib/iterio.pyc differ diff --git a/env/lib/python2.7/site-packages/werkzeug/contrib/jsrouting.pyc b/env/lib/python2.7/site-packages/werkzeug/contrib/jsrouting.pyc index 463c644..4289a63 100644 Binary files a/env/lib/python2.7/site-packages/werkzeug/contrib/jsrouting.pyc and b/env/lib/python2.7/site-packages/werkzeug/contrib/jsrouting.pyc differ diff --git a/env/lib/python2.7/site-packages/werkzeug/contrib/limiter.pyc b/env/lib/python2.7/site-packages/werkzeug/contrib/limiter.pyc index 547193b..3269a4e 100644 Binary files a/env/lib/python2.7/site-packages/werkzeug/contrib/limiter.pyc and b/env/lib/python2.7/site-packages/werkzeug/contrib/limiter.pyc differ diff --git a/env/lib/python2.7/site-packages/werkzeug/contrib/lint.pyc b/env/lib/python2.7/site-packages/werkzeug/contrib/lint.pyc index a4873e0..1641f7c 100644 Binary files a/env/lib/python2.7/site-packages/werkzeug/contrib/lint.pyc and b/env/lib/python2.7/site-packages/werkzeug/contrib/lint.pyc differ diff --git a/env/lib/python2.7/site-packages/werkzeug/contrib/profiler.pyc b/env/lib/python2.7/site-packages/werkzeug/contrib/profiler.pyc index 38354ac..b8f9c56 100644 Binary files a/env/lib/python2.7/site-packages/werkzeug/contrib/profiler.pyc and b/env/lib/python2.7/site-packages/werkzeug/contrib/profiler.pyc differ diff --git a/env/lib/python2.7/site-packages/werkzeug/contrib/securecookie.pyc b/env/lib/python2.7/site-packages/werkzeug/contrib/securecookie.pyc index c04013e..683d7eb 100644 Binary files a/env/lib/python2.7/site-packages/werkzeug/contrib/securecookie.pyc and b/env/lib/python2.7/site-packages/werkzeug/contrib/securecookie.pyc differ diff --git a/env/lib/python2.7/site-packages/werkzeug/contrib/sessions.pyc b/env/lib/python2.7/site-packages/werkzeug/contrib/sessions.pyc index edfc310..acdc364 100644 Binary files a/env/lib/python2.7/site-packages/werkzeug/contrib/sessions.pyc and b/env/lib/python2.7/site-packages/werkzeug/contrib/sessions.pyc differ diff --git a/env/lib/python2.7/site-packages/werkzeug/contrib/testtools.pyc b/env/lib/python2.7/site-packages/werkzeug/contrib/testtools.pyc index e843938..af2e452 100644 Binary files a/env/lib/python2.7/site-packages/werkzeug/contrib/testtools.pyc and b/env/lib/python2.7/site-packages/werkzeug/contrib/testtools.pyc differ diff --git a/env/lib/python2.7/site-packages/werkzeug/contrib/wrappers.pyc b/env/lib/python2.7/site-packages/werkzeug/contrib/wrappers.pyc index e969cb7..302a509 100644 Binary files a/env/lib/python2.7/site-packages/werkzeug/contrib/wrappers.pyc and b/env/lib/python2.7/site-packages/werkzeug/contrib/wrappers.pyc differ diff --git a/env/lib/python2.7/site-packages/werkzeug/datastructures.pyc b/env/lib/python2.7/site-packages/werkzeug/datastructures.pyc index 2e771a5..0e522f3 100644 Binary files a/env/lib/python2.7/site-packages/werkzeug/datastructures.pyc and b/env/lib/python2.7/site-packages/werkzeug/datastructures.pyc differ diff --git a/env/lib/python2.7/site-packages/werkzeug/debug/__init__.pyc b/env/lib/python2.7/site-packages/werkzeug/debug/__init__.pyc index 099acad..7ba52cb 100644 Binary files a/env/lib/python2.7/site-packages/werkzeug/debug/__init__.pyc and b/env/lib/python2.7/site-packages/werkzeug/debug/__init__.pyc differ diff --git a/env/lib/python2.7/site-packages/werkzeug/debug/console.pyc b/env/lib/python2.7/site-packages/werkzeug/debug/console.pyc index afad7bd..d1b3dee 100644 Binary files a/env/lib/python2.7/site-packages/werkzeug/debug/console.pyc and b/env/lib/python2.7/site-packages/werkzeug/debug/console.pyc differ diff --git a/env/lib/python2.7/site-packages/werkzeug/debug/repr.pyc b/env/lib/python2.7/site-packages/werkzeug/debug/repr.pyc index cafc0ec..9f17e7f 100644 Binary files a/env/lib/python2.7/site-packages/werkzeug/debug/repr.pyc and b/env/lib/python2.7/site-packages/werkzeug/debug/repr.pyc differ diff --git a/env/lib/python2.7/site-packages/werkzeug/debug/tbtools.pyc b/env/lib/python2.7/site-packages/werkzeug/debug/tbtools.pyc index 17c35ec..d8c8f71 100644 Binary files a/env/lib/python2.7/site-packages/werkzeug/debug/tbtools.pyc and b/env/lib/python2.7/site-packages/werkzeug/debug/tbtools.pyc differ diff --git a/env/lib/python2.7/site-packages/werkzeug/exceptions.pyc b/env/lib/python2.7/site-packages/werkzeug/exceptions.pyc index 7af88fb..b62be8e 100644 Binary files a/env/lib/python2.7/site-packages/werkzeug/exceptions.pyc and b/env/lib/python2.7/site-packages/werkzeug/exceptions.pyc differ diff --git a/env/lib/python2.7/site-packages/werkzeug/formparser.pyc b/env/lib/python2.7/site-packages/werkzeug/formparser.pyc index c815b51..d1dc5d5 100644 Binary files a/env/lib/python2.7/site-packages/werkzeug/formparser.pyc and b/env/lib/python2.7/site-packages/werkzeug/formparser.pyc differ diff --git a/env/lib/python2.7/site-packages/werkzeug/http.pyc b/env/lib/python2.7/site-packages/werkzeug/http.pyc index a946454..7841b03 100644 Binary files a/env/lib/python2.7/site-packages/werkzeug/http.pyc and b/env/lib/python2.7/site-packages/werkzeug/http.pyc differ diff --git a/env/lib/python2.7/site-packages/werkzeug/local.pyc b/env/lib/python2.7/site-packages/werkzeug/local.pyc index 5a2f712..5ce97f0 100644 Binary files a/env/lib/python2.7/site-packages/werkzeug/local.pyc and b/env/lib/python2.7/site-packages/werkzeug/local.pyc differ diff --git a/env/lib/python2.7/site-packages/werkzeug/posixemulation.pyc b/env/lib/python2.7/site-packages/werkzeug/posixemulation.pyc index 9098d84..dbd2d5a 100644 Binary files a/env/lib/python2.7/site-packages/werkzeug/posixemulation.pyc and b/env/lib/python2.7/site-packages/werkzeug/posixemulation.pyc differ diff --git a/env/lib/python2.7/site-packages/werkzeug/routing.py b/env/lib/python2.7/site-packages/werkzeug/routing.py index e1a2120..9cce70a 100644 --- a/env/lib/python2.7/site-packages/werkzeug/routing.py +++ b/env/lib/python2.7/site-packages/werkzeug/routing.py @@ -564,14 +564,35 @@ def __init__(self, string, defaults=None, subdomain=None, methods=None, self._trace = self._converters = self._regex = self._weights = None def empty(self): - """Return an unbound copy of this rule. This can be useful if you - want to reuse an already bound URL for another map.""" + """ + Return an unbound copy of this rule. + + This can be useful if want to reuse an already bound URL for another + map. See ``get_empty_kwargs`` to override what keyword arguments are + provided to the new copy. + """ + return type(self)(self.rule, **self.get_empty_kwargs()) + + def get_empty_kwargs(self): + """ + Provides kwargs for instantiating empty copy with empty() + + Use this method to provide custom keyword arguments to the subclass of + ``Rule`` when calling ``some_rule.empty()``. Helpful when the subclass + has custom keyword arguments that are needed at instantiation. + + Must return a ``dict`` that will be provided as kwargs to the new + instance of ``Rule``, following the initial ``self.rule`` value which + is always provided as the first, required positional argument. + """ defaults = None if self.defaults: defaults = dict(self.defaults) - return type(self)(self.rule, defaults, self.subdomain, self.methods, - self.build_only, self.endpoint, self.strict_slashes, - self.redirect_to, self.alias, self.host) + return dict(defaults=defaults, subdomain=self.subdomain, + methods=self.methods, build_only=self.build_only, + endpoint=self.endpoint, strict_slashes=self.strict_slashes, + redirect_to=self.redirect_to, alias=self.alias, + host=self.host) def get_rules(self, map): yield self @@ -1613,6 +1634,13 @@ def build(self, endpoint, values=None, method=None, force_external=False, >>> urls.build("index", {'q': 'My Searchstring'}) '/?q=My+Searchstring' + When processing those additional values, lists are furthermore + interpreted as multiple values (as per + :py:class:`werkzeug.datastructures.MultiDict`): + + >>> urls.build("index", {'q': ['a', 'b', 'c']}) + '/?q=a&q=b&q=c' + If a rule does not exist when building a `BuildError` exception is raised. @@ -1637,10 +1665,11 @@ def build(self, endpoint, values=None, method=None, force_external=False, """ self.map.update() if values: - if not isinstance(values, MultiDict): - values = MultiDict(values) - valueiter = iteritems(values, multi=True) - values = MultiDict((k, v) for k, v in valueiter if v is not None) + if isinstance(values, MultiDict): + valueiter = iteritems(values, multi=True) + else: + valueiter = iteritems(values) + values = dict((k, v) for k, v in valueiter if v is not None) else: values = {} diff --git a/env/lib/python2.7/site-packages/werkzeug/routing.pyc b/env/lib/python2.7/site-packages/werkzeug/routing.pyc index 93a74e9..75a2a94 100644 Binary files a/env/lib/python2.7/site-packages/werkzeug/routing.pyc and b/env/lib/python2.7/site-packages/werkzeug/routing.pyc differ diff --git a/env/lib/python2.7/site-packages/werkzeug/script.py b/env/lib/python2.7/site-packages/werkzeug/script.py index 11425f5..622943c 100644 --- a/env/lib/python2.7/site-packages/werkzeug/script.py +++ b/env/lib/python2.7/site-packages/werkzeug/script.py @@ -309,7 +309,9 @@ def action(hostname=('h', hostname), port=('p', port), """Start a new development server.""" from werkzeug.serving import run_simple app = app_factory() - run_simple(hostname, port, app, reloader, debugger, evalex, - extra_files, 1, threaded, processes, + run_simple(hostname, port, app, + use_reloader=reloader, use_debugger=debugger, + use_evalex=evalex, extra_files=extra_files, + reloader_interval=1, threaded=threaded, processes=processes, static_files=static_files, ssl_context=ssl_context) return action diff --git a/env/lib/python2.7/site-packages/werkzeug/script.pyc b/env/lib/python2.7/site-packages/werkzeug/script.pyc index e4bebc4..c8aebd5 100644 Binary files a/env/lib/python2.7/site-packages/werkzeug/script.pyc and b/env/lib/python2.7/site-packages/werkzeug/script.pyc differ diff --git a/env/lib/python2.7/site-packages/werkzeug/security.pyc b/env/lib/python2.7/site-packages/werkzeug/security.pyc index a171f4b..d45b360 100644 Binary files a/env/lib/python2.7/site-packages/werkzeug/security.pyc and b/env/lib/python2.7/site-packages/werkzeug/security.pyc differ diff --git a/env/lib/python2.7/site-packages/werkzeug/serving.py b/env/lib/python2.7/site-packages/werkzeug/serving.py index a033fcc..afa35a3 100644 --- a/env/lib/python2.7/site-packages/werkzeug/serving.py +++ b/env/lib/python2.7/site-packages/werkzeug/serving.py @@ -548,7 +548,7 @@ def run_simple(hostname, port, application, use_reloader=False, .. versionadded:: 0.9 Added command-line interface. - .. versionadded:: 1.0 + .. versionadded:: 0.10 Improved the reloader and added support for changing the backend through the `reloader_type` parameter. See :ref:`reloader` for more information. @@ -566,7 +566,8 @@ def run_simple(hostname, port, application, use_reloader=False, :param reloader_interval: the interval for the reloader in seconds. :param reloader_type: the type of reloader to use. The default is auto detection. Valid values are ``'stat'`` and - ``'watchdog'``. + ``'watchdog'``. See :ref:`reloader` for more + information. :param threaded: should the process handle each request in a separate thread? :param processes: if greater than 1 then handle each request in a new process diff --git a/env/lib/python2.7/site-packages/werkzeug/serving.pyc b/env/lib/python2.7/site-packages/werkzeug/serving.pyc index 879fa97..347a83e 100644 Binary files a/env/lib/python2.7/site-packages/werkzeug/serving.pyc and b/env/lib/python2.7/site-packages/werkzeug/serving.pyc differ diff --git a/env/lib/python2.7/site-packages/werkzeug/test.py b/env/lib/python2.7/site-packages/werkzeug/test.py index 9e7d0ce..8912cc3 100644 --- a/env/lib/python2.7/site-packages/werkzeug/test.py +++ b/env/lib/python2.7/site-packages/werkzeug/test.py @@ -864,29 +864,29 @@ def start_response(status, headers, exc_info=None): response[:] = [status, headers] return buffer.append - app_iter = app(environ, start_response) + app_rv = app(environ, start_response) + close_func = getattr(app_rv, 'close', None) + app_iter = iter(app_rv) # when buffering we emit the close call early and convert the # application iterator into a regular list if buffered: - close_func = getattr(app_iter, 'close', None) try: app_iter = list(app_iter) finally: if close_func is not None: close_func() - # otherwise we iterate the application iter until we have - # a response, chain the already received data with the already - # collected data and wrap it in a new `ClosingIterator` if - # we have a close callable. + # otherwise we iterate the application iter until we have a response, chain + # the already received data with the already collected data and wrap it in + # a new `ClosingIterator` if we need to restore a `close` callable from the + # original return value. else: while not response: buffer.append(next(app_iter)) if buffer: - close_func = getattr(app_iter, 'close', None) app_iter = chain(buffer, app_iter) - if close_func is not None: - app_iter = ClosingIterator(app_iter, close_func) + if close_func is not None and app_iter is not app_rv: + app_iter = ClosingIterator(app_iter, close_func) return app_iter, response[0], Headers(response[1]) diff --git a/env/lib/python2.7/site-packages/werkzeug/test.pyc b/env/lib/python2.7/site-packages/werkzeug/test.pyc index 3ae8463..44624ac 100644 Binary files a/env/lib/python2.7/site-packages/werkzeug/test.pyc and b/env/lib/python2.7/site-packages/werkzeug/test.pyc differ diff --git a/env/lib/python2.7/site-packages/werkzeug/testapp.pyc b/env/lib/python2.7/site-packages/werkzeug/testapp.pyc index a06497e..3c17d23 100644 Binary files a/env/lib/python2.7/site-packages/werkzeug/testapp.pyc and b/env/lib/python2.7/site-packages/werkzeug/testapp.pyc differ diff --git a/env/lib/python2.7/site-packages/werkzeug/urls.pyc b/env/lib/python2.7/site-packages/werkzeug/urls.pyc index b1fc419..025fda2 100644 Binary files a/env/lib/python2.7/site-packages/werkzeug/urls.pyc and b/env/lib/python2.7/site-packages/werkzeug/urls.pyc differ diff --git a/env/lib/python2.7/site-packages/werkzeug/useragents.pyc b/env/lib/python2.7/site-packages/werkzeug/useragents.pyc index 588478b..8345547 100644 Binary files a/env/lib/python2.7/site-packages/werkzeug/useragents.pyc and b/env/lib/python2.7/site-packages/werkzeug/useragents.pyc differ diff --git a/env/lib/python2.7/site-packages/werkzeug/utils.pyc b/env/lib/python2.7/site-packages/werkzeug/utils.pyc index 306b9fa..30ef8dc 100644 Binary files a/env/lib/python2.7/site-packages/werkzeug/utils.pyc and b/env/lib/python2.7/site-packages/werkzeug/utils.pyc differ diff --git a/env/lib/python2.7/site-packages/werkzeug/wrappers.pyc b/env/lib/python2.7/site-packages/werkzeug/wrappers.pyc index ebdff3c..6755d80 100644 Binary files a/env/lib/python2.7/site-packages/werkzeug/wrappers.pyc and b/env/lib/python2.7/site-packages/werkzeug/wrappers.pyc differ diff --git a/env/lib/python2.7/site-packages/werkzeug/wsgi.pyc b/env/lib/python2.7/site-packages/werkzeug/wsgi.pyc index 33562d5..4656bda 100644 Binary files a/env/lib/python2.7/site-packages/werkzeug/wsgi.pyc and b/env/lib/python2.7/site-packages/werkzeug/wsgi.pyc differ diff --git a/env/man/man1/nosetests.1 b/env/man/man1/nosetests.1 new file mode 100644 index 0000000..5772845 --- /dev/null +++ b/env/man/man1/nosetests.1 @@ -0,0 +1,581 @@ +.\" Man page generated from reStructuredText. +. +.TH "NOSETESTS" "1" "April 04, 2015" "1.3" "nose" +.SH NAME +nosetests \- Nicer testing for Python +. +.nr rst2man-indent-level 0 +. +.de1 rstReportMargin +\\$1 \\n[an-margin] +level \\n[rst2man-indent-level] +level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] +- +\\n[rst2man-indent0] +\\n[rst2man-indent1] +\\n[rst2man-indent2] +.. +.de1 INDENT +.\" .rstReportMargin pre: +. RS \\$1 +. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] +. nr rst2man-indent-level +1 +.\" .rstReportMargin post: +.. +.de UNINDENT +. RE +.\" indent \\n[an-margin] +.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] +.nr rst2man-indent-level -1 +.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] +.in \\n[rst2man-indent\\n[rst2man-indent-level]]u +.. +.SH NICER TESTING FOR PYTHON +.SS SYNOPSIS +.INDENT 0.0 +.INDENT 3.5 +nosetests [options] [names] +.UNINDENT +.UNINDENT +.SS DESCRIPTION +.sp +nose collects tests automatically from python source files, +directories and packages found in its working directory (which +defaults to the current working directory). Any python source file, +directory or package that matches the testMatch regular expression +(by default: \fI(?:^|[b_.\-])[Tt]est)\fP will be collected as a test (or +source for collection of tests). In addition, all other packages +found in the working directory will be examined for python source files +or directories that match testMatch. Package discovery descends all +the way down the tree, so package.tests and package.sub.tests and +package.sub.sub2.tests will all be collected. +.sp +Within a test directory or package, any python source file matching +testMatch will be examined for test cases. Within a test module, +functions and classes whose names match testMatch and TestCase +subclasses with any name will be loaded and executed as tests. Tests +may use the assert keyword or raise AssertionErrors to indicate test +failure. TestCase subclasses may do the same or use the various +TestCase methods available. +.sp +\fBIt is important to note that the default behavior of nose is to +not include tests from files which are executable.\fP To include +tests from such files, remove their executable bit or use +the \-\-exe flag (see \(aqOptions\(aq section below). +.SS Selecting Tests +.sp +To specify which tests to run, pass test names on the command line: +.INDENT 0.0 +.INDENT 3.5 +.sp +.nf +.ft C +nosetests only_test_this.py +.ft P +.fi +.UNINDENT +.UNINDENT +.sp +Test names specified may be file or module names, and may optionally +indicate the test case to run by separating the module or file name +from the test case name with a colon. Filenames may be relative or +absolute. Examples: +.INDENT 0.0 +.INDENT 3.5 +.sp +.nf +.ft C +nosetests test.module +nosetests another.test:TestCase.test_method +nosetests a.test:TestCase +nosetests /path/to/test/file.py:test_function +.ft P +.fi +.UNINDENT +.UNINDENT +.sp +You may also change the working directory where nose looks for tests +by using the \-w switch: +.INDENT 0.0 +.INDENT 3.5 +.sp +.nf +.ft C +nosetests \-w /path/to/tests +.ft P +.fi +.UNINDENT +.UNINDENT +.sp +Note, however, that support for multiple \-w arguments is now deprecated +and will be removed in a future release. As of nose 0.10, you can get +the same behavior by specifying the target directories \fIwithout\fP +the \-w switch: +.INDENT 0.0 +.INDENT 3.5 +.sp +.nf +.ft C +nosetests /path/to/tests /another/path/to/tests +.ft P +.fi +.UNINDENT +.UNINDENT +.sp +Further customization of test selection and loading is possible +through the use of plugins. +.sp +Test result output is identical to that of unittest, except for +the additional features (error classes, and plugin\-supplied +features such as output capture and assert introspection) detailed +in the options below. +.SS Configuration +.sp +In addition to passing command\-line options, you may also put +configuration options in your project\(aqs \fIsetup.cfg\fP file, or a .noserc +or nose.cfg file in your home directory. In any of these standard +ini\-style config files, you put your nosetests configuration in a +\fB[nosetests]\fP section. Options are the same as on the command line, +with the \-\- prefix removed. For options that are simple switches, you +must supply a value: +.INDENT 0.0 +.INDENT 3.5 +.sp +.nf +.ft C +[nosetests] +verbosity=3 +with\-doctest=1 +.ft P +.fi +.UNINDENT +.UNINDENT +.sp +All configuration files that are found will be loaded and their +options combined. You can override the standard config file loading +with the \fB\-c\fP option. +.SS Using Plugins +.sp +There are numerous nose plugins available via easy_install and +elsewhere. To use a plugin, just install it. The plugin will add +command line options to nosetests. To verify that the plugin is installed, +run: +.INDENT 0.0 +.INDENT 3.5 +.sp +.nf +.ft C +nosetests \-\-plugins +.ft P +.fi +.UNINDENT +.UNINDENT +.sp +You can add \-v or \-vv to that command to show more information +about each plugin. +.sp +If you are running nose.main() or nose.run() from a script, you +can specify a list of plugins to use by passing a list of plugins +with the plugins keyword argument. +.SS 0.9 plugins +.sp +nose 1.0 can use SOME plugins that were written for nose 0.9. The +default plugin manager inserts a compatibility wrapper around 0.9 +plugins that adapts the changed plugin api calls. However, plugins +that access nose internals are likely to fail, especially if they +attempt to access test case or test suite classes. For example, +plugins that try to determine if a test passed to startTest is an +individual test or a suite will fail, partly because suites are no +longer passed to startTest and partly because it\(aqs likely that the +plugin is trying to find out if the test is an instance of a class +that no longer exists. +.SS 0.10 and 0.11 plugins +.sp +All plugins written for nose 0.10 and 0.11 should work with nose 1.0. +.SS Options +.INDENT 0.0 +.TP +.B \-V, \-\-version +Output nose version and exit +.UNINDENT +.INDENT 0.0 +.TP +.B \-p, \-\-plugins +Output list of available plugins and exit. Combine with higher verbosity for greater detail +.UNINDENT +.INDENT 0.0 +.TP +.B \-v=DEFAULT, \-\-verbose=DEFAULT +Be more verbose. [NOSE_VERBOSE] +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-verbosity=VERBOSITY +Set verbosity; \-\-verbosity=2 is the same as \-v +.UNINDENT +.INDENT 0.0 +.TP +.B \-q=DEFAULT, \-\-quiet=DEFAULT +Be less verbose +.UNINDENT +.INDENT 0.0 +.TP +.B \-c=FILES, \-\-config=FILES +Load configuration from config file(s). May be specified multiple times; in that case, all config files will be loaded and combined +.UNINDENT +.INDENT 0.0 +.TP +.B \-w=WHERE, \-\-where=WHERE +Look for tests in this directory. May be specified multiple times. The first directory passed will be used as the working directory, in place of the current working directory, which is the default. Others will be added to the list of tests to execute. [NOSE_WHERE] +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-py3where=PY3WHERE +Look for tests in this directory under Python 3.x. Functions the same as \(aqwhere\(aq, but only applies if running under Python 3.x or above. Note that, if present under 3.x, this option completely replaces any directories specified with \(aqwhere\(aq, so the \(aqwhere\(aq option becomes ineffective. [NOSE_PY3WHERE] +.UNINDENT +.INDENT 0.0 +.TP +.B \-m=REGEX, \-\-match=REGEX, \-\-testmatch=REGEX +Files, directories, function names, and class names that match this regular expression are considered tests. Default: (?:^|[b_./\-])[Tt]est [NOSE_TESTMATCH] +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-tests=NAMES +Run these tests (comma\-separated list). This argument is useful mainly from configuration files; on the command line, just pass the tests to run as additional arguments with no switch. +.UNINDENT +.INDENT 0.0 +.TP +.B \-l=DEFAULT, \-\-debug=DEFAULT +Activate debug logging for one or more systems. Available debug loggers: nose, nose.importer, nose.inspector, nose.plugins, nose.result and nose.selector. Separate multiple names with a comma. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-debug\-log=FILE +Log debug messages to this file (default: sys.stderr) +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-logging\-config=FILE, \-\-log\-config=FILE +Load logging config from this file \-\- bypasses all other logging config settings. +.UNINDENT +.INDENT 0.0 +.TP +.B \-I=REGEX, \-\-ignore\-files=REGEX +Completely ignore any file that matches this regular expression. Takes precedence over any other settings or plugins. Specifying this option will replace the default setting. Specify this option multiple times to add more regular expressions [NOSE_IGNORE_FILES] +.UNINDENT +.INDENT 0.0 +.TP +.B \-e=REGEX, \-\-exclude=REGEX +Don\(aqt run tests that match regular expression [NOSE_EXCLUDE] +.UNINDENT +.INDENT 0.0 +.TP +.B \-i=REGEX, \-\-include=REGEX +This regular expression will be applied to files, directories, function names, and class names for a chance to include additional tests that do not match TESTMATCH. Specify this option multiple times to add more regular expressions [NOSE_INCLUDE] +.UNINDENT +.INDENT 0.0 +.TP +.B \-x, \-\-stop +Stop running tests after the first error or failure +.UNINDENT +.INDENT 0.0 +.TP +.B \-P, \-\-no\-path\-adjustment +Don\(aqt make any changes to sys.path when loading tests [NOSE_NOPATH] +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-exe +Look for tests in python modules that are executable. Normal behavior is to exclude executable modules, since they may not be import\-safe [NOSE_INCLUDE_EXE] +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-noexe +DO NOT look for tests in python modules that are executable. (The default on the windows platform is to do so.) +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-traverse\-namespace +Traverse through all path entries of a namespace package +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-first\-package\-wins, \-\-first\-pkg\-wins, \-\-1st\-pkg\-wins +nose\(aqs importer will normally evict a package from sys.modules if it sees a package with the same name in a different location. Set this option to disable that behavior. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-no\-byte\-compile +Prevent nose from byte\-compiling the source into .pyc files while nose is scanning for and running tests. +.UNINDENT +.INDENT 0.0 +.TP +.B \-a=ATTR, \-\-attr=ATTR +Run only tests that have attributes specified by ATTR [NOSE_ATTR] +.UNINDENT +.INDENT 0.0 +.TP +.B \-A=EXPR, \-\-eval\-attr=EXPR +Run only tests for whose attributes the Python expression EXPR evaluates to True [NOSE_EVAL_ATTR] +.UNINDENT +.INDENT 0.0 +.TP +.B \-s, \-\-nocapture +Don\(aqt capture stdout (any stdout output will be printed immediately) [NOSE_NOCAPTURE] +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-nologcapture +Disable logging capture plugin. Logging configuration will be left intact. [NOSE_NOLOGCAPTURE] +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-logging\-format=FORMAT +Specify custom format to print statements. Uses the same format as used by standard logging handlers. [NOSE_LOGFORMAT] +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-logging\-datefmt=FORMAT +Specify custom date/time format to print statements. Uses the same format as used by standard logging handlers. [NOSE_LOGDATEFMT] +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-logging\-filter=FILTER +Specify which statements to filter in/out. By default, everything is captured. If the output is too verbose, +use this option to filter out needless output. +Example: filter=foo will capture statements issued ONLY to + foo or foo.what.ever.sub but not foobar or other logger. +Specify multiple loggers with comma: filter=foo,bar,baz. +If any logger name is prefixed with a minus, eg filter=\-foo, +it will be excluded rather than included. Default: exclude logging messages from nose itself (\-nose). [NOSE_LOGFILTER] +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-logging\-clear\-handlers +Clear all other logging handlers +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-logging\-level=DEFAULT +Set the log level to capture +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-with\-coverage +Enable plugin Coverage: +Activate a coverage report using Ned Batchelder\(aqs coverage module. + [NOSE_WITH_COVERAGE] +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-cover\-package=PACKAGE +Restrict coverage output to selected packages [NOSE_COVER_PACKAGE] +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-cover\-erase +Erase previously collected coverage statistics before run +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-cover\-tests +Include test modules in coverage report [NOSE_COVER_TESTS] +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-cover\-min\-percentage=DEFAULT +Minimum percentage of coverage for tests to pass [NOSE_COVER_MIN_PERCENTAGE] +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-cover\-inclusive +Include all python files under working directory in coverage report. Useful for discovering holes in test coverage if not all files are imported by the test suite. [NOSE_COVER_INCLUSIVE] +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-cover\-html +Produce HTML coverage information +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-cover\-html\-dir=DIR +Produce HTML coverage information in dir +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-cover\-branches +Include branch coverage in coverage report [NOSE_COVER_BRANCHES] +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-cover\-xml +Produce XML coverage information +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-cover\-xml\-file=FILE +Produce XML coverage information in file +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-pdb +Drop into debugger on failures or errors +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-pdb\-failures +Drop into debugger on failures +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-pdb\-errors +Drop into debugger on errors +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-no\-deprecated +Disable special handling of DeprecatedTest exceptions. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-with\-doctest +Enable plugin Doctest: +Activate doctest plugin to find and run doctests in non\-test modules. + [NOSE_WITH_DOCTEST] +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-doctest\-tests +Also look for doctests in test modules. Note that classes, methods and functions should have either doctests or non\-doctest tests, not both. [NOSE_DOCTEST_TESTS] +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-doctest\-extension=EXT +Also look for doctests in files with this extension [NOSE_DOCTEST_EXTENSION] +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-doctest\-result\-variable=VAR +Change the variable name set to the result of the last interpreter command from the default \(aq_\(aq. Can be used to avoid conflicts with the _() function used for text translation. [NOSE_DOCTEST_RESULT_VAR] +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-doctest\-fixtures=SUFFIX +Find fixtures for a doctest file in module with this name appended to the base name of the doctest file +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-doctest\-options=OPTIONS +Specify options to pass to doctest. Eg. \(aq+ELLIPSIS,+NORMALIZE_WHITESPACE\(aq +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-with\-isolation +Enable plugin IsolationPlugin: +Activate the isolation plugin to isolate changes to external +modules to a single test module or package. The isolation plugin +resets the contents of sys.modules after each test module or +package runs to its state before the test. PLEASE NOTE that this +plugin should not be used with the coverage plugin, or in any other case +where module reloading may produce undesirable side\-effects. + [NOSE_WITH_ISOLATION] +.UNINDENT +.INDENT 0.0 +.TP +.B \-d, \-\-detailed\-errors, \-\-failure\-detail +Add detail to error output by attempting to evaluate failed asserts [NOSE_DETAILED_ERRORS] +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-with\-profile +Enable plugin Profile: +Use this plugin to run tests using the hotshot profiler. + [NOSE_WITH_PROFILE] +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-profile\-sort=SORT +Set sort order for profiler output +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-profile\-stats\-file=FILE +Profiler stats file; default is a new temp file on each run +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-profile\-restrict=RESTRICT +Restrict profiler output. See help for pstats.Stats for details +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-no\-skip +Disable special handling of SkipTest exceptions. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-with\-id +Enable plugin TestId: +Activate to add a test id (like #1) to each test name output. Activate +with \-\-failed to rerun failing tests only. + [NOSE_WITH_ID] +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-id\-file=FILE +Store test ids found in test runs in this file. Default is the file .noseids in the working directory. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-failed +Run the tests that failed in the last test run. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-processes=NUM +Spread test run among this many processes. Set a number equal to the number of processors or cores in your machine for best results. Pass a negative number to have the number of processes automatically set to the number of cores. Passing 0 means to disable parallel testing. Default is 0 unless NOSE_PROCESSES is set. [NOSE_PROCESSES] +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-process\-timeout=SECONDS +Set timeout for return of results from each test runner process. Default is 10. [NOSE_PROCESS_TIMEOUT] +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-process\-restartworker +If set, will restart each worker process once their tests are done, this helps control memory leaks from killing the system. [NOSE_PROCESS_RESTARTWORKER] +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-with\-xunit +Enable plugin Xunit: This plugin provides test results in the standard XUnit XML format. [NOSE_WITH_XUNIT] +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-xunit\-file=FILE +Path to xml file to store the xunit report in. Default is nosetests.xml in the working directory [NOSE_XUNIT_FILE] +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-xunit\-testsuite\-name=PACKAGE +Name of the testsuite in the xunit xml, generated by plugin. Default test suite name is nosetests. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-all\-modules +Enable plugin AllModules: Collect tests from all python modules. + [NOSE_ALL_MODULES] +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-collect\-only +Enable collect\-only: +Collect and output test names only, don\(aqt run any tests. + [COLLECT_ONLY] +.UNINDENT +.SH AUTHOR +Nose developers +.SH COPYRIGHT +2009, Jason Pellerin +.\" Generated by docutils manpage writer. +. diff --git a/env/pip-selfcheck.json b/env/pip-selfcheck.json index c6f2f50..34a9600 100644 --- a/env/pip-selfcheck.json +++ b/env/pip-selfcheck.json @@ -1 +1 @@ -{"last_check":"2015-03-08T16:22:26Z","pypi_version":"6.0.8"} \ No newline at end of file +{"last_check":"2015-06-26T22:41:39Z","pypi_version":"7.0.3"} \ No newline at end of file diff --git a/helper.py b/helper.py index 645cead..ada5b7b 100644 --- a/helper.py +++ b/helper.py @@ -23,6 +23,8 @@ def get_user_info(user_email, connection): 'followers': list(t[0] for t in followee_follower), 'subscriptions': list(t[0] for t in subs)} + + def get_forum_info(forum_sname, connection, related=[]): cursor = connection.cursor() if cursor.execute("select * from forums where shortname = '{}'".format(forum_sname)) == 0: @@ -34,6 +36,9 @@ def get_forum_info(forum_sname, connection, related=[]): 'short_name': forum[2], 'user': forum[4] if 'user' not in related else get_user_info(forum[4], connection)} + + + def get_thread_info(thread_id, connection, related=[]): cursor = connection.cursor() if cursor.execute("select * from threads where id = {}".format(thread_id)) == 0: diff --git a/helper.pyc b/helper.pyc index fdfa106..e06596e 100644 Binary files a/helper.pyc and b/helper.pyc differ diff --git a/helper2_0.py b/helper2_0.py new file mode 100644 index 0000000..1b59469 --- /dev/null +++ b/helper2_0.py @@ -0,0 +1,98 @@ +import MySQLdb + +class DoesNotExist(Exception): + pass + +class DB(): + def __init__(self): + self.connection = MySQLdb.connect(host="localhost", user="root", db="db_api") + #self.get_cursor().execute("SET FOREIGN_KEY_CHECKS = 0;"); + def get_cursor(self, modif=None): + self.connection.ping(True) + return self.connection.cursor(modif) + def commit(self): + self.connection.commit() + + + +def get_forum_info(forum_sname, db, related=[]): + cursor = db.get_cursor() + if cursor.execute("select * from forums where shortname = %s",(forum_sname,)) == 0: + raise DoesNotExist + forum = cursor.fetchone() + cursor.close() + return {'id': forum[0], + 'name': forum[1], + 'short_name': forum[2], + 'user': forum[4] if 'user' not in related else get_user_info(forum[4], db)} + + +def get_user_info(user_email, db): + cursor = db.get_cursor() + if cursor.execute("select * from users where email = %s",(user_email,)) == 0: + raise DoesNotExist + user = cursor.fetchone() + cursor.execute("select followee from follows where follower = %s",(user[3],)) + follower_followee = cursor.fetchall() + cursor.execute("select follower from follows where followee = %s",(user[3],)) + followee_follower = cursor.fetchall() + cursor.execute("select thread from subscribes where user = %s",(user[3],)) + subs = cursor.fetchall() + cursor.close() + return {'id':user[0], + 'name': user[1], + 'username': user[2], + 'email': user[3], + 'about':user[4], + 'isAnonymous':bool(user[5]), + 'following': list(t[0] for t in follower_followee), + 'followers': list(t[0] for t in followee_follower), + 'subscriptions': list(t[0] for t in subs)} + +def get_thread_info(thread_id, db, related=[]): + cursor = db.get_cursor() + if cursor.execute("select * from threads where id = %s", (thread_id,)) == 0: + raise DoesNotExist + thread = cursor.fetchone() + cursor.close() + return { "date": thread[1].strftime("%Y-%m-%d %H:%M:%S"), + "dislikes": thread[10], + "forum": thread[2] if 'forum' not in related else get_forum_info(thread[2], db), + "id": thread[0], + "isClosed": bool(thread[3]), + "isDeleted": bool(thread[4]), + "likes":thread[9], + "message": thread[5], + "points": thread[11], + "posts":thread[12], + "slug": thread[6], + "title": thread[7], + "user": thread[8] if 'user' not in related else get_user_info(thread[8], db) + } + +def get_post_info(post_id, db, related=[]): + cursor = db.get_cursor() + if cursor.execute("select * from posts where id = {}".format(post_id)) == 0: + raise DoesNotExist + post = cursor.fetchone() + cursor.close() + return { "date": str(post[1]), + "dislikes": post[13], + "forum": post[2] if 'forum' not in related else get_forum_info(post[2], db), + "id": post[0], + "isApproved": bool(post[4]), + "isDeleted": bool(post[5]), + "isEdited": bool(post[6]), + "isHighlighted": bool(post[3]), + "isSpam": bool(post[7]), + "likes": post[12], + "message": post[8], + "parent": post[9], + "points": post[14], + "thread": post[10] if 'thread' not in related else get_thread_info(post[10],db), + "user": post[11] if 'user' not in related else get_user_info(post[11],db) + } + +def right_index(index): + len = 1000000000 + index + return str(len)[1:] diff --git a/helper2_0.pyc b/helper2_0.pyc new file mode 100644 index 0000000..e000f0e Binary files /dev/null and b/helper2_0.pyc differ diff --git a/migrations/migration_db_v_0_2 b/migrations/migration_db_v_0_2 new file mode 100644 index 0000000..12bf555 --- /dev/null +++ b/migrations/migration_db_v_0_2 @@ -0,0 +1,117 @@ +create table users ( + id int auto_increment unique, + name varchar(60), + username varchar(30), + email varchar(30) primary key, + about text, + isAnonymous boolean +); + + + +create table forums ( + id int auto_increment unique, + name varchar(60) not null unique, + shortname varchar(60) primary key, + date timestamp DEFAULT current_timestamp, + user varchar(30) not null, + foreign key (user) references users(email) on update cascade on delete cascade +); + + + + +create table follows ( + id int primary key auto_increment, + followee varchar(30) not null, + follower varchar(30) not null, + unique (followee, follower), + foreign key (followee) references users(email) on update cascade on delete cascade, + foreign key (follower) references users(email) on update cascade on delete cascade +); + +create table threads ( + id int primary key auto_increment, + date timestamp DEFAULT current_timestamp, + forum varchar(60) not null, + isClosed boolean, + isDeleted boolean, + message text not null, + slug varchar(60) not null, + title varchar(60) not null, + user varchar(30) not null, + likes int default 0, + dislikes int default 0, + points int default 0, + posts int default 0, + foreign key (user) references users(email) on update cascade on delete cascade, + foreign key (forum) references forums(shortname) on update cascade on delete cascade +); + + + + +create table subscribes ( + id int primary key auto_increment, + thread int not null, + user varchar(30) not null, + unique (thread, user), + foreign key (user) references users(email) on update cascade on delete cascade, + foreign key (thread) references threads(id) on update cascade on delete cascade +); + +create table posts ( + id int primary key auto_increment, + date timestamp DEFAULT current_timestamp, + forum varchar(60) not null, + isHighlighted boolean default false, + isApproved boolean default false, + isDeleted boolean default false, + isEdited boolean default false, + isSpam boolean default false, + message text not null, + parent int default null, + thread int not null, + user varchar(30) not null, + likes int default 0, + dislikes int default 0, + points int default 0, + mpath varchar(255) default null, + foreign key (user) references users(email) on update cascade on delete cascade, + foreign key (forum) references forums(shortname) on update cascade on delete cascade, + foreign key (thread) references threads(id) on update cascade on delete cascade +); + + + + +INDEXES + +ALTER TABLE follows ADD INDEX (follower,followee) +ALTER TABLE follows ADD INDEX (followee,follower) + +ALTER TABLE subscribes ADD INDEX (user, thread) + +ALTER TABLE threads ADD INDEX (forum, date) + +ALTER TABLE threads ADD INDEX (user, date) + +ALTER TABLE posts ADD INDEX (forum, date) + +ALTER TABLE posts ADD INDEX (forum, user) + +ALTER TABLE posts ADD INDEX (thread, date) + +ALTER TABLE users ADD INDEX (name,email) + + + + + + + + + + + +