Browse Source

Merge pull request #1424 from TechEmpower/python-cherrypy

Python: Add cherrypy
Hamilton Turner 10 years ago
parent
commit
2dffba3945

+ 1 - 0
.travis.yml

@@ -116,6 +116,7 @@ env:
     - "TESTDIR=PHP/phreeze"
     - "TESTDIR=Python/API-Hour"
     - "TESTDIR=Python/bottle"
+    - "TESTDIR=Python/cherrypy"
     - "TESTDIR=Python/django"
     - "TESTDIR=Python/falcon"
     - "TESTDIR=Python/flask"

+ 40 - 0
frameworks/Python/cherrypy/README.md

@@ -0,0 +1,40 @@
+# CherryPy Benchmark Test 
+
+Single file test, [app.py](app.py)
+
+## Description
+
+CherryPy framework (http://cherrypy.org)
+
+### Database
+
+MySQL
+
+### Server
+
+* CherryPy
+
+## Test URLs
+### JSON Encoding
+
+http://localhost:8080/json
+
+### Plaintext
+
+http://localhost:8080/plaintext
+
+### DB
+
+http://localhost:8080/db
+
+### Query
+
+http://localhost:8080/queries?queries=2
+
+### Update
+
+http://localhost:8080/updates?queries=2
+
+### Fortune
+
+http://localhost:8080/fortune

+ 128 - 0
frameworks/Python/cherrypy/app.py

@@ -0,0 +1,128 @@
+import os
+import sys
+from functools import partial
+from operator import attrgetter
+from random import randint
+import json
+import bleach
+
+import cherrypy
+from sqlalchemy.ext.declarative import declarative_base
+from sqlalchemy import Column
+from sqlalchemy.types import String, Integer
+
+Base = declarative_base()
+
+if sys.version_info[0] == 3:
+    xrange = range
+
+def getQueryNum(queryString):
+    try:
+        int(queryString)
+        return int(queryString)
+    except ValueError:
+        return 1
+
+class Fortune(Base):
+    __tablename__ = "fortune"
+
+    id = Column(Integer, primary_key = True)
+    message = Column(String)
+
+    def serialize(self):
+        return {
+            'id' : self.id,
+            'message' : self.message
+        }
+
+class World(Base):
+    __tablename__ = "world"
+
+    id = Column(Integer, primary_key = True)
+    randomNumber = Column(Integer)
+
+    def serialize(self):
+        return {
+            'id' : self.id,
+            'randomNumber' : self.randomNumber
+        }
+
+class CherryPyBenchmark(object):
+
+    @cherrypy.expose
+    @cherrypy.tools.json_out()
+    def json(self):
+        cherrypy.response.headers["Content-Type"] = "application/json"
+        json_message = {"message" : "Hello, world!"}
+        return json_message
+
+
+    @cherrypy.expose
+    def plaintext(self):
+        return "Hello, world!"
+
+    @cherrypy.expose
+    @cherrypy.tools.json_out()
+    def db(self):
+        cherrypy.response.headers["Content-Type"] = "application/json"
+        wid = randint(1, 10000)
+        world = cherrypy.request.db.query(World).get(wid).serialize()
+        return world
+
+    @cherrypy.expose
+    @cherrypy.tools.json_out()
+    def queries(self, queries=1):
+        num_queries = getQueryNum(queries)
+        if num_queries < 1:
+            num_queries = 1
+        if num_queries > 500:
+            num_queries = 500
+
+        rp = partial(randint, 1, 10000)
+        get = cherrypy.request.db.query(World).get
+        worlds = [get(rp()).serialize() for _ in xrange(num_queries)]
+        return worlds
+
+    @cherrypy.expose
+    @cherrypy.tools.json_out()
+    def updates(self, queries=1):
+        cherrypy.response.headers["Content-Type"] = "application/json"
+        num_queries = getQueryNum(queries)
+        if num_queries < 1:
+            num_queries = 1
+        if num_queries > 500:
+            num_queries = 500
+
+        worlds = []
+        rp = partial(randint, 1, 10000)
+        ids = [rp() for _ in xrange(num_queries)]
+        ids.sort() # To avoid deadlock
+        for id in ids:
+            world = cherrypy.request.db.query(World).get(id)
+            world.randomNumber = rp()
+            worlds.append(world.serialize())
+        return worlds
+
+    @cherrypy.expose
+    def fortune(self):
+        fortunes = cherrypy.request.db.query(Fortune).all()
+        fortunes.append(Fortune(id=0, message="Additional fortune added at request time."))
+        fortunes.sort(key=attrgetter("message"))
+        html = "<!DOCTYPE html><html><head><title>Fortunes</title></head><body><table><tr><th>id</th><th>message</th></tr>"
+        for f in fortunes:
+            html += "<tr><td>" + str(f.id) + "</td><td>" + bleach.clean(f.message) + "</td></tr>"
+        html += "</table></body></html>"
+        return html
+
+if __name__ == "__main__":
+    # Register the SQLAlchemy plugin
+    from saplugin import SAEnginePlugin
+    DBDRIVER = 'mysql'
+    DBHOSTNAME = os.environ.get('DBHOST', 'localhost')
+    DATABASE_URI = '%s://benchmarkdbuser:benchmarkdbpass@%s:3306/hello_world?charset=utf8' % (DBDRIVER, DBHOSTNAME)
+    SAEnginePlugin(cherrypy.engine, DATABASE_URI).subscribe()
+    
+    # Register the SQLAlchemy tool
+    from satool import SATool
+    cherrypy.tools.db = SATool()
+    cherrypy.quickstart(CherryPyBenchmark(), '', {'/': {'tools.db.on': True}})

+ 7 - 0
frameworks/Python/cherrypy/bash_profile.sh

@@ -0,0 +1,7 @@
+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

+ 49 - 0
frameworks/Python/cherrypy/benchmark_config

@@ -0,0 +1,49 @@
+{
+  "framework": "cherrypy",
+  "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": "CherryPy",
+      "language": "Python",
+      "orm": "Full",
+      "platform": "CherryPy",
+      "webserver": "None",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "cherrypy",
+      "notes": "CPython 2.7"
+    },
+     "py3": {
+      "setup_file": "setup_py3",
+      "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": "CherryPy",
+      "language": "Python",
+      "orm": "Full",
+      "platform": "CherryPy",
+      "webserver": "None",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "cherrypy-py3",
+      "notes": "CPython 3.4"
+    }
+  }]
+}

+ 11 - 0
frameworks/Python/cherrypy/install.sh

@@ -0,0 +1,11 @@
+#!/bin/bash
+
+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
+

+ 6 - 0
frameworks/Python/cherrypy/requirements.txt

@@ -0,0 +1,6 @@
+cherrypy==3.6.0
+
+bleach==1.4.1
+
+SQLAlchemy==0.9.9
+mysqlclient==1.3.6

+ 61 - 0
frameworks/Python/cherrypy/saplugin.py

@@ -0,0 +1,61 @@
+import cherrypy
+from cherrypy.process import wspbus, plugins
+from sqlalchemy import create_engine
+from sqlalchemy.orm import scoped_session, sessionmaker
+
+__all__ = ['SAEnginePlugin']
+        
+class SAEnginePlugin(plugins.SimplePlugin):
+    def __init__(self, bus, connection_string=None):
+        """
+        The plugin is registered to the CherryPy engine and therefore
+        is part of the bus (the engine *is* a bus) registery.
+ 
+        We use this plugin to create the SA engine. At the same time,
+        when the plugin starts we create the tables into the database
+        using the mapped class of the global metadata.
+        """
+        plugins.SimplePlugin.__init__(self, bus)
+        self.sa_engine = None
+        self.connection_string = connection_string
+        self.session = scoped_session(sessionmaker(autoflush=True,
+                                                   autocommit=False))
+ 
+    def start(self):
+        self.bus.log('Starting up DB access')
+        self.sa_engine = create_engine(self.connection_string, echo=False)
+        self.bus.subscribe("bind-session", self.bind)
+        self.bus.subscribe("commit-session", self.commit)
+ 
+    def stop(self):
+        self.bus.log('Stopping down DB access')
+        self.bus.unsubscribe("bind-session", self.bind)
+        self.bus.unsubscribe("commit-session", self.commit)
+        if self.sa_engine:
+            self.sa_engine.dispose()
+            self.sa_engine = None
+ 
+    def bind(self):
+        """
+        Whenever this plugin receives the 'bind-session' command, it applies
+        this method and to bind the current session to the engine.
+
+        It then returns the session to the caller.
+        """
+        self.session.configure(bind=self.sa_engine)
+        return self.session
+
+    def commit(self):
+        """
+        Commits the current transaction or rollbacks if an error occurs.
+
+        In all cases, the current session is unbound and therefore
+        not usable any longer.
+        """
+        try:
+            self.session.commit()
+        except:
+            self.session.rollback()  
+            raise
+        finally:
+            self.session.remove()

+ 46 - 0
frameworks/Python/cherrypy/satool.py

@@ -0,0 +1,46 @@
+import cherrypy
+
+__all__ = ['SATool']
+
+class SATool(cherrypy.Tool):
+    def __init__(self):
+        """
+        The SA tool is responsible for associating a SA session
+        to the SA engine and attaching it to the current request.
+        Since we are running in a multithreaded application,
+        we use the scoped_session that will create a session
+        on a per thread basis so that you don't worry about
+        concurrency on the session object itself.
+ 
+        This tools binds a session to the engine each time
+        a requests starts and commits/rollbacks whenever
+        the request terminates.
+        """
+        cherrypy.Tool.__init__(self, 'on_start_resource',
+                               self.bind_session,
+                               priority=20)
+ 
+    def _setup(self):
+        cherrypy.Tool._setup(self)
+        cherrypy.request.hooks.attach('on_end_resource',
+                                      self.commit_transaction,
+                                      priority=80)
+ 
+    def bind_session(self):
+        """
+        Attaches a session to the request's scope by requesting
+        the SA plugin to bind a session to the SA engine.
+        """
+        session = cherrypy.engine.publish('bind-session').pop()
+        cherrypy.request.db = session
+ 
+    def commit_transaction(self):
+        """
+        Commits the current transaction or rolls back
+        if an error occurs. Removes the session handle
+        from the request's scope.
+        """
+        if not hasattr(cherrypy.request, 'db'):
+            return
+        cherrypy.request.db = None
+        cherrypy.engine.publish('commit-session')

+ 3 - 0
frameworks/Python/cherrypy/setup.sh

@@ -0,0 +1,3 @@
+#!/bin/bash
+
+$PY2 app.py &

+ 3 - 0
frameworks/Python/cherrypy/setup_py3.sh

@@ -0,0 +1,3 @@
+#!/bin/bash
+
+$PY3 app.py &