import warnings
warnings.filterwarnings('ignore',
message=r'Module .*? is being added to sys\.path', append=True)ここに書いてありました:
http://lucumr.pocoo.org/2008/2/19/sick-of-pkg-resources-warnings
import warnings
warnings.filterwarnings('ignore',
message=r'Module .*? is being added to sys\.path', append=True)10-25 05:33PM 31.791 / 200 344ms 544cpu_ms 10-25 05:34PM 07.998 / 200 354ms 544cpu_ms 10-25 05:34PM 37.308 / 200 355ms 563cpu_ms 10-25 05:35PM 00.452 / 200 336ms 525cpu_ms 10-25 05:35PM 27.638 / 200 332ms 525cpu_ms 10-25 05:40PM 45.837 / 200 365ms 544cpu_ms
10-20 09:29AM 32.796 / 200 332ms 369cpu_ms 10-20 09:31AM 45.991 / 200 278ms 330cpu_ms 10-20 09:33AM 56.086 / 200 352ms 369cpu_ms 10-20 09:36AM 16.630 / 200 245ms 350cpu_ms 10-20 09:39AM 13.148 / 200 266ms 350cpu_ms
10-20 09:30AM 13.555 / 200 504ms 700cpu_ms 10-20 09:32AM 27.076 / 200 436ms 641cpu_ms 10-20 09:34AM 37.841 / 200 417ms 621cpu_ms 10-20 09:37AM 01.469 / 200 471ms 641cpu_ms 10-20 09:41AM 40.874 / 200 454ms 660cpu_ms
10-20 09:31AM 00.640 / 200 663ms 1010cpu_ms 10-20 09:33AM 12.117 / 200 594ms 991cpu_ms 10-20 09:35AM 29.921 / 200 654ms 1030cpu_ms 10-20 09:38AM 00.888 / 200 629ms 1030cpu_ms 10-20 09:46AM 07.109 / 200 702ms 1108cpu_msAdditionally, I did some tests for serving by hot instances:
10-20 09:39AM 23.025 / 200 6ms 3cpu_ms 10-20 09:39AM 24.104 / 200 6ms 5cpu_ms 10-20 09:39AM 25.212 / 200 9ms 5cpu_ms 10-20 09:39AM 26.310 / 200 6ms 4cpu_ms 10-20 09:39AM 27.414 / 200 7ms 4cpu_ms
10-20 09:41AM 42.991 / 200 13ms 5cpu_ms 10-20 09:41AM 45.691 / 200 6ms 5cpu_ms 10-20 09:41AM 46.538 / 200 7ms 5cpu_ms 10-20 09:41AM 47.506 / 200 9ms 8cpu_ms 10-20 09:41AM 48.536 / 200 8ms 5cpu_ms
10-20 09:46AM 09.021 / 200 10ms 10cpu_ms 10-20 09:46AM 09.949 / 200 8ms 8cpu_ms 10-20 09:46AM 10.869 / 200 8ms 6cpu_ms 10-20 09:46AM 11.834 / 200 11ms 11cpu_ms 10-20 09:46AM 12.743 / 200 9ms 10cpu_ms
The template used is common among these frameworks.
index.html:<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"%gt;
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Top Page - myapp</title>
</head>
<body>
{{ message }}
</body>
</html>
Here are the application settings for each frameworks: #!/usr/bin/env python
#
# Copyright 2007 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import logging
import os
import wsgiref.handlers
from google.appengine.ext import webapp
from google.appengine.ext import db
from google.appengine.ext.webapp import template
class MainHandler(webapp.RequestHandler):
def get(self):
contents = {'message': 'hello'}
path = os.path.join(os.path.dirname(__file__), 'index.html')
self.response.out.write(template.render(path, contents))
def main():
logging.getLogger().setLevel(logging.debug)
application = webapp.WSGIApplication([('/', MainHandler)],
debug=True)
wsgiref.handlers.CGIHandler().run(application)
if __name__ == '__main__':
main()
# -*- coding: utf-8 -*-
"""
A sample of kay settings.
:Copyright: (c) 2009 Accense Technology, Inc.
Takashi Matsuo ,
All rights reserved.
:license: BSD, see LICENSE for more details.
"""
DEFAULT_TIMEZONE = 'Asia/Tokyo'
DEBUG = False
SECRET_KEY = 'ReplaceItWithSecretString'
SESSION_PREFIX = 'gaesess:'
COOKIE_AGE = 1209600 # 2 weeks
COOKIE_NAME = 'KAY_SESSION'
ADD_APP_PREFIX_TO_KIND = True
ADMINS = (
)
TEMPLATE_DIRS = (
)
USE_I18N = False
DEFAULT_LANG = 'en'
INSTALLED_APPS = (
'myapp',
)
APP_MOUNT_POINTS = {
'myapp': '/',
}
CONTEXT_PROCESSORS = (
)
JINJA2_FILTERS = {
}
MIDDLEWARE_CLASSES = (
)
AUTH_USER_BACKEND = 'kay.auth.backend.GoogleBackend'
AUTH_USER_MODEL = 'kay.auth.models.GoogleUser'
myapp/urls.py: # -*- coding: utf-8 -*-
# myapp.urls
from werkzeug.routing import (
Map, Rule, Submount,
EndpointPrefix, RuleTemplate,
)
import myapp.views
def make_rules():
return [
EndpointPrefix('myapp/', [
Rule('/', endpoint='index'),
]),
]
all_views = {
'myapp/index': myapp.views.index,
}
myapp/views.py: # -*- coding: utf-8 -*-
# myapp.views
from kay.utils import render_to_response
# Create your views here.
def index(request):
return render_to_response('myapp/index.html', {'message': 'Hello'})
# -*- coding: utf-8 -*-
from ragendja.settings_pre import *
# Increase this when you update your media on the production site, so users
# don't have to refresh their cache. By setting this your MEDIA_URL
# automatically becomes /media/MEDIA_VERSION/
MEDIA_VERSION = 1
# By hosting media on a different domain we can get a speedup (more parallel
# browser connections).
#if on_production_server or not have_appserver:
# MEDIA_URL = 'http://media.mydomain.com/media/%d/'
# Add base media (jquery can be easily added via INSTALLED_APPS)
COMBINE_MEDIA = {
'combined-%(LANGUAGE_CODE)s.js': (
# See documentation why site_data can be useful:
# http://code.google.com/p/app-engine-patch/wiki/MediaGenerator
'.site_data.js',
),
'combined-%(LANGUAGE_DIR)s.css': (
'global/look.css',
),
}
# Change your email settings
if on_production_server:
DEFAULT_FROM_EMAIL = 'bla@bla.com'
SERVER_EMAIL = DEFAULT_FROM_EMAIL
# Make this unique, and don't share it with anybody.
SECRET_KEY = '1234567890'
#ENABLE_PROFILER = True
#ONLY_FORCED_PROFILE = True
#PROFILE_PERCENTAGE = 25
#SORT_PROFILE_RESULTS_BY = 'cumulative' # default is 'time'
# Profile only datastore calls
#PROFILE_PATTERN = 'ext.db..+\((?:get|get_by_key_name|fetch|count|put)\)'
# Enable I18N and set default language to 'en'
USE_I18N = False
LANGUAGE_CODE = 'en'
# Restrict supported languages (and JS media generation)
LANGUAGES = (
('de', 'German'),
('en', 'English'),
)
TEMPLATE_CONTEXT_PROCESSORS = (
)
MIDDLEWARE_CLASSES = (
)
# Google authentication
AUTH_USER_MODULE = 'ragendja.auth.google_models'
AUTH_ADMIN_MODULE = 'ragendja.auth.google_admin'
# Hybrid Django/Google authentication
#AUTH_USER_MODULE = 'ragendja.auth.hybrid_models'
LOGIN_URL = '/account/login/'
LOGOUT_URL = '/account/logout/'
LOGIN_REDIRECT_URL = '/'
INSTALLED_APPS = (
# Add jquery support (app is in "common" folder). This automatically
# adds jquery to your COMBINE_MEDIA['combined-%(LANGUAGE_CODE)s.js']
# Note: the order of your INSTALLED_APPS specifies the order in which
# your app-specific media files get combined, so jquery should normally
# come first.
'appenginepatcher',
'ragendja',
'myapp',
)
# List apps which should be left out from app settings and urlsauto loading
IGNORE_APP_SETTINGS = IGNORE_APP_URLSAUTO = (
# Example:
# 'django.contrib.admin',
# 'django.contrib.auth',
# 'yetanotherapp',
)
# Remote access to production server (e.g., via manage.py shell --remote)
DATABASE_OPTIONS = {
# Override remoteapi handler's path (default: '/remote_api').
# This is a good idea, so you make it not too easy for hackers. ;)
# Don't forget to also update your app.yaml!
#'remote_url': '/remote-secret-url',
# !!!Normally, the following settings should not be used!!!
# Always use remoteapi (no need to add manage.py --remote option)
#'use_remote': True,
# Change appid for remote connection (by default it's the same as in
# your app.yaml)
#'remote_id': 'otherappid',
# Change domain (default: .appspot.com)
#'remote_host': 'bla.com',
}
from ragendja.settings_post import *
urls.py(minimized): # -*- coding: utf-8 -*-
from django.conf.urls.defaults import *
from ragendja.urlsauto import urlpatterns
from ragendja.auth.urls import urlpatterns as auth_patterns
urlpatterns = auth_patterns + patterns('',
(r'', include('myapp.urls')),
) + urlpatterns
myapp/urls.py: # -*- coding: utf-8 -*- from django.conf.urls.defaults import * urlpatterns = patterns( 'myapp.views', url(r'^$', 'index', name='myapp_index'), )myapp/views.py:
# -*- coding: utf-8 -*-
from ragendja.template import render_to_response
def index(request):
return render_to_response(request, 'myapp/index.html',
{'message': 'Hello'})
There are significant defferences among warm-up costs of these three frameworks. But when it comes to serving by warm instances, the defferences are rather small.
Actually you can choose whatever you want, but what if you must to choose one framework among these three options?
webapp+django template is really fast(actually without django template, webapp is much faster than this, but I think its unfair ;-P), so If you really need speed, webapp might be the best option.
Having said that, sometimes we need a fancy way for constructing rather complex applications. Perhaps you can choose Kay/app-engine-patch if you can accept 2 or 3 times(obviously compared with webapp) costs on cold-startup.
If you love Django admin capability and hardly throw it away, only app-engine-patch could be your option.
Actually I don't know much about app-engine-patch, so these settings is not enough. Please let me know if that's the case. If you make some tests similar to these tests, I'd be glad to know the result.
10-20 03:43PM 28.814 / 200 411ms 602cpu_ms 10-20 03:44PM 31.751 / 200 367ms 563cpu_ms 10-20 03:45PM 09.913 / 200 407ms 563cpu_ms 10-20 03:46PM 23.932 / 200 413ms 602cpu_ms 10-20 03:47PM 19.530 / 200 416ms 583cpu_ms
10-20 03:47PM 21.859 / 200 6ms 4cpu_ms 10-20 03:47PM 23.003 / 200 8ms 5cpu_ms 10-20 03:47PM 24.177 / 200 6ms 4cpu_ms 10-20 03:47PM 25.166 / 200 6ms 4cpu_ms 10-20 03:47PM 26.181 / 200 5ms 4cpu_ms
# -*- coding: utf-8 -*-
"""
urls
~~~~
URL definitions.
:copyright: 2009 by tipfy.org.
:license: BSD, see LICENSE.txt for more details.
"""
from tipfy import Rule
urls = [
Rule('/', endpoint='home', handler='hello:HelloWorldHandler'),
]
hello.py: from tipfy import RequestHandler
from tipfy.ext.jinja2 import render_response
class HelloWorldHandler(RequestHandler):
def get(self, **kwargs):
context = {'message': 'hello'}
return render_response('index.html', **context)
$ tree kay-pyjamas-sample
kay-pyjamas-sample
|-- JSONRPCExample.py
`-- public
`-- JSONRPCExample.html
1 directory, 2 files
import pyjd # dummy in pyjs
from pyjamas.ui.RootPanel import RootPanel
from pyjamas.ui.TextArea import TextArea
from pyjamas.ui.Label import Label
from pyjamas.ui.Button import Button
from pyjamas.ui.HTML import HTML
from pyjamas.ui.VerticalPanel import VerticalPanel
from pyjamas.ui.HorizontalPanel import HorizontalPanel
from pyjamas.ui.ListBox import ListBox
from pyjamas.JSONService import JSONProxy
class JSONRPCExample:
def onModuleLoad(self):
self.TEXT_WAITING = "Waiting for response..."
self.TEXT_ERROR = "Server Error"
self.METHOD_ECHO = "Echo"
self.METHOD_REVERSE = "Reverse"
self.METHOD_UPPERCASE = "UPPERCASE"
self.METHOD_LOWERCASE = "lowercase"
self.methods = [self.METHOD_ECHO, self.METHOD_REVERSE, self.METHOD_UPPERCASE, self.METHOD_LOWERCASE]
self.remote_py = EchoServicePython()
self.status=Label()
self.text_area = TextArea()
self.text_area.setText("""{'Test'} [\"String\"]
\tTest Tab
Test Newline\n
after newline
""" + r"""Literal String:
{'Test'} [\"String\"]
""")
self.text_area.setCharacterWidth(80)
self.text_area.setVisibleLines(8)
self.method_list = ListBox()
self.method_list.setName("hello")
self.method_list.setVisibleItemCount(1)
for method in self.methods:
self.method_list.addItem(method)
self.method_list.setSelectedIndex(0)
method_panel = HorizontalPanel()
method_panel.add(HTML("Remote string method to call: "))
method_panel.add(self.method_list)
method_panel.setSpacing(8)
self.button_py = Button("Send to Python Service", self)
buttons = HorizontalPanel()
buttons.add(self.button_py)
buttons.setSpacing(8)
info = """JSON-RPC Example
This example demonstrates the calling of server services with
JSON-RPC.
Enter some text below, and press a button to send the text
"""
to an Echo service on your server. An echo service simply sends the exact same text back that it receives.
panel = VerticalPanel()
panel.add(HTML(info))
panel.add(self.text_area)
panel.add(method_panel)
panel.add(buttons)
panel.add(self.status)
RootPanel().add(panel)
def onClick(self, sender):
self.status.setText(self.TEXT_WAITING)
method = self.methods[self.method_list.getSelectedIndex()]
text = self.text_area.getText()
id = -1
if method == self.METHOD_ECHO:
id = self.remote_py.echo(text, self)
elif method == self.METHOD_REVERSE:
id = self.remote_py.reverse(text, self)
elif method == self.METHOD_UPPERCASE:
id = self.remote_py.uppercase(text, self)
elif method == self.METHOD_LOWERCASE:
id = self.remote_py.lowercase(text, self)
if id<0:
self.status.setText(self.TEXT_ERROR)
def onRemoteResponse(self, response, request_info):
self.status.setText(response)
def onRemoteError(self, code, message, request_info):
self.status.setText("Server Error or Invalid Response: ERROR %d - %s" %
(code, message))
class EchoServicePython(JSONProxy):
def __init__(self):
JSONProxy.__init__(self, "/json", ["echo", "reverse", "uppercase", "lowercase"])
if __name__ == '__main__':
# for pyjd, set up a web server and load the HTML from there:
# this convinces the browser engine that the AJAX will be loaded
# from the same URI base as the URL, it's all a bit messy...
pyjd.setup("http://127.0.0.1/examples/jsonrpc/public/JSONRPCExample.html")
app = JSONRPCExample()
app.onModuleLoad()
pyjd.run()
$ cd kay-pyjamas-sample
$ ${PYJAMAS_HOME}/bin/pyjsbuild -o ${PROJECT_DIR}/media JSONRPCExample.py
# -*- coding: utf-8 -*-
# myapp.urls
from werkzeug.routing import (
Map, Rule, Submount,
EndpointPrefix, RuleTemplate,
)
import myapp.views
def make_rules():
return [
EndpointPrefix('myapp/', [
Rule('/', endpoint='index'),
Rule('/json', endpoint='json_rpc'),
]),
]
all_views = {
'myapp/index': myapp.views.index,
'myapp/json_rpc': myapp.views.json_rpc,
}
# -*- coding: utf-8 -*-
# myapp.views
# ... (snip)
import simplejson
# ... (snip)
def json_uppercase(args):
return [args[0].upper()]
def json_echo(args):
return [args[0]]
def json_reverse(args):
return [args[0][::-1]]
def json_lowercase(args):
return [args[0].lower()]
def json_rpc(request):
if request.method:
args = simplejson.loads(request.data)
method_name = 'json_%s' % args[u"method"]
json_func = globals().get(method_name)
json_params = args[u"params"]
json_method_id = args[u"id"]
result = json_func(json_params)
args.pop(u"method")
args["result"] = result[0]
args["error"] = None
return Response(simplejson.dumps(args), content_type="application/json")
else:
return Response('Error')
$ tree kay-pyjamas-sample
kay-pyjamas-sample
|-- JSONRPCExample.py
`-- public
`-- JSONRPCExample.html
1 directory, 2 files
import pyjd # dummy in pyjs
from pyjamas.ui.RootPanel import RootPanel
from pyjamas.ui.TextArea import TextArea
from pyjamas.ui.Label import Label
from pyjamas.ui.Button import Button
from pyjamas.ui.HTML import HTML
from pyjamas.ui.VerticalPanel import VerticalPanel
from pyjamas.ui.HorizontalPanel import HorizontalPanel
from pyjamas.ui.ListBox import ListBox
from pyjamas.JSONService import JSONProxy
class JSONRPCExample:
def onModuleLoad(self):
self.TEXT_WAITING = "Waiting for response..."
self.TEXT_ERROR = "Server Error"
self.METHOD_ECHO = "Echo"
self.METHOD_REVERSE = "Reverse"
self.METHOD_UPPERCASE = "UPPERCASE"
self.METHOD_LOWERCASE = "lowercase"
self.methods = [self.METHOD_ECHO, self.METHOD_REVERSE, self.METHOD_UPPERCASE, self.METHOD_LOWERCASE]
self.remote_py = EchoServicePython()
self.status=Label()
self.text_area = TextArea()
self.text_area.setText("""{'Test'} [\"String\"]
\tTest Tab
Test Newline\n
after newline
""" + r"""Literal String:
{'Test'} [\"String\"]
""")
self.text_area.setCharacterWidth(80)
self.text_area.setVisibleLines(8)
self.method_list = ListBox()
self.method_list.setName("hello")
self.method_list.setVisibleItemCount(1)
for method in self.methods:
self.method_list.addItem(method)
self.method_list.setSelectedIndex(0)
method_panel = HorizontalPanel()
method_panel.add(HTML("Remote string method to call: "))
method_panel.add(self.method_list)
method_panel.setSpacing(8)
self.button_py = Button("Send to Python Service", self)
buttons = HorizontalPanel()
buttons.add(self.button_py)
buttons.setSpacing(8)
info = """JSON-RPC Example
This example demonstrates the calling of server services with
JSON-RPC.
Enter some text below, and press a button to send the text
"""
to an Echo service on your server. An echo service simply sends the exact same text back that it receives.
panel = VerticalPanel()
panel.add(HTML(info))
panel.add(self.text_area)
panel.add(method_panel)
panel.add(buttons)
panel.add(self.status)
RootPanel().add(panel)
def onClick(self, sender):
self.status.setText(self.TEXT_WAITING)
method = self.methods[self.method_list.getSelectedIndex()]
text = self.text_area.getText()
id = -1
if method == self.METHOD_ECHO:
id = self.remote_py.echo(text, self)
elif method == self.METHOD_REVERSE:
id = self.remote_py.reverse(text, self)
elif method == self.METHOD_UPPERCASE:
id = self.remote_py.uppercase(text, self)
elif method == self.METHOD_LOWERCASE:
id = self.remote_py.lowercase(text, self)
if id<0:
self.status.setText(self.TEXT_ERROR)
def onRemoteResponse(self, response, request_info):
self.status.setText(response)
def onRemoteError(self, code, message, request_info):
self.status.setText("Server Error or Invalid Response: ERROR %d - %s" %
(code, message))
class EchoServicePython(JSONProxy):
def __init__(self):
JSONProxy.__init__(self, "/json", ["echo", "reverse", "uppercase", "lowercase"])
if __name__ == '__main__':
# for pyjd, set up a web server and load the HTML from there:
# this convinces the browser engine that the AJAX will be loaded
# from the same URI base as the URL, it's all a bit messy...
pyjd.setup("http://127.0.0.1/examples/jsonrpc/public/JSONRPCExample.html")
app = JSONRPCExample()
app.onModuleLoad()
pyjd.run()
$ cd kay-pyjamas-sample
$ ${PYJAMAS_HOME}/bin/pyjsbuild -o ${PROJECT_DIR}/media JSONRPCExample.py
# -*- coding: utf-8 -*-
# myapp.urls
from werkzeug.routing import (
Map, Rule, Submount,
EndpointPrefix, RuleTemplate,
)
import myapp.views
def make_rules():
return [
EndpointPrefix('myapp/', [
Rule('/', endpoint='index'),
Rule('/json', endpoint='json_rpc'),
]),
]
all_views = {
'myapp/index': myapp.views.index,
'myapp/json_rpc': myapp.views.json_rpc,
}
# -*- coding: utf-8 -*-
# myapp.views
# ... 省略
import simplejson
# ... 省略
def json_uppercase(args):
return [args[0].upper()]
def json_echo(args):
return [args[0]]
def json_reverse(args):
return [args[0][::-1]]
def json_lowercase(args):
return [args[0].lower()]
def json_rpc(request):
if request.method:
args = simplejson.loads(request.data)
method_name = 'json_%s' % args[u"method"]
json_func = globals().get(method_name)
json_params = args[u"params"]
json_method_id = args[u"id"]
result = json_func(json_params)
args.pop(u"method")
args["result"] = result[0]
args["error"] = None
return Response(simplejson.dumps(args), content_type="application/json")
else:
return Response('Error')
$ hg clone https://kay-framework.googlecode.com/hg/ kay
$ python kay/manage.py startproject myproject
$ tree myproject
myproject
|-- app.yaml
|-- kay -> /Users/tmatsuo/work/tmp/kay/kay
|-- manage.py -> /Users/tmatsuo/work/tmp/kay/manage.py
|-- settings.py
`-- urls.py
1 directory, 4 files
$ cd myproject
$ python manage.py startapp myapp
$ tree myapp
myapp
|-- __init__.py
|-- models.py
|-- templates
| `-- index.html
|-- urls.py
`-- views.py
1 directory, 5 files
#$/usr/bin/python
#..
#..
INSTALLED_APPS = (
'kay.sessions',
'myapp',
)
APP_MOUNT_POINTS = {
'myapp': '/',
}
$ python manage.py runserver
INFO 2009-08-04 05:48:21,339 appengine_rpc.py:157] Server: appengine.google.com
...
...
INFO 2009-08-04 05:48:21,448 dev_appserver_main.py:465] Running application myproject on port 8080: http://localhost:8080
$ python manage.py appcfg update
diff -r 8ffe4e637e17 werkzeug/debug/console.py
--- a/werkzeug/debug/console.py Sat Apr 25 17:06:40 2009 +0000
+++ b/werkzeug/debug/console.py Fri May 15 00:40:32 2009 +0900
@@ -32,6 +32,12 @@
def close(self):
pass
+ def flush(self):
+ pass
+
+ def seek(self, n, mode=0):
+ pass
+
def reset(self):
val = ''.join(self._buffer)
del self._buffer[:]
@@ -48,12 +54,18 @@
def writelines(self, x):
self._write(escape(''.join(x)))
+ def readline(self):
+ if len(self._buffer) == 0:
+ return ''
+ ret = self._buffer[0]
+ del self._buffer[0]
+ return ret
class ThreadedStream(object):
"""Thread-local wrapper for sys.stdout for the interactive console."""
def push():
- if sys.stdout is sys.__stdout__:
+ if not isinstance(sys.stdout, ThreadedStream):
sys.stdout = ThreadedStream()
_local.stream = HTMLStringO()
push = staticmethod(push)