소스 검색

adds Python Klein framework using default Twisted Web server

Keith Newman 10 년 전
부모
커밋
8b03c94bac

+ 38 - 0
frameworks/Python/klein/README.md

@@ -0,0 +1,38 @@
+# Twisted Klein Benchmark Test
+
+This is the Klein portion of a [benchmarking tests suite](../../) 
+comparing a variety of web development platforms.
+
+The information below is specific to Klein. For further guidance, 
+review the [documentation](http://frameworkbenchmarks.readthedocs.org/en/latest/). 
+Also note that there is additional information provided in 
+the [Python README](../).
+
+## Description
+
+Klein framework (https://github.com/twisted/klein)
+
+## Infrastructure Software
+
+### Server
+
+* gunicorn+meinheld on CPython
+* Tornado on PyPy
+
+## Test Paths & Sources
+
+All of the test implementations are located within a single file ([app.py](app.py)).
+
+* [JSON Serialization](app.py): "/json"
+* [Single Database Query](app.py): "/db"
+* [Multiple Database Queries](app.py): "/queries?queries=5"
+* [Fortunes](app.py): "/fortune"
+* [Database Updates](app.py): "/updates?queries=5"
+* [Plaintext](app.py): "/plaintext"
+
+## Get Help
+
+### Resources
+
+* [Klein Source Code](https://github.com/twisted/klein)
+

+ 123 - 0
frameworks/Python/klein/app.py

@@ -0,0 +1,123 @@
+# -*- coding: utf-8 -*-
+
+import os
+import sys
+import json
+
+import bleach
+
+from random import randint
+from functools import partial
+from operator import attrgetter
+
+from klein import Klein, run, route
+
+from jinja2 import Environment, PackageLoader
+
+from sqlalchemy.ext.declarative import declarative_base
+from sqlalchemy import create_engine, Column
+from sqlalchemy.types import String, Integer, Unicode
+from sqlalchemy.orm import sessionmaker
+
+if sys.version_info[0] == 3:
+    xrange = range
+
+DBDRIVER = 'mysql'
+DBHOSTNAME = os.environ.get('DBHOST', 'localhost')
+DATABASE_URI = '%s://benchmarkdbuser:benchmarkdbpass@%s:3306/hello_world?charset=utf8' % (DBDRIVER, DBHOSTNAME)
+
+Base = declarative_base()
+db_engine = create_engine(DATABASE_URI)
+Session = sessionmaker(bind=db_engine)
+db_session = Session()
+
+env = Environment(loader=PackageLoader("app", "templates"))
+
+app = Klein()
+
+class Fortune(Base):
+    __tablename__ = "Fortune"
+    id = Column(Integer, primary_key=True)
+    message = Column(String)
+
+    def serialize(self):
+        return {
+            'id': self.id,
+            'randomNumber': self.randomNumber,
+        }
+
+class World(Base):
+    __tablename__ = "World"
+    id = Column(Integer, primary_key=True)
+    randomNumber = Column(Integer)
+    def serialize(self):
+        return {
+            'id': self.id,
+            'randomNumber': self.randomNumber,
+        }
+
+def getQueryNum(queryString):
+    try:
+        num_queries = int(queryString)
+        if num_queries < 1:
+            return 1
+        if num_queries > 500:
+            return 500
+        return num_queries
+    except ValueError:
+         return 1
+
[email protected]("/plaintext")
+def plaintext(request):
+	request.setHeader("Content-Type", "text/plain; charset=UTF-8")
+	return "Hello, World!"
+
[email protected]("/json")
+def jsonHandler(request):
+	request.setHeader("Content-Type", "application/json; charset=UTF-8")
+	return json.dumps({"message": "Hello, World!"})
+
[email protected]("/db")
+def db(request):
+	request.setHeader("Content-Type", "application/json; charset=UTF-8")	
+	wid = randint(1, 10000)
+	world = db_session.query(World).get(wid).serialize() 
+	return json.dumps(world)
+
[email protected]("/queries")
+def queries(request):
+	request.setHeader("Content-Type", "application/json; charset=UTF-8")	
+	num_queries = getQueryNum(request.args.get("queries")[0])
+	rp = partial(randint, 1, 10000)
+	get = db_session.query(World).get
+	worlds = [get(rp()).serialize() for _ in xrange(num_queries)]
+	return json.dumps(worlds)
+
[email protected]("/updates")
+def updates(request):
+	request.setHeader("Content-Type", "application/json; charset=UTF-8")
+	num_queries = getQueryNum(request.args.get("queries")[0])
+	worlds = []
+	rp = partial(randint, 1, 10000)
+	ids = [rp() for _ in xrange(num_queries)]
+	ids.sort()
+	for id in ids:
+		world = db_session.query(World).get(id)
+		world.randomNumber = rp()
+		worlds.append(world.serialize())
+	db_session.commit()
+	return json.dumps(worlds)
+
[email protected]("/fortune")
+def fortune(request):
+	request.setHeader("Content-Type", "text/html; charset=UTF-8")
+	fortunes = db_session.query(Fortune).all()
+	fortunes.append(Fortune(id=0, message="Additional fortune added at request time."))
+	fortunes.sort(key=attrgetter("message"))
+	for f in fortunes:
+		f.message = bleach.clean(f.message)
+	template = env.get_template("fortunes.html")
+	return template.render(fortunes=fortunes)
+
+if __name__ == "__main__":
+    app.run("localhost", 8080)

+ 27 - 0
frameworks/Python/klein/benchmark_config.json

@@ -0,0 +1,27 @@
+{
+  "framework": "klein",
+  "tests": [{
+    "default": {
+      "setup_file": "setup",
+      "json_url": "/json",
+      "db_url": "/db",
+      "query_url": "/queries?queries=",
+      "fortune_url": "/fortune",
+      "update_url": "/updates?queries=",
+      "plaintext_url": "/plaintext",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Micro",
+      "database": "MySQL",
+      "framework": "Twisted Klein",
+      "language": "Python",
+      "orm": "Full",
+      "platform": "Twisted Web",
+      "webserver": "None",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "Twisted Klein",
+      "notes": "CPython 2.7"
+    }
+  }]
+}

+ 25 - 0
frameworks/Python/klein/gunicorn_conf.py

@@ -0,0 +1,25 @@
+import multiprocessing
+import os
+import sys
+
+_is_pypy = hasattr(sys, 'pypy_version_info')
+_is_travis = os.environ.get('TRAVIS') == 'true'
+
+workers = multiprocessing.cpu_count() * 3
+if _is_travis:
+    workers = 2
+
+bind = "0.0.0.0:8080"
+keepalive = 120
+errorlog = '-'
+pidfile = 'gunicorn.pid'
+
+if _is_pypy:
+    worker_class = "tornado"
+else:
+    worker_class = "meinheld.gmeinheld.MeinheldWorker"
+
+    def post_fork(server, worker):
+        # Disalbe access log
+        import meinheld.server
+        meinheld.server.set_access_logger(None)

+ 19 - 0
frameworks/Python/klein/install.sh

@@ -0,0 +1,19 @@
+#!/bin/bash
+
+export PY2_ROOT=$IROOT/py2
+export PY2=$PY2_ROOT/bin/python
+export PY2_PIP=$PY2_ROOT/bin/pip
+
+export PY3_ROOT=$IROOT/py3
+export PY3=$PY3_ROOT/bin/python3
+export PY3_PIP=$PY3_ROOT/bin/pip3
+
+mkdir -p $IROOT/.pip_cache
+export PIP_DOWNLOAD_CACHE=$IROOT/.pip_cache
+
+fw_depends python2 python3
+
+$PY2_PIP install --install-option="--prefix=${PY2_ROOT}" -r $TROOT/requirements.txt
+
+$PY3_PIP install --install-option="--prefix=${PY3_ROOT}" -r $TROOT/requirements.txt
+

+ 5 - 0
frameworks/Python/klein/requirements.txt

@@ -0,0 +1,5 @@
+klein==15.0.0
+
+bleach==1.4.1
+SQLAlchemy==0.9.9
+jinja2==2.7.3

+ 6 - 0
frameworks/Python/klein/setup.sh

@@ -0,0 +1,6 @@
+#!/bin/bash
+
+export PY2_ROOT=$IROOT/py2
+export PY2=$PY2_ROOT/bin/python
+
+$PY2 app.py &

+ 20 - 0
frameworks/Python/klein/templates/fortunes.html

@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>Fortunes</title>
+    </head>
+    <body>
+        <table>
+            <tr>
+                <th>id</th>
+                <th>message</th>
+            </tr>
+            {%for fortune in fortunes %}
+                <tr>
+                    <td>{{ fortune.id }}</td>
+                    <td>{{ fortune.message }}</td>
+                </tr>
+            {% endfor %}
+        </table>
+    </body>
+</html>