many pep8 improvements

This commit is contained in:
mdipierro committed 2012-10-19 12:33:53 -05:00
1 parent 71ec15190b
commit 6aa5edc7ff
117 files changed
+7160 -5670

No files matched your search

+1 -1
View File
@@ -1 +1 @@
Version 2.1.1 (2012-10-19 10:37:01) dev Version 2.1.1 (2012-10-19 12:33:48) dev
-1
View File
@@ -1 +0,0 @@
+66 -46
View File
@@ -16,44 +16,47 @@ import urllib
path = os.path.dirname(os.path.abspath(__file__)) path = os.path.dirname(os.path.abspath(__file__))
os.chdir(path) os.chdir(path)
sys.path = [path]+[p for p in sys.path if not p==path] sys.path = [path] + [p for p in sys.path if not p == path]
import gluon.main import gluon.main
from gluon.fileutils import read_file, write_file from gluon.fileutils import read_file, write_file
class Servers: class Servers:
@staticmethod @staticmethod
def cgi(app, address=None, **options): def cgi(app, address=None, **options):
from wsgiref.handlers import CGIHandler from wsgiref.handlers import CGIHandler
CGIHandler().run(app) # Just ignore host and port here CGIHandler().run(app) # Just ignore host and port here
@staticmethod @staticmethod
def flup(app,address, **options): def flup(app, address, **options):
import flup.server.fcgi import flup.server.fcgi
flup.server.fcgi.WSGIServer(app, bindAddress=address).run() flup.server.fcgi.WSGIServer(app, bindAddress=address).run()
@staticmethod @staticmethod
def wsgiref(app,address,**options): # pragma: no cover def wsgiref(app, address, **options): # pragma: no cover
from wsgiref.simple_server import make_server, WSGIRequestHandler from wsgiref.simple_server import make_server, WSGIRequestHandler
class QuietHandler(WSGIRequestHandler): class QuietHandler(WSGIRequestHandler):
def log_request(*args, **kw): pass def log_request(*args, **kw):
pass
options['handler_class'] = QuietHandler options['handler_class'] = QuietHandler
srv = make_server(address[0],address[1],app,**options) srv = make_server(address[0], address[1], app, **options)
srv.serve_forever() srv.serve_forever()
@staticmethod @staticmethod
def cherrypy(app,address, **options): def cherrypy(app, address, **options):
from cherrypy import wsgiserver from cherrypy import wsgiserver
server = wsgiserver.CherryPyWSGIServer(address, app) server = wsgiserver.CherryPyWSGIServer(address, app)
server.start() server.start()
@staticmethod @staticmethod
def rocket(app,address, **options): def rocket(app, address, **options):
from gluon.rocket import CherryPyWSGIServer from gluon.rocket import CherryPyWSGIServer
server = CherryPyWSGIServer(address, app) server = CherryPyWSGIServer(address, app)
server.start() server.start()
@staticmethod @staticmethod
def rocket_with_repoze_profiler(app,address, **options): def rocket_with_repoze_profiler(app, address, **options):
from gluon.rocket import CherryPyWSGIServer from gluon.rocket import CherryPyWSGIServer
from repoze.profile.profiler import AccumulatingProfileMiddleware from repoze.profile.profiler import AccumulatingProfileMiddleware
from gluon.settings import global_settings from gluon.settings import global_settings
@@ -63,44 +66,46 @@ class Servers:
log_filename='wsgi.prof', log_filename='wsgi.prof',
discard_first_request=True, discard_first_request=True,
flush_at_shutdown=True, flush_at_shutdown=True,
path = '/__profile__' path='/__profile__'
) )
server = CherryPyWSGIServer(address, wrapped) server = CherryPyWSGIServer(address, wrapped)
server.start() server.start()
@staticmethod @staticmethod
def paste(app,address,**options): def paste(app, address, **options):
from paste import httpserver from paste import httpserver
from paste.translogger import TransLogger from paste.translogger import TransLogger
httpserver.serve(app, host=address[0], port=address[1], **options) httpserver.serve(app, host=address[0], port=address[1], **options)
@staticmethod @staticmethod
def fapws(app,address, **options): def fapws(app, address, **options):
import fapws._evwsgi as evwsgi import fapws._evwsgi as evwsgi
from fapws import base from fapws import base
evwsgi.start(address[0],str(address[1])) evwsgi.start(address[0], str(address[1]))
evwsgi.set_base_module(base) evwsgi.set_base_module(base)
def app(environ, start_response): def app(environ, start_response):
environ['wsgi.multiprocess'] = False environ['wsgi.multiprocess'] = False
return app(environ, start_response) return app(environ, start_response)
evwsgi.wsgi_cb(('',app)) evwsgi.wsgi_cb(('', app))
evwsgi.run() evwsgi.run()
@staticmethod @staticmethod
def gevent(app,address, **options): def gevent(app, address, **options):
from gevent import monkey; monkey.patch_all() from gevent import monkey
monkey.patch_all()
from gevent import pywsgi from gevent import pywsgi
from gevent.pool import Pool from gevent.pool import Pool
pywsgi.WSGIServer(address, app, spawn = 'workers' in options and Pool(int(options.workers)) or 'default').serve_forever() pywsgi.WSGIServer(address, app, spawn='workers' in options and Pool(
int(options.workers)) or 'default').serve_forever()
@staticmethod @staticmethod
def bjoern(app,address, **options): def bjoern(app, address, **options):
import bjoern import bjoern
bjoern.run(app, *address) bjoern.run(app, *address)
@staticmethod @staticmethod
def tornado(app,address, **options): def tornado(app, address, **options):
import tornado.wsgi import tornado.wsgi
import tornado.httpserver import tornado.httpserver
import tornado.ioloop import tornado.ioloop
@@ -110,7 +115,7 @@ class Servers:
tornado.ioloop.IOLoop.instance().start() tornado.ioloop.IOLoop.instance().start()
@staticmethod @staticmethod
def twisted(app,address, **options): def twisted(app, address, **options):
from twisted.web import server, wsgi from twisted.web import server, wsgi
from twisted.python.threadpool import ThreadPool from twisted.python.threadpool import ThreadPool
from twisted.internet import reactor from twisted.internet import reactor
@@ -122,42 +127,44 @@ class Servers:
reactor.run() reactor.run()
@staticmethod @staticmethod
def diesel(app,address, **options): def diesel(app, address, **options):
from diesel.protocols.wsgi import WSGIApplication from diesel.protocols.wsgi import WSGIApplication
app = WSGIApplication(app, port=address[1]) app = WSGIApplication(app, port=address[1])
app.run() app.run()
@staticmethod @staticmethod
def gunicorn(app,address, **options): def gunicorn(app, address, **options):
from gunicorn.app.base import Application from gunicorn.app.base import Application
config = {'bind': "%s:%d" % address} config = {'bind': "%s:%d" % address}
config.update(options) config.update(options)
sys.argv = ['anyserver.py'] sys.argv = ['anyserver.py']
class GunicornApplication(Application): class GunicornApplication(Application):
def init(self, parser, opts, args): def init(self, parser, opts, args):
return config return config
def load(self): def load(self):
return app return app
g = GunicornApplication() g = GunicornApplication()
g.run() g.run()
@staticmethod @staticmethod
def eventlet(app,address, **options): def eventlet(app, address, **options):
from eventlet import wsgi, listen from eventlet import wsgi, listen
wsgi.server(listen(address), app) wsgi.server(listen(address), app)
@staticmethod @staticmethod
def mongrel2(app,address,**options): def mongrel2(app, address, **options):
import uuid import uuid
sys.path.append(os.path.abspath(os.path.dirname(__file__))) sys.path.append(os.path.abspath(os.path.dirname(__file__)))
from mongrel2 import handler from mongrel2 import handler
conn = handler.Connection(str(uuid.uuid4()), conn = handler.Connection(str(uuid.uuid4()),
"tcp://127.0.0.1:9997", "tcp://127.0.0.1:9997",
"tcp://127.0.0.1:9996") "tcp://127.0.0.1:9996")
mongrel2_handler(app,conn,debug=False) mongrel2_handler(app, conn, debug=False)
def run(servername,ip,port,softcron=True,logging=False,profiler=None): def run(servername, ip, port, softcron=True, logging=False, profiler=None):
if logging: if logging:
application = gluon.main.appfactory(wsgiapp=gluon.main.wsgibase, application = gluon.main.appfactory(wsgiapp=gluon.main.wsgibase,
logfilename='httpserver.log', logfilename='httpserver.log',
@@ -167,9 +174,10 @@ def run(servername,ip,port,softcron=True,logging=False,profiler=None):
if softcron: if softcron:
from gluon.settings import global_settings from gluon.settings import global_settings
global_settings.web2py_crontype = 'soft' global_settings.web2py_crontype = 'soft'
getattr(Servers,servername)(application,(ip,int(port))) getattr(Servers, servername)(application, (ip, int(port)))
def mongrel2_handler(application,conn,debug=False):
def mongrel2_handler(application, conn, debug=False):
""" """
Based on : Based on :
https://github.com/berry/Mongrel2-WSGI-Handler/blob/master/wsgi-handler.py https://github.com/berry/Mongrel2-WSGI-Handler/blob/master/wsgi-handler.py
@@ -194,20 +202,23 @@ def mongrel2_handler(application,conn,debug=False):
# and responses. Unless I have missed something. # and responses. Unless I have missed something.
while True: while True:
if debug: print "WAITING FOR REQUEST" if debug:
print "WAITING FOR REQUEST"
# receive a request # receive a request
req = conn.recv() req = conn.recv()
if debug: print "REQUEST BODY: %r\n" % req.body if debug:
print "REQUEST BODY: %r\n" % req.body
if req.is_disconnect(): if req.is_disconnect():
if debug: print "DISCONNECT" if debug:
continue #effectively ignore the disconnect from the client print "DISCONNECT"
continue # effectively ignore the disconnect from the client
# Set a couple of environment attributes a.k.a. header attributes # Set a couple of environment attributes a.k.a. header attributes
# that are a must according to PEP 333 # that are a must according to PEP 333
environ = req.headers environ = req.headers
environ['SERVER_PROTOCOL'] = 'HTTP/1.1' # SimpleHandler expects a server_protocol, lets assume it is HTTP 1.1 environ['SERVER_PROTOCOL'] = 'HTTP/1.1' # SimpleHandler expects a server_protocol, lets assume it is HTTP 1.1
environ['REQUEST_METHOD'] = environ['METHOD'] environ['REQUEST_METHOD'] = environ['METHOD']
if ':' in environ['Host']: if ':' in environ['Host']:
environ['SERVER_NAME'] = environ['Host'].split(':')[0] environ['SERVER_NAME'] = environ['Host'].split(':')[0]
@@ -215,17 +226,19 @@ def mongrel2_handler(application,conn,debug=False):
else: else:
environ['SERVER_NAME'] = environ['Host'] environ['SERVER_NAME'] = environ['Host']
environ['SERVER_PORT'] = '' environ['SERVER_PORT'] = ''
environ['SCRIPT_NAME'] = '' # empty for now environ['SCRIPT_NAME'] = '' # empty for now
environ['PATH_INFO'] = urllib.unquote(environ['PATH']) environ['PATH_INFO'] = urllib.unquote(environ['PATH'])
if '?' in environ['URI']: if '?' in environ['URI']:
environ['QUERY_STRING'] = environ['URI'].split('?')[1] environ['QUERY_STRING'] = environ['URI'].split('?')[1]
else: else:
environ['QUERY_STRING'] = '' environ['QUERY_STRING'] = ''
if 'Content-Length' in environ: if 'Content-Length' in environ:
environ['CONTENT_LENGTH'] = environ['Content-Length'] # necessary for POST to work with Django environ['CONTENT_LENGTH'] = environ[
'Content-Length'] # necessary for POST to work with Django
environ['wsgi.input'] = req.body environ['wsgi.input'] = req.body
if debug: print "ENVIRON: %r\n" % environ if debug:
print "ENVIRON: %r\n" % environ
# SimpleHandler needs file-like stream objects for # SimpleHandler needs file-like stream objects for
# requests, errors and responses # requests, errors and responses
@@ -234,7 +247,8 @@ def mongrel2_handler(application,conn,debug=False):
respIO = StringIO.StringIO() respIO = StringIO.StringIO()
# execute the application # execute the application
handler = SimpleHandler(reqIO, respIO, errIO, environ, multithread = False, multiprocess = False) handler = SimpleHandler(reqIO, respIO, errIO, environ,
multithread=False, multiprocess=False)
handler.run(application) handler.run(application)
# Get the response and filter out the response (=data) itself, # Get the response and filter out the response (=data) itself,
@@ -258,11 +272,15 @@ def mongrel2_handler(application,conn,debug=False):
errors = errIO.getvalue() errors = errIO.getvalue()
# return the response # return the response
if debug: print "RESPONSE: %r\n" % response if debug:
print "RESPONSE: %r\n" % response
if errors: if errors:
if debug: print "ERRORS: %r" % errors if debug:
print "ERRORS: %r" % errors
data = "%s\r\n\r\n%s" % (data, errors) data = "%s\r\n\r\n%s" % (data, errors)
conn.reply_http(req, data, code = code, status = status, headers = headers) conn.reply_http(
req, data, code=code, status=status, headers=headers)
def main(): def main():
usage = "python anyserver.py -s tornado -i 127.0.0.1 -p 8000 -l -P" usage = "python anyserver.py -s tornado -i 127.0.0.1 -p 8000 -l -P"
@@ -282,7 +300,7 @@ def main():
default=False, default=False,
dest='profiler', dest='profiler',
help='profiler filename') help='profiler filename')
servers = ', '.join(x for x in dir(Servers) if not x[0]=='_') servers = ', '.join(x for x in dir(Servers) if not x[0] == '_')
parser.add_option('-s', parser.add_option('-s',
'--server', '--server',
default='rocket', default='rocket',
@@ -304,8 +322,10 @@ def main():
dest='workers', dest='workers',
help='number of workers number') help='number of workers number')
(options, args) = parser.parse_args() (options, args) = parser.parse_args()
print 'starting %s on %s:%s...' % (options.server,options.ip,options.port) print 'starting %s on %s:%s...' % (
run(options.server,options.ip,options.port,logging=options.logging,profiler=options.profiler) options.server, options.ip, options.port)
run(options.server, options.ip, options.port,
logging=options.logging, profiler=options.profiler)
if __name__=='__main__': if __name__ == '__main__':
main() main()
+1 -1
View File
@@ -56,7 +56,7 @@ import wsgiref.handlers
path = os.path.dirname(os.path.abspath(__file__)) path = os.path.dirname(os.path.abspath(__file__))
os.chdir(path) os.chdir(path)
sys.path = [path]+[p for p in sys.path if not p==path] sys.path = [path] + [p for p in sys.path if not p == path]
import gluon.main import gluon.main
+1 -1
View File
@@ -34,7 +34,7 @@ import os
path = os.path.dirname(os.path.abspath(__file__)) path = os.path.dirname(os.path.abspath(__file__))
os.chdir(path) os.chdir(path)
sys.path = [path]+[p for p in sys.path if not p==path] sys.path = [path] + [p for p in sys.path if not p == path]
import gluon.main import gluon.main
import gluon.contrib.gateways.fcgi as fcgi import gluon.contrib.gateways.fcgi as fcgi
+3 -2
View File
@@ -33,7 +33,7 @@ import wsgiref.handlers
import datetime import datetime
path = os.path.dirname(os.path.abspath(__file__)) path = os.path.dirname(os.path.abspath(__file__))
sys.path = [path]+[p for p in sys.path if not p==path] sys.path = [path] + [p for p in sys.path if not p == path]
sys.modules['cPickle'] = sys.modules['pickle'] sys.modules['cPickle'] = sys.modules['pickle']
@@ -83,7 +83,7 @@ def wsgiapp(env, res):
if global_settings.web2py_runtime == 'gae:development': if global_settings.web2py_runtime == 'gae:development':
gluon.admin.create_missing_folders() gluon.admin.create_missing_folders()
web2py_path = global_settings.applications_parent # backward compatibility web2py_path = global_settings.applications_parent # backward compatibility
return gluon.main.wsgibase(env, res) return gluon.main.wsgibase(env, res)
@@ -91,6 +91,7 @@ def wsgiapp(env, res):
if LOG_STATS or DEBUG: if LOG_STATS or DEBUG:
wsgiapp = log_stats(wsgiapp) wsgiapp = log_stats(wsgiapp)
def main(): def main():
"""Run the wsgi app""" """Run the wsgi app"""
run_wsgi_app(wsgiapp) run_wsgi_app(wsgiapp)
+1 -1
View File
@@ -10,7 +10,7 @@ Web2Py framework modules
======================== ========================
""" """
__all__ = ['A', 'B', 'BEAUTIFY', 'BODY', 'BR', 'CAT', 'CENTER', 'CLEANUP', 'CODE', 'CRYPT', 'DAL', 'DIV', 'EM', 'EMBED', 'FIELDSET', 'FORM', 'Field', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'HEAD', 'HR', 'HTML', 'HTTP', 'I', 'IFRAME', 'IMG', 'INPUT', 'IS_ALPHANUMERIC', 'IS_DATE', 'IS_DATETIME', 'IS_DATETIME_IN_RANGE', 'IS_DATE_IN_RANGE', 'IS_DECIMAL_IN_RANGE', 'IS_EMAIL', 'IS_EMPTY_OR', 'IS_EQUAL_TO', 'IS_EXPR', 'IS_FLOAT_IN_RANGE', 'IS_IMAGE', 'IS_INT_IN_RANGE', 'IS_IN_DB', 'IS_IN_SET', 'IS_IPV4', 'IS_LENGTH', 'IS_LIST_OF', 'IS_LOWER', 'IS_MATCH', 'IS_NOT_EMPTY', 'IS_NOT_IN_DB', 'IS_NULL_OR', 'IS_SLUG', 'IS_STRONG', 'IS_TIME', 'IS_UPLOAD_FILENAME', 'IS_UPPER', 'IS_URL', 'LABEL', 'LEGEND', 'LI', 'LINK', 'LOAD', 'MARKMIN', 'MENU', 'META', 'OBJECT', 'OL', 'ON', 'OPTGROUP', 'OPTION', 'P', 'PRE', 'SCRIPT', 'SELECT', 'SPAN', 'SQLFORM', 'SQLTABLE', 'STRONG', 'STYLE', 'TABLE', 'TAG', 'TBODY', 'TD', 'TEXTAREA', 'TFOOT', 'TH', 'THEAD', 'TITLE', 'TR', 'TT', 'UL', 'URL', 'XHTML', 'XML','redirect','current','embed64'] __all__ = ['A', 'B', 'BEAUTIFY', 'BODY', 'BR', 'CAT', 'CENTER', 'CLEANUP', 'CODE', 'CRYPT', 'DAL', 'DIV', 'EM', 'EMBED', 'FIELDSET', 'FORM', 'Field', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'HEAD', 'HR', 'HTML', 'HTTP', 'I', 'IFRAME', 'IMG', 'INPUT', 'IS_ALPHANUMERIC', 'IS_DATE', 'IS_DATETIME', 'IS_DATETIME_IN_RANGE', 'IS_DATE_IN_RANGE', 'IS_DECIMAL_IN_RANGE', 'IS_EMAIL', 'IS_EMPTY_OR', 'IS_EQUAL_TO', 'IS_EXPR', 'IS_FLOAT_IN_RANGE', 'IS_IMAGE', 'IS_INT_IN_RANGE', 'IS_IN_DB', 'IS_IN_SET', 'IS_IPV4', 'IS_LENGTH', 'IS_LIST_OF', 'IS_LOWER', 'IS_MATCH', 'IS_NOT_EMPTY', 'IS_NOT_IN_DB', 'IS_NULL_OR', 'IS_SLUG', 'IS_STRONG', 'IS_TIME', 'IS_UPLOAD_FILENAME', 'IS_UPPER', 'IS_URL', 'LABEL', 'LEGEND', 'LI', 'LINK', 'LOAD', 'MARKMIN', 'MENU', 'META', 'OBJECT', 'OL', 'ON', 'OPTGROUP', 'OPTION', 'P', 'PRE', 'SCRIPT', 'SELECT', 'SPAN', 'SQLFORM', 'SQLTABLE', 'STRONG', 'STYLE', 'TABLE', 'TAG', 'TBODY', 'TD', 'TEXTAREA', 'TFOOT', 'TH', 'THEAD', 'TITLE', 'TR', 'TT', 'UL', 'URL', 'XHTML', 'XML', 'redirect', 'current', 'embed64']
from globals import current from globals import current
from html import * from html import *
+31 -18
View File
@@ -23,6 +23,7 @@ from http import HTTP
if not global_settings.web2py_runtime_gae: if not global_settings.web2py_runtime_gae:
import site import site
def apath(path='', r=None): def apath(path='', r=None):
""" """
Builds a path inside an application folder Builds a path inside an application folder
@@ -95,6 +96,7 @@ def app_pack_compiled(app, request, raise_ex=False):
raise raise
return None return None
def app_cleanup(app, request): def app_cleanup(app, request):
""" """
Removes session, cache and error files Removes session, cache and error files
@@ -113,7 +115,7 @@ def app_cleanup(app, request):
if os.path.exists(path): if os.path.exists(path):
for f in os.listdir(path): for f in os.listdir(path):
try: try:
if f[:1]!='.': os.unlink(os.path.join(path,f)) if f[:1] != '.': os.unlink(os.path.join(path, f))
except IOError: except IOError:
r = False r = False
@@ -122,7 +124,7 @@ def app_cleanup(app, request):
if os.path.exists(path): if os.path.exists(path):
for f in os.listdir(path): for f in os.listdir(path):
try: try:
if f[:1]!='.': recursive_unlink(os.path.join(path,f)) if f[:1] != '.': recursive_unlink(os.path.join(path, f))
except IOError: except IOError:
r = False r = False
@@ -131,7 +133,7 @@ def app_cleanup(app, request):
if os.path.exists(path): if os.path.exists(path):
for f in os.listdir(path): for f in os.listdir(path):
try: try:
if f[:1]!='.': os.unlink(os.path.join(path,f)) if f[:1] != '.': os.unlink(os.path.join(path, f))
except IOError: except IOError:
r = False r = False
return r return r
@@ -158,7 +160,8 @@ def app_compile(app, request):
remove_compiled_application(folder) remove_compiled_application(folder)
return tb return tb
def app_create(app, request,force=False,key=None,info=False):
def app_create(app, request, force=False, key=None, info=False):
""" """
Create a copy of welcome.w2p (scaffolding) app Create a copy of welcome.w2p (scaffolding) app
@@ -186,17 +189,17 @@ def app_create(app, request,force=False,key=None,info=False):
return False return False
try: try:
w2p_unpack('welcome.w2p', path) w2p_unpack('welcome.w2p', path)
for subfolder in ['models','views','controllers', 'databases', for subfolder in ['models', 'views', 'controllers', 'databases',
'modules','cron','errors','sessions', 'modules', 'cron', 'errors', 'sessions',
'languages','static','private','uploads']: 'languages', 'static', 'private', 'uploads']:
subpath = os.path.join(path,subfolder) subpath = os.path.join(path, subfolder)
if not os.path.exists(subpath): if not os.path.exists(subpath):
os.mkdir(subpath) os.mkdir(subpath)
db = os.path.join(path, 'models', 'db.py') db = os.path.join(path, 'models', 'db.py')
if os.path.exists(db): if os.path.exists(db):
data = read_file(db) data = read_file(db)
data = data.replace('<your secret key>', data = data.replace('<your secret key>',
'sha512:'+(key or web2py_uuid())) 'sha512:' + (key or web2py_uuid()))
write_file(db, data) write_file(db, data)
if info: if info:
return True, None return True, None
@@ -283,6 +286,7 @@ def app_uninstall(app, request):
except Exception: except Exception:
return False return False
def plugin_pack(app, plugin_name, request): def plugin_pack(app, plugin_name, request):
""" """
Builds a w2p package for the application Builds a w2p package for the application
@@ -302,12 +306,14 @@ def plugin_pack(app, plugin_name, request):
filename of the w2p file or None on error filename of the w2p file or None on error
""" """
try: try:
filename = apath('../deposit/web2py.plugin.%s.w2p' % plugin_name, request) filename = apath(
'../deposit/web2py.plugin.%s.w2p' % plugin_name, request)
w2p_pack_plugin(filename, apath(app, request), plugin_name) w2p_pack_plugin(filename, apath(app, request), plugin_name)
return filename return filename
except Exception: except Exception:
return False return False
def plugin_install(app, fobj, request, filename): def plugin_install(app, fobj, request, filename):
""" """
Installs an application: Installs an application:
@@ -345,6 +351,7 @@ def plugin_install(app, fobj, request, filename):
os.unlink(upname) os.unlink(upname)
return False return False
def check_new_version(myversion, version_URL): def check_new_version(myversion, version_URL):
""" """
Compares current web2py's version with the latest stable web2py version. Compares current web2py's version with the latest stable web2py version.
@@ -375,6 +382,7 @@ def check_new_version(myversion, version_URL):
else: else:
return False, version return False, version
def unzip(filename, dir, subfolder=''): def unzip(filename, dir, subfolder=''):
""" """
Unzips filename into dir (.zip only, no .gz etc) Unzips filename into dir (.zip only, no .gz etc)
@@ -382,7 +390,7 @@ def unzip(filename, dir, subfolder=''):
""" """
filename = abspath(filename) filename = abspath(filename)
if not zipfile.is_zipfile(filename): if not zipfile.is_zipfile(filename):
raise RuntimeError, 'Not a valid zipfile' raise RuntimeError('Not a valid zipfile')
zf = zipfile.ZipFile(filename) zf = zipfile.ZipFile(filename)
if not subfolder.endswith('/'): if not subfolder.endswith('/'):
subfolder = subfolder + '/' subfolder = subfolder + '/'
@@ -392,7 +400,7 @@ def unzip(filename, dir, subfolder=''):
continue continue
#print name[n:] #print name[n:]
if name.endswith('/'): if name.endswith('/'):
folder = os.path.join(dir,name[n:]) folder = os.path.join(dir, name[n:])
if not os.path.exists(folder): if not os.path.exists(folder):
os.mkdir(folder) os.mkdir(folder)
else: else:
@@ -421,7 +429,7 @@ def upgrade(request, url='http://web2py.com'):
if not gluon_parent.endswith('/'): if not gluon_parent.endswith('/'):
gluon_parent = gluon_parent + '/' gluon_parent = gluon_parent + '/'
(check, version) = check_new_version(web2py_version, (check, version) = check_new_version(web2py_version,
url+'/examples/default/version') url + '/examples/default/version')
if not check: if not check:
return (False, 'Already latest version') return (False, 'Already latest version')
if os.path.exists(os.path.join(gluon_parent, 'web2py.exe')): if os.path.exists(os.path.join(gluon_parent, 'web2py.exe')):
@@ -442,35 +450,40 @@ def upgrade(request, url='http://web2py.com'):
file = None file = None
try: try:
write_file(filename, urllib.urlopen(full_url).read(), 'wb') write_file(filename, urllib.urlopen(full_url).read(), 'wb')
except Exception,e: except Exception, e:
return False, e return False, e
try: try:
unzip(filename, destination, subfolder) unzip(filename, destination, subfolder)
return True, None return True, None
except Exception,e: except Exception, e:
return False, e return False, e
def add_path_first(path): def add_path_first(path):
sys.path = [path]+[p for p in sys.path if (not p==path and not p==(path+'/'))] sys.path = [path] + [p for p in sys.path if (
not p == path and not p == (path + '/'))]
if not global_settings.web2py_runtime_gae: if not global_settings.web2py_runtime_gae:
site.addsitedir(path) site.addsitedir(path)
def create_missing_folders(): def create_missing_folders():
if not global_settings.web2py_runtime_gae: if not global_settings.web2py_runtime_gae:
for path in ('applications', 'deposit', 'site-packages', 'logs'): for path in ('applications', 'deposit', 'site-packages', 'logs'):
path = abspath(path, gluon=True) path = abspath(path, gluon=True)
if not os.path.exists(path): if not os.path.exists(path):
os.mkdir(path) os.mkdir(path)
paths = (global_settings.gluon_parent, abspath('site-packages', gluon=True), abspath('gluon', gluon=True), '') paths = (global_settings.gluon_parent, abspath(
'site-packages', gluon=True), abspath('gluon', gluon=True), '')
[add_path_first(path) for path in paths] [add_path_first(path) for path in paths]
def create_missing_app_folders(request): def create_missing_app_folders(request):
if not global_settings.web2py_runtime_gae: if not global_settings.web2py_runtime_gae:
if request.folder not in global_settings.app_folders: if request.folder not in global_settings.app_folders:
for subfolder in ('models', 'views', 'controllers', 'databases', for subfolder in ('models', 'views', 'controllers', 'databases',
'modules', 'cron', 'errors', 'sessions', 'modules', 'cron', 'errors', 'sessions',
'languages', 'static', 'private', 'uploads'): 'languages', 'static', 'private', 'uploads'):
path = os.path.join(request.folder, subfolder) path = os.path.join(request.folder, subfolder)
if not os.path.exists(path): if not os.path.exists(path):
os.mkdir(path) os.mkdir(path)
global_settings.app_folders.add(request.folder) global_settings.app_folders.add(request.folder)
+45 -33
View File
@@ -40,6 +40,7 @@ __all__ = ['Cache', 'lazy_cache']
DEFAULT_TIME_EXPIRE = 300 DEFAULT_TIME_EXPIRE = 300
class CacheAbstract(object): class CacheAbstract(object):
""" """
Abstract class for cache implementations. Abstract class for cache implementations.
@@ -73,7 +74,7 @@ class CacheAbstract(object):
raise NotImplementedError raise NotImplementedError
def __call__(self, key, f, def __call__(self, key, f,
time_expire = DEFAULT_TIME_EXPIRE): time_expire=DEFAULT_TIME_EXPIRE):
""" """
Tries retrieve the value corresponding to `key` from the cache of the Tries retrieve the value corresponding to `key` from the cache of the
object exists and if it did not expire, else it called the function `f` object exists and if it did not expire, else it called the function `f`
@@ -130,6 +131,7 @@ class CacheAbstract(object):
if r.match(str(key)): if r.match(str(key)):
del storage[key] del storage[key]
class CacheInRam(CacheAbstract): class CacheInRam(CacheAbstract):
""" """
Ram based caching Ram based caching
@@ -147,8 +149,10 @@ class CacheInRam(CacheAbstract):
self.request = request self.request = request
def initialize(self): def initialize(self):
if self.initialized: return if self.initialized:
else: self.initialized = True return
else:
self.initialized = True
self.locker.acquire() self.locker.acquire()
request = self.request request = self.request
if request: if request:
@@ -172,13 +176,14 @@ class CacheInRam(CacheAbstract):
self._clear(storage, regex) self._clear(storage, regex)
if not CacheAbstract.cache_stats_name in storage.keys(): if not CacheAbstract.cache_stats_name in storage.keys():
storage[CacheAbstract.cache_stats_name] = {'hit_total': 0,'misses': 0} storage[CacheAbstract.cache_stats_name] = {
'hit_total': 0, 'misses': 0}
self.locker.release() self.locker.release()
def __call__(self, key, f, def __call__(self, key, f,
time_expire = DEFAULT_TIME_EXPIRE, time_expire=DEFAULT_TIME_EXPIRE,
destroyer = None): destroyer=None):
""" """
Attention! cache.ram does not copy the cached object. It just stores a reference to it. Attention! cache.ram does not copy the cached object. It just stores a reference to it.
Turns out the deepcopying the object has some problems: Turns out the deepcopying the object has some problems:
@@ -269,14 +274,15 @@ class CacheOnDisk(CacheAbstract):
try: try:
storage = shelve.open(self.shelve_name) storage = shelve.open(self.shelve_name)
except: except:
logger.error('corrupted cache file %s, will try rebuild it' \ logger.error('corrupted cache file %s, will try rebuild it'
% (self.shelve_name)) % (self.shelve_name))
storage = None storage = None
if not storage and os.path.exists(self.shelve_name): if not storage and os.path.exists(self.shelve_name):
os.unlink(self.shelve_name) os.unlink(self.shelve_name)
storage = shelve.open(self.shelve_name) storage = shelve.open(self.shelve_name)
if not CacheAbstract.cache_stats_name in storage.keys(): if not CacheAbstract.cache_stats_name in storage.keys():
storage[CacheAbstract.cache_stats_name] = {'hit_total':0, 'misses': 0} storage[CacheAbstract.cache_stats_name] = {
'hit_total': 0, 'misses': 0}
storage.sync() storage.sync()
except Exception, e: except Exception, e:
if storage: if storage:
@@ -286,7 +292,8 @@ class CacheOnDisk(CacheAbstract):
portalocker.unlock(locker) portalocker.unlock(locker)
locker.close() locker.close()
locked = False locked = False
raise RuntimeError, 'unable to create/re-create cache file %s' % self.shelve_name raise RuntimeError(
'unable to create/re-create cache file %s' % self.shelve_name)
self.locker = locker self.locker = locker
self.locked = locked self.locked = locked
self.storage = storage self.storage = storage
@@ -298,8 +305,10 @@ class CacheOnDisk(CacheAbstract):
self.folder = folder self.folder = folder
def initialize(self): def initialize(self):
if self.initialized: return if self.initialized:
else: self.initialized = True return
else:
self.initialized = True
folder = self.folder folder = self.folder
request = self.request request = self.request
@@ -312,8 +321,8 @@ class CacheOnDisk(CacheAbstract):
### we need this because of a possible bug in shelve that may ### we need this because of a possible bug in shelve that may
### or may not lock ### or may not lock
self.locker_name = os.path.join(folder,'cache.lock') self.locker_name = os.path.join(folder, 'cache.lock')
self.shelve_name = os.path.join(folder,'cache.shelve') self.shelve_name = os.path.join(folder, 'cache.shelve')
def clear(self, regex=None): def clear(self, regex=None):
self.initialize() self.initialize()
@@ -328,7 +337,7 @@ class CacheOnDisk(CacheAbstract):
self._close_shelve_and_unlock() self._close_shelve_and_unlock()
def __call__(self, key, f, def __call__(self, key, f,
time_expire = DEFAULT_TIME_EXPIRE): time_expire=DEFAULT_TIME_EXPIRE):
self.initialize() self.initialize()
dt = time_expire dt = time_expire
storage = self._open_shelve_and_lock() storage = self._open_shelve_and_lock()
@@ -346,7 +355,7 @@ class CacheOnDisk(CacheAbstract):
else: else:
value = f() value = f()
storage[key] = (now, value) storage[key] = (now, value)
storage[CacheAbstract.cache_stats_name]['misses']+=1 storage[CacheAbstract.cache_stats_name]['misses'] += 1
storage.sync() storage.sync()
finally: finally:
self._close_shelve_and_unlock() self._close_shelve_and_unlock()
@@ -365,8 +374,9 @@ class CacheOnDisk(CacheAbstract):
self._close_shelve_and_unlock() self._close_shelve_and_unlock()
return value return value
class CacheAction(object): class CacheAction(object):
def __init__(self,func,key,time_expire,cache,cache_model): def __init__(self, func, key, time_expire, cache, cache_model):
self.__name__ = func.__name__ self.__name__ = func.__name__
self.__doc__ = func.__doc__ self.__doc__ = func.__doc__
self.func = func self.func = func
@@ -374,17 +384,18 @@ class CacheAction(object):
self.time_expire = time_expire self.time_expire = time_expire
self.cache = cache self.cache = cache
self.cache_model = cache_model self.cache_model = cache_model
def __call__(self,*a,**b):
def __call__(self, *a, **b):
if not self.key: if not self.key:
key2 = self.__name__+':'+repr(a)+':'+repr(b) key2 = self.__name__ + ':' + repr(a) + ':' + repr(b)
else: else:
key2 = self.key.replace('%(name)s',self.__name__)\ key2 = self.key.replace('%(name)s', self.__name__)\
.replace('%(args)s',str(a)).replace('%(vars)s',str(b)) .replace('%(args)s', str(a)).replace('%(vars)s', str(b))
cache_model = self.cache_model cache_model = self.cache_model
if not cache_model or isinstance(cache_model,str): if not cache_model or isinstance(cache_model, str):
cache_model = getattr(self.cache,cache_model or 'ram') cache_model = getattr(self.cache, cache_model or 'ram')
return cache_model(key2, return cache_model(key2,
lambda a=a,b=b:self.func(*a,**b), lambda a=a, b=b: self.func(*a, **b),
self.time_expire) self.time_expire)
@@ -424,9 +435,9 @@ class Cache(object):
logger.warning('no cache.disk (AttributeError)') logger.warning('no cache.disk (AttributeError)')
def __call__(self, def __call__(self,
key = None, key=None,
time_expire = DEFAULT_TIME_EXPIRE, time_expire=DEFAULT_TIME_EXPIRE,
cache_model = None): cache_model=None):
""" """
Decorator function that can be used to cache any function/method. Decorator function that can be used to cache any function/method.
@@ -459,8 +470,8 @@ class Cache(object):
`request.env.path_info` as key. `request.env.path_info` as key.
""" """
def tmp(func,cache=self,cache_model=cache_model): def tmp(func, cache=self, cache_model=cache_model):
return CacheAction(func,key,time_expire,self,cache_model) return CacheAction(func, key, time_expire, self, cache_model)
return tmp return tmp
@staticmethod @staticmethod
@@ -473,7 +484,7 @@ class Cache(object):
cache_model(prefix + key, f, time_expire) cache_model(prefix + key, f, time_expire)
def lazy_cache(key=None,time_expire=None,cache_model='ram'): def lazy_cache(key=None, time_expire=None, cache_model='ram'):
""" """
can be used to cache any function including in modules, can be used to cache any function including in modules,
as long as the cached function is only called within a web2py request as long as the cached function is only called within a web2py request
@@ -481,11 +492,12 @@ def lazy_cache(key=None,time_expire=None,cache_model='ram'):
the time_expire defaults to None (no cache expiration) the time_expire defaults to None (no cache expiration)
if cache_model is "ram" then the model is current.cache.ram, etc. if cache_model is "ram" then the model is current.cache.ram, etc.
""" """
def decorator(f,key=key,time_expire=time_expire,cache_model=cache_model): def decorator(f, key=key, time_expire=time_expire, cache_model=cache_model):
key = key or repr(f) key = key or repr(f)
def g(*c,**d):
def g(*c, **d):
from gluon import current from gluon import current
return current.cache(key,time_expire,cache_model)(f)(*c,**d) return current.cache(key, time_expire, cache_model)(f)(*c, **d)
g.__name__ = f.__name__ g.__name__ = f.__name__
return g return g
return decorator return decorator
+79 -65
View File
@@ -48,7 +48,7 @@ except:
logger.warning('unable to import py_compile') logger.warning('unable to import py_compile')
is_pypy = settings.global_settings.is_pypy is_pypy = settings.global_settings.is_pypy
is_gae = settings.global_settings.web2py_runtime_gae is_gae = settings.global_settings.web2py_runtime_gae
is_jython = settings.global_settings.is_jython is_jython = settings.global_settings.is_jython
pjoin = os.path.join pjoin = os.path.join
@@ -95,6 +95,7 @@ _TEST()
CACHED_REGEXES = {} CACHED_REGEXES = {}
CACHED_REGEXES_MAX_SIZE = 1000 CACHED_REGEXES_MAX_SIZE = 1000
def re_compile(regex): def re_compile(regex):
try: try:
return CACHED_REGEXES[regex] return CACHED_REGEXES[regex]
@@ -104,6 +105,7 @@ def re_compile(regex):
compiled_regex = CACHED_REGEXES[regex] = re.compile(regex) compiled_regex = CACHED_REGEXES[regex] = re.compile(regex)
return compiled_regex return compiled_regex
class mybuiltin(object): class mybuiltin(object):
""" """
NOTE could simple use a dict and populate it, NOTE could simple use a dict and populate it,
@@ -114,14 +116,16 @@ class mybuiltin(object):
try: try:
return getattr(__builtin__, key) return getattr(__builtin__, key)
except AttributeError: except AttributeError:
raise KeyError, key raise KeyError(key)
def __setitem__(self, key, value): def __setitem__(self, key, value):
setattr(self, key, value) setattr(self, key, value)
def LOAD(c=None, f='index', args=None, vars=None, def LOAD(c=None, f='index', args=None, vars=None,
extension=None, target=None,ajax=False,ajax_trap=False, extension=None, target=None, ajax=False, ajax_trap=False,
url=None,user_signature=False, timeout=None, times=1, url=None, user_signature=False, timeout=None, times=1,
content='loading...',**attr): content='loading...', **attr):
""" LOAD a component into the action's document """ LOAD a component into the action's document
Timing options: Timing options:
@@ -134,13 +138,14 @@ def LOAD(c=None, f='index', args=None, vars=None,
is added on page loading without delay. is added on page loading without delay.
""" """
from html import TAG, DIV, URL, SCRIPT, XML from html import TAG, DIV, URL, SCRIPT, XML
if args is None: args = [] if args is None:
args = []
vars = Storage(vars or {}) vars = Storage(vars or {})
target = target or 'c'+str(random.random())[2:] target = target or 'c' + str(random.random())[2:]
attr['_id']=target attr['_id'] = target
request = current.request request = current.request
if '.' in f: if '.' in f:
f, extension = f.rsplit('.',1) f, extension = f.rsplit('.', 1)
if url or ajax: if url or ajax:
url = url or URL(request.application, c, f, r=request, url = url or URL(request.application, c, f, r=request,
args=args, vars=vars, extension=extension, args=args, vars=vars, extension=extension,
@@ -160,19 +165,20 @@ def LOAD(c=None, f='index', args=None, vars=None,
if not isinstance(timeout, (int, long)): if not isinstance(timeout, (int, long)):
raise ValueError("Timeout argument must be an integer or None") raise ValueError("Timeout argument must be an integer or None")
elif timeout <= 0: elif timeout <= 0:
raise ValueError("Timeout argument must be greater than zero or None") raise ValueError(
"Timeout argument must be greater than zero or None")
statement = "web2py_component('%s','%s', %s, %s);" \ statement = "web2py_component('%s','%s', %s, %s);" \
% (url, target, timeout, times) % (url, target, timeout, times)
else: else:
statement = "web2py_component('%s','%s');" % (url, target) statement = "web2py_component('%s','%s');" % (url, target)
script = SCRIPT(statement, _type="text/javascript") script = SCRIPT(statement, _type="text/javascript")
if not content is None: if not content is None:
return TAG[''](script, DIV(content,**attr)) return TAG[''](script, DIV(content, **attr))
else: else:
return TAG[''](script) return TAG[''](script)
else: else:
if not isinstance(args,(list,tuple)): if not isinstance(args, (list, tuple)):
args = [args] args = [args]
c = c or request.controller c = c or request.controller
other_request = Storage(request) other_request = Storage(request)
@@ -186,17 +192,17 @@ def LOAD(c=None, f='index', args=None, vars=None,
other_request.post_vars = Storage() other_request.post_vars = Storage()
other_response = Response() other_response = Response()
other_request.env.path_info = '/' + \ other_request.env.path_info = '/' + \
'/'.join([request.application,c,f] + \ '/'.join([request.application, c, f] +
map(str, other_request.args)) map(str, other_request.args))
other_request.env.query_string = \ other_request.env.query_string = \
vars and URL(vars=vars).split('?')[1] or '' vars and URL(vars=vars).split('?')[1] or ''
other_request.env.http_web2py_component_location = \ other_request.env.http_web2py_component_location = \
request.env.path_info request.env.path_info
other_request.cid = target other_request.cid = target
other_request.env.http_web2py_component_element = target other_request.env.http_web2py_component_element = target
other_response.view = '%s/%s.%s' % (c,f, other_request.extension) other_response.view = '%s/%s.%s' % (c, f, other_request.extension)
other_environment = copy.copy(current.globalenv) ### NASTY other_environment = copy.copy(current.globalenv) # NASTY
other_response._view_environment = other_environment other_response._view_environment = other_environment
other_response.generic_patterns = \ other_response.generic_patterns = \
@@ -218,40 +224,41 @@ def LOAD(c=None, f='index', args=None, vars=None,
js = None js = None
if ajax_trap: if ajax_trap:
link = URL(request.application, c, f, r=request, link = URL(request.application, c, f, r=request,
args=args, vars=vars, extension=extension, args=args, vars=vars, extension=extension,
user_signature=user_signature) user_signature=user_signature)
js = "web2py_trap_form('%s','%s');" % (link, target) js = "web2py_trap_form('%s','%s');" % (link, target)
script = js and SCRIPT(js,_type="text/javascript") or '' script = js and SCRIPT(js, _type="text/javascript") or ''
return TAG[''](DIV(XML(page),**attr),script) return TAG[''](DIV(XML(page), **attr), script)
class LoadFactory(object): class LoadFactory(object):
""" """
Attention: this helper is new and experimental Attention: this helper is new and experimental
""" """
def __init__(self,environment): def __init__(self, environment):
self.environment = environment self.environment = environment
def __call__(self, c=None, f='index', args=None, vars=None, def __call__(self, c=None, f='index', args=None, vars=None,
extension=None, target=None,ajax=False,ajax_trap=False, extension=None, target=None, ajax=False, ajax_trap=False,
url=None,user_signature=False, content='loading...',**attr): url=None, user_signature=False, content='loading...', **attr):
if args is None: args = [] if args is None:
args = []
vars = Storage(vars or {}) vars = Storage(vars or {})
import globals import globals
target = target or 'c'+str(random.random())[2:] target = target or 'c' + str(random.random())[2:]
attr['_id']=target attr['_id'] = target
request = self.environment['request'] request = self.environment['request']
if '.' in f: if '.' in f:
f, extension = f.rsplit('.',1) f, extension = f.rsplit('.', 1)
if url or ajax: if url or ajax:
url = url or html.URL(request.application, c, f, r=request, url = url or html.URL(request.application, c, f, r=request,
args=args, vars=vars, extension=extension, args=args, vars=vars, extension=extension,
user_signature=user_signature) user_signature=user_signature)
script = html.SCRIPT('web2py_component("%s","%s")' % (url, target), script = html.SCRIPT('web2py_component("%s","%s")' % (url, target),
_type="text/javascript") _type="text/javascript")
return html.TAG[''](script, html.DIV(content,**attr)) return html.TAG[''](script, html.DIV(content, **attr))
else: else:
if not isinstance(args,(list,tuple)): if not isinstance(args, (list, tuple)):
args = [args] args = [args]
c = c or request.controller c = c or request.controller
@@ -266,15 +273,15 @@ class LoadFactory(object):
other_request.post_vars = Storage() other_request.post_vars = Storage()
other_response = globals.Response() other_response = globals.Response()
other_request.env.path_info = '/' + \ other_request.env.path_info = '/' + \
'/'.join([request.application,c,f] + \ '/'.join([request.application, c, f] +
map(str, other_request.args)) map(str, other_request.args))
other_request.env.query_string = \ other_request.env.query_string = \
vars and html.URL(vars=vars).split('?')[1] or '' vars and html.URL(vars=vars).split('?')[1] or ''
other_request.env.http_web2py_component_location = \ other_request.env.http_web2py_component_location = \
request.env.path_info request.env.path_info
other_request.cid = target other_request.cid = target
other_request.env.http_web2py_component_element = target other_request.env.http_web2py_component_element = target
other_response.view = '%s/%s.%s' % (c,f, other_request.extension) other_response.view = '%s/%s.%s' % (c, f, other_request.extension)
other_environment = copy.copy(self.environment) other_environment = copy.copy(self.environment)
other_response._view_environment = other_environment other_response._view_environment = other_environment
other_response.generic_patterns = \ other_response.generic_patterns = \
@@ -299,8 +306,8 @@ class LoadFactory(object):
args=args, vars=vars, extension=extension, args=args, vars=vars, extension=extension,
user_signature=user_signature) user_signature=user_signature)
js = "web2py_trap_form('%s','%s');" % (link, target) js = "web2py_trap_form('%s','%s');" % (link, target)
script = js and html.SCRIPT(js,_type="text/javascript") or '' script = js and html.SCRIPT(js, _type="text/javascript") or ''
return html.TAG[''](html.DIV(html.XML(page),**attr),script) return html.TAG[''](html.DIV(html.XML(page), **attr), script)
def local_import_aux(name, reload_force=False, app='welcome'): def local_import_aux(name, reload_force=False, app='welcome'):
@@ -321,7 +328,7 @@ def local_import_aux(name, reload_force=False, app='welcome'):
This prevents conflict between applications and un-necessary execs. This prevents conflict between applications and un-necessary execs.
It can be used to import any module, including regular Python modules. It can be used to import any module, including regular Python modules.
""" """
items = name.replace('/','.') items = name.replace('/', '.')
name = "applications.%s.modules.%s" % (app, items) name = "applications.%s.modules.%s" % (app, items)
module = __import__(name) module = __import__(name)
for item in name.split(".")[1:]: for item in name.split(".")[1:]:
@@ -355,12 +362,14 @@ OLD IMPLEMENTATION:
file.close() file.close()
imp.release_lock() imp.release_lock()
if not module: if not module:
raise ImportError, "cannot find module %s in %s" % (filename, modulepath) raise ImportError, "cannot find module %s in %s" % (
filename, modulepath)
return module return module
""" """
_base_environment_ = dict((k,getattr(html,k)) for k in html.__all__) _base_environment_ = dict((k, getattr(html, k)) for k in html.__all__)
_base_environment_.update((k,getattr(validators,k)) for k in validators.__all__) _base_environment_.update(
(k, getattr(validators, k)) for k in validators.__all__)
_base_environment_['__builtins__'] = __builtins__ _base_environment_['__builtins__'] = __builtins__
_base_environment_['HTTP'] = HTTP _base_environment_['HTTP'] = HTTP
_base_environment_['redirect'] = redirect _base_environment_['redirect'] = redirect
@@ -372,6 +381,7 @@ _base_environment_['SQLFORM'] = SQLFORM
_base_environment_['SQLTABLE'] = SQLTABLE _base_environment_['SQLTABLE'] = SQLTABLE
_base_environment_['LOAD'] = LOAD _base_environment_['LOAD'] = LOAD
def build_environment(request, response, session, store_current=True): def build_environment(request, response, session, store_current=True):
""" """
Build the environment dictionary into which web2py files are executed. Build the environment dictionary into which web2py files are executed.
@@ -384,7 +394,7 @@ def build_environment(request, response, session, store_current=True):
# Enable standard conditional models (i.e., /*.py, /[controller]/*.py, and # Enable standard conditional models (i.e., /*.py, /[controller]/*.py, and
# /[controller]/[function]/*.py) # /[controller]/[function]/*.py)
response.models_to_run = [r'^\w+\.py$', r'^%s/\w+\.py$' % request.controller, response.models_to_run = [r'^\w+\.py$', r'^%s/\w+\.py$' % request.controller,
r'^%s/%s/\w+\.py$' % (request.controller, request.function)] r'^%s/%s/\w+\.py$' % (request.controller, request.function)]
t = environment['T'] = translator(request) t = environment['T'] = translator(request)
c = environment['cache'] = Cache(request) c = environment['cache'] = Cache(request)
@@ -398,23 +408,24 @@ def build_environment(request, response, session, store_current=True):
current.cache = c current.cache = c
global __builtins__ global __builtins__
if is_jython: # jython hack if is_jython: # jython hack
__builtins__ = mybuiltin() __builtins__ = mybuiltin()
elif is_pypy: # apply the same hack to pypy too elif is_pypy: # apply the same hack to pypy too
__builtins__ = mybuiltin() __builtins__ = mybuiltin()
else: else:
__builtins__['__import__'] = __builtin__.__import__ ### WHY? __builtins__['__import__'] = __builtin__.__import__ # WHY?
environment['request'] = request environment['request'] = request
environment['response'] = response environment['response'] = response
environment['session'] = session environment['session'] = session
environment['local_import'] = \ environment['local_import'] = \
lambda name, reload=False, app=request.application:\ lambda name, reload=False, app=request.application:\
local_import_aux(name,reload,app) local_import_aux(name, reload, app)
BaseAdapter.set_folder(pjoin(request.folder, 'databases')) BaseAdapter.set_folder(pjoin(request.folder, 'databases'))
response._view_environment = copy.copy(environment) response._view_environment = copy.copy(environment)
custom_import_install() custom_import_install()
return environment return environment
def save_pyc(filename): def save_pyc(filename):
""" """
Bytecode compiles the file `filename` Bytecode compiles the file `filename`
@@ -431,7 +442,7 @@ def read_pyc(filename):
""" """
data = read_file(filename, 'rb') data = read_file(filename, 'rb')
if not is_gae and data[:4] != imp.get_magic(): if not is_gae and data[:4] != imp.get_magic():
raise SystemError, 'compiled code is incompatible' raise SystemError('compiled code is incompatible')
return marshal.loads(data[8:]) return marshal.loads(data[8:])
@@ -458,7 +469,7 @@ def compile_models(folder):
path = pjoin(folder, 'models') path = pjoin(folder, 'models')
for file in listdir(path, '.+\.py$'): for file in listdir(path, '.+\.py$'):
data = read_file(pjoin(path, file)) data = read_file(pjoin(path, file))
filename = pjoin(folder, 'compiled','models',file) filename = pjoin(folder, 'compiled', 'models', file)
mktree(filename) mktree(filename)
write_file(filename, data) write_file(filename, data)
save_pyc(filename) save_pyc(filename)
@@ -473,14 +484,14 @@ def compile_controllers(folder):
path = pjoin(folder, 'controllers') path = pjoin(folder, 'controllers')
for file in listdir(path, '.+\.py$'): for file in listdir(path, '.+\.py$'):
### why is this here? save_pyc(pjoin(path, file)) ### why is this here? save_pyc(pjoin(path, file))
data = read_file(pjoin(path,file)) data = read_file(pjoin(path, file))
exposed = regex_expose.findall(data) exposed = regex_expose.findall(data)
for function in exposed: for function in exposed:
command = data + "\nresponse._vars=response._caller(%s)\n" % \ command = data + "\nresponse._vars=response._caller(%s)\n" % \
function function
filename = pjoin(folder, 'compiled', ('controllers/' filename = pjoin(folder, 'compiled', ('controllers/'
+ file[:-3]).replace('/', '_') + file[:-3]).replace('/', '_')
+ '_' + function + '.py') + '_' + function + '.py')
write_file(filename, command) write_file(filename, command)
save_pyc(filename) save_pyc(filename)
os.unlink(filename) os.unlink(filename)
@@ -500,19 +511,19 @@ def run_models_in(environment):
for model in listdir(cpath, '^models_\w+\.pyc$', 0): for model in listdir(cpath, '^models_\w+\.pyc$', 0):
restricted(read_pyc(model), environment, layer=model) restricted(read_pyc(model), environment, layer=model)
path = pjoin(cpath, 'models') path = pjoin(cpath, 'models')
models = listdir(path, '^\w+\.pyc$',0,sort=False) models = listdir(path, '^\w+\.pyc$', 0, sort=False)
compiled=True compiled = True
else: else:
path = pjoin(folder, 'models') path = pjoin(folder, 'models')
models = listdir(path, '^\w+\.py$',0,sort=False) models = listdir(path, '^\w+\.py$', 0, sort=False)
compiled=False compiled = False
n = len(path) + 1 n = len(path) + 1
for model in models: for model in models:
regex = environment['response'].models_to_run regex = environment['response'].models_to_run
if isinstance(regex, list): if isinstance(regex, list):
regex = re_compile('|'.join(regex)) regex = re_compile('|'.join(regex))
file = model[n:].replace(os.path.sep, '/').replace('.pyc', '.py') file = model[n:].replace(os.path.sep, '/').replace('.pyc', '.py')
if not regex.search(file) and c!= 'appadmin': if not regex.search(file) and c != 'appadmin':
continue continue
elif compiled: elif compiled:
code = read_pyc(model) code = read_pyc(model)
@@ -538,7 +549,7 @@ def run_controller_in(controller, function, environment):
badf = 'invalid function (%s/%s)' % (controller, function) badf = 'invalid function (%s/%s)' % (controller, function)
if os.path.exists(path): if os.path.exists(path):
filename = pjoin(path, 'controllers_%s_%s.pyc' filename = pjoin(path, 'controllers_%s_%s.pyc'
% (controller, function)) % (controller, function))
if not os.path.exists(filename): if not os.path.exists(filename):
raise HTTP(404, raise HTTP(404,
rewrite.THREAD_LOCAL.routes.error_message % badf, rewrite.THREAD_LOCAL.routes.error_message % badf,
@@ -548,7 +559,8 @@ def run_controller_in(controller, function, environment):
# TESTING: adjust the path to include site packages # TESTING: adjust the path to include site packages
from settings import global_settings from settings import global_settings
from admin import abspath, add_path_first from admin import abspath, add_path_first
paths = (global_settings.gluon_parent, abspath('site-packages', gluon=True), abspath('gluon', gluon=True), '') paths = (global_settings.gluon_parent, abspath(
'site-packages', gluon=True), abspath('gluon', gluon=True), '')
[add_path_first(path) for path in paths] [add_path_first(path) for path in paths]
# TESTING END # TESTING END
@@ -578,18 +590,19 @@ def run_controller_in(controller, function, environment):
code = "%s\nresponse._vars=response._caller(%s)\n" % (code, function) code = "%s\nresponse._vars=response._caller(%s)\n" % (code, function)
if is_gae: if is_gae:
layer = filename + ':' + function layer = filename + ':' + function
code = getcfs(layer, filename, lambda: compile2(code,layer)) code = getcfs(layer, filename, lambda: compile2(code, layer))
restricted(code, environment, filename) restricted(code, environment, filename)
response = environment['response'] response = environment['response']
vars=response._vars vars = response._vars
if response.postprocessing: if response.postprocessing:
vars = reduce(lambda vars, p: p(vars), response.postprocessing, vars) vars = reduce(lambda vars, p: p(vars), response.postprocessing, vars)
if isinstance(vars,unicode): if isinstance(vars, unicode):
vars = vars.encode('utf8') vars = vars.encode('utf8')
elif hasattr(vars,'xml') and callable(vars.xml): elif hasattr(vars, 'xml') and callable(vars.xml):
vars = vars.xml() vars = vars.xml()
return vars return vars
def run_view_in(environment): def run_view_in(environment):
""" """
Executes the view for the requested action. Executes the view for the requested action.
@@ -606,7 +619,7 @@ def run_view_in(environment):
if response.generic_patterns: if response.generic_patterns:
patterns = response.generic_patterns patterns = response.generic_patterns
regex = re_compile('|'.join(map(fnmatch.translate, patterns))) regex = re_compile('|'.join(map(fnmatch.translate, patterns)))
short_action = '%(controller)s/%(function)s.%(extension)s' % request short_action = '%(controller)s/%(function)s.%(extension)s' % request
allow_generic = regex.search(short_action) allow_generic = regex.search(short_action)
else: else:
allow_generic = False allow_generic = False
@@ -626,7 +639,7 @@ def run_view_in(environment):
files.append('views_generic.pyc') files.append('views_generic.pyc')
# end backward compatibility code # end backward compatibility code
for f in files: for f in files:
filename = pjoin(path,f) filename = pjoin(path, f)
if os.path.exists(filename): if os.path.exists(filename):
code = read_pyc(filename) code = read_pyc(filename)
restricted(code, environment, layer=filename) restricted(code, environment, layer=filename)
@@ -648,13 +661,14 @@ def run_view_in(environment):
ccode = getcfs(layer, filename, ccode = getcfs(layer, filename,
lambda: compile2(parse_template(view, lambda: compile2(parse_template(view,
pjoin(folder, 'views'), pjoin(folder, 'views'),
context=environment),layer)) context=environment), layer))
else: else:
ccode = parse_template(view, ccode = parse_template(view,
pjoin(folder, 'views'), pjoin(folder, 'views'),
context=environment) context=environment)
restricted(ccode, environment, layer) restricted(ccode, environment, layer)
def remove_compiled_application(folder): def remove_compiled_application(folder):
""" """
Deletes the folder `compiled` containing the compiled application. Deletes the folder `compiled` containing the compiled application.
@@ -662,7 +676,7 @@ def remove_compiled_application(folder):
try: try:
shutil.rmtree(pjoin(folder, 'compiled')) shutil.rmtree(pjoin(folder, 'compiled'))
path = pjoin(folder, 'controllers') path = pjoin(folder, 'controllers')
for file in listdir(path,'.*\.pyc$',drop=False): for file in listdir(path, '.*\.pyc$', drop=False):
os.unlink(file) os.unlink(file)
except OSError: except OSError:
pass pass
+5 -5
View File
@@ -700,7 +700,7 @@ CONTENT_TYPE = {
'.zabw': 'application/x-abiword', '.zabw': 'application/x-abiword',
'.zip': 'application/zip', '.zip': 'application/zip',
'.zoo': 'application/x-zoo', '.zoo': 'application/x-zoo',
} }
def contenttype(filename, default='text/plain'): def contenttype(filename, default='text/plain'):
@@ -709,11 +709,11 @@ def contenttype(filename, default='text/plain'):
""" """
i = filename.rfind('.') i = filename.rfind('.')
if i>=0: if i >= 0:
default = CONTENT_TYPE.get(filename[i:].lower(),default) default = CONTENT_TYPE.get(filename[i:].lower(), default)
j = filename.rfind('.', 0, i) j = filename.rfind('.', 0, i)
if j>=0: if j >= 0:
default = CONTENT_TYPE.get(filename[j:].lower(),default) default = CONTENT_TYPE.get(filename[j:].lower(), default)
if default.startswith('text/'): if default.startswith('text/'):
default += '; charset=utf-8' default += '; charset=utf-8'
return default return default
+38 -27
View File
@@ -21,6 +21,7 @@ import urllib
_known_tuple_types = {} _known_tuple_types = {}
class NamedTupleBase(tuple): class NamedTupleBase(tuple):
"""Base class for named tuples with the __new__ operator set, named tuples """Base class for named tuples with the __new__ operator set, named tuples
yielded by the namedtuple() function will subclass this and add yielded by the namedtuple() function will subclass this and add
@@ -29,7 +30,7 @@ class NamedTupleBase(tuple):
"""Create a new instance of this fielded tuple""" """Create a new instance of this fielded tuple"""
# May need to unpack named field values here # May need to unpack named field values here
if kws: if kws:
values = list(args) + [None]*(len(cls._fields) - len(args)) values = list(args) + [None] * (len(cls._fields) - len(args))
fields = dict((val, idx) for idx, val in enumerate(cls._fields)) fields = dict((val, idx) for idx, val in enumerate(cls._fields))
for kw, val in kws.iteritems(): for kw, val in kws.iteritems():
assert kw in kws, "%r not in field list" % kw assert kw in kws, "%r not in field list" % kw
@@ -37,6 +38,7 @@ class NamedTupleBase(tuple):
args = tuple(values) args = tuple(values)
return tuple.__new__(cls, args) return tuple.__new__(cls, args)
def namedtuple(typename, fieldnames): def namedtuple(typename, fieldnames):
""" """
>>> import namedtuples >>> import namedtuples
@@ -75,24 +77,26 @@ def namedtuple(typename, fieldnames):
# Done # Done
return new_tuple_type return new_tuple_type
class AIM: class AIM:
class AIMError(Exception): class AIMError(Exception):
def __init__(self, value): def __init__(self, value):
self.parameter = value self.parameter = value
def __str__(self): def __str__(self):
return str(self.parameter) return str(self.parameter)
def __init__(self, login, transkey, testmode=False): def __init__(self, login, transkey, testmode=False):
if str(login).strip() == '' or login == None: if str(login).strip() == '' or login is None:
raise AIM.AIMError('No login name provided') raise AIM.AIMError('No login name provided')
if str(transkey).strip() == '' or transkey == None: if str(transkey).strip() == '' or transkey is None:
raise AIM.AIMError('No transaction key provided') raise AIM.AIMError('No transaction key provided')
if testmode != True and testmode != False: if testmode != True and testmode != False:
raise AIM.AIMError('Invalid value for testmode. Must be True or False. "{0}" given.'.format(testmode)) raise AIM.AIMError('Invalid value for testmode. Must be True or False. "{0}" given.'.format(testmode))
self.testmode = testmode self.testmode = testmode
self.proxy = None; self.proxy = None
self.delimiter = '|' self.delimiter = '|'
self.results = [] self.results = []
self.error = True self.error = True
@@ -117,8 +121,9 @@ class AIM:
else: else:
url = 'https://secure.authorize.net/gateway/transact.dll' url = 'https://secure.authorize.net/gateway/transact.dll'
if self.proxy == None: if self.proxy is None:
self.results += str(urllib.urlopen(url, encoded_args).read()).split(self.delimiter) self.results += str(urllib.urlopen(
url, encoded_args).read()).split(self.delimiter)
else: else:
opener = urllib.FancyURLopener(self.proxy) opener = urllib.FancyURLopener(self.proxy)
opened = opener.open(url, encoded_args) opened = opener.open(url, encoded_args)
@@ -147,36 +152,37 @@ class AIM:
raise AIM.AIMError(self.response.ResponseText) raise AIM.AIMError(self.response.ResponseText)
def setTransaction(self, creditcard, expiration, total, cvv=None, tax=None, invoice=None): def setTransaction(self, creditcard, expiration, total, cvv=None, tax=None, invoice=None):
if str(creditcard).strip() == '' or creditcard == None: if str(creditcard).strip() == '' or creditcard is None:
raise AIM.AIMError('No credit card number passed to setTransaction(): {0}'.format(creditcard)) raise AIM.AIMError('No credit card number passed to setTransaction(): {0}'.format(creditcard))
if str(expiration).strip() == '' or expiration == None: if str(expiration).strip() == '' or expiration is None:
raise AIM.AIMError('No expiration number to setTransaction(): {0}'.format(expiration)) raise AIM.AIMError('No expiration number to setTransaction(): {0}'.format(expiration))
if str(total).strip() == '' or total == None: if str(total).strip() == '' or total is None:
raise AIM.AIMError('No total amount passed to setTransaction(): {0}'.format(total)) raise AIM.AIMError('No total amount passed to setTransaction(): {0}'.format(total))
self.setParameter('x_card_num', creditcard) self.setParameter('x_card_num', creditcard)
self.setParameter('x_exp_date', expiration) self.setParameter('x_exp_date', expiration)
self.setParameter('x_amount', total) self.setParameter('x_amount', total)
if cvv != None: if cvv is not None:
self.setParameter('x_card_code', cvv) self.setParameter('x_card_code', cvv)
if tax != None: if tax is not None:
self.setParameter('x_tax', tax) self.setParameter('x_tax', tax)
if invoice != None: if invoice is not None:
self.setParameter('x_invoice_num', invoice) self.setParameter('x_invoice_num', invoice)
def setTransactionType(self, transtype=None): def setTransactionType(self, transtype=None):
types = ['AUTH_CAPTURE', 'AUTH_ONLY', 'PRIOR_AUTH_CAPTURE', 'CREDIT', 'CAPTURE_ONLY', 'VOID'] types = ['AUTH_CAPTURE', 'AUTH_ONLY', 'PRIOR_AUTH_CAPTURE',
'CREDIT', 'CAPTURE_ONLY', 'VOID']
if transtype.upper() not in types: if transtype.upper() not in types:
raise AIM.AIMError('Incorrect Transaction Type passed to setTransactionType(): {0}'.format(transtype)) raise AIM.AIMError('Incorrect Transaction Type passed to setTransactionType(): {0}'.format(transtype))
self.setParameter('x_type', transtype.upper()) self.setParameter('x_type', transtype.upper())
def setProxy(self, proxy=None): def setProxy(self, proxy=None):
if str(proxy).strip() == '' or proxy == None: if str(proxy).strip() == '' or proxy is None:
raise AIM.AIMError('No proxy passed to setProxy()') raise AIM.AIMError('No proxy passed to setProxy()')
self.proxy = {'http': str(proxy).strip()} self.proxy = {'http': str(proxy).strip()}
def setParameter(self, key=None, value=None): def setParameter(self, key=None, value=None):
if key != None and value != None and str(key).strip() != '' and str(value).strip() != '': if key is not None and value is not None and str(key).strip() != '' and str(value).strip() != '':
self.parameters[key] = str(value).strip() self.parameters[key] = str(value).strip()
else: else:
raise AIM.AIMError('Incorrect parameters passed to setParameter(): {0}:{1}'.format(key, value)) raise AIM.AIMError('Incorrect parameters passed to setParameter(): {0}:{1}'.format(key, value))
@@ -194,10 +200,11 @@ class AIM:
responses = ['', 'Approved', 'Declined', 'Error'] responses = ['', 'Approved', 'Declined', 'Error']
return responses[int(self.results[0])] return responses[int(self.results[0])]
def process(creditcard,expiration,total,cvv=None,tax=None,invoice=None,
login='cnpdev4289', transkey='SR2P8g4jdEn7vFLQ',testmode=True): def process(creditcard, expiration, total, cvv=None, tax=None, invoice=None,
payment = AIM(login,transkey,testmode) login='cnpdev4289', transkey='SR2P8g4jdEn7vFLQ', testmode=True):
expiration = expiration.replace('/','') payment = AIM(login, transkey, testmode)
expiration = expiration.replace('/', '')
payment.setTransaction(creditcard, expiration, total, cvv, tax, invoice) payment.setTransaction(creditcard, expiration, total, cvv, tax, invoice)
try: try:
payment.process() payment.process()
@@ -205,6 +212,7 @@ def process(creditcard,expiration,total,cvv=None,tax=None,invoice=None,
except AIM.AIMError: except AIM.AIMError:
return False return False
def test(): def test():
import socket import socket
import sys import sys
@@ -215,12 +223,14 @@ def test():
total = '1.00' total = '1.00'
cvv = '123' cvv = '123'
tax = '0.00' tax = '0.00'
invoice = str(time())[4:10] # get a random invoice number invoice = str(time())[4:10] # get a random invoice number
try: try:
payment = AIM('cnpdev4289', 'SR2P8g4jdEn7vFLQ', True) payment = AIM('cnpdev4289', 'SR2P8g4jdEn7vFLQ', True)
payment.setTransaction(creditcard, expiration, total, cvv, tax, invoice) payment.setTransaction(
payment.setParameter('x_duplicate_window', 180) # three minutes duplicate windows creditcard, expiration, total, cvv, tax, invoice)
payment.setParameter(
'x_duplicate_window', 180) # three minutes duplicate windows
payment.setParameter('x_cust_id', '1324') # customer ID payment.setParameter('x_cust_id', '1324') # customer ID
payment.setParameter('x_first_name', 'John') payment.setParameter('x_first_name', 'John')
payment.setParameter('x_last_name', 'Conde') payment.setParameter('x_last_name', 'Conde')
@@ -232,7 +242,8 @@ def test():
payment.setParameter('x_country', 'US') payment.setParameter('x_country', 'US')
payment.setParameter('x_phone', '800-555-1234') payment.setParameter('x_phone', '800-555-1234')
payment.setParameter('x_description', 'Test Transaction') payment.setParameter('x_description', 'Test Transaction')
payment.setParameter('x_customer_ip', socket.gethostbyname(socket.gethostname())) payment.setParameter(
'x_customer_ip', socket.gethostbyname(socket.gethostname()))
payment.setParameter('x_email', 'john@example.com') payment.setParameter('x_email', 'john@example.com')
payment.setParameter('x_email_customer', False) payment.setParameter('x_email_customer', False)
payment.process() payment.process()
@@ -251,9 +262,9 @@ def test():
except AIM.AIMError, e: except AIM.AIMError, e:
print "Exception thrown:", e print "Exception thrown:", e
print 'An error occured' print 'An error occured'
print 'approved',payment.isApproved() print 'approved', payment.isApproved()
print 'declined',payment.isDeclined() print 'declined', payment.isDeclined()
print 'error',payment.isError() print 'error', payment.isError()
if __name__=='__main__': if __name__ == '__main__':
test() test()
+31 -26
View File
@@ -15,25 +15,27 @@ __all__ = ['DowCommerce']
from operator import itemgetter from operator import itemgetter
import urllib import urllib
class DowCommerce: class DowCommerce:
class DowCommerceError(Exception): class DowCommerceError(Exception):
def __init__(self, value): def __init__(self, value):
self.parameter = value self.parameter = value
def __str__(self): def __str__(self):
return str(self.parameter) return str(self.parameter)
def __init__(self, username=None, password=None, demomode=False): def __init__(self, username=None, password=None, demomode=False):
if not demomode: if not demomode:
if str(username).strip() == '' or username == None: if str(username).strip() == '' or username is None:
raise DowCommerce.DowCommerceError('No username provided') raise DowCommerce.DowCommerceError('No username provided')
if str(password).strip() == '' or password == None: if str(password).strip() == '' or password is None:
raise DowCommerce.DowCommerceError('No password provided') raise DowCommerce.DowCommerceError('No password provided')
else: else:
username = 'demo' username = 'demo'
password = 'password' password = 'password'
self.proxy = None; self.proxy = None
self.delimiter = '&' self.delimiter = '&'
self.results = {} self.results = {}
self.error = True self.error = True
@@ -45,11 +47,11 @@ class DowCommerce:
self.setParameter('username', username) self.setParameter('username', username)
self.setParameter('password', password) self.setParameter('password', password)
def process(self): def process(self):
encoded_args = urllib.urlencode(self.parameters) encoded_args = urllib.urlencode(self.parameters)
if self.proxy == None: if self.proxy is None:
results = str(urllib.urlopen(self.url, encoded_args).read()).split(self.delimiter) results = str(urllib.urlopen(
self.url, encoded_args).read()).split(self.delimiter)
else: else:
opener = urllib.FancyURLopener(self.proxy) opener = urllib.FancyURLopener(self.proxy)
opened = opener.open(self.url, encoded_args) opened = opener.open(self.url, encoded_args)
@@ -59,7 +61,7 @@ class DowCommerce:
opened.close() opened.close()
for result in results: for result in results:
(key,val) = result.split('=') (key, val) = result.split('=')
self.results[key] = val self.results[key] = val
if self.results['response'] == '1': if self.results['response'] == '1':
@@ -80,17 +82,18 @@ class DowCommerce:
self.declined = False self.declined = False
raise DowCommerce.DowCommerceError(self.results) raise DowCommerce.DowCommerceError(self.results)
def setTransaction(self, creditcard, expiration, total, cvv=None, orderid=None, orderdescription=None, def setTransaction(
ipaddress=None, tax=None, shipping=None, self, creditcard, expiration, total, cvv=None, orderid=None, orderdescription=None,
firstname=None, lastname=None, company=None, address1=None, address2=None, city=None, state=None, zipcode=None, ipaddress=None, tax=None, shipping=None,
country=None, phone=None, fax=None, emailaddress=None, website=None, firstname=None, lastname=None, company=None, address1=None, address2=None, city=None, state=None, zipcode=None,
shipping_firstname=None, shipping_lastname=None, shipping_company=None, shipping_address1=None, shipping_address2=None, country=None, phone=None, fax=None, emailaddress=None, website=None,
shipping_city=None, shipping_state=None, shipping_zipcode = None, shipping_country=None, shipping_emailaddress=None): shipping_firstname=None, shipping_lastname=None, shipping_company=None, shipping_address1=None, shipping_address2=None,
if str(creditcard).strip() == '' or creditcard == None: shipping_city=None, shipping_state=None, shipping_zipcode=None, shipping_country=None, shipping_emailaddress=None):
if str(creditcard).strip() == '' or creditcard is None:
raise DowCommerce.DowCommerceError('No credit card number passed to setTransaction(): {0}'.format(creditcard)) raise DowCommerce.DowCommerceError('No credit card number passed to setTransaction(): {0}'.format(creditcard))
if str(expiration).strip() == '' or expiration == None: if str(expiration).strip() == '' or expiration is None:
raise DowCommerce.DowCommerceError('No expiration number passed to setTransaction(): {0}'.format(expiration)) raise DowCommerce.DowCommerceError('No expiration number passed to setTransaction(): {0}'.format(expiration))
if str(total).strip() == '' or total == None: if str(total).strip() == '' or total is None:
raise DowCommerce.DowCommerceError('No total amount passed to setTransaction(): {0}'.format(total)) raise DowCommerce.DowCommerceError('No total amount passed to setTransaction(): {0}'.format(total))
self.setParameter('ccnumber', creditcard) self.setParameter('ccnumber', creditcard)
@@ -165,12 +168,12 @@ class DowCommerce:
self.setParameter('type', transtype.lower()) self.setParameter('type', transtype.lower())
def setProxy(self, proxy=None): def setProxy(self, proxy=None):
if str(proxy).strip() == '' or proxy == None: if str(proxy).strip() == '' or proxy is None:
raise DowCommerce.DowCommerceError('No proxy passed to setProxy()') raise DowCommerce.DowCommerceError('No proxy passed to setProxy()')
self.proxy = {'http': str(proxy).strip()} self.proxy = {'http': str(proxy).strip()}
def setParameter(self, key=None, value=None): def setParameter(self, key=None, value=None):
if key != None and value != None and str(key).strip() != '' and str(value).strip() != '': if key is not None and value is not None and str(key).strip() != '' and str(value).strip() != '':
self.parameters[key] = str(value).strip() self.parameters[key] = str(value).strip()
else: else:
raise DowCommerce.DowCommerceError('Incorrect parameters passed to setParameter(): {0}:{1}'.format(key, value)) raise DowCommerce.DowCommerceError('Incorrect parameters passed to setParameter(): {0}:{1}'.format(key, value))
@@ -194,6 +197,7 @@ class DowCommerce:
def getResponseText(self): def getResponseText(self):
return self.results['responsetext'] return self.results['responsetext']
def test(): def test():
import socket import socket
import sys import sys
@@ -212,13 +216,14 @@ def test():
total = '1.00' total = '1.00'
cvv = '999' cvv = '999'
tax = '0.00' tax = '0.00'
orderid = str(time())[4:10] # get a random invoice number orderid = str(time())[4:10] # get a random invoice number
try: try:
payment = DowCommerce(demomode=True) payment = DowCommerce(demomode=True)
payment.setTransaction(creditcard, expiration, total, cvv=cvv, tax=tax, orderid=orderid, orderdescription='Test Transaction', payment.setTransaction(
firstname='John', lastname='Doe', company='Acme', address1='123 Min Street', city='Hometown', state='VA', creditcard, expiration, total, cvv=cvv, tax=tax, orderid=orderid, orderdescription='Test Transaction',
zipcode='12345', country='US', phone='888-555-1212', emailaddress='john@noemail.local', ipaddress='192.168.1.1') firstname='John', lastname='Doe', company='Acme', address1='123 Min Street', city='Hometown', state='VA',
zipcode='12345', country='US', phone='888-555-1212', emailaddress='john@noemail.local', ipaddress='192.168.1.1')
payment.process() payment.process()
if payment.isApproved(): if payment.isApproved():
@@ -231,9 +236,9 @@ def test():
except DowCommerce.DowCommerceError, e: except DowCommerce.DowCommerceError, e:
print "Exception thrown:", e print "Exception thrown:", e
print 'An error occured' print 'An error occured'
print 'approved',payment.isApproved() print 'approved', payment.isApproved()
print 'declined',payment.isDeclined() print 'declined', payment.isDeclined()
print 'error',payment.isError() print 'error', payment.isError()
if __name__=='__main__': if __name__ == '__main__':
test() test()
-1
View File
@@ -1 +0,0 @@
+29 -16
View File
@@ -42,7 +42,9 @@ revision3.com
viddler.com viddler.com
""" """
import re, cgi, sys import re
import cgi
import sys
from simplejson import loads from simplejson import loads
import urllib import urllib
import uuid import uuid
@@ -75,23 +77,28 @@ EMBED_MAPS = [
'http://revision3.com/api/oembed/'), 'http://revision3.com/api/oembed/'),
(re.compile('http://\S+.viddler.com/\S+'), (re.compile('http://\S+.viddler.com/\S+'),
'http://lab.viddler.com/services/oembed/'), 'http://lab.viddler.com/services/oembed/'),
] ]
def image(url): def image(url):
return '<img src="%s" style="max-width:100%%"/>' % url return '<img src="%s" style="max-width:100%%"/>' % url
def audio(url): def audio(url):
return '<audio controls="controls" style="max-width:100%%"><source src="%s" /></audio>' % url return '<audio controls="controls" style="max-width:100%%"><source src="%s" /></audio>' % url
def video(url): def video(url):
return '<video controls="controls" style="max-width:100%%"><source src="%s" /></video>' % url return '<video controls="controls" style="max-width:100%%"><source src="%s" /></video>' % url
def googledoc_viewer(url): def googledoc_viewer(url):
return '<iframe src="http://docs.google.com/viewer?url=%s&embedded=true" style="max-width:100%%"></iframe>' % urllib.quote(url) return '<iframe src="http://docs.google.com/viewer?url=%s&embedded=true" style="max-width:100%%"></iframe>' % urllib.quote(url)
def web2py_component(url): def web2py_component(url):
code = str(uuid.uuid4()) code = str(uuid.uuid4())
return '<div id="%s"></div><script>\nweb2py_component("%s","%s");\n</script>' % (code,url,code) return '<div id="%s"></div><script>\nweb2py_component("%s","%s");\n</script>' % (code, url, code)
EXTENSION_MAPS = { EXTENSION_MAPS = {
'png': image, 'png': image,
@@ -126,33 +133,37 @@ EXTENSION_MAPS = {
'xps': googledoc_viewer, 'xps': googledoc_viewer,
} }
class VimeoURLOpener(urllib.FancyURLopener): class VimeoURLOpener(urllib.FancyURLopener):
"Vimeo blocks the urllib user agent for some reason" "Vimeo blocks the urllib user agent for some reason"
version = "Mozilla/4.0" version = "Mozilla/4.0"
urllib._urlopener = VimeoURLOpener() urllib._urlopener = VimeoURLOpener()
def oembed(url): def oembed(url):
for k,v in EMBED_MAPS: for k, v in EMBED_MAPS:
if k.match(url): if k.match(url):
oembed = v+'?format=json&url='+cgi.escape(url) oembed = v + '?format=json&url=' + cgi.escape(url)
try: try:
data = urllib.urlopen(oembed).read() data = urllib.urlopen(oembed).read()
print data print data
return loads(data) # json! return loads(data) # json!
except: except:
pass pass
return {} return {}
def extension(url): def extension(url):
return url.split('?')[0].split('.')[-1].lower() return url.split('?')[0].split('.')[-1].lower()
def expand_one(url,cdict):
def expand_one(url, cdict):
# try ombed but first check in cache # try ombed but first check in cache
if cdict and url in cdict: if cdict and url in cdict:
r = cdict[url] r = cdict[url]
else: else:
r = oembed(url) r = oembed(url)
if isinstance(cdict,dict): if isinstance(cdict, dict):
cdict[url] = r cdict[url] = r
# if oembed service # if oembed service
if 'html' in r: if 'html' in r:
@@ -170,21 +181,23 @@ def expand_one(url,cdict):
# else regular link # else regular link
return '<a href="%(u)s">%(u)s</a>' % dict(u=url) return '<a href="%(u)s">%(u)s</a>' % dict(u=url)
def expand_html(html,cdict=None):
def expand_html(html, cdict=None):
if not have_soup: if not have_soup:
raise RuntimeError, "Missing BeautifulSoup" raise RuntimeError("Missing BeautifulSoup")
soup = BeautifulSoup(html) soup = BeautifulSoup(html)
comments = soup.findAll(text=lambda text:isinstance(text, Comment)) comments = soup.findAll(text=lambda text: isinstance(text, Comment))
[comment.extract() for comment in comments] [comment.extract() for comment in comments]
for txt in soup.findAll(text=True): for txt in soup.findAll(text=True):
if not txt.parent.name in ('a','script','pre','code','embed','object','audio','video'): if not txt.parent.name in ('a', 'script', 'pre', 'code', 'embed', 'object', 'audio', 'video'):
ntxt = regex_link.sub( ntxt = regex_link.sub(
lambda match: expand_one(match.group(0),cdict), txt) lambda match: expand_one(match.group(0), cdict), txt)
txt.replaceWith(BeautifulSoup(ntxt)) txt.replaceWith(BeautifulSoup(ntxt))
return str(soup) return str(soup)
def test(): def test():
example=""" example = """
<h3>Fringilla nisi parturient nullam</h3> <h3>Fringilla nisi parturient nullam</h3>
<p>http://www.youtube.com/watch?v=IWBFiI5RrA0</p> <p>http://www.youtube.com/watch?v=IWBFiI5RrA0</p>
<p>http://www.web2py.com/examples/static/images/logo_bw.png</p> <p>http://www.web2py.com/examples/static/images/logo_bw.png</p>
@@ -198,8 +211,8 @@ laoreet tortor.</p>
""" """
return expand_html(example) return expand_html(example)
if __name__=="__main__": if __name__ == "__main__":
if len(sys.argv)>1: if len(sys.argv) > 1:
print expand_html(open(sys.argv[1]).read()) print expand_html(open(sys.argv[1]).read())
else: else:
print test() print test()
+12 -11
View File
@@ -12,6 +12,7 @@ cache.ram=cache.disk=MemcacheClient(request)
import time import time
from google.appengine.api.memcache import Client from google.appengine.api.memcache import Client
class MemcacheClient(object): class MemcacheClient(object):
client = Client() client = Client()
@@ -24,12 +25,12 @@ class MemcacheClient(object):
key, key,
f, f,
time_expire=300, time_expire=300,
): ):
key = '%s/%s' % (self.request.application, key) key = '%s/%s' % (self.request.application, key)
dt = time_expire dt = time_expire
value = None value = None
obj = self.client.get(key) obj = self.client.get(key)
if obj and (dt == None or obj[0] > time.time() - dt): if obj and (dt is None or obj[0] > time.time() - dt):
value = obj[1] value = obj[1]
elif f is None: elif f is None:
if obj: if obj:
@@ -47,21 +48,21 @@ class MemcacheClient(object):
self.client.set(key, (time.time(), value)) self.client.set(key, (time.time(), value))
return value return value
def clear(self, key = None): def clear(self, key=None):
if key: if key:
key = '%s/%s' % (self.request.application, key) key = '%s/%s' % (self.request.application, key)
self.client.delete(key) self.client.delete(key)
else: else:
self.client.flush_all() self.client.flush_all()
def delete(self,*a,**b): def delete(self, *a, **b):
return self.client.delete(*a,**b) return self.client.delete(*a, **b)
def get(self,*a,**b): def get(self, *a, **b):
return self.client.delete(*a,**b) return self.client.delete(*a, **b)
def set(self,*a,**b): def set(self, *a, **b):
return self.client.delete(*a,**b) return self.client.delete(*a, **b)
def flush_all(self,*a,**b): def flush_all(self, *a, **b):
return self.client.delete(*a,**b) return self.client.delete(*a, **b)
+8 -5
View File
@@ -51,7 +51,8 @@ def autoretry_datastore_timeouts(attempts=5.0, interval=0.1, exponent=2.0):
:param exponent: rate of exponential back-off. :param exponent: rate of exponential back-off.
""" """
import time, logging import time
import logging
from google.appengine.api import apiproxy_stub_map from google.appengine.api import apiproxy_stub_map
from google.appengine.runtime import apiproxy_errors from google.appengine.runtime import apiproxy_errors
from google.appengine.datastore import datastore_pb from google.appengine.datastore import datastore_pb
@@ -60,8 +61,8 @@ def autoretry_datastore_timeouts(attempts=5.0, interval=0.1, exponent=2.0):
interval = float(interval) interval = float(interval)
exponent = float(exponent) exponent = float(exponent)
wrapped = apiproxy_stub_map.MakeSyncCall wrapped = apiproxy_stub_map.MakeSyncCall
errors = {datastore_pb.Error.TIMEOUT:'Timeout', errors = {datastore_pb.Error.TIMEOUT: 'Timeout',
datastore_pb.Error.CONCURRENT_TRANSACTION:'TransactionFailedError'} datastore_pb.Error.CONCURRENT_TRANSACTION: 'TransactionFailedError'}
def wrapper(*args, **kwargs): def wrapper(*args, **kwargs):
count = 0.0 count = 0.0
@@ -70,10 +71,12 @@ def autoretry_datastore_timeouts(attempts=5.0, interval=0.1, exponent=2.0):
return wrapped(*args, **kwargs) return wrapped(*args, **kwargs)
except apiproxy_errors.ApplicationError, err: except apiproxy_errors.ApplicationError, err:
errno = err.application_error errno = err.application_error
if errno not in errors: raise if errno not in errors:
raise
sleep = (exponent ** count) * interval sleep = (exponent ** count) * interval
count += 1.0 count += 1.0
if count > attempts: raise if count > attempts:
raise
msg = "Datastore %s: retry #%d in %s seconds.\n%s" msg = "Datastore %s: retry #%d in %s seconds.\n%s"
vals = '' vals = ''
if count == 1.0: if count == 1.0:
+25 -16
View File
@@ -12,6 +12,7 @@ from gluon.sanitizer import sanitize
from gluon.contrib.markmin.markmin2latex import markmin2latex from gluon.contrib.markmin.markmin2latex import markmin2latex
from gluon.contrib.markmin.markmin2pdf import markmin2pdf from gluon.contrib.markmin.markmin2pdf import markmin2pdf
def wrapper(f): def wrapper(f):
def g(data): def g(data):
try: try:
@@ -25,39 +26,47 @@ def wrapper(f):
raise HTTP(405, '%s error' % e) raise HTTP(405, '%s error' % e)
return g return g
def latex_from_html(html): def latex_from_html(html):
markmin=TAG(html).element('body').flatten(markmin_serializer) markmin = TAG(html).element('body').flatten(markmin_serializer)
return XML(markmin2latex(markmin)) return XML(markmin2latex(markmin))
def pdflatex_from_html(html): def pdflatex_from_html(html):
if os.system('which pdflatex > /dev/null')==0: if os.system('which pdflatex > /dev/null') == 0:
markmin=TAG(html).element('body').flatten(markmin_serializer) markmin = TAG(html).element('body').flatten(markmin_serializer)
out,warnings,errors=markmin2pdf(markmin) out, warnings, errors = markmin2pdf(markmin)
if errors: if errors:
current.response.headers['Content-Type']='text/html' current.response.headers['Content-Type'] = 'text/html'
raise HTTP(405,HTML(BODY(H1('errors'), raise HTTP(405, HTML(BODY(H1('errors'),
UL(*errors), UL(*errors),
H1('warnings'), H1('warnings'),
UL(*warnings))).xml()) UL(*warnings))).xml())
else: else:
return XML(out) return XML(out)
def pyfpdf_from_html(html): def pyfpdf_from_html(html):
request = current.request request = current.request
def image_map(path): def image_map(path):
if path.startswith('/%s/static/' % request.application): if path.startswith('/%s/static/' % request.application):
return os.path.join(request.folder,path.split('/',2)[2]) return os.path.join(request.folder, path.split('/', 2)[2])
return 'http%s://%s%s' % (request.is_https and 's' or '',request.env.http_host, path) return 'http%s://%s%s' % (request.is_https and 's' or '', request.env.http_host, path)
class MyFPDF(FPDF, HTMLMixin): pass
pdf=MyFPDF() class MyFPDF(FPDF, HTMLMixin):
pass
pdf = MyFPDF()
pdf.add_page() pdf.add_page()
html = sanitize(html, escape=False) #### should have better list of allowed tags html = sanitize(
pdf.write_html(html,image_map=image_map) html, escape=False) # should have better list of allowed tags
pdf.write_html(html, image_map=image_map)
return XML(pdf.output(dest='S')) return XML(pdf.output(dest='S'))
def pdf_from_html(html): def pdf_from_html(html):
# try use latex and pdflatex # try use latex and pdflatex
if os.system('which pdflatex > /dev/null')==0: if os.system('which pdflatex > /dev/null') == 0:
return pdflatex_from_html(html) return pdflatex_from_html(html)
else: else:
return pyfpdf_from_html(html) return pyfpdf_from_html(html)
+4 -3
View File
@@ -1,5 +1,6 @@
from gluon import XML from gluon import XML
def button(merchant_id="123456789012345", def button(merchant_id="123456789012345",
products=[dict(name="shoes", products=[dict(name="shoes",
quantity=1, quantity=1,
@@ -8,8 +9,8 @@ def button(merchant_id="123456789012345",
description="running shoes black")]): description="running shoes black")]):
t = '<input name="item_%(key)s_%(k)s" type="hidden" value="%(value)s"/>' t = '<input name="item_%(key)s_%(k)s" type="hidden" value="%(value)s"/>'
list_products = '' list_products = ''
for k,product in enumerate(products): for k, product in enumerate(products):
for key,value in product.items(): for key, value in product.items():
list_products += t % dict(k=k+1,key=key,value=value) list_products += t % dict(k=k + 1, key=key, value=value)
button = '<form action="https://checkout.google.com/api/checkout/v2/checkoutForm/Merchant/%s" id="BB_BuyButtonForm" method="post" name="BB_BuyButtonForm" target="_top">%s<input name="_charset_" type="hidden" value="utf-8"/><input alt="" src="https://checkout.google.com/buttons/buy.gif?merchant_id=%s&amp;w=117&amp;h=48&amp;style=white&amp;variant=text&amp;loc=en_US" type="image"/></form>' % (merchant_id, list_products, merchant_id) button = '<form action="https://checkout.google.com/api/checkout/v2/checkoutForm/Merchant/%s" id="BB_BuyButtonForm" method="post" name="BB_BuyButtonForm" target="_top">%s<input name="_charset_" type="hidden" value="utf-8"/><input alt="" src="https://checkout.google.com/buttons/buy.gif?merchant_id=%s&amp;w=117&amp;h=48&amp;style=white&amp;variant=text&amp;loc=en_US" type="image"/></form>' % (merchant_id, list_products, merchant_id)
return XML(button) return XML(button)
+1 -1
View File
@@ -1,5 +1,5 @@
# this file exists for backward compatibility # this file exists for backward compatibility
__all__ = ['DAL','Field','drivers','gae'] __all__ = ['DAL', 'Field', 'drivers', 'gae']
from gluon.dal import DAL, Field, Table, Query, Set, Expression, Row, Rows, drivers, BaseAdapter, SQLField, SQLTable, SQLXorable, SQLQuery, SQLSet, SQLRows, SQLStorage, SQLDB, GQLDB, SQLALL, SQLCustomType, gae from gluon.dal import DAL, Field, Table, Query, Set, Expression, Row, Rows, drivers, BaseAdapter, SQLField, SQLTable, SQLXorable, SQLQuery, SQLSet, SQLRows, SQLStorage, SQLDB, GQLDB, SQLALL, SQLCustomType, gae
+2
View File
@@ -22,6 +22,7 @@
######################################################################### #########################################################################
from gluon import current from gluon import current
class RESIZE(object): class RESIZE(object):
def __init__(self, nx=160, ny=80, error_message=' image resize'): def __init__(self, nx=160, ny=80, error_message=' image resize'):
(self.nx, self.ny, self.error_message) = (nx, ny, error_message) (self.nx, self.ny, self.error_message) = (nx, ny, error_message)
@@ -43,6 +44,7 @@ class RESIZE(object):
else: else:
return (value, None) return (value, None)
def THUMB(image, nx=120, ny=120, gae=False, name='thumb'): def THUMB(image, nx=120, ny=120, gae=False, name='thumb'):
if image: if image:
if not gae: if not gae:
-3
View File
@@ -1,3 +0,0 @@
+3 -5
View File
@@ -11,9 +11,9 @@ def basic_auth(server="http://127.0.0.1"):
""" """
def basic_login_aux(username, def basic_login_aux(username,
password, password,
server=server): server=server):
key = base64.b64encode(username+':'+password) key = base64.b64encode(username + ':' + password)
headers = {'Authorization': 'Basic ' + key} headers = {'Authorization': 'Basic ' + key}
request = urllib2.Request(server, None, headers) request = urllib2.Request(server, None, headers)
try: try:
@@ -22,5 +22,3 @@ def basic_auth(server="http://127.0.0.1"):
except (urllib2.URLError, urllib2.HTTPError): except (urllib2.URLError, urllib2.HTTPError):
return False return False
return basic_login_aux return basic_login_aux
@@ -25,6 +25,7 @@ from gluon.storage import Storage
from gluon.tools import fetch from gluon.tools import fetch
import gluon.contrib.simplejson as json import gluon.contrib.simplejson as json
class BrowserID(object): class BrowserID(object):
""" """
from gluon.contrib.login_methods.browserid_account import BrowserID from gluon.contrib.login_methods.browserid_account import BrowserID
@@ -34,17 +35,17 @@ class BrowserID(object):
""" """
def __init__(self, def __init__(self,
request, request,
audience = "", audience="",
assertion_post_url = "", assertion_post_url="",
prompt = "BrowserID Login", prompt="BrowserID Login",
issuer = "browserid.org", issuer="browserid.org",
verify_url = "https://browserid.org/verify", verify_url="https://browserid.org/verify",
browserid_js = "https://browserid.org/include.js", browserid_js="https://browserid.org/include.js",
browserid_button = "https://browserid.org/i/sign_in_red.png", browserid_button="https://browserid.org/i/sign_in_red.png",
crypto_js = "https://crypto-js.googlecode.com/files/2.2.0-crypto-md5.js", crypto_js="https://crypto-js.googlecode.com/files/2.2.0-crypto-md5.js",
on_login_failure = None, on_login_failure=None,
): ):
self.request = request self.request = request
self.audience = audience self.audience = audience
@@ -67,13 +68,13 @@ class BrowserID(object):
if request.vars.assertion: if request.vars.assertion:
audience = self.audience audience = self.audience
issuer = self.issuer issuer = self.issuer
assertion = XML(request.vars.assertion,sanitize=True) assertion = XML(request.vars.assertion, sanitize=True)
verify_data = {'assertion':assertion,'audience':audience} verify_data = {'assertion': assertion, 'audience': audience}
auth_info_json = fetch(self.verify_url,data=verify_data) auth_info_json = fetch(self.verify_url, data=verify_data)
j = json.loads(auth_info_json) j = json.loads(auth_info_json)
epoch_time = int(time.time()*1000) # we need 13 digit epoch time epoch_time = int(time.time() * 1000) # we need 13 digit epoch time
if j["status"] == "okay" and j["audience"] == audience and j['issuer'] == issuer and j['expires'] >= epoch_time: if j["status"] == "okay" and j["audience"] == audience and j['issuer'] == issuer and j['expires'] >= epoch_time:
return dict(email = j['email']) return dict(email=j['email'])
elif self.on_login_failure: elif self.on_login_failure:
redirect('http://google.com') redirect('http://google.com')
else: else:
@@ -83,9 +84,8 @@ class BrowserID(object):
def login_form(self): def login_form(self):
request = self.request request = self.request
onclick = "javascript:navigator.id.getVerifiedEmail(gotVerifiedEmail) ; return false" onclick = "javascript:navigator.id.getVerifiedEmail(gotVerifiedEmail) ; return false"
form = DIV(SCRIPT(_src=self.browserid_js,_type="text/javascript"), form = DIV(SCRIPT(_src=self.browserid_js, _type="text/javascript"),
SCRIPT(_src=self.crypto_js,_type="text/javascript"), SCRIPT(_src=self.crypto_js, _type="text/javascript"),
A(IMG(_src=self.browserid_button,_alt=self.prompt),_href="#",_onclick=onclick,_class="browserid",_title="Login With BrowserID"), A(IMG(_src=self.browserid_button, _alt=self.prompt), _href="#", _onclick=onclick, _class="browserid", _title="Login With BrowserID"),
SCRIPT(self.asertion_js)) SCRIPT(self.asertion_js))
return form return form
+57 -52
View File
@@ -11,7 +11,8 @@ Tinkered by Szabolcs Gyuris < szimszo n @ o regpreshaz dot eu>
from gluon import current, redirect from gluon import current, redirect
class CasAuth( object ):
class CasAuth(object):
""" """
Login will be done via Web2py's CAS application, instead of web2py's Login will be done via Web2py's CAS application, instead of web2py's
login form. login form.
@@ -39,101 +40,105 @@ class CasAuth( object ):
user's username. user's username.
""" """
def __init__(self, g=None, ### g for backward compatibility ### def __init__(self, g=None, # g for backward compatibility ###
urlbase = "https://web2py.com/cas/cas", urlbase="https://web2py.com/cas/cas",
actions=['login','validate','logout'], actions=['login', 'validate', 'logout'],
maps=dict(username=lambda v:v.get('username',v['user']), maps=dict(username=lambda v: v.get('username', v['user']),
email=lambda v:v.get('email',None), email=lambda v: v.get('email', None),
user_id=lambda v:v['user']), user_id=lambda v: v['user']),
casversion = 1, casversion=1,
casusername = 'cas:user' casusername='cas:user'
): ):
self.urlbase=urlbase self.urlbase = urlbase
self.cas_login_url="%s/%s"%(self.urlbase,actions[0]) self.cas_login_url = "%s/%s" % (self.urlbase, actions[0])
self.cas_check_url="%s/%s"%(self.urlbase,actions[1]) self.cas_check_url = "%s/%s" % (self.urlbase, actions[1])
self.cas_logout_url="%s/%s"%(self.urlbase,actions[2]) self.cas_logout_url = "%s/%s" % (self.urlbase, actions[2])
self.maps=maps self.maps = maps
self.casversion = casversion self.casversion = casversion
self.casusername = casusername self.casusername = casusername
http_host=current.request.env.http_x_forwarded_host http_host = current.request.env.http_x_forwarded_host
if not http_host: http_host=current.request.env.http_host if not http_host:
if current.request.env.wsgi_url_scheme in [ 'https', 'HTTPS' ]: http_host = current.request.env.http_host
if current.request.env.wsgi_url_scheme in ['https', 'HTTPS']:
scheme = 'https' scheme = 'https'
else: else:
scheme = 'http' scheme = 'http'
self.cas_my_url='%s://%s%s'%( scheme, http_host, current.request.env.path_info ) self.cas_my_url = '%s://%s%s' % (
scheme, http_host, current.request.env.path_info)
def login_url( self, next = "/" ): def login_url(self, next="/"):
current.session.token=self._CAS_login() current.session.token = self._CAS_login()
return next return next
def logout_url( self, next = "/" ):
current.session.token=None def logout_url(self, next="/"):
current.session.auth=None current.session.token = None
current.session.auth = None
self._CAS_logout() self._CAS_logout()
return next return next
def get_user( self ):
user=current.session.token def get_user(self):
user = current.session.token
if user: if user:
d = {'source':'web2py cas'} d = {'source': 'web2py cas'}
for key in self.maps: for key in self.maps:
d[key]=self.maps[key](user) d[key] = self.maps[key](user)
return d return d
return None return None
def _CAS_login( self ):
def _CAS_login(self):
""" """
exposed as CAS.login(request) exposed as CAS.login(request)
returns a token on success, None on failed authentication returns a token on success, None on failed authentication
""" """
import urllib import urllib
self.ticket=current.request.vars.ticket self.ticket = current.request.vars.ticket
if not current.request.vars.ticket: if not current.request.vars.ticket:
redirect( "%s?service=%s"% (self.cas_login_url, redirect("%s?service=%s" % (self.cas_login_url,
self.cas_my_url)) self.cas_my_url))
else: else:
url="%s?service=%s&ticket=%s" % (self.cas_check_url, url = "%s?service=%s&ticket=%s" % (self.cas_check_url,
self.cas_my_url, self.cas_my_url,
self.ticket ) self.ticket)
data=urllib.urlopen( url ).read() data = urllib.urlopen(url).read()
if data.startswith('yes') or data.startswith('no'): if data.startswith('yes') or data.startswith('no'):
data = data.split('\n') data = data.split('\n')
if data[0]=='yes': if data[0] == 'yes':
if ':' in data[1]: # for Compatibility with Custom CAS if ':' in data[1]: # for Compatibility with Custom CAS
items = data[1].split(':') items = data[1].split(':')
a = items[0] a = items[0]
b = len(items)>1 and items[1] or a b = len(items) > 1 and items[1] or a
c = len(items)>2 and items[2] or b c = len(items) > 2 and items[2] or b
else: else:
a = b = c = data[1] a = b = c = data[1]
return dict(user=a,email=b,username=c) return dict(user=a, email=b, username=c)
return None return None
import xml.dom.minidom as dom import xml.dom.minidom as dom
import xml.parsers.expat as expat import xml.parsers.expat as expat
try: try:
dxml=dom.parseString(data) dxml = dom.parseString(data)
envelop = dxml.getElementsByTagName("cas:authenticationSuccess") envelop = dxml.getElementsByTagName(
if len(envelop)>0: "cas:authenticationSuccess")
if len(envelop) > 0:
res = dict() res = dict()
for x in envelop[0].childNodes: for x in envelop[0].childNodes:
if x.nodeName.startswith('cas:') and len(x.childNodes): if x.nodeName.startswith('cas:') and len(x.childNodes):
key = x.nodeName[4:].encode('utf8') key = x.nodeName[4:].encode('utf8')
value = x.childNodes[0].nodeValue.encode('utf8') value = x.childNodes[0].nodeValue.encode('utf8')
if not key in res: if not key in res:
res[key]=value res[key] = value
else: else:
if not isinstance(res[key],list): if not isinstance(res[key], list):
res[key]=[res[key]] res[key] = [res[key]]
res[key].append(value) res[key].append(value)
return res return res
except expat.ExpatError: pass except expat.ExpatError:
return None # fallback pass
return None # fallback
def _CAS_logout(self):
def _CAS_logout( self ):
""" """
exposed CAS.logout() exposed CAS.logout()
redirects to the CAS logout page redirects to the CAS logout page
""" """
import urllib import urllib
redirect("%s?service=%s" % (self.cas_logout_url,self.cas_my_url)) redirect("%s?service=%s" % (self.cas_logout_url, self.cas_my_url))
+41 -38
View File
@@ -18,11 +18,13 @@ from gluon.tools import fetch
from gluon.storage import Storage from gluon.storage import Storage
import gluon.contrib.simplejson as json import gluon.contrib.simplejson as json
class DropboxAccount(object): class DropboxAccount(object):
""" """
from gluon.contrib.login_methods.dropbox_account import DropboxAccount from gluon.contrib.login_methods.dropbox_account import DropboxAccount
auth.settings.actions_disabled=['register','change_password','request_reset_password'] auth.settings.actions_disabled=['register','change_password',
'request_reset_password']
auth.settings.login_form = DropboxAccount(request, auth.settings.login_form = DropboxAccount(request,
key="...", key="...",
secret="...", secret="...",
@@ -34,47 +36,45 @@ class DropboxAccount(object):
def __init__(self, def __init__(self,
request, request,
key = "", key="",
secret = "", secret="",
access_type="app_folder", access_type="app_folder",
login_url = "", login_url="",
on_login_failure=None, on_login_failure=None,
): ):
self.request=request self.request = request
self.key=key self.key = key
self.secret=secret self.secret = secret
self.access_type=access_type self.access_type = access_type
self.login_url = login_url self.login_url = login_url
self.on_login_failure = on_login_failure self.on_login_failure = on_login_failure
self.sess = session.DropboxSession( self.sess = session.DropboxSession(
self.key,self.secret,self.access_type) self.key, self.secret, self.access_type)
def get_user(self): def get_user(self):
request = self.request request = self.request
if not current.session.dropbox_request_token: if not current.session.dropbox_request_token:
return None return None
elif not current.session.dropbox_access_token: elif not current.session.dropbox_access_token:
request_token = current.session.dropbox_request_token request_token = current.session.dropbox_request_token
self.sess.set_request_token(request_token[0],request_token[1]) self.sess.set_request_token(request_token[0], request_token[1])
access_token = self.sess.obtain_access_token(self.sess.token) access_token = self.sess.obtain_access_token(self.sess.token)
current.session.dropbox_access_token = \ current.session.dropbox_access_token = \
(access_token.key,access_token.secret) (access_token.key, access_token.secret)
else: else:
access_token = current.session.dropbox_access_token access_token = current.session.dropbox_access_token
self.sess.set_token(access_token[0],access_token[1]) self.sess.set_token(access_token[0], access_token[1])
user = Storage() user = Storage()
self.client = client.DropboxClient(self.sess) self.client = client.DropboxClient(self.sess)
data = self.client.account_info() data = self.client.account_info()
display_name = data.get('display_name','').split(' ',1) display_name = data.get('display_name', '').split(' ', 1)
user = dict(email = data.get('email',None), user = dict(email=data.get('email', None),
first_name = display_name[0], first_name=display_name[0],
last_name = display_name[-1], last_name=display_name[-1],
registration_id = data.get('uid',None)) registration_id=data.get('uid', None))
if not user['registration_id'] and self.on_login_failure: if not user['registration_id'] and self.on_login_failure:
redirect(self.on_login_failure) redirect(self.on_login_failure)
return user return user
@@ -83,7 +83,7 @@ class DropboxAccount(object):
request_token = self.sess.obtain_request_token() request_token = self.sess.obtain_request_token()
current.session.dropbox_request_token = \ current.session.dropbox_request_token = \
(request_token.key,request_token.secret) (request_token.key, request_token.secret)
dropbox_url = self.sess.build_authorize_url(request_token, dropbox_url = self.sess.build_authorize_url(request_token,
self.login_url) self.login_url)
redirect(dropbox_url) redirect(dropbox_url)
@@ -93,29 +93,32 @@ class DropboxAccount(object):
_style="width:400px;height:240px;") _style="width:400px;height:240px;")
return form return form
def logout_url(self, next = "/"): def logout_url(self, next="/"):
current.session.dropbox_request_token=None current.session.dropbox_request_token = None
current.session.auth=None current.session.auth = None
redirect('https://www.dropbox.com/logout') redirect('https://www.dropbox.com/logout')
return next return next
def put(self,filename,file):
return json.loads(self.client.put_file(filename,file))['bytes'] def put(self, filename, file):
def get(self,filename,file): return json.loads(self.client.put_file(filename, file))['bytes']
def get(self, filename, file):
return self.client.get_file(filename) return self.client.get_file(filename)
def dir(self,path):
def dir(self, path):
return json.loads(self.client.metadata(path)) return json.loads(self.client.metadata(path))
def use_dropbox(auth,filename='private/dropbox.key',**kwargs):
path = os.path.join(current.request.folder,filename) def use_dropbox(auth, filename='private/dropbox.key', **kwargs):
path = os.path.join(current.request.folder, filename)
if os.path.exists(path): if os.path.exists(path):
request = current.request request = current.request
key,secret,access_type = open(path,'r').read().strip().split(':') key, secret, access_type = open(path, 'r').read().strip().split(':')
host = current.request.env.http_host host = current.request.env.http_host
login_url = "http://%s/%s/default/user/login" % \ login_url = "http://%s/%s/default/user/login" % \
(host,request.application) (host, request.application)
auth.settings.actions_disabled = \ auth.settings.actions_disabled = \
['register','change_password','request_reset_password'] ['register', 'change_password', 'request_reset_password']
auth.settings.login_form = DropboxAccount( auth.settings.login_form = DropboxAccount(
request,key=key,secret=secret,access_type=access_type, request, key=key, secret=secret, access_type=access_type,
login_url = login_url,**kwargs) login_url=login_url, **kwargs)
+4 -4
View File
@@ -1,6 +1,7 @@
import smtplib import smtplib
import logging import logging
def email_auth(server="smtp.gmail.com:587", def email_auth(server="smtp.gmail.com:587",
domain="@gmail.com", domain="@gmail.com",
tls_mode=None): tls_mode=None):
@@ -17,9 +18,9 @@ def email_auth(server="smtp.gmail.com:587",
domain=domain, domain=domain,
tls_mode=tls_mode): tls_mode=tls_mode):
if domain: if domain:
if not isinstance(domain,(list,tuple)): if not isinstance(domain, (list, tuple)):
domain=[str(domain)] domain = [str(domain)]
if not [d for d in domain if email[-len(d):]==d]: if not [d for d in domain if email[-len(d):] == d]:
return False return False
(host, port) = server.split(':') (host, port) = server.split(':')
if tls_mode is None: # then auto detect if tls_mode is None: # then auto detect
@@ -43,4 +44,3 @@ def email_auth(server="smtp.gmail.com:587",
pass pass
return False return False
return email_auth_aux return email_auth_aux
@@ -8,6 +8,7 @@ So user can choose the built-in login or extended login methods.
from gluon import current, DIV from gluon import current, DIV
class ExtendedLoginForm(object): class ExtendedLoginForm(object):
""" """
Put extended_login_form under web2py/gluon/contrib/login_methods folder. Put extended_login_form under web2py/gluon/contrib/login_methods folder.
@@ -22,7 +23,8 @@ class ExtendedLoginForm(object):
api_key="...", api_key="...",
domain="...", domain="...",
url = "http://localhost:8000/%s/default/user/login" % request.application) url = "http://localhost:8000/%s/default/user/login" % request.application)
extended_login_form = ExtendedLoginForm(auth, alt_login_form, signals=['token']) extended_login_form = ExtendedLoginForm(
auth, alt_login_form, signals=['token'])
auth.settings.login_form = extended_login_form auth.settings.login_form = extended_login_form
@@ -37,7 +39,7 @@ class ExtendedLoginForm(object):
auth, auth,
alt_login_form, alt_login_form,
signals=[], signals=[],
login_arg = 'login' login_arg='login'
): ):
self.auth = auth self.auth = auth
self.alt_login_form = alt_login_form self.alt_login_form = alt_login_form
@@ -50,7 +52,7 @@ class ExtendedLoginForm(object):
""" """
if hasattr(self.alt_login_form, 'get_user'): if hasattr(self.alt_login_form, 'get_user'):
return self.alt_login_form.get_user() return self.alt_login_form.get_user()
return None # let gluon.tools.Auth.get_or_create_user do the rest return None # let gluon.tools.Auth.get_or_create_user do the rest
def login_url(self, next): def login_url(self, next):
""" """
@@ -91,8 +93,8 @@ class ExtendedLoginForm(object):
args = request.args args = request.args
if (self.signals and if (self.signals and
any([True for signal in self.signals if request.vars.has_key(signal)]) any([True for signal in self.signals if signal in request.vars])
): ):
return self.alt_login_form.login_form() return self.alt_login_form.login_form()
self.auth.settings.login_form = self.auth self.auth.settings.login_form = self.auth
@@ -101,5 +103,3 @@ class ExtendedLoginForm(object):
form.components.append(self.alt_login_form.login_form()) form.components.append(self.alt_login_form.login_form())
return form return form
@@ -11,6 +11,7 @@ Thanks to Hans Donner <hans.donner@pobox.com> for GaeGoogleAccount.
from google.appengine.api import users from google.appengine.api import users
class GaeGoogleAccount(object): class GaeGoogleAccount(object):
""" """
Login will be done via Google's Appengine login object, instead of web2py's Login will be done via Google's Appengine login object, instead of web2py's
@@ -35,5 +36,3 @@ class GaeGoogleAccount(object):
if user: if user:
return dict(nickname=user.nickname(), email=user.email(), return dict(nickname=user.nickname(), email=user.email(),
user_id=user.user_id(), source="google account") user_id=user.user_id(), source="google account")
+28 -15
View File
@@ -188,18 +188,23 @@ def ldap_auth(server='ldap', port=None,
str(custom_scope), str(manage_groups))) str(custom_scope), str(manage_groups)))
if manage_user: if manage_user:
if user_firstname_attrib.count(':') > 0: if user_firstname_attrib.count(':') > 0:
(user_firstname_attrib, user_firstname_part) = user_firstname_attrib.split(':', 1) (user_firstname_attrib,
user_firstname_part) = user_firstname_attrib.split(':', 1)
user_firstname_part = (int(user_firstname_part) - 1) user_firstname_part = (int(user_firstname_part) - 1)
else: else:
user_firstname_part = None user_firstname_part = None
if user_lastname_attrib.count(':') > 0: if user_lastname_attrib.count(':') > 0:
(user_lastname_attrib, user_lastname_part) = user_lastname_attrib.split(':', 1) (user_lastname_attrib,
user_lastname_part) = user_lastname_attrib.split(':', 1)
user_lastname_part = (int(user_lastname_part) - 1) user_lastname_part = (int(user_lastname_part) - 1)
else: else:
user_lastname_part = None user_lastname_part = None
user_firstname_attrib = ldap.filter.escape_filter_chars(user_firstname_attrib) user_firstname_attrib = ldap.filter.escape_filter_chars(
user_lastname_attrib = ldap.filter.escape_filter_chars(user_lastname_attrib) user_firstname_attrib)
user_mail_attrib = ldap.filter.escape_filter_chars(user_mail_attrib) user_lastname_attrib = ldap.filter.escape_filter_chars(
user_lastname_attrib)
user_mail_attrib = ldap.filter.escape_filter_chars(
user_mail_attrib)
try: try:
if allowed_groups: if allowed_groups:
if not is_user_in_allowed_groups(username, password): if not is_user_in_allowed_groups(username, password):
@@ -310,7 +315,8 @@ def ldap_auth(server='ldap', port=None,
basedns = ldap_basedn basedns = ldap_basedn
else: else:
basedns = [ldap_basedn] basedns = [ldap_basedn]
filter = '(&(uid=%s)(%s))' % (ldap.filter.escape_filter_chars(username), filterstr) filter = '(&(uid=%s)(%s))' % (
ldap.filter.escape_filter_chars(username), filterstr)
found = False found = False
for basedn in basedns: for basedn in basedns:
try: try:
@@ -338,7 +344,8 @@ def ldap_auth(server='ldap', port=None,
else: else:
basedns = [ldap_basedn] basedns = [ldap_basedn]
filter = '(&(%s=%s)(%s))' % (username_attrib, filter = '(&(%s=%s)(%s))' % (username_attrib,
ldap.filter.escape_filter_chars(username), ldap.filter.escape_filter_chars(
username),
filterstr) filterstr)
if custom_scope == 'subtree': if custom_scope == 'subtree':
ldap_scope = ldap.SCOPE_SUBTREE ldap_scope = ldap.SCOPE_SUBTREE
@@ -368,14 +375,16 @@ def ldap_auth(server='ldap', port=None,
logger.info('[%s] Manage user data' % str(username)) logger.info('[%s] Manage user data' % str(username))
try: try:
if user_firstname_part is not None: if user_firstname_part is not None:
store_user_firstname = result[user_firstname_attrib][0].split(' ', 1)[user_firstname_part] store_user_firstname = result[user_firstname_attrib][
0].split(' ', 1)[user_firstname_part]
else: else:
store_user_firstname = result[user_firstname_attrib][0] store_user_firstname = result[user_firstname_attrib][0]
except KeyError, e: except KeyError, e:
store_user_firstname = None store_user_firstname = None
try: try:
if user_lastname_part is not None: if user_lastname_part is not None:
store_user_lastname = result[user_lastname_attrib][0].split(' ', 1)[user_lastname_part] store_user_lastname = result[user_lastname_attrib][
0].split(' ', 1)[user_lastname_part]
else: else:
store_user_lastname = result[user_lastname_attrib][0] store_user_lastname = result[user_lastname_attrib][0]
except KeyError, e: except KeyError, e:
@@ -464,16 +473,19 @@ def ldap_auth(server='ldap', port=None,
# #
# Get all group name where the user is in actually in ldap # Get all group name where the user is in actually in ldap
# ######################################################### # #########################################################
ldap_groups_of_the_user = get_user_groups_from_ldap(username, password) ldap_groups_of_the_user = get_user_groups_from_ldap(
username, password)
# #
# Get all group name where the user is in actually in local db # Get all group name where the user is in actually in local db
# ############################################################# # #############################################################
try: try:
db_user_id = db(db.auth_user.username == username).select(db.auth_user.id).first().id db_user_id = db(db.auth_user.username == username).select(
db.auth_user.id).first().id
except: except:
try: try:
db_user_id = db(db.auth_user.email == username).select(db.auth_user.id).first().id db_user_id = db(db.auth_user.email == username).select(
db.auth_user.id).first().id
except AttributeError, e: except AttributeError, e:
# #
# There is no user in local db # There is no user in local db
@@ -486,7 +498,8 @@ def ldap_auth(server='ldap', port=None,
db_user_id = db.auth_user.insert(email=username, db_user_id = db.auth_user.insert(email=username,
first_name=username) first_name=username)
if not db_user_id: if not db_user_id:
logging.error('There is no username or email for %s!' % username) logging.error(
'There is no username or email for %s!' % username)
raise raise
db_group_search = db((db.auth_membership.user_id == db_user_id) & db_group_search = db((db.auth_membership.user_id == db_user_id) &
(db.auth_user.id == db.auth_membership.user_id) & (db.auth_user.id == db.auth_membership.user_id) &
@@ -520,7 +533,8 @@ def ldap_auth(server='ldap', port=None,
gid = db.auth_group.insert(role=group_to_add, gid = db.auth_group.insert(role=group_to_add,
description='Generated from LDAP') description='Generated from LDAP')
else: else:
gid = db(db.auth_group.role == group_to_add).select(db.auth_group.id).first().id gid = db(db.auth_group.role == group_to_add).select(
db.auth_group.id).first().id
db.auth_membership.insert(user_id=db_user_id, db.auth_membership.insert(user_id=db_user_id,
group_id=gid) group_id=gid)
except: except:
@@ -634,4 +648,3 @@ def ldap_auth(server='ldap', port=None,
if filterstr[0] == '(' and filterstr[-1] == ')': # rfc4515 syntax if filterstr[0] == '(' and filterstr[-1] == ')': # rfc4515 syntax
filterstr = filterstr[1:-1] # parens added again where used filterstr = filterstr[1:-1] # parens added again where used
return ldap_auth_aux return ldap_auth_aux
+11 -11
View File
@@ -14,7 +14,8 @@ from gluon.http import HTTP
try: try:
import linkedin import linkedin
except ImportError: except ImportError:
raise HTTP(400,"linkedin module not found") raise HTTP(400, "linkedin module not found")
class LinkedInAccount(object): class LinkedInAccount(object):
""" """
@@ -28,9 +29,9 @@ class LinkedInAccount(object):
""" """
def __init__(self,request,key,secret,return_url): def __init__(self, request, key, secret, return_url):
self.request = request self.request = request
self.api = linkedin.LinkedIn(key,secret,return_url) self.api = linkedin.LinkedIn(key, secret, return_url)
self.token = result = self.api.requestToken() self.token = result = self.api.requestToken()
def login_url(self, next="/"): def login_url(self, next="/"):
@@ -40,13 +41,12 @@ class LinkedInAccount(object):
return '' return ''
def get_user(self): def get_user(self):
result = self.request.vars.verifier and self.api.accessToken(verifier = self.request.vars.verifier ) result = self.request.vars.verifier and self.api.accessToken(
verifier=self.request.vars.verifier)
if result: if result:
profile = self.api.GetProfile() profile = self.api.GetProfile()
profile = self.api.GetProfile(profile).public_url = "http://www.linkedin.com/in/ozgurv" profile = self.api.GetProfile(
return dict(first_name = profile.first_name, profile).public_url = "http://www.linkedin.com/in/ozgurv"
last_name = profile.last_name, return dict(first_name=profile.first_name,
username = profile.id) last_name=profile.last_name,
username=profile.id)
+40 -38
View File
@@ -13,6 +13,7 @@ from gluon.tools import fetch
from gluon.storage import Storage from gluon.storage import Storage
import gluon.contrib.simplejson as json import gluon.contrib.simplejson as json
class Loginza(object): class Loginza(object):
""" """
@@ -23,13 +24,13 @@ class Loginza(object):
def __init__(self, def __init__(self,
request, request,
url = "", url="",
embed = True, embed=True,
auth_url = "http://loginza.ru/api/authinfo", auth_url="http://loginza.ru/api/authinfo",
language = "en", language="en",
prompt = "loginza", prompt="loginza",
on_login_failure = None, on_login_failure=None,
): ):
self.request = request self.request = request
self.token_url = url self.token_url = url
@@ -46,49 +47,50 @@ class Loginza(object):
# FIXME: what if email is unique=True # FIXME: what if email is unique=True
self.mappings["http://twitter.com/"] = lambda profile:\ self.mappings["http://twitter.com/"] = lambda profile:\
dict(registration_id = profile.get("identity",""), dict(registration_id=profile.get("identity", ""),
username = profile.get("nickname",""), username=profile.get("nickname", ""),
email = profile.get("email",""), email=profile.get("email", ""),
last_name = profile.get("name","").get("full_name",""), last_name=profile.get("name", "").get("full_name", ""),
#avatar = profile.get("photo",""), #avatar = profile.get("photo",""),
) )
self.mappings["https://www.google.com/accounts/o8/ud"] = lambda profile:\ self.mappings["https://www.google.com/accounts/o8/ud"] = lambda profile:\
dict(registration_id = profile.get("identity",""), dict(registration_id=profile.get("identity", ""),
username = profile.get("name","").get("full_name",""), username=profile.get("name", "").get("full_name", ""),
email = profile.get("email",""), email=profile.get("email", ""),
first_name = profile.get("name","").get("first_name",""), first_name=profile.get("name", "").get("first_name", ""),
last_name = profile.get("name","").get("last_name",""), last_name=profile.get("name", "").get("last_name", ""),
#avatar = profile.get("photo",""), #avatar = profile.get("photo",""),
) )
self.mappings["http://vkontakte.ru/"] = lambda profile:\ self.mappings["http://vkontakte.ru/"] = lambda profile:\
dict(registration_id=profile.get("identity",""), dict(registration_id=profile.get("identity", ""),
username = profile.get("name","").get("full_name",""), username=profile.get("name", "").get("full_name", ""),
email = profile.get("email",""), email=profile.get("email", ""),
first_name = profile.get("name","").get("first_name",""), first_name=profile.get("name", "").get("first_name", ""),
last_name = profile.get("name","").get("last_name",""), last_name=profile.get("name", "").get("last_name", ""),
#avatar = profile.get("photo",""), #avatar = profile.get("photo",""),
) )
self.mappings.default = lambda profile:\ self.mappings.default = lambda profile:\
dict(registration_id = profile.get("identity",""), dict(registration_id=profile.get("identity", ""),
username = profile.get("name","").get("full_name"), username=profile.get("name", "").get("full_name"),
email = profile.get("email",""), email=profile.get("email", ""),
first_name = profile.get("name","").get("first_name",""), first_name=profile.get("name", "").get("first_name", ""),
last_name = profile.get("name","").get("last_name",""), last_name=profile.get("name", "").get("last_name", ""),
#avatar = profile.get("photo",""), #avatar = profile.get("photo",""),
) )
def get_user(self): def get_user(self):
request = self.request request = self.request
if request.vars.token: if request.vars.token:
user = Storage() user = Storage()
data = urllib.urlencode(dict(token = request.vars.token)) data = urllib.urlencode(dict(token=request.vars.token))
auth_info_json = fetch(self.auth_url+'?'+data) auth_info_json = fetch(self.auth_url + '?' + data)
#print auth_info_json #print auth_info_json
auth_info = json.loads(auth_info_json) auth_info = json.loads(auth_info_json)
if auth_info["identity"] != None: if auth_info["identity"] is not None:
self.profile = auth_info self.profile = auth_info
provider = self.profile["provider"] provider = self.profile["provider"]
user = self.mappings.get(provider, self.mappings.default)(self.profile) user = self.mappings.get(
provider, self.mappings.default)(self.profile)
#user["password"] = ??? #user["password"] = ???
#user["avatar"] = ??? #user["avatar"] = ???
return user return user
@@ -106,8 +108,8 @@ class Loginza(object):
_frameborder="no", _frameborder="no",
_style="width:359px;height:300px;") _style="width:359px;height:300px;")
else: else:
form = DIV(A(self.prompt, _href=LOGINZA_URL % (self.language, self.token_url), _class="loginza"), form = DIV(
SCRIPT(_src="https://s3-eu-west-1.amazonaws.com/s1.loginza.ru/js/widget.js", _type="text/javascript")) A(self.prompt, _href=LOGINZA_URL % (
self.language, self.token_url), _class="loginza"),
SCRIPT(_src="https://s3-eu-west-1.amazonaws.com/s1.loginza.ru/js/widget.js", _type="text/javascript"))
return form return form
+17 -11
View File
@@ -4,6 +4,7 @@ import time
from hashlib import md5 from hashlib import md5
from gluon.dal import DAL from gluon.dal import DAL
def motp_auth(db=DAL('sqlite://storage.sqlite'), def motp_auth(db=DAL('sqlite://storage.sqlite'),
time_offset=60): time_offset=60):
@@ -44,7 +45,8 @@ def motp_auth(db=DAL('sqlite://storage.sqlite'),
writable=False, readable=False, default='')) writable=False, readable=False, default=''))
##validators ##validators
custom_auth_table = db[auth.settings.table_user_name] # get the custom_auth_table custom_auth_table = db[auth.settings.table_user_name]
# get the custom_auth_table
custom_auth_table.first_name.requires = \ custom_auth_table.first_name.requires = \
IS_NOT_EMPTY(error_message=auth.messages.is_empty) IS_NOT_EMPTY(error_message=auth.messages.is_empty)
custom_auth_table.last_name.requires = \ custom_auth_table.last_name.requires = \
@@ -76,14 +78,15 @@ def motp_auth(db=DAL('sqlite://storage.sqlite'),
- as of now user field is hardcoded to email. Some way of selecting user table and user field. - as of now user field is hardcoded to email. Some way of selecting user table and user field.
""" """
def verify_otp(otp,pin,secret,offset=60): def verify_otp(otp, pin, secret, offset=60):
epoch_time = int(time.time()) epoch_time = int(time.time())
time_start = int(str(epoch_time - offset)[:-1]) time_start = int(str(epoch_time - offset)[:-1])
time_end = int(str(epoch_time + offset)[:-1]) time_end = int(str(epoch_time + offset)[:-1])
for t in range(time_start-1,time_end+1): for t in range(time_start - 1, time_end + 1):
to_hash = str(t)+secret+pin to_hash = str(t) + secret + pin
hash = md5(to_hash).hexdigest()[:6] hash = md5(to_hash).hexdigest()[:6]
if otp == hash: return True if otp == hash:
return True
return False return False
def motp_auth_aux(email, def motp_auth_aux(email,
@@ -91,15 +94,18 @@ def motp_auth(db=DAL('sqlite://storage.sqlite'),
db=db, db=db,
offset=time_offset): offset=time_offset):
if db: if db:
user_data = db(db.auth_user.email == email ).select().first() user_data = db(db.auth_user.email == email).select().first()
if user_data: if user_data:
if user_data['motp_secret'] and user_data['motp_pin']: if user_data['motp_secret'] and user_data['motp_pin']:
motp_secret = user_data['motp_secret'] motp_secret = user_data['motp_secret']
motp_pin = user_data['motp_pin'] motp_pin = user_data['motp_pin']
otp_check = verify_otp(password,motp_pin,motp_secret,offset=offset) otp_check = verify_otp(
if otp_check: return True password, motp_pin, motp_secret, offset=offset)
else: return False if otp_check:
else: return False return True
else:
return False
else:
return False
return False return False
return motp_auth_aux return motp_auth_aux
+15 -20
View File
@@ -19,6 +19,7 @@ from urllib2 import urlopen
import urllib2 import urllib2
from urllib import urlencode from urllib import urlencode
class OAuthAccount(object): class OAuthAccount(object):
""" """
Login will be done via OAuth Framework, instead of web2py's Login will be done via OAuth Framework, instead of web2py's
@@ -51,7 +52,8 @@ class OAuthAccount(object):
TOKEN_URL="..." TOKEN_URL="..."
ACCESS_TOKEN_URL="..." ACCESS_TOKEN_URL="..."
from gluon.contrib.login_methods.oauth10a_account import OAuthAccount from gluon.contrib.login_methods.oauth10a_account import OAuthAccount
auth.settings.login_form=OAuthAccount(globals(),CLIENT_ID,CLIENT_SECRET, AUTH_URL, TOKEN_URL, ACCESS_TOKEN_URL) auth.settings.login_form=OAuthAccount(globals(
),CLIENT_ID,CLIENT_SECRET, AUTH_URL, TOKEN_URL, ACCESS_TOKEN_URL)
""" """
@@ -61,20 +63,20 @@ class OAuthAccount(object):
Appends the _next action to the generated url so the flows continues. Appends the _next action to the generated url so the flows continues.
""" """
r = self.request r = self.request
http_host=r.env.http_x_forwarded_for http_host = r.env.http_x_forwarded_for
if not http_host: http_host=r.env.http_host if not http_host:
http_host = r.env.http_host
url_scheme = r.env.wsgi_url_scheme url_scheme = r.env.wsgi_url_scheme
if next: if next:
path_info = next path_info = next
else: else:
path_info = r.env.path_info path_info = r.env.path_info
uri = '%s://%s%s' %(url_scheme, http_host, path_info) uri = '%s://%s%s' % (url_scheme, http_host, path_info)
if r.get_vars and not next: if r.get_vars and not next:
uri += '?' + urlencode(r.get_vars) uri += '?' + urlencode(r.get_vars)
return uri return uri
def accessToken(self): def accessToken(self):
"""Return the access token generated by the authenticating server. """Return the access token generated by the authenticating server.
@@ -97,12 +99,11 @@ class OAuthAccount(object):
token.set_verifier(self.request.vars.oauth_verifier) token.set_verifier(self.request.vars.oauth_verifier)
client = oauth.Client(self.consumer, token) client = oauth.Client(self.consumer, token)
resp, content = client.request(self.access_token_url, "POST") resp, content = client.request(self.access_token_url, "POST")
if str(resp['status']) != '200': if str(resp['status']) != '200':
self.session.request_token = None self.session.request_token = None
self.globals['redirect'](self.globals['URL'](f='user',args='logout')) self.globals['redirect'](self.globals[
'URL'](f='user', args='logout'))
self.session.access_token = oauth.Token.from_string(content) self.session.access_token = oauth.Token.from_string(content)
@@ -111,7 +112,7 @@ class OAuthAccount(object):
self.session.access_token = None self.session.access_token = None
return None return None
def __init__(self, g, client_id, client_secret, auth_url, token_url, access_token_url): def __init__(self, g, client_id, client_secret, auth_url, token_url, access_token_url):
self.globals = g self.globals = g
self.client_id = client_id self.client_id = client_id
self.client_secret = client_secret self.client_secret = client_secret
@@ -125,7 +126,6 @@ class OAuthAccount(object):
# consumer init # consumer init
self.consumer = oauth.Consumer(self.client_id, self.client_secret) self.consumer = oauth.Consumer(self.client_id, self.client_secret)
def login_url(self, next="/"): def login_url(self, next="/"):
self.__oauth_login(next) self.__oauth_login(next)
return next return next
@@ -142,7 +142,7 @@ class OAuthAccount(object):
is, this function must be implemented for the specific is, this function must be implemented for the specific
provider. provider.
''' '''
raise NotImplementedError, "Must override get_user()" raise NotImplementedError("Must override get_user()")
def __oauth_login(self, next): def __oauth_login(self, next):
'''This method redirects the user to the authenticating form '''This method redirects the user to the authenticating form
@@ -163,10 +163,11 @@ class OAuthAccount(object):
# putting it in the body seems to work. # putting it in the body seems to work.
callback_url = self.__redirect_uri(next) callback_url = self.__redirect_uri(next)
data = urlencode(dict(oauth_callback=callback_url)) data = urlencode(dict(oauth_callback=callback_url))
resp, content = client.request(self.token_url, "POST", body=data) resp, content = client.request(self.token_url, "POST", body=data)
if resp['status'] != '200': if resp['status'] != '200':
self.session.request_token = None self.session.request_token = None
self.globals['redirect'](self.globals['URL'](f='user',args='logout')) self.globals['redirect'](self.globals[
'URL'](f='user', args='logout'))
# Store the request token in session. # Store the request token in session.
request_token = self.session.request_token = oauth.Token.from_string(content) request_token = self.session.request_token = oauth.Token.from_string(content)
@@ -174,18 +175,12 @@ class OAuthAccount(object):
# Redirect the user to the authentication URL and pass the callback url. # Redirect the user to the authentication URL and pass the callback url.
data = urlencode(dict(oauth_token=request_token.key, data = urlencode(dict(oauth_token=request_token.key,
oauth_callback=callback_url)) oauth_callback=callback_url))
auth_request_url = self.auth_url + '?' +data auth_request_url = self.auth_url + '?' + data
HTTP = self.globals['HTTP'] HTTP = self.globals['HTTP']
raise HTTP(307, raise HTTP(307,
"You are not authenticated: you are being redirected to the <a href='" + auth_request_url + "'> authentication server</a>", "You are not authenticated: you are being redirected to the <a href='" + auth_request_url + "'> authentication server</a>",
Location=auth_request_url) Location=auth_request_url)
return None return None
+24 -25
View File
@@ -17,6 +17,7 @@ import urllib2
from urllib import urlencode from urllib import urlencode
from gluon import current, redirect, HTTP from gluon import current, redirect, HTTP
class OAuthAccount(object): class OAuthAccount(object):
""" """
Login will be done via OAuth Framework, instead of web2py's Login will be done via OAuth Framework, instead of web2py's
@@ -84,7 +85,8 @@ class OAuthAccount(object):
username = user['id']) username = user['id'])
auth.settings.actions_disabled=['register','change_password','request_reset_password','profile'] auth.settings.actions_disabled=['register',
'change_password','request_reset_password','profile']
auth.settings.login_form=FaceBookAccount() auth.settings.login_form=FaceBookAccount()
Any optional arg in the constructor will be passed asis to remote Any optional arg in the constructor will be passed asis to remote
@@ -99,8 +101,9 @@ server for requests. It can be used for the optional"scope" parameters for Face
""" """
r = current.request r = current.request
http_host=r.env.http_x_forwarded_for http_host = r.env.http_x_forwarded_for
if not http_host: http_host=r.env.http_host if not http_host:
http_host = r.env.http_host
url_scheme = r.env.wsgi_url_scheme url_scheme = r.env.wsgi_url_scheme
if next: if next:
@@ -112,7 +115,6 @@ server for requests. It can be used for the optional"scope" parameters for Face
uri += '?' + urlencode(r.get_vars) uri += '?' + urlencode(r.get_vars)
return uri return uri
def __build_url_opener(self, uri): def __build_url_opener(self, uri):
""" """
Build the url opener for managing HTTP Basic Athentication Build the url opener for managing HTTP Basic Athentication
@@ -128,7 +130,6 @@ server for requests. It can be used for the optional"scope" parameters for Face
opener = urllib2.build_opener(auth_handler) opener = urllib2.build_opener(auth_handler)
return opener return opener
def accessToken(self): def accessToken(self):
""" """
Return the access token generated by the authenticating server. Return the access token generated by the authenticating server.
@@ -137,7 +138,7 @@ server for requests. It can be used for the optional"scope" parameters for Face
Otherwise the token is fetched from the auth server. Otherwise the token is fetched from the auth server.
""" """
if current.session.token and current.session.token.has_key('expires'): if current.session.token and 'expires' in current.session.token:
expires = current.session.token['expires'] expires = current.session.token['expires']
# reuse token until expiration # reuse token until expiration
if expires == 0 or expires > time.time(): if expires == 0 or expires > time.time():
@@ -159,19 +160,19 @@ server for requests. It can be used for the optional"scope" parameters for Face
print tmp print tmp
raise Exception(tmp) raise Exception(tmp)
finally: finally:
del current.session.code # throw it away del current.session.code # throw it away
if open_url: if open_url:
try: try:
data = open_url.read() data = open_url.read()
tokendata = cgi.parse_qs(data) tokendata = cgi.parse_qs(data)
current.session.token = \ current.session.token = \
dict([(k,v[-1]) for k,v in tokendata.items()]) dict([(k, v[-1]) for k, v in tokendata.items()])
# set expiration absolute time try to avoid broken # set expiration absolute time try to avoid broken
# implementations where "expires_in" becomes "expires" # implementations where "expires_in" becomes "expires"
if current.session.token.has_key('expires_in'): if 'expires_in' in current.session.token:
exps = 'expires_in' exps = 'expires_in'
elif current.session.token.has_key('expires'): elif 'expires' in current.session.token:
exps = 'expires' exps = 'expires'
else: else:
exps = None exps = None
@@ -217,11 +218,12 @@ server for requests. It can be used for the optional"scope" parameters for Face
Override this method by sublcassing the class. Override this method by sublcassing the class.
""" """
if not current.session.token: return None if not current.session.token:
return dict(first_name = 'Pinco', return None
last_name = 'Pallino', return dict(first_name='Pinco',
username = 'pincopallino') last_name='Pallino',
raise NotImplementedError, "Must override get_user()" username='pincopallino')
raise NotImplementedError("Must override get_user()")
# Following code is never executed. It can be used as example # Following code is never executed. It can be used as example
# for overriding in subclasses. # for overriding in subclasses.
@@ -239,10 +241,9 @@ server for requests. It can be used for the optional"scope" parameters for Face
self.graph = None self.graph = None
if user: if user:
return dict(first_name = user['first_name'], return dict(first_name=user['first_name'],
last_name = user['last_name'], last_name=user['last_name'],
username = user['id']) username=user['id'])
def __oauth_login(self, next): def __oauth_login(self, next):
""" """
@@ -258,13 +259,13 @@ server for requests. It can be used for the optional"scope" parameters for Face
if not self.accessToken(): if not self.accessToken():
if not current.request.vars.code: if not current.request.vars.code:
current.session.redirect_uri=self.__redirect_uri(next) current.session.redirect_uri = self.__redirect_uri(next)
data = dict(redirect_uri=current.session.redirect_uri, data = dict(redirect_uri=current.session.redirect_uri,
response_type='code', response_type='code',
client_id=self.client_id) client_id=self.client_id)
if self.args: if self.args:
data.update(self.args) data.update(self.args)
auth_request_url = self.auth_url + "?" +urlencode(data) auth_request_url = self.auth_url + "?" + urlencode(data)
raise HTTP(307, raise HTTP(307,
"You are not authenticated: you are being redirected to the <a href='" + auth_request_url + "'> authentication server</a>", "You are not authenticated: you are being redirected to the <a href='" + auth_request_url + "'> authentication server</a>",
Location=auth_request_url) Location=auth_request_url)
@@ -273,5 +274,3 @@ server for requests. It can be used for the optional"scope" parameters for Face
self.accessToken() self.accessToken()
return current.session.code return current.session.code
return None return None
+93 -76
View File
@@ -49,6 +49,7 @@ except ImportError, err:
DEFAULT = lambda: None DEFAULT = lambda: None
class OpenIDAuth(object): class OpenIDAuth(object):
""" """
OpenIDAuth OpenIDAuth
@@ -94,7 +95,7 @@ class OpenIDAuth(object):
if not auth.settings.table_user: if not auth.settings.table_user:
raise raise
self.table_user = self.auth.settings.table_user self.table_user = self.auth.settings.table_user
self.openid_expiration = 15 #minutes self.openid_expiration = 15 # minutes
self.messages = self._define_messages() self.messages = self._define_messages()
@@ -116,7 +117,7 @@ class OpenIDAuth(object):
messages.flash_openid_associated = 'OpenID associated' messages.flash_openid_associated = 'OpenID associated'
messages.flash_associate_openid = 'Please login or register an account for this OpenID.' messages.flash_associate_openid = 'Please login or register an account for this OpenID.'
messages.p_openid_not_registered = "This Open ID haven't be registered. " \ messages.p_openid_not_registered = "This Open ID haven't be registered. " \
+ "Please login to associate with it or register an account for it." + "Please login to associate with it or register an account for it."
messages.flash_openid_authenticated = 'OpenID authenticated successfully.' messages.flash_openid_authenticated = 'OpenID authenticated successfully.'
messages.flash_openid_fail_authentication = 'OpenID authentication failed. (Error message: %s)' messages.flash_openid_fail_authentication = 'OpenID authentication failed. (Error message: %s)'
messages.flash_openid_canceled = 'OpenID authentication canceled by user.' messages.flash_openid_canceled = 'OpenID authentication canceled by user.'
@@ -158,7 +159,7 @@ class OpenIDAuth(object):
and not processed yet. Else return the OpenID form for login. and not processed yet. Else return the OpenID form for login.
""" """
request = current.request request = current.request
if request.vars.has_key('janrain_nonce') and not self._processed(): if 'janrain_nonce' in request.vars and not self._processed():
self._process_response() self._process_response()
return self.auth() return self.auth()
return self._form() return self._form()
@@ -172,12 +173,12 @@ class OpenIDAuth(object):
args = request.args args = request.args
if args[0] == 'logout': if args[0] == 'logout':
return True # Let logout_url got called return True # Let logout_url got called
if current.session.w2popenid: if current.session.w2popenid:
w2popenid = current.session.w2popenid w2popenid = current.session.w2popenid
db = self.db db = self.db
if (w2popenid.ok is True and w2popenid.oid): # OpenID authenticated if (w2popenid.ok is True and w2popenid.oid): # OpenID authenticated
if self._w2popenid_expired(w2popenid): if self._w2popenid_expired(w2popenid):
del(current.session.w2popenid) del(current.session.w2popenid)
flash = self.messages.flash_openid_expired flash = self.messages.flash_openid_expired
@@ -196,22 +197,23 @@ class OpenIDAuth(object):
if current.session.w2popenid: if current.session.w2popenid:
del(current.session.w2popenid) del(current.session.w2popenid)
current.session.flash = self.messages.flash_openid_associated current.session.flash = self.messages.flash_openid_associated
if request.vars.has_key(nextvar): if nextvar in request.vars:
redirect(request.vars[nextvar]) redirect(request.vars[nextvar])
redirect(self.auth.settings.login_next) redirect(self.auth.settings.login_next)
if not request.vars.has_key(nextvar): if nextvar not in request.vars:
# no next var, add it and do login again # no next var, add it and do login again
# so if user login or register can go back here to associate the OpenID # so if user login or register can go back here to associate the OpenID
redirect(URL(r=request, redirect(URL(r=request,
args=['login'], args=['login'],
vars={nextvar:self.login_url})) vars={nextvar: self.login_url}))
self.login_form = self._form_with_notification() self.login_form = self._form_with_notification()
current.session.flash = self.messages.flash_associate_openid current.session.flash = self.messages.flash_associate_openid
return None # need to login or register to associate this openid return None # need to login or register to associate this openid
# Get existed OpenID user # Get existed OpenID user
user = db(self.table_user.id==alt_login.user).select().first() user = db(
self.table_user.id == alt_login.user).select().first()
if user: if user:
if current.session.w2popenid: if current.session.w2popenid:
del(current.session.w2popenid) del(current.session.w2popenid)
@@ -219,16 +221,17 @@ class OpenIDAuth(object):
username = 'username' username = 'username'
elif 'email' in self.table_user.fields(): elif 'email' in self.table_user.fields():
username = 'email' username = 'email'
return {username: user[username]} if user else None # login success (almost) return {username: user[username]} if user else None # login success (almost)
return None # just start to login return None # just start to login
def _find_matched_openid(self, db, oid, type_='openid'): def _find_matched_openid(self, db, oid, type_='openid'):
""" """
Get the matched OpenID for given Get the matched OpenID for given
""" """
query = ((db.alt_logins.username == oid) & (db.alt_logins.type == type_)) query = (
alt_login = db(query).select().first() # Get the OpenID record (db.alt_logins.username == oid) & (db.alt_logins.type == type_))
alt_login = db(query).select().first() # Get the OpenID record
return alt_login return alt_login
def _associate_user_openid(self, user, oid): def _associate_user_openid(self, user, oid):
@@ -275,7 +278,6 @@ class OpenIDAuth(object):
self.db) self.db)
return self.consumerhelper return self.consumerhelper
def _form(self, style=None): def _form(self, style=None):
form = DIV(H3(self.messages.h_openid_login), self._login_form(style)) form = DIV(H3(self.messages.h_openid_login), self._login_form(style))
return form return form
@@ -300,7 +302,7 @@ background-color: transparent;
padding-left: 18px; padding-left: 18px;
width: 400px; width: 400px;
""" """
style = style.replace("\n","") style = style.replace("\n", "")
request = current.request request = current.request
session = current.session session = current.session
@@ -308,21 +310,25 @@ width: 400px;
hidden_next_input = "" hidden_next_input = ""
if _next == 'profile': if _next == 'profile':
profile_url = URL(r=request, f='user', args=['profile']) profile_url = URL(r=request, f='user', args=['profile'])
hidden_next_input = INPUT(_type="hidden", _name="_next", _value=profile_url) hidden_next_input = INPUT(
form = FORM(openid_field_label or self.messages.label_alt_login_username, _type="hidden", _name="_next", _value=profile_url)
INPUT(_type="input", _name="oid", form = FORM(
requires=IS_NOT_EMPTY(error_message=messages.openid_fail_discover), openid_field_label or self.messages.label_alt_login_username,
_style=style), INPUT(_type="input", _name="oid",
hidden_next_input, requires=IS_NOT_EMPTY(
INPUT(_type="submit", _value=submit_button or messages.submit_button), error_message=messages.openid_fail_discover),
" ", _style=style),
A(messages.comment_openid_signin, hidden_next_input,
_href=messages.comment_openid_help_url, INPUT(_type="submit",
_title=messages.comment_openid_help_title, _value=submit_button or messages.submit_button),
_class='openid-identifier', " ",
_target="_blank"), A(messages.comment_openid_signin,
_action=self.login_url _href=messages.comment_openid_help_url,
) _title=messages.comment_openid_help_title,
_class='openid-identifier',
_target="_blank"),
_action=self.login_url
)
if form.accepts(request.vars, session): if form.accepts(request.vars, session):
oid = request.vars.oid oid = request.vars.oid
consumerhelper = self._init_consumerhelper() consumerhelper = self._init_consumerhelper()
@@ -332,8 +338,9 @@ width: 400px;
warning_openid_fail(session) warning_openid_fail(session)
redirect(url) redirect(url)
try: try:
if request.vars.has_key('_next'): if '_next' in request.vars:
return_to_url = self.return_to_url + '?_next=' + request.vars._next return_to_url = self.return_to_url + \
'?_next=' + request.vars._next
url = consumerhelper.begin(oid, self.realm, return_to_url) url = consumerhelper.begin(oid, self.realm, return_to_url)
except DiscoveryFailure: except DiscoveryFailure:
warning_openid_fail(session) warning_openid_fail(session)
@@ -353,7 +360,8 @@ width: 400px;
""" """
Set expiration for OpenID authentication. Set expiration for OpenID authentication.
""" """
w2popenid.expiration = datetime.now() + timedelta(minutes=self.openid_expiration) w2popenid.expiration = datetime.now(
) + timedelta(minutes=self.openid_expiration)
def _w2popenid_expired(self, w2popenid): def _w2popenid_expired(self, w2popenid):
""" """
@@ -369,7 +377,8 @@ width: 400px;
request = current.request request = current.request
request_vars = request.vars request_vars = request.vars
consumerhelper = self._init_consumerhelper() consumerhelper = self._init_consumerhelper()
process_status = consumerhelper.process_response(request_vars, self.return_to_url) process_status = consumerhelper.process_response(
request_vars, self.return_to_url)
if process_status == "success": if process_status == "success":
w2popenid = current.session.w2popenid w2popenid = current.session.w2popenid
user_data = self.consumerhelper.sreg() user_data = self.consumerhelper.sreg()
@@ -388,7 +397,7 @@ width: 400px;
def list_user_openids(self): def list_user_openids(self):
messages = self.messages messages = self.messages
request = current.request request = current.request
if request.vars.has_key('delete_openid'): if 'delete_openid' in request.vars:
self.remove_openid(request.vars.delete_openid) self.remove_openid(request.vars.delete_openid)
query = self.db.alt_logins.user == self.auth.user.id query = self.db.alt_logins.user == self.auth.user.id
@@ -397,8 +406,8 @@ width: 400px;
for alt_login in alt_logins: for alt_login in alt_logins:
username = alt_login.username username = alt_login.username
delete_href = URL(r=request, f='user', delete_href = URL(r=request, f='user',
args=['profile'], args=['profile'],
vars={'delete_openid': username}) vars={'delete_openid': username})
delete_link = A(messages.a_delete, _href=delete_href) delete_link = A(messages.a_delete, _href=delete_href)
l.append(LI(username, " ", delete_link)) l.append(LI(username, " ", delete_link))
@@ -409,23 +418,23 @@ width: 400px;
_next='profile', _next='profile',
submit_button=messages.submit_button_add, submit_button=messages.submit_button_add,
openid_field_label=messages.label_add_alt_login_username) openid_field_label=messages.label_add_alt_login_username)
) )
return openid_list return openid_list
def remove_openid(self, openid): def remove_openid(self, openid):
query = self.db.alt_logins.username == openid query = self.db.alt_logins.username == openid
self.db(query).delete() self.db(query).delete()
class ConsumerHelper(object): class ConsumerHelper(object):
""" """
ConsumerHelper knows the python-openid and ConsumerHelper knows the python-openid and
""" """
def __init__(self, session, db): def __init__(self, session, db):
self.session = session self.session = session
store = self._init_store(db) store = self._init_store(db)
self.consumer = openid.consumer.consumer.Consumer(session, store) self.consumer = openid.consumer.consumer.Consumer(session, store)
def _init_store(self, db): def _init_store(self, db):
""" """
@@ -434,7 +443,7 @@ class ConsumerHelper(object):
if not hasattr(self, "store"): if not hasattr(self, "store"):
store = Web2pyStore(db) store = Web2pyStore(db)
session = self.session session = self.session
if not session.has_key('w2popenid'): if 'w2popenid' not in session:
session.w2popenid = Storage() session.w2popenid = Storage()
self.store = store self.store = store
return self.store return self.store
@@ -446,7 +455,7 @@ class ConsumerHelper(object):
w2popenid = self.session.w2popenid w2popenid = self.session.w2popenid
w2popenid.oid = oid w2popenid.oid = oid
auth_req = self.consumer.begin(oid) auth_req = self.consumer.begin(oid)
auth_req.addExtension(SRegRequest(required=['email','nickname'])) auth_req.addExtension(SRegRequest(required=['email', 'nickname']))
url = auth_req.redirectURL(return_to=return_to_url, realm=realm) url = auth_req.redirectURL(return_to=return_to_url, realm=realm)
return url return url
@@ -504,19 +513,27 @@ class Web2pyStore(OpenIDStore):
if self.table_oid_associations_name not in self.database: if self.table_oid_associations_name not in self.database:
self.database.define_table(self.table_oid_associations_name, self.database.define_table(self.table_oid_associations_name,
Field('server_url', 'string', length=2047, required=True), Field('server_url',
Field('handle', 'string', length=255, required=True), 'string', length=2047, required=True),
Field('secret', 'blob', required=True), Field('handle',
Field('issued', 'integer', required=True), 'string', length=255, required=True),
Field('lifetime', 'integer', required=True), Field('secret', 'blob', required=True),
Field('assoc_type', 'string', length=64, required=True) Field('issued',
) 'integer', required=True),
Field('lifetime',
'integer', required=True),
Field('assoc_type',
'string', length=64, required=True)
)
if self.table_oid_nonces_name not in self.database: if self.table_oid_nonces_name not in self.database:
self.database.define_table(self.table_oid_nonces_name, self.database.define_table(self.table_oid_nonces_name,
Field('server_url', 'string', length=2047, required=True), Field('server_url',
Field('timestamp', 'integer', required=True), 'string', length=2047, required=True),
Field('salt', 'string', length=40, required=True) Field('timestamp',
) 'integer', required=True),
Field('salt', 'string',
length=40, required=True)
)
def storeAssociation(self, server_url, association): def storeAssociation(self, server_url, association):
""" """
@@ -525,14 +542,15 @@ class Web2pyStore(OpenIDStore):
""" """
db = self.database db = self.database
query = (db.oid_associations.server_url == server_url) & (db.oid_associations.handle == association.handle) query = (db.oid_associations.server_url == server_url) & (
db.oid_associations.handle == association.handle)
db(query).delete() db(query).delete()
db.oid_associations.insert(server_url = server_url, db.oid_associations.insert(server_url=server_url,
handle = association.handle, handle=association.handle,
secret = association.secret, secret=association.secret,
issued = association.issued, issued=association.issued,
lifetime = association.lifetime, lifetime=association.lifetime,
assoc_type = association.assoc_type), 'insert '*10 assoc_type=association.assoc_type), 'insert ' * 10
def getAssociation(self, server_url, handle=None): def getAssociation(self, server_url, handle=None):
""" """
@@ -550,7 +568,8 @@ class Web2pyStore(OpenIDStore):
if len(keep_assoc) == 0: if len(keep_assoc) == 0:
return None return None
else: else:
assoc = keep_assoc.pop() # pop the last one as it should be the latest one assoc = keep_assoc.pop(
) # pop the last one as it should be the latest one
return Association(assoc['handle'], return Association(assoc['handle'],
assoc['secret'], assoc['secret'],
assoc['issued'], assoc['issued'],
@@ -559,8 +578,9 @@ class Web2pyStore(OpenIDStore):
def removeAssociation(self, server_url, handle): def removeAssociation(self, server_url, handle):
db = self.database db = self.database
query = (db.oid_associations.server_url == server_url) & (db.oid_associations.handle == handle) query = (db.oid_associations.server_url == server_url) & (
return db(query).delete() != None db.oid_associations.handle == handle)
return db(query).delete() is not None
def useNonce(self, server_url, timestamp, salt): def useNonce(self, server_url, timestamp, salt):
""" """
@@ -575,10 +595,10 @@ class Web2pyStore(OpenIDStore):
if db(query).count() > 0: if db(query).count() > 0:
return False return False
else: else:
db.oid_nonces.insert(server_url = server_url, db.oid_nonces.insert(server_url=server_url,
timestamp = timestamp, timestamp=timestamp,
salt = salt) salt=salt)
return True return True
def _removeExpiredAssocations(self, rows): def _removeExpiredAssocations(self, rows):
""" """
@@ -599,7 +619,7 @@ class Web2pyStore(OpenIDStore):
keep_assoc.append(r) keep_assoc.append(r)
for r in remove_assoc: for r in remove_assoc:
del db.oid_associations[r['id']] del db.oid_associations[r['id']]
return (keep_assoc, len(remove_assoc)) # return tuple (list of valid associations, number of deleted associations) return (keep_assoc, len(remove_assoc)) # return tuple (list of valid associations, number of deleted associations)
def cleanupNonces(self): def cleanupNonces(self):
""" """
@@ -619,7 +639,7 @@ class Web2pyStore(OpenIDStore):
db = self.database db = self.database
query = (db.oid_associations.id > 0) query = (db.oid_associations.id > 0)
return self._removeExpiredAssocations(db(query).select())[1] #return number of assoc removed return self._removeExpiredAssocations(db(query).select())[1] # return number of assoc removed
def cleanup(self): def cleanup(self):
""" """
@@ -628,6 +648,3 @@ class Web2pyStore(OpenIDStore):
""" """
return self.cleanupNonces(), self.cleanupAssociations() return self.cleanupNonces(), self.cleanupAssociations()
+1 -2
View File
@@ -1,5 +1,6 @@
from gluon.contrib.pam import authenticate from gluon.contrib.pam import authenticate
def pam_auth(): def pam_auth():
""" """
to use pam_login: to use pam_login:
@@ -19,5 +20,3 @@ def pam_auth():
return authenticate(username, password) return authenticate(username, password)
return pam_auth_aux return pam_auth_aux
+47 -41
View File
@@ -19,11 +19,13 @@ from gluon.tools import fetch
from gluon.storage import Storage from gluon.storage import Storage
import gluon.contrib.simplejson as json import gluon.contrib.simplejson as json
class RPXAccount(object): class RPXAccount(object):
""" """
from gluon.contrib.login_methods.rpx_account import RPXAccount from gluon.contrib.login_methods.rpx_account import RPXAccount
auth.settings.actions_disabled=['register','change_password','request_reset_password'] auth.settings.actions_disabled=['register','change_password',
'request_reset_password']
auth.settings.login_form = RPXAccount(request, auth.settings.login_form = RPXAccount(request,
api_key="...", api_key="...",
domain="...", domain="...",
@@ -32,18 +34,18 @@ class RPXAccount(object):
def __init__(self, def __init__(self,
request, request,
api_key = "", api_key="",
domain = "", domain="",
url = "", url="",
embed = True, embed=True,
auth_url = "https://rpxnow.com/api/v2/auth_info", auth_url="https://rpxnow.com/api/v2/auth_info",
language= "en", language="en",
prompt='rpx', prompt='rpx',
on_login_failure = None, on_login_failure=None,
): ):
self.request=request self.request = request
self.api_key=api_key self.api_key = api_key
self.embed = embed self.embed = embed
self.auth_url = auth_url self.auth_url = auth_url
self.domain = domain self.domain = domain
@@ -54,38 +56,40 @@ class RPXAccount(object):
self.on_login_failure = on_login_failure self.on_login_failure = on_login_failure
self.mappings = Storage() self.mappings = Storage()
dn = {'givenName':'','familyName':''} dn = {'givenName': '', 'familyName': ''}
self.mappings.Facebook = lambda profile, dn=dn:\ self.mappings.Facebook = lambda profile, dn=dn:\
dict(registration_id = profile.get("identifier",""), dict(registration_id=profile.get("identifier", ""),
username = profile.get("preferredUsername",""), username=profile.get("preferredUsername", ""),
email = profile.get("email",""), email=profile.get("email", ""),
first_name = profile.get("name",dn).get("givenName",""), first_name=profile.get("name", dn).get("givenName", ""),
last_name = profile.get("name",dn).get("familyName","")) last_name=profile.get("name", dn).get("familyName", ""))
self.mappings.Google = lambda profile, dn=dn:\ self.mappings.Google = lambda profile, dn=dn:\
dict(registration_id=profile.get("identifier",""), dict(registration_id=profile.get("identifier", ""),
username=profile.get("preferredUsername",""), username=profile.get("preferredUsername", ""),
email=profile.get("email",""), email=profile.get("email", ""),
first_name=profile.get("name",dn).get("givenName",""), first_name=profile.get("name", dn).get("givenName", ""),
last_name=profile.get("name",dn).get("familyName","")) last_name=profile.get("name", dn).get("familyName", ""))
self.mappings.default = lambda profile:\ self.mappings.default = lambda profile:\
dict(registration_id=profile.get("identifier",""), dict(registration_id=profile.get("identifier", ""),
username=profile.get("preferredUsername",""), username=profile.get("preferredUsername", ""),
email=profile.get("email",""), email=profile.get("email", ""),
first_name=profile.get("preferredUsername",""), first_name=profile.get("preferredUsername", ""),
last_name='') last_name='')
def get_user(self): def get_user(self):
request = self.request request = self.request
if request.vars.token: if request.vars.token:
user = Storage() user = Storage()
data = urllib.urlencode(dict(apiKey = self.api_key, token=request.vars.token)) data = urllib.urlencode(
auth_info_json = fetch(self.auth_url+'?'+data) dict(apiKey=self.api_key, token=request.vars.token))
auth_info_json = fetch(self.auth_url + '?' + data)
auth_info = json.loads(auth_info_json) auth_info = json.loads(auth_info_json)
if auth_info['stat'] == 'ok': if auth_info['stat'] == 'ok':
self.profile = auth_info['profile'] self.profile = auth_info['profile']
provider = re.sub('[^\w\-]','',self.profile['providerName']) provider = re.sub('[^\w\-]', '', self.profile['providerName'])
user = self.mappings.get(provider,self.mappings.default)(self.profile) user = self.mappings.get(
provider, self.mappings.default)(self.profile)
return user return user
elif self.on_login_failure: elif self.on_login_failure:
redirect(self.on_login_failure) redirect(self.on_login_failure)
@@ -95,12 +99,14 @@ class RPXAccount(object):
request = self.request request = self.request
args = request.args args = request.args
if self.embed: if self.embed:
JANRAIN_URL = \ JANRAIN_URL = \
"https://%s.rpxnow.com/openid/embed?token_url=%s&language_preference=%s" "https://%s.rpxnow.com/openid/embed?token_url=%s&language_preference=%s"
rpxform = IFRAME(_src=JANRAIN_URL % (self.domain,self.token_url,self.language), rpxform = IFRAME(
_scrolling="no", _src=JANRAIN_URL % (
_frameborder="no", self.domain, self.token_url, self.language),
_style="width:400px;height:240px;") _scrolling="no",
_frameborder="no",
_style="width:400px;height:240px;")
else: else:
JANRAIN_URL = \ JANRAIN_URL = \
"https://%s.rpxnow.com/openid/v2/signin?token_url=%s" "https://%s.rpxnow.com/openid/v2/signin?token_url=%s"
@@ -114,15 +120,15 @@ class RPXAccount(object):
_type="text/javascript")) _type="text/javascript"))
return rpxform return rpxform
def use_janrain(auth,filename='private/janrain.key',**kwargs):
path = os.path.join(current.request.folder,filename) def use_janrain(auth, filename='private/janrain.key', **kwargs):
path = os.path.join(current.request.folder, filename)
if os.path.exists(path): if os.path.exists(path):
request = current.request request = current.request
domain,key = open(path,'r').read().strip().split(':') domain, key = open(path, 'r').read().strip().split(':')
host = current.request.env.http_host host = current.request.env.http_host
url = URL('default', 'user', args='login', scheme=True) url = URL('default', 'user', args='login', scheme=True)
auth.settings.actions_disabled = \ auth.settings.actions_disabled = \
['register','change_password','request_reset_password'] ['register', 'change_password', 'request_reset_password']
auth.settings.login_form = RPXAccount( auth.settings.login_form = RPXAccount(
request, api_key=key,domain=domain, url = url,**kwargs) request, api_key=key, domain=domain, url=url, **kwargs)
+19 -26
View File
@@ -11,13 +11,12 @@ Adds support for x509 authentication.
from gluon.globals import current from gluon.globals import current
from gluon.storage import Storage from gluon.storage import Storage
from gluon.http import HTTP,redirect from gluon.http import HTTP, redirect
#requires M2Crypto #requires M2Crypto
from M2Crypto import X509 from M2Crypto import X509
class X509Auth(object): class X509Auth(object):
""" """
Login using x509 cert from client. Login using x509 cert from client.
@@ -29,8 +28,6 @@ class X509Auth(object):
""" """
def __init__(self): def __init__(self):
self.request = current.request self.request = current.request
self.ssl_client_raw_cert = self.request.env.ssl_client_raw_cert self.ssl_client_raw_cert = self.request.env.ssl_client_raw_cert
@@ -41,10 +38,11 @@ class X509Auth(object):
if self.ssl_client_raw_cert: if self.ssl_client_raw_cert:
x509=X509.load_cert_string(self.ssl_client_raw_cert, X509.FORMAT_PEM) x509 = X509.load_cert_string(
self.ssl_client_raw_cert, X509.FORMAT_PEM)
# extract it from the cert # extract it from the cert
self.serial = self.request.env.ssl_client_serial or ('%x' % x509.get_serial_number()).upper() self.serial = self.request.env.ssl_client_serial or (
'%x' % x509.get_serial_number()).upper()
subject = x509.get_subject() subject = x509.get_subject()
@@ -53,23 +51,17 @@ class X509Auth(object):
# cn = self.subject.cn # cn = self.subject.cn
self.subject = Storage(filter(None, self.subject = Storage(filter(None,
map(lambda x: map(lambda x:
(x,map(lambda y: (x, map(lambda y:
y.get_data().as_text(), y.get_data(
subject.get_entries_by_nid(subject.nid[x]))), ).as_text(),
subject.get_entries_by_nid(subject.nid[x]))),
subject.nid.keys()))) subject.nid.keys())))
def login_form(self, **args): def login_form(self, **args):
raise HTTP(403,'Login not allowed. No valid x509 crentials') raise HTTP(403, 'Login not allowed. No valid x509 crentials')
def login_url(self, next="/"): def login_url(self, next="/"):
raise HTTP(403,'Login not allowed. No valid x509 crentials') raise HTTP(403, 'Login not allowed. No valid x509 crentials')
def logout_url(self, next="/"): def logout_url(self, next="/"):
return next return next
@@ -86,10 +78,14 @@ class X509Auth(object):
p = profile = dict() p = profile = dict()
username = p['username'] = reduce(lambda a,b: '%s | %s' % (a,b), self.subject.CN or self.subject.commonName) username = p['username'] = reduce(lambda a, b: '%s | %s' % (
p['first_name'] = reduce(lambda a,b: '%s | %s' % (a,b),self.subject.givenName or username) a, b), self.subject.CN or self.subject.commonName)
p['last_name'] = reduce(lambda a,b: '%s | %s' % (a,b),self.subject.surname) p['first_name'] = reduce(lambda a, b: '%s | %s' % (a, b),
p['email'] = reduce(lambda a,b: '%s | %s' % (a,b),self.subject.Email or self.subject.emailAddress) self.subject.givenName or username)
p['last_name'] = reduce(
lambda a, b: '%s | %s' % (a, b), self.subject.surname)
p['email'] = reduce(lambda a, b: '%s | %s' % (
a, b), self.subject.Email or self.subject.emailAddress)
# IMPORTANT WE USE THE CERT SERIAL AS UNIQUE KEY FOR THE USER # IMPORTANT WE USE THE CERT SERIAL AS UNIQUE KEY FOR THE USER
p['registration_id'] = self.serial p['registration_id'] = self.serial
@@ -100,6 +96,3 @@ class X509Auth(object):
p['certificate'] = self.ssl_client_raw_cert p['certificate'] = self.ssl_client_raw_cert
return profile return profile
+61 -62
View File
@@ -44,22 +44,21 @@ SQL_DIALECTS = {'memcache': {
'is not null': 'IS NOT NULL', 'is not null': 'IS NOT NULL',
'extract': None, 'extract': None,
'left join': None, 'left join': None,
}} }}
def cleanup(text): def cleanup(text):
if re.compile('[^0-9a-zA-Z_]').findall(text): if re.compile('[^0-9a-zA-Z_]').findall(text):
raise SyntaxError, \ raise SyntaxError('Can\'t cleanup \'%s\': only [0-9a-zA-Z_] allowed in table and field names' % text)
'Can\'t cleanup \'%s\': only [0-9a-zA-Z_] allowed in table and field names' % text
return text return text
def assert_filter_fields(*fields): def assert_filter_fields(*fields):
for field in fields: for field in fields:
if isinstance(field, (Field, Expression)) and field.type\ if isinstance(field, (Field, Expression)) and field.type\
in ['text', 'blob']: in ['text', 'blob']:
raise SyntaxError, 'AppEngine does not index by: %s'\ raise SyntaxError('AppEngine does not index by: %s'
% field.type % field.type)
def dateobj_to_datetime(object): def dateobj_to_datetime(object):
@@ -78,7 +77,7 @@ def dateobj_to_datetime(object):
object.minute, object.minute,
object.second, object.second,
object.microsecond, object.microsecond,
) )
return object return object
@@ -96,7 +95,7 @@ def sqlhtml_validators(field_type, length):
'time': validators.IS_TIME(), 'time': validators.IS_TIME(),
'datetime': validators.IS_DATETIME(), 'datetime': validators.IS_DATETIME(),
'reference': validators.IS_INT_IN_RANGE(0, 1e100), 'reference': validators.IS_INT_IN_RANGE(0, 1e100),
} }
try: try:
return v[field_type[:9]] return v[field_type[:9]]
except KeyError: except KeyError:
@@ -114,7 +113,8 @@ class DALStorage(dict):
def __setattr__(self, key, value): def __setattr__(self, key, value):
if key in self: if key in self:
raise SyntaxError, 'Object \'%s\'exists and cannot be redefined' % key raise SyntaxError(
'Object \'%s\'exists and cannot be redefined' % key)
self[key] = value self[key] = value
def __repr__(self): def __repr__(self):
@@ -151,14 +151,14 @@ class MEMDB(DALStorage):
tablename, tablename,
*fields, *fields,
**args **args
): ):
tablename = cleanup(tablename) tablename = cleanup(tablename)
if tablename in dir(self) or tablename[0] == '_': if tablename in dir(self) or tablename[0] == '_':
raise SyntaxError, 'invalid table name: %s' % tablename raise SyntaxError('invalid table name: %s' % tablename)
if not tablename in self.tables: if not tablename in self.tables:
self.tables.append(tablename) self.tables.append(tablename)
else: else:
raise SyntaxError, 'table already defined: %s' % tablename raise SyntaxError('table already defined: %s' % tablename)
t = self[tablename] = Table(self, tablename, *fields) t = self[tablename] = Table(self, tablename, *fields)
t._create() t._create()
return t return t
@@ -190,7 +190,7 @@ class Table(DALStorage):
db, db,
tablename, tablename,
*fields *fields
): ):
self._db = db self._db = db
self._tablename = tablename self._tablename = tablename
self.fields = SQLCallableList() self.fields = SQLCallableList()
@@ -219,24 +219,24 @@ class Table(DALStorage):
if field.type[:9] == 'reference': if field.type[:9] == 'reference':
referenced = field.type[10:].strip() referenced = field.type[10:].strip()
if not referenced: if not referenced:
raise SyntaxError, \ raise SyntaxError('Table %s: reference \'%s\' to nothing!' % (
'Table %s: reference \'%s\' to nothing!' % (self._tablename, k) self._tablename, k))
if not referenced in self._db: if not referenced in self._db:
raise SyntaxError, \ raise SyntaxError(
'Table: table %s does not exist' % referenced 'Table: table %s does not exist' % referenced)
referee = self._db[referenced] referee = self._db[referenced]
ftype = \ ftype = \
self._db._translator[field.type[:9]]( self._db._translator[field.type[:9]](
self._db[referenced]._tableobj) self._db[referenced]._tableobj)
if self._tablename in referee.fields: # ## THIS IS OK if self._tablename in referee.fields: # ## THIS IS OK
raise SyntaxError, \ raise SyntaxError('Field: table \'%s\' has same name as a field '
'Field: table \'%s\' has same name as a field ' \ 'in referenced table \'%s\'' % (
'in referenced table \'%s\'' % (self._tablename, referenced) self._tablename, referenced))
self._db[referenced]._referenced_by.append((self._tablename, self._db[referenced]._referenced_by.append((self._tablename,
field.name)) field.name))
elif not field.type in self._db._translator\ elif not field.type in self._db._translator\
or not self._db._translator[field.type]: or not self._db._translator[field.type]:
raise SyntaxError, 'Field: unkown field type %s' % field.type raise SyntaxError('Field: unkown field type %s' % field.type)
self._tableobj = self._db.client self._tableobj = self._db.client
return None return None
@@ -269,11 +269,11 @@ class Table(DALStorage):
def update(self, id, **fields): def update(self, id, **fields):
for field in fields: for field in fields:
if not field in fields and self[field].default\ if not field in fields and self[field].default\
!= None: is not None:
fields[field] = self[field].default fields[field] = self[field].default
if field in fields: if field in fields:
fields[field] = obj_represent(fields[field], fields[field] = obj_represent(fields[field],
self[field].type, self._db) self[field].type, self._db)
return self._tableobj.set(self._id_to_key(id), fields) return self._tableobj.set(self._id_to_key(id), fields)
def delete(self, id): def delete(self, id):
@@ -293,7 +293,7 @@ class Table(DALStorage):
if self._tableobj.set(shard_id, '0'): if self._tableobj.set(shard_id, '0'):
id = 0 id = 0
else: else:
raise Exception, 'cannot set memcache' raise Exception('cannot set memcache')
return long(str(shard) + str(id)) return long(str(shard) + str(id))
def __str__(self): def __str__(self):
@@ -307,7 +307,7 @@ class Expression(object):
name, name,
type='string', type='string',
db=None, db=None,
): ):
(self.name, self.type, self._db) = (name, type, db) (self.name, self.type, self._db) = (name, type, db)
def __str__(self): def __str__(self):
@@ -393,11 +393,11 @@ class Field(Expression):
notnull=False, notnull=False,
unique=False, unique=False,
uploadfield=True, uploadfield=True,
): ):
self.name = cleanup(fieldname) self.name = cleanup(fieldname)
if fieldname in dir(Table) or fieldname[0] == '_': if fieldname in dir(Table) or fieldname[0] == '_':
raise SyntaxError, 'Field: invalid field name: %s' % fieldname raise SyntaxError('Field: invalid field name: %s' % fieldname)
if isinstance(type, Table): if isinstance(type, Table):
type = 'reference ' + type._tablename type = 'reference ' + type._tablename
if not length: if not length:
@@ -437,9 +437,9 @@ MEMDB.Field = Field # ## required by gluon/globals.py session.connect
def obj_represent(object, fieldtype, db): def obj_represent(object, fieldtype, db):
if object != None: if object is not None:
if fieldtype == 'date' and not isinstance(object, if fieldtype == 'date' and not isinstance(object,
datetime.date): datetime.date):
(y, m, d) = [int(x) for x in str(object).strip().split('-')] (y, m, d) = [int(x) for x in str(object).strip().split('-')]
object = datetime.date(y, m, d) object = datetime.date(y, m, d)
elif fieldtype == 'time' and not isinstance(object, datetime.time): elif fieldtype == 'time' and not isinstance(object, datetime.time):
@@ -450,7 +450,7 @@ def obj_represent(object, fieldtype, db):
(h, mi, s) = time_items + [0] (h, mi, s) = time_items + [0]
object = datetime.time(h, mi, s) object = datetime.time(h, mi, s)
elif fieldtype == 'datetime' and not isinstance(object, elif fieldtype == 'datetime' and not isinstance(object,
datetime.datetime): datetime.datetime):
(y, m, d) = [int(x) for x in (y, m, d) = [int(x) for x in
str(object)[:10].strip().split('-')] str(object)[:10].strip().split('-')]
time_items = [int(x) for x in time_items = [int(x) for x in
@@ -466,7 +466,7 @@ def obj_represent(object, fieldtype, db):
h, h,
mi, mi,
s, s,
) )
elif fieldtype == 'integer' and not isinstance(object, long): elif fieldtype == 'integer' and not isinstance(object, long):
object = long(object) object = long(object)
@@ -496,10 +496,10 @@ class Query(object):
left, left,
op=None, op=None,
right=None, right=None,
): ):
if isinstance(right, (Field, Expression)): if isinstance(right, (Field, Expression)):
raise SyntaxError, \ raise SyntaxError(
'Query: right side of filter must be a value or entity' 'Query: right side of filter must be a value or entity')
if isinstance(left, Field) and left.name == 'id': if isinstance(left, Field) and left.name == 'id':
if op == '=': if op == '=':
self.get_one = \ self.get_one = \
@@ -507,8 +507,8 @@ class Query(object):
id=long(right)) id=long(right))
return return
else: else:
raise SyntaxError, 'only equality by id is supported' raise SyntaxError('only equality by id is supported')
raise SyntaxError, 'not supported' raise SyntaxError('not supported')
def __str__(self): def __str__(self):
return str(self.left) return str(self.left)
@@ -539,7 +539,7 @@ class Set(object):
self.where = where self.where = where
self._tables.insert(0, where.get_all) self._tables.insert(0, where.get_all)
elif hasattr(where, 'get_one') and isinstance(where.get_one, elif hasattr(where, 'get_one') and isinstance(where.get_one,
QueryException): QueryException):
self.where = where.get_one self.where = where.get_one
else: else:
@@ -553,9 +553,8 @@ class Set(object):
def __call__(self, where): def __call__(self, where):
if isinstance(self.where, QueryException) or isinstance(where, if isinstance(self.where, QueryException) or isinstance(where,
QueryException): QueryException):
raise SyntaxError, \ raise SyntaxError('neither self.where nor where can be a QueryException instance')
'neither self.where nor where can be a QueryException instance'
if self.where: if self.where:
return Set(self._db, self.where & where) return Set(self._db, self.where & where)
else: else:
@@ -564,9 +563,9 @@ class Set(object):
def _get_table_or_raise(self): def _get_table_or_raise(self):
tablenames = list(set(self._tables)) # unique tablenames = list(set(self._tables)) # unique
if len(tablenames) < 1: if len(tablenames) < 1:
raise SyntaxError, 'Set: no tables selected' raise SyntaxError('Set: no tables selected')
if len(tablenames) > 1: if len(tablenames) > 1:
raise SyntaxError, 'Set: no join in appengine' raise SyntaxError('Set: no join in appengine')
return self._db[tablenames[0]]._tableobj return self._db[tablenames[0]]._tableobj
def _getitem_exception(self): def _getitem_exception(self):
@@ -597,7 +596,7 @@ class Set(object):
if isinstance(self.where, QueryException): if isinstance(self.where, QueryException):
return self._select_except() return self._select_except()
else: else:
raise SyntaxError, 'select arguments not supported' raise SyntaxError('select arguments not supported')
def count(self): def count(self):
return len(self.select()) return len(self.select())
@@ -609,7 +608,7 @@ class Set(object):
return return
self._db[tablename].delete(id) self._db[tablename].delete(id)
else: else:
raise Exception, 'deletion not implemented' raise Exception('deletion not implemented')
def update(self, **update_fields): def update(self, **update_fields):
if isinstance(self.where, QueryException): if isinstance(self.where, QueryException):
@@ -620,7 +619,7 @@ class Set(object):
setattr(item, key, value) setattr(item, key, value)
self._db[tablename].update(id, **item) self._db[tablename].update(id, **item)
else: else:
raise Exception, 'update not implemented' raise Exception('update not implemented')
def update_record( def update_record(
@@ -628,7 +627,7 @@ def update_record(
s, s,
id, id,
a, a,
): ):
item = s.get(id) item = s.get(id)
for (key, value) in a.items(): for (key, value) in a.items():
t[key] = value t[key] = value
@@ -650,7 +649,7 @@ class Rows(object):
db, db,
response, response,
*colnames *colnames
): ):
self._db = db self._db = db
self.colnames = colnames self.colnames = colnames
self.response = response self.response = response
@@ -660,9 +659,9 @@ class Rows(object):
def __getitem__(self, i): def __getitem__(self, i):
if i >= len(self.response) or i < 0: if i >= len(self.response) or i < 0:
raise SyntaxError, 'Rows: no such row: %i' % i raise SyntaxError('Rows: no such row: %i' % i)
if len(self.response[0]) != len(self.colnames): if len(self.response[0]) != len(self.colnames):
raise SyntaxError, 'Rows: internal error' raise SyntaxError('Rows: internal error')
row = DALStorage() row = DALStorage()
for j in xrange(len(self.colnames)): for j in xrange(len(self.colnames)):
value = self.response[i][j] value = self.response[i][j]
@@ -684,7 +683,7 @@ class Rows(object):
referee = field.type[10:].strip() referee = field.type[10:].strip()
rid = value rid = value
row[tablename][fieldname] = rid row[tablename][fieldname] = rid
elif field.type == 'boolean' and value != None: elif field.type == 'boolean' and value is not None:
# row[tablename][fieldname]=Set(self._db[referee].id==rid) # row[tablename][fieldname]=Set(self._db[referee].id==rid)
@@ -692,13 +691,13 @@ class Rows(object):
row[tablename][fieldname] = True row[tablename][fieldname] = True
else: else:
row[tablename][fieldname] = False row[tablename][fieldname] = False
elif field.type == 'date' and value != None\ elif field.type == 'date' and value is not None\
and not isinstance(value, datetime.date): and not isinstance(value, datetime.date):
(y, m, d) = [int(x) for x in (y, m, d) = [int(x) for x in
str(value).strip().split('-')] str(value).strip().split('-')]
row[tablename][fieldname] = datetime.date(y, m, d) row[tablename][fieldname] = datetime.date(y, m, d)
elif field.type == 'time' and value != None\ elif field.type == 'time' and value is not None\
and not isinstance(value, datetime.time): and not isinstance(value, datetime.time):
time_items = [int(x) for x in time_items = [int(x) for x in
str(value).strip().split(':')[:3]] str(value).strip().split(':')[:3]]
if len(time_items) == 3: if len(time_items) == 3:
@@ -706,8 +705,8 @@ class Rows(object):
else: else:
(h, mi, s) = time_items + [0] (h, mi, s) = time_items + [0]
row[tablename][fieldname] = datetime.time(h, mi, s) row[tablename][fieldname] = datetime.time(h, mi, s)
elif field.type == 'datetime' and value != None\ elif field.type == 'datetime' and value is not None\
and not isinstance(value, datetime.datetime): and not isinstance(value, datetime.datetime):
(y, m, d) = [int(x) for x in (y, m, d) = [int(x) for x in
str(value)[:10].strip().split('-')] str(value)[:10].strip().split('-')]
time_items = [int(x) for x in time_items = [int(x) for x in
@@ -723,19 +722,19 @@ class Rows(object):
h, h,
mi, mi,
s, s,
) )
else: else:
row[tablename][fieldname] = value row[tablename][fieldname] = value
if fieldname == 'id': if fieldname == 'id':
id = row[tablename].id id = row[tablename].id
row[tablename].update_record = lambda t = row[tablename], \ row[tablename].update_record = lambda t = row[tablename], \
s = self._db[tablename], id = id, **a: update_record(t, s = self._db[tablename], id = id, **a: update_record(t,
s, id, a) s, id, a)
for (referee_table, referee_name) in \ for (referee_table, referee_name) in \
table._referenced_by: table._referenced_by:
s = self._db[referee_table][referee_name] s = self._db[referee_table][referee_name]
row[tablename][referee_table] = Set(self._db, s row[tablename][referee_table] = Set(self._db, s
== id) == id)
if len(row.keys()) == 1: if len(row.keys()) == 1:
return row[row.keys()[0]] return row[row.keys()[0]]
return row return row
+33 -32
View File
@@ -11,7 +11,7 @@ Modified for inclusion into web2py by: Ross Peoples <ross.peoples@gmail.com>
""" """
from StringIO import StringIO # The pure-Python StringIO supports unicode. from StringIO import StringIO # The pure-Python StringIO supports unicode.
import re import re
@@ -20,7 +20,7 @@ __version__ = '0.1.4'
def remove_comments(css): def remove_comments(css):
"""Remove all CSS comment blocks.""" """Remove all CSS comment blocks."""
iemac = False iemac = False
preserve = False preserve = False
comment_start = css.find("/*") comment_start = css.find("/*")
@@ -28,7 +28,7 @@ def remove_comments(css):
# Preserve comments that look like `/*!...*/`. # Preserve comments that look like `/*!...*/`.
# Slicing is used to make sure we don"t get an IndexError. # Slicing is used to make sure we don"t get an IndexError.
preserve = css[comment_start + 2:comment_start + 3] == "!" preserve = css[comment_start + 2:comment_start + 3] == "!"
comment_end = css.find("*/", comment_start + 2) comment_end = css.find("*/", comment_start + 2)
if comment_end < 0: if comment_end < 0:
if not preserve: if not preserve:
@@ -48,22 +48,22 @@ def remove_comments(css):
else: else:
comment_start = comment_end + 2 comment_start = comment_end + 2
comment_start = css.find("/*", comment_start) comment_start = css.find("/*", comment_start)
return css return css
def remove_unnecessary_whitespace(css): def remove_unnecessary_whitespace(css):
"""Remove unnecessary whitespace characters.""" """Remove unnecessary whitespace characters."""
def pseudoclasscolon(css): def pseudoclasscolon(css):
""" """
Prevents 'p :link' from becoming 'p:link'. Prevents 'p :link' from becoming 'p:link'.
Translates 'p :link' into 'p ___PSEUDOCLASSCOLON___link'; this is Translates 'p :link' into 'p ___PSEUDOCLASSCOLON___link'; this is
translated back again later. translated back again later.
""" """
regex = re.compile(r"(^|\})(([^\{\:])+\:)+([^\{]*\{)") regex = re.compile(r"(^|\})(([^\{\:])+\:)+([^\{]*\{)")
match = regex.search(css) match = regex.search(css)
while match: while match:
@@ -73,43 +73,43 @@ def remove_unnecessary_whitespace(css):
css[match.end():]]) css[match.end():]])
match = regex.search(css) match = regex.search(css)
return css return css
css = pseudoclasscolon(css) css = pseudoclasscolon(css)
# Remove spaces from before things. # Remove spaces from before things.
css = re.sub(r"\s+([!{};:>+\(\)\],])", r"\1", css) css = re.sub(r"\s+([!{};:>+\(\)\],])", r"\1", css)
# If there is a `@charset`, then only allow one, and move to the beginning. # If there is a `@charset`, then only allow one, and move to the beginning.
css = re.sub(r"^(.*)(@charset \"[^\"]*\";)", r"\2\1", css) css = re.sub(r"^(.*)(@charset \"[^\"]*\";)", r"\2\1", css)
css = re.sub(r"^(\s*@charset [^;]+;\s*)+", r"\1", css) css = re.sub(r"^(\s*@charset [^;]+;\s*)+", r"\1", css)
# Put the space back in for a few cases, such as `@media screen` and # Put the space back in for a few cases, such as `@media screen` and
# `(-webkit-min-device-pixel-ratio:0)`. # `(-webkit-min-device-pixel-ratio:0)`.
css = re.sub(r"\band\(", "and (", css) css = re.sub(r"\band\(", "and (", css)
# Put the colons back. # Put the colons back.
css = css.replace('___PSEUDOCLASSCOLON___', ':') css = css.replace('___PSEUDOCLASSCOLON___', ':')
# Remove spaces from after things. # Remove spaces from after things.
css = re.sub(r"([!{}:;>+\(\[,])\s+", r"\1", css) css = re.sub(r"([!{}:;>+\(\[,])\s+", r"\1", css)
return css return css
def remove_unnecessary_semicolons(css): def remove_unnecessary_semicolons(css):
"""Remove unnecessary semicolons.""" """Remove unnecessary semicolons."""
return re.sub(r";+\}", "}", css) return re.sub(r";+\}", "}", css)
def remove_empty_rules(css): def remove_empty_rules(css):
"""Remove empty rules.""" """Remove empty rules."""
return re.sub(r"[^\}\{]+\{\}", "", css) return re.sub(r"[^\}\{]+\{\}", "", css)
def normalize_rgb_colors_to_hex(css): def normalize_rgb_colors_to_hex(css):
"""Convert `rgb(51,102,153)` to `#336699`.""" """Convert `rgb(51,102,153)` to `#336699`."""
regex = re.compile(r"rgb\s*\(\s*([0-9,\s]+)\s*\)") regex = re.compile(r"rgb\s*\(\s*([0-9,\s]+)\s*\)")
match = regex.search(css) match = regex.search(css)
while match: while match:
@@ -122,39 +122,40 @@ def normalize_rgb_colors_to_hex(css):
def condense_zero_units(css): def condense_zero_units(css):
"""Replace `0(px, em, %, etc)` with `0`.""" """Replace `0(px, em, %, etc)` with `0`."""
return re.sub(r"([\s:])(0)(px|em|%|in|cm|mm|pc|pt|ex)", r"\1\2", css) return re.sub(r"([\s:])(0)(px|em|%|in|cm|mm|pc|pt|ex)", r"\1\2", css)
def condense_multidimensional_zeros(css): def condense_multidimensional_zeros(css):
"""Replace `:0 0 0 0;`, `:0 0 0;` etc. with `:0;`.""" """Replace `:0 0 0 0;`, `:0 0 0;` etc. with `:0;`."""
css = css.replace(":0 0 0 0;", ":0;") css = css.replace(":0 0 0 0;", ":0;")
css = css.replace(":0 0 0;", ":0;") css = css.replace(":0 0 0;", ":0;")
css = css.replace(":0 0;", ":0;") css = css.replace(":0 0;", ":0;")
# Revert `background-position:0;` to the valid `background-position:0 0;`. # Revert `background-position:0;` to the valid `background-position:0 0;`.
css = css.replace("background-position:0;", "background-position:0 0;") css = css.replace("background-position:0;", "background-position:0 0;")
return css return css
def condense_floating_points(css): def condense_floating_points(css):
"""Replace `0.6` with `.6` where possible.""" """Replace `0.6` with `.6` where possible."""
return re.sub(r"(:|\s)0+\.(\d+)", r"\1.\2", css) return re.sub(r"(:|\s)0+\.(\d+)", r"\1.\2", css)
def condense_hex_colors(css): def condense_hex_colors(css):
"""Shorten colors from #AABBCC to #ABC where possible.""" """Shorten colors from #AABBCC to #ABC where possible."""
regex = re.compile(r"([^\"'=\s])(\s*)#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])") regex = re.compile(r"([^\"'=\s])(\s*)#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])")
match = regex.search(css) match = regex.search(css)
while match: while match:
first = match.group(3) + match.group(5) + match.group(7) first = match.group(3) + match.group(5) + match.group(7)
second = match.group(4) + match.group(6) + match.group(8) second = match.group(4) + match.group(6) + match.group(8)
if first.lower() == second.lower(): if first.lower() == second.lower():
css = css.replace(match.group(), match.group(1) + match.group(2) + '#' + first) css = css.replace(
match.group(), match.group(1) + match.group(2) + '#' + first)
match = regex.search(css, match.end() - 3) match = regex.search(css, match.end() - 3)
else: else:
match = regex.search(css, match.end()) match = regex.search(css, match.end())
@@ -163,19 +164,19 @@ def condense_hex_colors(css):
def condense_whitespace(css): def condense_whitespace(css):
"""Condense multiple adjacent whitespace characters into one.""" """Condense multiple adjacent whitespace characters into one."""
return re.sub(r"\s+", " ", css) return re.sub(r"\s+", " ", css)
def condense_semicolons(css): def condense_semicolons(css):
"""Condense multiple adjacent semicolon characters into one.""" """Condense multiple adjacent semicolon characters into one."""
return re.sub(r";;+", ";", css) return re.sub(r";;+", ";", css)
def wrap_css_lines(css, line_length): def wrap_css_lines(css, line_length):
"""Wrap the lines of the given CSS to an approximate length.""" """Wrap the lines of the given CSS to an approximate length."""
lines = [] lines = []
line_start = 0 line_start = 0
for i, char in enumerate(css): for i, char in enumerate(css):
@@ -183,7 +184,7 @@ def wrap_css_lines(css, line_length):
if char == '}' and (i - line_start >= line_length): if char == '}' and (i - line_start >= line_length):
lines.append(css[line_start:i + 1]) lines.append(css[line_start:i + 1])
line_start = i + 1 line_start = i + 1
if line_start < len(css): if line_start < len(css):
lines.append(css[line_start:]) lines.append(css[line_start:])
return '\n'.join(lines) return '\n'.join(lines)
@@ -212,19 +213,19 @@ def cssmin(css, wrap=None):
def main(): def main():
import optparse import optparse
import sys import sys
p = optparse.OptionParser( p = optparse.OptionParser(
prog="cssmin", version=__version__, prog="cssmin", version=__version__,
usage="%prog [--wrap N]", usage="%prog [--wrap N]",
description="""Reads raw CSS from stdin, and writes compressed CSS to stdout.""") description="""Reads raw CSS from stdin, and writes compressed CSS to stdout.""")
p.add_option( p.add_option(
'-w', '--wrap', type='int', default=None, metavar='N', '-w', '--wrap', type='int', default=None, metavar='N',
help="Wrap output to approximately N chars per line.") help="Wrap output to approximately N chars per line.")
options, args = p.parse_args() options, args = p.parse_args()
sys.stdout.write(cssmin(sys.stdin.read(), wrap=options.wrap)) sys.stdout.write(cssmin(sys.stdin.read(), wrap=options.wrap))
if __name__ == '__main__': if __name__ == '__main__':
main() main()
+10 -8
View File
@@ -2,12 +2,14 @@
import re import re
def minify(response): def minify(response):
def _replace(match): def _replace(match):
match = match.group() match = match.group()
# save whole <pre>, <textarea> tags, and opening <!-- (so it doesn't break <script>) # save whole <pre>, <textarea> tags, and opening <!-- (so it doesn't break <script>)
# otherwise, replace all whitespace with a single space character # otherwise, replace all whitespace with a single space character
return match if match.startswith(('<pre', '<textarea', '<!--')) else ' ' return match if match.startswith(('<pre', '<textarea', '<!--')) else ' '
cpat = re.compile(r'\s+|<pre(.*?)</pre>|<textarea(.*?)</textarea>|<!--\s', re.DOTALL) cpat = re.compile(
return cpat.sub(_replace, response) r'\s+|<pre(.*?)</pre>|<textarea(.*?)</textarea>|<!--\s', re.DOTALL)
return cpat.sub(_replace, response)
+44 -27
View File
@@ -49,7 +49,7 @@ Both python 2 and python 3 are supported.
.. _jsmin.c by Douglas Crockford: .. _jsmin.c by Douglas Crockford:
http://www.crockford.com/javascript/jsmin.c http://www.crockford.com/javascript/jsmin.c
Original author of Python version: Andr\xe9 Malo Original author of Python version: Andr\xe9 Malo
Home page: http://opensource.perlig.de/rjsmin/ Home page: http://opensource.perlig.de/rjsmin/
Modified by Ross Peoples <ross.peoples@gmail.com> for inclusion into web2py. Modified by Ross Peoples <ross.peoples@gmail.com> for inclusion into web2py.
@@ -96,7 +96,7 @@ def _make_jsmin(extended=True, python_only=True):
try: try:
xrange xrange
except NameError: except NameError:
xrange = range # pylint: disable = W0622 xrange = range # pylint: disable = W0622
space_chars = r'[\000-\011\013\014\016-\040]' space_chars = r'[\000-\011\013\014\016-\040]'
@@ -151,13 +151,13 @@ def _make_jsmin(extended=True, python_only=True):
last != first and chr(last) or '' last != first and chr(last) or ''
) for first, last in result]) ) for first, last in result])
return _re.sub(r'([\000-\040\047])', # for better portability return _re.sub(r'([\000-\040\047])', # for better portability
lambda m: '\\%03o' % ord(m.group(1)), (sequentize(result) lambda m: '\\%03o' % ord(m.group(1)), (sequentize(result)
.replace('\\', '\\\\') .replace('\\', '\\\\')
.replace('[', '\\[') .replace('[', '\\[')
.replace(']', '\\]') .replace(']', '\\]')
) )
) )
def id_literal_(what): def id_literal_(what):
""" Make id_literal like char class """ """ Make id_literal like char class """
@@ -190,25 +190,33 @@ def _make_jsmin(extended=True, python_only=True):
r'|(?:(?<=%(preregex1)s)%(space)s*(%(regex)s[^\047"/\000-\040]*))' r'|(?:(?<=%(preregex1)s)%(space)s*(%(regex)s[^\047"/\000-\040]*))'
r'|(?:(?<=%(preregex2)s)%(space)s*(%(regex)s[^\047"/\000-\040]*))' r'|(?:(?<=%(preregex2)s)%(space)s*(%(regex)s[^\047"/\000-\040]*))'
r'|(?<=%(id_literal_close)s)' r'|(?<=%(id_literal_close)s)'
r'%(space)s*(?:(%(newline)s)%(space)s*)+' r'%(space)s*(?:(%(newline)s)%(space)s*)+'
r'(?=%(id_literal_open)s)' r'(?=%(id_literal_open)s)'
r'|(?<=%(id_literal)s)(%(space)s)+(?=%(id_literal)s)' r'|(?<=%(id_literal)s)(%(space)s)+(?=%(id_literal)s)'
r'|%(space)s+' r'|%(space)s+'
r'|(?:%(newline)s%(space)s*)+' r'|(?:%(newline)s%(space)s*)+'
) % locals()).sub ) % locals()).sub
def space_subber(match): def space_subber(match):
""" Substitution callback """ """ Substitution callback """
# pylint: disable = C0321, R0911 # pylint: disable = C0321, R0911
groups = match.groups() groups = match.groups()
if groups[0]: return groups[0] if groups[0]:
elif groups[1]: return groups[1] return groups[0]
elif groups[2]: return groups[2] elif groups[1]:
elif groups[3]: return groups[3] return groups[1]
elif groups[4]: return '\n' elif groups[2]:
elif groups[5]: return ' ' return groups[2]
else: return '' elif groups[3]:
return groups[3]
elif groups[4]:
return '\n'
elif groups[5]:
return ' '
else:
return ''
def jsmin(script): # pylint: disable = W0621 def jsmin(script): # pylint: disable = W0621
r""" r"""
Minify javascript based on `jsmin.c by Douglas Crockford`_\. Minify javascript based on `jsmin.c by Douglas Crockford`_\.
@@ -239,20 +247,26 @@ def _make_jsmin(extended=True, python_only=True):
r'|(%(space)s)+' r'|(%(space)s)+'
r'|(?:(%(newline)s)%(space)s*)+' r'|(?:(%(newline)s)%(space)s*)+'
) % locals()).sub ) % locals()).sub
def space_norm_subber(match): def space_norm_subber(match):
""" Substitution callback """ """ Substitution callback """
# pylint: disable = C0321 # pylint: disable = C0321
groups = match.groups() groups = match.groups()
if groups[0]: return groups[0] if groups[0]:
elif groups[1]: return groups[1].replace('\r', '\n') + groups[2] return groups[0]
elif groups[3]: return ' ' elif groups[1]:
elif groups[4]: return '\n' return groups[1].replace('\r', '\n') + groups[2]
elif groups[3]:
return ' '
elif groups[4]:
return '\n'
space_sub1 = _re.compile(( space_sub1 = _re.compile((
r'[\040\n]?(%(strings)s|%(pre_regex)s%(regex)s)' r'[\040\n]?(%(strings)s|%(pre_regex)s%(regex)s)'
r'|\040(%(not_id_literal)s)' r'|\040(%(not_id_literal)s)'
r'|\n(%(not_id_literal_open)s)' r'|\n(%(not_id_literal_open)s)'
) % locals()).sub ) % locals()).sub
def space_subber1(match): def space_subber1(match):
""" Substitution callback """ """ Substitution callback """
groups = match.groups() groups = match.groups()
@@ -264,6 +278,7 @@ def _make_jsmin(extended=True, python_only=True):
r'|(%(not_id_literal)s)\040' r'|(%(not_id_literal)s)\040'
r'|(%(not_id_literal_close)s)\n' r'|(%(not_id_literal_close)s)\n'
) % locals()).sub ) % locals()).sub
def space_subber2(match): def space_subber2(match):
""" Substitution callback """ """ Substitution callback """
groups = match.groups() groups = match.groups()
@@ -295,10 +310,11 @@ def _make_jsmin(extended=True, python_only=True):
:Rtype: ``str`` :Rtype: ``str``
""" """
return space_sub2(space_subber2, return space_sub2(space_subber2,
space_sub1(space_subber1, space_sub1(space_subber1,
space_norm_sub(space_norm_subber, '\n%s\n' % script) space_norm_sub(space_norm_subber,
) '\n%s\n' % script)
).strip() )
).strip()
return jsmin return jsmin
jsmin = _make_jsmin() jsmin = _make_jsmin()
@@ -311,6 +327,7 @@ jsmin = _make_jsmin()
# jsmin.jsmin(script) # jsmin.jsmin(script)
# #
def jsmin_for_posers(script): def jsmin_for_posers(script):
r""" r"""
Minify javascript based on `jsmin.c by Douglas Crockford`_\. Minify javascript based on `jsmin.c by Douglas Crockford`_\.
+30 -28
View File
@@ -14,24 +14,27 @@ import os
import hashlib import hashlib
import re import re
def read_binary_file(filename): def read_binary_file(filename):
f = open(filename,'rb') f = open(filename, 'rb')
data = f.read() data = f.read()
f.close() f.close()
return data return data
def write_binary_file(filename,data):
f =open(filename,'wb') def write_binary_file(filename, data):
f = open(filename, 'wb')
f.write(data) f.write(data)
f.close() f.close()
def fix_links(css,static_path):
def fix_links(css, static_path):
return re.sub(r'url\((["\'])\.\./', 'url(\\1' + static_path, css) return re.sub(r'url\((["\'])\.\./', 'url(\\1' + static_path, css)
def minify(files, path_info, folder, optimize_css, optimize_js, def minify(files, path_info, folder, optimize_css, optimize_js,
ignore_concat = [], ignore_concat=[],
ignore_minify = ['/jquery.js', '/anytime.js']): ignore_minify=['/jquery.js', '/anytime.js']):
""" """
Input: Input:
@@ -45,7 +48,7 @@ def minify(files, path_info, folder, optimize_css, optimize_js,
Returns a new list of: Returns a new list of:
- filename (absolute or relative, css or js, actual or temporary) or - filename (absolute or relative, css or js, actual or temporary) or
- ('css:inline','...css..') - ('css:inline','...css..')
- ('js:inline','...js..') - ('js:inline','...js..')
""" """
optimize_css = optimize_css or '' optimize_css = optimize_css or ''
optimize_js = optimize_js or '' optimize_js = optimize_js or ''
@@ -55,27 +58,27 @@ def minify(files, path_info, folder, optimize_css, optimize_js,
concat_js = 'concat' in optimize_js concat_js = 'concat' in optimize_js
minify_js = 'minify' in optimize_js minify_js = 'minify' in optimize_js
inline_js = 'inline' in optimize_js inline_js = 'inline' in optimize_js
static_path,temp = path_info.rsplit('/',1) static_path, temp = path_info.rsplit('/', 1)
new_files = [] new_files = []
css = [] css = []
js = [] js = []
processed = [] processed = []
for k,filename in enumerate(files): for k, filename in enumerate(files):
if not filename.startswith('/') or \ if not filename.startswith('/') or \
any(filename.endswith(x) \ any(filename.endswith(x)
for x in ignore_concat): for x in ignore_concat):
new_files.append(filename) new_files.append(filename)
continue continue
abs_filename = os.path.join( abs_filename = os.path.join(
folder,'static', filename[len(static_path)+1:]) folder, 'static', filename[len(static_path) + 1:])
if filename.lower().endswith('.css'): if filename.lower().endswith('.css'):
processed.append(filename) processed.append(filename)
spath_info, sfilename = \ spath_info, sfilename = \
path_info.split('/'), filename.split('/') path_info.split('/'), filename.split('/')
u = 0 u = 0
for i,a in enumerate(sfilename): for i, a in enumerate(sfilename):
try: try:
if a != spath_info[i]: if a != spath_info[i]:
u = i u = i
@@ -99,8 +102,8 @@ def minify(files, path_info, folder, optimize_css, optimize_js,
if minify_js and \ if minify_js and \
not filename.endswith('.min.js') and \ not filename.endswith('.min.js') and \
not any(filename.endswith(x) \ not any(filename.endswith(x)
for x in ignore_minify): for x in ignore_minify):
js.append(jsmin.jsmin(contents)) js.append(jsmin.jsmin(contents))
else: else:
js.append(contents) js.append(contents)
@@ -110,32 +113,31 @@ def minify(files, path_info, folder, optimize_css, optimize_js,
if css and concat_css: if css and concat_css:
css = '\n\n'.join(contents for contents in css) css = '\n\n'.join(contents for contents in css)
if not inline_css: if not inline_css:
temppath = os.path.join(folder,'static',temp) temppath = os.path.join(folder, 'static', temp)
if not os.path.exists(temppath): if not os.path.exists(temppath):
os.mkdir(temppath) os.mkdir(temppath)
dest = "compressed_%s.css" % dest_key dest = "compressed_%s.css" % dest_key
tempfile = os.path.join(temppath, dest) tempfile = os.path.join(temppath, dest)
write_binary_file(tempfile,css) write_binary_file(tempfile, css)
css = path_info+'/%s' % dest css = path_info + '/%s' % dest
new_files.append(css) new_files.append(css)
else: else:
new_files.append(('css:inline',css)) new_files.append(('css:inline', css))
else: else:
new_files += css new_files += css
if js and concat_js: if js and concat_js:
js = '\n'.join(contents for contents in js) js = '\n'.join(contents for contents in js)
if inline_js: if inline_js:
js = ('js:inline',js) js = ('js:inline', js)
else: else:
temppath = os.path.join(folder,'static',temp) temppath = os.path.join(folder, 'static', temp)
if not os.path.exists(temppath): if not os.path.exists(temppath):
os.mkdir(temppath) os.mkdir(temppath)
dest = "compressed_%s.js" % dest_key dest = "compressed_%s.js" % dest_key
tempfile = os.path.join(folder,'static',temp,dest) tempfile = os.path.join(folder, 'static', temp, dest)
write_binary_file(tempfile,js) write_binary_file(tempfile, js)
js = path_info+'/%s' % dest js = path_info + '/%s' % dest
new_files.append(js) new_files.append(js)
else: else:
new_files += js new_files += js
return new_files return new_files
+20 -15
View File
@@ -24,7 +24,7 @@ CALLOC.argtypes = [c_uint, c_uint]
STRDUP = LIBC.strdup STRDUP = LIBC.strdup
STRDUP.argstypes = [c_char_p] STRDUP.argstypes = [c_char_p]
STRDUP.restype = POINTER(c_char) # NOT c_char_p !!!! STRDUP.restype = POINTER(c_char) # NOT c_char_p !!!!
# Various constants # Various constants
PAM_PROMPT_ECHO_OFF = 1 PAM_PROMPT_ECHO_OFF = 1
@@ -32,56 +32,61 @@ PAM_PROMPT_ECHO_ON = 2
PAM_ERROR_MSG = 3 PAM_ERROR_MSG = 3
PAM_TEXT_INFO = 4 PAM_TEXT_INFO = 4
class PamHandle(Structure): class PamHandle(Structure):
"""wrapper class for pam_handle_t""" """wrapper class for pam_handle_t"""
_fields_ = [ _fields_ = [
("handle", c_void_p) ("handle", c_void_p)
] ]
def __init__(self): def __init__(self):
Structure.__init__(self) Structure.__init__(self)
self.handle = 0 self.handle = 0
class PamMessage(Structure): class PamMessage(Structure):
"""wrapper class for pam_message structure""" """wrapper class for pam_message structure"""
_fields_ = [ _fields_ = [
("msg_style", c_int), ("msg_style", c_int),
("msg", c_char_p), ("msg", c_char_p),
] ]
def __repr__(self): def __repr__(self):
return "<PamMessage %i '%s'>" % (self.msg_style, self.msg) return "<PamMessage %i '%s'>" % (self.msg_style, self.msg)
class PamResponse(Structure): class PamResponse(Structure):
"""wrapper class for pam_response structure""" """wrapper class for pam_response structure"""
_fields_ = [ _fields_ = [
("resp", c_char_p), ("resp", c_char_p),
("resp_retcode", c_int), ("resp_retcode", c_int),
] ]
def __repr__(self): def __repr__(self):
return "<PamResponse %i '%s'>" % (self.resp_retcode, self.resp) return "<PamResponse %i '%s'>" % (self.resp_retcode, self.resp)
CONV_FUNC = CFUNCTYPE(c_int, CONV_FUNC = CFUNCTYPE(c_int,
c_int, POINTER(POINTER(PamMessage)), c_int, POINTER(POINTER(PamMessage)),
POINTER(POINTER(PamResponse)), c_void_p) POINTER(POINTER(PamResponse)), c_void_p)
class PamConv(Structure): class PamConv(Structure):
"""wrapper class for pam_conv structure""" """wrapper class for pam_conv structure"""
_fields_ = [ _fields_ = [
("conv", CONV_FUNC), ("conv", CONV_FUNC),
("appdata_ptr", c_void_p) ("appdata_ptr", c_void_p)
] ]
PAM_START = LIBPAM.pam_start PAM_START = LIBPAM.pam_start
PAM_START.restype = c_int PAM_START.restype = c_int
PAM_START.argtypes = [c_char_p, c_char_p, POINTER(PamConv), PAM_START.argtypes = [c_char_p, c_char_p, POINTER(PamConv),
POINTER(PamHandle)] POINTER(PamHandle)]
PAM_AUTHENTICATE = LIBPAM.pam_authenticate PAM_AUTHENTICATE = LIBPAM.pam_authenticate
PAM_AUTHENTICATE.restype = c_int PAM_AUTHENTICATE.restype = c_int
PAM_AUTHENTICATE.argtypes = [PamHandle, c_int] PAM_AUTHENTICATE.argtypes = [PamHandle, c_int]
def authenticate(username, password, service='login'): def authenticate(username, password, service='login'):
"""Returns True if the given username and password authenticate for the """Returns True if the given username and password authenticate for the
given service. Returns False otherwise given service. Returns False otherwise
+28 -21
View File
@@ -24,6 +24,7 @@ import cmd
import pydoc import pydoc
import threading import threading
class Qdb(bdb.Bdb): class Qdb(bdb.Bdb):
"Qdb Debugger Backend" "Qdb Debugger Backend"
@@ -36,7 +37,7 @@ class Qdb(bdb.Bdb):
self.frame = None self.frame = None
self.i = 1 # sequential RPC call id self.i = 1 # sequential RPC call id
self.waiting = False self.waiting = False
self.pipe = pipe # for communication self.pipe = pipe # for communication
self._wait_for_mainpyfile = False self._wait_for_mainpyfile = False
self._wait_for_breakpoint = False self._wait_for_breakpoint = False
self.mainpyfile = "" self.mainpyfile = ""
@@ -67,7 +68,7 @@ class Qdb(bdb.Bdb):
# dispatch message (JSON RPC like) # dispatch message (JSON RPC like)
method = getattr(self, request['method']) method = getattr(self, request['method'])
response['result'] = method.__call__(*request['args'], response['result'] = method.__call__(*request['args'],
**request.get('kwargs', {})) **request.get('kwargs', {}))
except Exception, e: except Exception, e:
response['error'] = {'code': 0, 'message': str(e)} response['error'] = {'code': 0, 'message': str(e)}
# send the result for normal method calls, not for notifications # send the result for normal method calls, not for notifications
@@ -83,7 +84,7 @@ class Qdb(bdb.Bdb):
self.pull_actions() self.pull_actions()
# process the frame (see Bdb.trace_dispatch) # process the frame (see Bdb.trace_dispatch)
if self.quitting: if self.quitting:
return # None return # None
if event == 'line': if event == 'line':
return self.dispatch_line(frame) return self.dispatch_line(frame)
if event == 'call': if event == 'call':
@@ -106,7 +107,7 @@ class Qdb(bdb.Bdb):
"""This function is called when we stop or break at this line.""" """This function is called when we stop or break at this line."""
if self._wait_for_mainpyfile: if self._wait_for_mainpyfile:
if (not self.canonic(frame.f_code.co_filename).startswith(self.mainpyfile) if (not self.canonic(frame.f_code.co_filename).startswith(self.mainpyfile)
or frame.f_lineno<= 0): or frame.f_lineno <= 0):
return return
self._wait_for_mainpyfile = 0 self._wait_for_mainpyfile = 0
if self._wait_for_breakpoint: if self._wait_for_breakpoint:
@@ -150,11 +151,11 @@ class Qdb(bdb.Bdb):
import __main__ import __main__
import imp import imp
__main__.__dict__.clear() __main__.__dict__.clear()
__main__.__dict__.update({"__name__" : "__main__", __main__.__dict__.update({"__name__": "__main__",
"__file__" : filename, "__file__": filename,
"__builtins__": __builtins__, "__builtins__": __builtins__,
"imp" : imp, # need for run "imp": imp, # need for run
}) })
# avoid stopping before we reach the main script # avoid stopping before we reach the main script
self._wait_for_mainpyfile = 1 self._wait_for_mainpyfile = 1
@@ -200,8 +201,8 @@ class Qdb(bdb.Bdb):
if self.params.get('environment'): if self.params.get('environment'):
kwargs['environment'] = self.do_environment() kwargs['environment'] = self.do_environment()
self.pipe.send({'method': 'interaction', 'id': None, self.pipe.send({'method': 'interaction', 'id': None,
'args': (filename, self.frame.f_lineno, line), 'args': (filename, self.frame.f_lineno, line),
'kwargs': kwargs}) 'kwargs': kwargs})
self.pull_actions() self.pull_actions()
@@ -278,7 +279,7 @@ class Qdb(bdb.Bdb):
filename = self.frame.f_code.co_filename filename = self.frame.f_code.co_filename
breaklist = self.get_file_breaks(filename) breaklist = self.get_file_breaks(filename)
lines = [] lines = []
for lineno in range(first, last+1): for lineno in range(first, last + 1):
line = linecache.getline(filename, lineno, line = linecache.getline(filename, lineno,
self.frame.f_globals) self.frame.f_globals)
if not line: if not line:
@@ -303,7 +304,7 @@ class Qdb(bdb.Bdb):
for bp in bdb.Breakpoint.bpbynumber: for bp in bdb.Breakpoint.bpbynumber:
if bp: if bp:
breaks.append((bp.number, bp.file, bp.line, breaks.append((bp.number, bp.file, bp.line,
bp.temporary, bp.enabled, bp.hits, bp.cond, )) bp.temporary, bp.enabled, bp.hits, bp.cond, ))
return breaks return breaks
def do_clear_breakpoint(self, filename, lineno): def do_clear_breakpoint(self, filename, lineno):
@@ -320,7 +321,7 @@ class Qdb(bdb.Bdb):
def do_eval(self, arg, safe=True): def do_eval(self, arg, safe=True):
ret = eval(arg, self.frame.f_globals, ret = eval(arg, self.frame.f_globals,
self.frame_locals) self.frame_locals)
if safe: if safe:
ret = pydoc.cram(repr(ret), 255) ret = pydoc.cram(repr(ret), 255)
return ret return ret
@@ -354,9 +355,11 @@ class Qdb(bdb.Bdb):
# converts the frame global and locals to a short text representation: # converts the frame global and locals to a short text representation:
if self.frame: if self.frame:
for name, value in self.frame_locals.items(): for name, value in self.frame_locals.items():
env['locals'][name] = pydoc.cram(repr(value), 255), repr(type(value)) env['locals'][name] = pydoc.cram(repr(
value), 255), repr(type(value))
for name, value in self.frame.f_globals.items(): for name, value in self.frame.f_globals.items():
env['globals'][name] = pydoc.cram(repr(value), 20), repr(type(value)) env['globals'][name] = pydoc.cram(repr(
value), 20), repr(type(value))
return env return env
def get_autocomplete_list(self, expression): def get_autocomplete_list(self, expression):
@@ -589,8 +592,8 @@ class Frontend(object):
result = self.readline() result = self.readline()
if result: if result:
response = {'version': '1.1', 'id': request.get('id'), response = {'version': '1.1', 'id': request.get('id'),
'result': result, 'result': result,
'error': None} 'error': None}
self.send(response) self.send(response)
return True return True
@@ -609,7 +612,8 @@ class Frontend(object):
# nested request received (i.e. readline)! process it! # nested request received (i.e. readline)! process it!
self.process_message(res) self.process_message(res)
elif long(req['id']) != long(res['id']): elif long(req['id']) != long(res['id']):
print "DEBUGGER wrong packet received: expecting id", req['id'], res['id'] print "DEBUGGER wrong packet received: expecting id", req[
'id'], res['id']
# protocol state is unknown # protocol state is unknown
elif 'error' in res and res['error']: elif 'error' in res and res['error']:
raise RPCError(res['error']['message']) raise RPCError(res['error']['message'])
@@ -736,7 +740,7 @@ class Cli(Frontend, cmd.Cmd):
return raw_input() return raw_input()
def postcmd(self, stop, line): def postcmd(self, stop, line):
return not line.startswith("h") # stop return not line.startswith("h") # stop
do_h = cmd.Cmd.do_help do_h = cmd.Cmd.do_help
@@ -838,6 +842,7 @@ def test():
class Test(Frontend): class Test(Frontend):
def interaction(self, *args): def interaction(self, *args):
print "interaction!", args print "interaction!", args
def exception(self, *args): def exception(self, *args):
print "exception", args print "exception", args
#raise RuntimeError("exception %s" % repr(args)) #raise RuntimeError("exception %s" % repr(args))
@@ -877,7 +882,7 @@ def main(host='localhost', port=6000, authkey='secret password'):
print "usage: pdb.py scriptfile [arg] ..." print "usage: pdb.py scriptfile [arg] ..."
sys.exit(2) sys.exit(2)
mainpyfile = sys.argv[1] # Get script filename mainpyfile = sys.argv[1] # Get script filename
if not os.path.exists(mainpyfile): if not os.path.exists(mainpyfile):
print 'Error:', mainpyfile, 'does not exist' print 'Error:', mainpyfile, 'does not exist'
sys.exit(1) sys.exit(1)
@@ -913,6 +918,8 @@ def main(host='localhost', port=6000, authkey='secret password'):
qdb = None qdb = None
def set_trace(host='localhost', port=6000, authkey='secret password'): def set_trace(host='localhost', port=6000, authkey='secret password'):
"Simplified interface to debug running programs" "Simplified interface to debug running programs"
global qdb, listener, conn global qdb, listener, conn
@@ -950,7 +957,7 @@ if __name__ == '__main__':
# Check environment for configuration parameters: # Check environment for configuration parameters:
kwargs = {} kwargs = {}
for param in 'host', 'port', 'authkey': for param in 'host', 'port', 'authkey':
if 'QDB_%s' % param.upper() in os.environ: if 'QDB_%s' % param.upper() in os.environ:
kwargs[param] = os.environ['QDB_%s' % param.upper()] kwargs[param] = os.environ['QDB_%s' % param.upper()]
if not sys.argv[1:]: if not sys.argv[1:]:
+16 -11
View File
@@ -17,6 +17,7 @@ logger = logging.getLogger("web2py.cache.redis")
locker = thread.allocate_lock() locker = thread.allocate_lock()
def RedisCache(*args, **vars): def RedisCache(*args, **vars):
""" """
Usage example: put in models Usage example: put in models
@@ -47,10 +48,11 @@ class RedisClient(object):
meta_storage = {} meta_storage = {}
MAX_RETRIES = 5 MAX_RETRIES = 5
RETRIES = 0 RETRIES = 0
def __init__(self, server='localhost:6379', db=None, debug=False): def __init__(self, server='localhost:6379', db=None, debug=False):
self.server = server self.server = server
self.db = db or 0 self.db = db or 0
host,port = (self.server.split(':')+['6379'])[:2] host, port = (self.server.split(':') + ['6379'])[:2]
port = int(port) port = int(port)
self.request = current.request self.request = current.request
self.debug = debug self.debug = debug
@@ -64,7 +66,7 @@ class RedisClient(object):
CacheAbstract.cache_stats_name: { CacheAbstract.cache_stats_name: {
'hit_total': 0, 'hit_total': 0,
'misses': 0, 'misses': 0,
}} }}
else: else:
self.storage = self.meta_storage[app] self.storage = self.meta_storage[app]
@@ -72,8 +74,8 @@ class RedisClient(object):
def __call__(self, key, f, time_expire=300): def __call__(self, key, f, time_expire=300):
try: try:
if time_expire == None: if time_expire is None:
time_expire = 24*60*60 time_expire = 24 * 60 * 60
newKey = self.__keyFormat__(key) newKey = self.__keyFormat__(key)
value = None value = None
obj = self.r_server.get(newKey) obj = self.r_server.get(newKey)
@@ -100,13 +102,15 @@ class RedisClient(object):
def retry_call(self, key, f, time_expire): def retry_call(self, key, f, time_expire):
self.RETRIES += 1 self.RETRIES += 1
if self.RETRIES <= self.MAX_RETRIES: if self.RETRIES <= self.MAX_RETRIES:
logger.error("sleeping %s seconds before reconnecting" % (2 * self.RETRIES)) logger.error("sleeping %s seconds before reconnecting" %
(2 * self.RETRIES))
time.sleep(2 * self.RETRIES) time.sleep(2 * self.RETRIES)
self.__init__(self.server, self.db, self.debug) self.__init__(self.server, self.db, self.debug)
return self.__call__(key, f, time_expire) return self.__call__(key, f, time_expire)
else: else:
self.RETRIES = 0 self.RETRIES = 0
raise ConnectionError , 'Redis instance is unavailable at %s' % (self.server) raise ConnectionError('Redis instance is unavailable at %s' % (
self.server))
def increment(self, key, value=1, time_expire=300): def increment(self, key, value=1, time_expire=300):
try: try:
@@ -129,7 +133,8 @@ class RedisClient(object):
return self.increment(key, value, time_expire) return self.increment(key, value, time_expire)
else: else:
self.RETRIES = 0 self.RETRIES = 0
raise ConnectionError , 'Redis instance is unavailable at %s' % (self.server) raise ConnectionError('Redis instance is unavailable at %s' % (
self.server))
def clear(self, regex): def clear(self, regex):
""" """
@@ -139,8 +144,8 @@ class RedisClient(object):
r = re.compile(regex) r = re.compile(regex)
prefix = "w2p:%s:" % (self.request.application) prefix = "w2p:%s:" % (self.request.application)
pipe = self.r_server.pipeline() pipe = self.r_server.pipeline()
for a in self.r_server.keys("%s*" % \ for a in self.r_server.keys("%s*" %
(prefix)): (prefix)):
if r.match(str(a).replace(prefix, '', 1)): if r.match(str(a).replace(prefix, '', 1)):
pipe.delete(a) pipe.delete(a)
pipe.execute() pipe.execute()
@@ -149,10 +154,10 @@ class RedisClient(object):
statscollector = self.r_server.info() statscollector = self.r_server.info()
if self.debug: if self.debug:
statscollector['w2p_stats'] = dict( statscollector['w2p_stats'] = dict(
hit_total = self.r_server.get( hit_total=self.r_server.get(
'web2py_cache_statistics:hit_total'), 'web2py_cache_statistics:hit_total'),
misses=self.r_server.get('web2py_cache_statistics:misses') misses=self.r_server.get('web2py_cache_statistics:misses')
) )
statscollector['w2p_keys'] = dict() statscollector['w2p_keys'] = dict()
for a in self.r_server.keys("w2p:%s:*" % ( for a in self.r_server.keys("w2p:%s:*" % (
self.request.application)): self.request.application)):
+13 -6
View File
@@ -16,6 +16,7 @@ logger = logging.getLogger("web2py.session.redis")
locker = thread.allocate_lock() locker = thread.allocate_lock()
def RedisSession(*args, **vars): def RedisSession(*args, **vars):
""" """
Usage example: put in models Usage example: put in models
@@ -48,7 +49,7 @@ class RedisClient(object):
""" """
self.server = server self.server = server
self.db = db or 0 self.db = db or 0
host,port = (self.server.split(':')+['6379'])[:2] host, port = (self.server.split(':') + ['6379'])[:2]
port = int(port) port = int(port)
self.debug = debug self.debug = debug
if current and current.request: if current and current.request:
@@ -63,12 +64,13 @@ class RedisClient(object):
return self.tablename return self.tablename
def Field(self, fieldname, type='string', length=None, default=None, def Field(self, fieldname, type='string', length=None, default=None,
required=False,requires=None): required=False, requires=None):
return None return None
def define_table(self,tablename,*fields,**args): def define_table(self, tablename, *fields, **args):
if not self.tablename: if not self.tablename:
self.tablename = MockTable(self, self.r_server, tablename, self.session_expiry) self.tablename = MockTable(
self, self.r_server, tablename, self.session_expiry)
return self.tablename return self.tablename
def __getitem__(self, key): def __getitem__(self, key):
@@ -82,6 +84,7 @@ class RedisClient(object):
#this is only called by session2trash.py #this is only called by session2trash.py
pass pass
class MockTable(object): class MockTable(object):
def __init__(self, db, r_server, tablename, session_expiry): def __init__(self, db, r_server, tablename, session_expiry):
@@ -89,7 +92,8 @@ class MockTable(object):
self.r_server = r_server self.r_server = r_server
self.tablename = tablename self.tablename = tablename
#set the namespace for sessions of this app #set the namespace for sessions of this app
self.keyprefix = 'w2p:sess:%s' % tablename.replace('web2py_session_', '') self.keyprefix = 'w2p:sess:%s' % tablename.replace(
'web2py_session_', '')
#fast auto-increment id (needed for session handling) #fast auto-increment id (needed for session handling)
self.serial = "%s:serial" % self.keyprefix self.serial = "%s:serial" % self.keyprefix
#index of all the session keys of this app #index of all the session keys of this app
@@ -125,6 +129,7 @@ class MockTable(object):
self.r_server.expire(key, self.session_expiry) self.r_server.expire(key, self.session_expiry)
return newid return newid
class MockQuery(object): class MockQuery(object):
"""a fake Query object that supports querying by id """a fake Query object that supports querying by id
and listing all keys. No other operation is supported and listing all keys. No other operation is supported
@@ -171,7 +176,8 @@ class MockQuery(object):
continue continue
val = Storage(val) val = Storage(val)
#add a delete_record method (necessary for sessions2trash.py) #add a delete_record method (necessary for sessions2trash.py)
val.delete_record = RecordDeleter(self.db, sess, self.keyprefix) val.delete_record = RecordDeleter(
self.db, sess, self.keyprefix)
rtn.append(val) rtn.append(val)
return rtn return rtn
else: else:
@@ -185,6 +191,7 @@ class MockQuery(object):
self.db.expire(key, self.session.expiry) self.db.expire(key, self.session.expiry)
return rtn return rtn
class RecordDeleter(object): class RecordDeleter(object):
"""Dumb record deleter to support sessions2trash.py""" """Dumb record deleter to support sessions2trash.py"""
+10 -7
View File
@@ -54,14 +54,14 @@ UNPICKLABLE_TYPES = (
types.TypeType, types.TypeType,
types.ClassType, types.ClassType,
types.FunctionType, types.FunctionType,
) )
# Unpicklable statements to seed new historys with. # Unpicklable statements to seed new historys with.
INITIAL_UNPICKLABLES = [ INITIAL_UNPICKLABLES = [
'import logging', 'import logging',
'import os', 'import os',
'import sys', 'import sys',
] ]
class History: class History:
@@ -154,6 +154,7 @@ class History:
if name in self.unpicklable_names: if name in self.unpicklable_names:
self.unpicklable_names.remove(name) self.unpicklable_names.remove(name)
def represent(obj): def represent(obj):
"""Returns a string representing the given object's value, which should allow the """Returns a string representing the given object's value, which should allow the
code below to determine whether the object changes over time. code below to determine whether the object changes over time.
@@ -163,6 +164,7 @@ def represent(obj):
except: except:
return repr(obj) return repr(obj)
def run(history, statement, env={}): def run(history, statement, env={}):
""" """
Evaluates a python statement in a given history and returns the result. Evaluates a python statement in a given history and returns the result.
@@ -180,7 +182,6 @@ def run(history, statement, env={}):
# single-line expressions such as 'class Foo: pass' evaluate happily. # single-line expressions such as 'class Foo: pass' evaluate happily.
statement += '\n\n' statement += '\n\n'
# log and compile the statement up front # log and compile the statement up front
try: try:
logging.info('Compiling and evaluating:\n%s' % statement) logging.info('Compiling and evaluating:\n%s' % statement)
@@ -224,7 +225,8 @@ def run(history, statement, env={}):
history.remove_global(name) history.remove_global(name)
# run! # run!
old_globals = dict((key,represent(value)) for key,value in statement_module.__dict__.items()) old_globals = dict((key, represent(
value)) for key, value in statement_module.__dict__.items())
try: try:
old_stdout, old_stderr = sys.stdout, sys.stderr old_stdout, old_stderr = sys.stdout, sys.stderr
try: try:
@@ -261,6 +263,7 @@ def run(history, statement, env={}):
sys.modules['__main__'] = old_main sys.modules['__main__'] = old_main
return output.getvalue() return output.getvalue()
if __name__=='__main__': if __name__ == '__main__':
history=History() history = History()
while True: print run(history, raw_input('>>> ')).rstrip() while True:
print run(history, raw_input('>>> ')).rstrip()
+6 -3
View File
@@ -46,8 +46,10 @@ class JSONDummyParser:
"json wrapper for xmlrpclib parser interfase" "json wrapper for xmlrpclib parser interfase"
def __init__(self): def __init__(self):
self.buf = StringIO() self.buf = StringIO()
def feed(self, data): def feed(self, data):
self.buf.write(data) self.buf.write(data)
def close(self): def close(self):
return self.buf.getvalue() return self.buf.getvalue()
@@ -72,6 +74,7 @@ class JSONTransportMixin:
class JSONTransport(JSONTransportMixin, Transport): class JSONTransport(JSONTransportMixin, Transport):
pass pass
class JSONSafeTransport(JSONTransportMixin, SafeTransport): class JSONSafeTransport(JSONTransportMixin, SafeTransport):
pass pass
@@ -79,7 +82,7 @@ class JSONSafeTransport(JSONTransportMixin, SafeTransport):
class ServerProxy(object): class ServerProxy(object):
"JSON RPC Simple Client Service Proxy" "JSON RPC Simple Client Service Proxy"
def __init__(self, uri, transport=None, encoding=None, verbose=0): def __init__(self, uri, transport=None, encoding=None, verbose=0):
self.location = uri # server location (url) self.location = uri # server location (url)
self.trace = verbose # show debug messages self.trace = verbose # show debug messages
self.exceptions = True # raise errors? (JSONRPCError) self.exceptions = True # raise errors? (JSONRPCError)
@@ -88,7 +91,7 @@ class ServerProxy(object):
type, uri = urllib.splittype(uri) type, uri = urllib.splittype(uri)
if type not in ("http", "https"): if type not in ("http", "https"):
raise IOError, "unsupported JSON-RPC protocol" raise IOError("unsupported JSON-RPC protocol")
self.__host, self.__handler = urllib.splithost(uri) self.__host, self.__handler = urllib.splithost(uri)
if transport is None: if transport is None:
@@ -118,7 +121,7 @@ class ServerProxy(object):
self.__handler, self.__handler,
request, request,
verbose=self.__verbose verbose=self.__verbose
) )
# store plain request and response for further debugging # store plain request and response for further debugging
self.json_request = request self.json_request = request
+108 -105
View File
@@ -1,112 +1,115 @@
SMSCODES = { SMSCODES = {
'Aliant':'@chat.wirefree.ca', 'Aliant': '@chat.wirefree.ca',
'Alltel':'@message.alltel.com', 'Alltel': '@message.alltel.com',
'Ameritech':'@paging.acswireless.com', 'Ameritech': '@paging.acswireless.com',
'AT&T':'@txt.att.net', 'AT&T': '@txt.att.net',
'AU by KDDI':'@ezweb.ne.jp', 'AU by KDDI': '@ezweb.ne.jp',
'BeeLine GSM':'@sms.beemail.ru', 'BeeLine GSM': '@sms.beemail.ru',
'Bell Mobility Canada':'@txt.bellmobility.ca', 'Bell Mobility Canada': '@txt.bellmobility.ca',
'Bellsouth':'@bellsouth.cl', 'Bellsouth': '@bellsouth.cl',
'BellSouth Mobility':'@blsdcs.net', 'BellSouth Mobility': '@blsdcs.net',
'Blue Sky Frog':'@blueskyfrog.com', 'Blue Sky Frog': '@blueskyfrog.com',
'Boost':'@myboostmobile.com', 'Boost': '@myboostmobile.com',
'Cellular South':'@csouth1.com', 'Cellular South': '@csouth1.com',
'CellularOne':'@mobile.celloneusa.com', 'CellularOne': '@mobile.celloneusa.com',
'CellularOne West':'@mycellone.com', 'CellularOne West': '@mycellone.com',
'Cincinnati Bell':'@gocbw.com', 'Cincinnati Bell': '@gocbw.com',
'Claro':'@clarotorpedo.com.br', 'Claro': '@clarotorpedo.com.br',
'Comviq':'@sms.comviq.se', 'Comviq': '@sms.comviq.se',
'Dutchtone/Orange-NL':'@sms.orange.nl', 'Dutchtone/Orange-NL': '@sms.orange.nl',
'Edge Wireless':'@sms.edgewireless.com', 'Edge Wireless': '@sms.edgewireless.com',
'EinsteinPCS / Airadigm Communications':'@einsteinsms.com', 'EinsteinPCS / Airadigm Communications': '@einsteinsms.com',
'EPlus':'@smsmail.eplus.de', 'EPlus': '@smsmail.eplus.de',
'Fido Canada':'@fido.ca', 'Fido Canada': '@fido.ca',
'Golden Telecom':'@sms.goldentele.com', 'Golden Telecom': '@sms.goldentele.com',
'Idea Cellular':'@ideacellular.net', 'Idea Cellular': '@ideacellular.net',
'Kyivstar':'@sms.kyivstar.net', 'Kyivstar': '@sms.kyivstar.net',
'LMT':'@sms.lmt.lv', 'LMT': '@sms.lmt.lv',
'Manitoba Telecom Systems':'@text.mtsmobility.com', 'Manitoba Telecom Systems': '@text.mtsmobility.com',
'Meteor':'@sms.mymeteor.ie', 'Meteor': '@sms.mymeteor.ie',
'Metro PCS':'@mymetropcs.com', 'Metro PCS': '@mymetropcs.com',
'Metrocall Pager':'@page.metrocall.com', 'Metrocall Pager': '@page.metrocall.com',
'MobileOne':'@m1.com.sg', 'MobileOne': '@m1.com.sg',
'Mobilfone':'@page.mobilfone.com', 'Mobilfone': '@page.mobilfone.com',
'Mobility Bermuda':'@ml.bm', 'Mobility Bermuda': '@ml.bm',
'Netcom':'@sms.netcom.no', 'Netcom': '@sms.netcom.no',
'Nextel':'@messaging.nextel.com', 'Nextel': '@messaging.nextel.com',
'NPI Wireless':'@npiwireless.com', 'NPI Wireless': '@npiwireless.com',
'O2':'@o2.co.uk', 'O2': '@o2.co.uk',
'O2 M-mail':'@mmail.co.uk', 'O2 M-mail': '@mmail.co.uk',
'Optus':'@optusmobile.com.au', 'Optus': '@optusmobile.com.au',
'Orange':'@orange.net', 'Orange': '@orange.net',
'Oskar':'@mujoskar.cz', 'Oskar': '@mujoskar.cz',
'Pagenet':'@pagenet.net', 'Pagenet': '@pagenet.net',
'PCS Rogers':'@pcs.rogers.com', 'PCS Rogers': '@pcs.rogers.com',
'Personal Communication':'@pcom.ru', 'Personal Communication': '@pcom.ru',
'Plus GSM Poland':'@text.plusgsm.pl', 'Plus GSM Poland': '@text.plusgsm.pl',
'Powertel':'@ptel.net', 'Powertel': '@ptel.net',
'Primtel':'@sms.primtel.ru', 'Primtel': '@sms.primtel.ru',
'PSC Wireless':'@sms.pscel.com', 'PSC Wireless': '@sms.pscel.com',
'Qualcomm':'@pager.qualcomm.com', 'Qualcomm': '@pager.qualcomm.com',
'Qwest':'@qwestmp.com', 'Qwest': '@qwestmp.com',
'Safaricom':'@safaricomsms.com', 'Safaricom': '@safaricomsms.com',
'Satelindo GSM':'@satelindogsm.com', 'Satelindo GSM': '@satelindogsm.com',
'SCS-900':'@scs-900.ru', 'SCS-900': '@scs-900.ru',
'Simple Freedom':'@text.simplefreedom.net', 'Simple Freedom': '@text.simplefreedom.net',
'Skytel - Alphanumeric':'@skytel.com', 'Skytel - Alphanumeric': '@skytel.com',
'Smart Telecom':'@mysmart.mymobile.ph', 'Smart Telecom': '@mysmart.mymobile.ph',
'Southern Linc':'@page.southernlinc.com', 'Southern Linc': '@page.southernlinc.com',
'Sprint PCS':'@messaging.sprintpcs.com', 'Sprint PCS': '@messaging.sprintpcs.com',
'Sprint PCS - Short Mail':'@sprintpcs.com', 'Sprint PCS - Short Mail': '@sprintpcs.com',
'SunCom':'@tms.suncom.com', 'SunCom': '@tms.suncom.com',
'SureWest Communications':'@mobile.surewest.com', 'SureWest Communications': '@mobile.surewest.com',
'SwissCom Mobile':'@bluewin.ch', 'SwissCom Mobile': '@bluewin.ch',
'T-Mobile Germany':'@T-D1-SMS.de', 'T-Mobile Germany': '@T-D1-SMS.de',
'T-Mobile Netherlands':'@gin.nl', 'T-Mobile Netherlands': '@gin.nl',
'T-Mobile UK':'@t-mobile.uk.net', 'T-Mobile UK': '@t-mobile.uk.net',
'T-Mobile USA (tmail)':'@tmail.com', 'T-Mobile USA (tmail)': '@tmail.com',
'T-Mobile USA (tmomail)':'@tmomail.net', 'T-Mobile USA (tmomail)': '@tmomail.net',
'Tele2 Latvia':'@sms.tele2.lv', 'Tele2 Latvia': '@sms.tele2.lv',
'Telefonica Movistar':'@movistar.net', 'Telefonica Movistar': '@movistar.net',
'Telenor':'@mobilpost.no', 'Telenor': '@mobilpost.no',
'Telia Denmark':'@gsm1800.telia.dk', 'Telia Denmark': '@gsm1800.telia.dk',
'Telus Mobility':'@msg.telus.com', 'Telus Mobility': '@msg.telus.com',
'The Phone House':'@sms.phonehouse.de', 'The Phone House': '@sms.phonehouse.de',
'TIM':'@timnet.com', 'TIM': '@timnet.com',
'UMC':'@sms.umc.com.ua', 'UMC': '@sms.umc.com.ua',
'Unicel':'@utext.com', 'Unicel': '@utext.com',
'US Cellular':'@email.uscc.net', 'US Cellular': '@email.uscc.net',
'Verizon Wireless (vtext)':'@vtext.com', 'Verizon Wireless (vtext)': '@vtext.com',
'Verizon Wireless (airtouchpaging)':'@airtouchpaging.com', 'Verizon Wireless (airtouchpaging)': '@airtouchpaging.com',
'Verizon Wireless (myairmail)':'@myairmail.com', 'Verizon Wireless (myairmail)': '@myairmail.com',
'Vessotel':'@pager.irkutsk.ru', 'Vessotel': '@pager.irkutsk.ru',
'Virgin Mobile Canada':'@vmobile.ca', 'Virgin Mobile Canada': '@vmobile.ca',
'Virgin Mobile USA':'@vmobl.com', 'Virgin Mobile USA': '@vmobl.com',
'Vodafone Italy':'@sms.vodafone.it', 'Vodafone Italy': '@sms.vodafone.it',
'Vodafone Japan (n)':'@n.vodafone.ne.jp', 'Vodafone Japan (n)': '@n.vodafone.ne.jp',
'Vodafone Japan (d)':'@d.vodafone.ne.jp', 'Vodafone Japan (d)': '@d.vodafone.ne.jp',
'Vodafone Japan (r)':'@r.vodafone.ne.jp', 'Vodafone Japan (r)': '@r.vodafone.ne.jp',
'Vodafone Japan (k)':'@k.vodafone.ne.jp', 'Vodafone Japan (k)': '@k.vodafone.ne.jp',
'Vodafone Japan (t)':'@t.vodafone.ne.jp', 'Vodafone Japan (t)': '@t.vodafone.ne.jp',
'Vodafone Japan (q)':'@q.vodafone.ne.jp', 'Vodafone Japan (q)': '@q.vodafone.ne.jp',
'Vodafone Japan (s)':'@s.vodafone.ne.jp', 'Vodafone Japan (s)': '@s.vodafone.ne.jp',
'Vodafone Japan (h)':'@h.vodafone.ne.jp', 'Vodafone Japan (h)': '@h.vodafone.ne.jp',
'Vodafone Japan (c)':'@c.vodafone.ne.jp', 'Vodafone Japan (c)': '@c.vodafone.ne.jp',
'Vodafone Spain':'@vodafone.es', 'Vodafone Spain': '@vodafone.es',
'Vodafone UK':'@vodafone.net', 'Vodafone UK': '@vodafone.net',
'Weblink Wireless':'@airmessage.net', 'Weblink Wireless': '@airmessage.net',
'WellCom':'@sms.welcome2well.com', 'WellCom': '@sms.welcome2well.com',
'WyndTell':'@wyndtell.com', 'WyndTell': '@wyndtell.com',
} }
def sms_email(number,provider):
def sms_email(number, provider):
""" """
>>> print sms_email('1 (312) 375-6536','T-Mobile USA (tmail)') >>> print sms_email('1 (312) 375-6536','T-Mobile USA (tmail)')
print 13123756536@tmail.com print 13123756536@tmail.com
""" """
import re import re
if number[0]=='+1': number=number[1:] if number[0] == '+1':
elif number[0]=='+': number=number[3:] number = number[1:]
elif number[:2]=='00': number=number[3:] elif number[0] == '+':
number=re.sub('[^\d]','',number) number = number[3:]
return number+SMSCODES[provider] elif number[:2] == '00': number = number[3:]
number = re.sub('[^\d]', '', number)
return number + SMSCODES[provider]
+64 -62
View File
@@ -9,13 +9,14 @@ import pickle
import copy import copy
import simplejson import simplejson
def quote(text): def quote(text):
return str(text).replace('\\', '\\\\').replace("'", "\\'") return str(text).replace('\\', '\\\\').replace("'", "\\'")
class Node: class Node:
def __init__(self, name, value, url='.', readonly=False, active=True, def __init__(self, name, value, url='.', readonly=False, active=True,
onchange=None, **kwarg): onchange=None, **kwarg):
self.url = url self.url = url
self.name = name self.name = name
self.value = str(value) self.value = str(value)
@@ -246,8 +247,8 @@ class Sheet:
r, c = int(r), int(c) r, c = int(r), int(c)
except (ValueError, IndexError, TypeError), e: except (ValueError, IndexError, TypeError), e:
error = "%s. %s" % \ error = "%s. %s" % \
("Unexpected position parameter", ("Unexpected position parameter",
"Must be a key of type 'rncn'") "Must be a key of type 'rncn'")
raise ValueError(error) raise ValueError(error)
return r, c return r, c
@@ -316,7 +317,6 @@ class Sheet:
return changes return changes
def process(self, request, db=None, db_callback=None): def process(self, request, db=None, db_callback=None):
""" """
call this in action that creates table, it will handle ajax callbacks call this in action that creates table, it will handle ajax callbacks
@@ -333,22 +333,22 @@ class Sheet:
if request.args(0) == 'focus': if request.args(0) == 'focus':
return "jQuery('#%(cell)s').val('%(value)s');" % \ return "jQuery('#%(cell)s').val('%(value)s');" % \
dict(cell=cell, value=quote(self[cell].value)) dict(cell=cell, value=quote(self[cell].value))
value = request.vars[cell] value = request.vars[cell]
self[cell] = value self[cell] = value
if request.args(0) == 'blur': if request.args(0) == 'blur':
return "jQuery('#%(cell)s').val('%(value)s');" % \ return "jQuery('#%(cell)s').val('%(value)s');" % \
dict(cell=cell, value=quote(self[cell].computed_value)) dict(cell=cell, value=quote(self[cell].computed_value))
elif request.args(0) == 'keyup': elif request.args(0) == 'keyup':
jquery = '' jquery = ''
for other_key in self.modified: for other_key in self.modified:
if other_key != cell: if other_key != cell:
jquery += "jQuery('#%(other_key)s').val('%(value)s');" % \ jquery += "jQuery('#%(other_key)s').val('%(value)s');" % \
dict(other_key=other_key, dict(other_key=other_key,
value=quote(self[other_key].computed_value)) value=quote(self[other_key].computed_value))
else: else:
# spreadsheet db update # spreadsheet db update
@@ -390,8 +390,8 @@ class Sheet:
return "odd" return "odd"
def __init__(self, rows, cols, url='.', readonly=False, def __init__(self, rows, cols, url='.', readonly=False,
active=True, onchange=None, value=None, data=None, active=True, onchange=None, value=None, data=None,
headers=None, update_button="", **kwarg): headers=None, update_button="", **kwarg):
""" """
Arguments: Arguments:
@@ -418,7 +418,7 @@ class Sheet:
self.nodes = {} self.nodes = {}
self.error = 'ERROR: %(error)s' self.error = 'ERROR: %(error)s'
self.allowed_keywords = ['for', 'in', 'if', 'else', 'and', 'or', 'not', self.allowed_keywords = ['for', 'in', 'if', 'else', 'and', 'or', 'not',
'i', 'j', 'k', 'x', 'y', 'z', 'sum'] 'i', 'j', 'k', 'x', 'y', 'z', 'sum']
self.value = value self.value = value
self.environment = {} self.environment = {}
self.attributes = self.get_attributes(kwarg) self.attributes = self.get_attributes(kwarg)
@@ -431,14 +431,14 @@ class Sheet:
self.update_button = update_button self.update_button = update_button
self.client = { self.client = {
"columns": {}, "columns": {},
"colnames": {}, "colnames": {},
"id_columns": [], "id_columns": [],
"id_colnames": {}, "id_colnames": {},
"cells": {}, "cells": {},
"modified": {}, "modified": {},
"headers": headers "headers": headers
} }
# if db and query: # if db and query:
if self.data is not None: if self.data is not None:
@@ -467,10 +467,10 @@ class Sheet:
self.client["id_columns"].append(y) self.client["id_columns"].append(y)
self.client["id_colnames"][colname] = y self.client["id_colnames"][colname] = y
for k in xrange(self.rows*self.cols): for k in xrange(self.rows * self.cols):
key = 'r%sc%s'%(k/self.cols, k%self.cols) key = 'r%sc%s' % (k / self.cols, k % self.cols)
r, c = self.position(key) r, c = self.position(key)
if self.client["cells"].has_key(key): if key in self.client["cells"]:
value = self.client["cells"][key] value = self.client["cells"][key]
# readonly id values # readonly id values
if c in self.client["id_columns"]: if c in self.client["id_columns"]:
@@ -485,7 +485,7 @@ class Sheet:
else: else:
value = '0.00' value = '0.00'
self.cell(key, value, self.cell(key, value,
readonly, active, onchange) readonly, active, onchange)
exec('from math import *', {}, self.environment) exec('from math import *', {}, self.environment)
@@ -565,7 +565,7 @@ class Sheet:
for col, data in cells.iteritems(): for col, data in cells.iteritems():
key = "r%sc%s" % (row, col) key = "r%sc%s" % (row, col)
active, onchange, readonly, cell_value = \ active, onchange, readonly, cell_value = \
self.get_cell_arguments(data, default=kwarg) self.get_cell_arguments(data, default=kwarg)
if value is None: if value is None:
v = cell_value v = cell_value
else: else:
@@ -575,7 +575,7 @@ class Sheet:
onchange=onchange, **attributes) onchange=onchange, **attributes)
else: else:
active, onchange, readonly, all_value = \ active, onchange, readonly, all_value = \
self.get_cell_arguments(kwarg) self.get_cell_arguments(kwarg)
for col, cell_value in enumerate(cells): for col, cell_value in enumerate(cells):
key = "r%sc%s" % (row, col) key = "r%sc%s" % (row, col)
if value is None: if value is None:
@@ -601,7 +601,7 @@ class Sheet:
for row, data in cells.iteritems(): for row, data in cells.iteritems():
key = "r%sc%s" % (row, col) key = "r%sc%s" % (row, col)
active, onchange, readonly, cell_value = \ active, onchange, readonly, cell_value = \
self.get_cell_arguments(data, default=kwarg) self.get_cell_arguments(data, default=kwarg)
if value is None: if value is None:
v = cell_value v = cell_value
else: else:
@@ -610,7 +610,7 @@ class Sheet:
onchange=onchange, **attributes) onchange=onchange, **attributes)
else: else:
active, onchange, readonly, all_value = \ active, onchange, readonly, all_value = \
self.get_cell_arguments(kwarg) self.get_cell_arguments(kwarg)
for row, cell_value in enumerate(cells): for row, cell_value in enumerate(cells):
key = "r%sc%s" % (row, col) key = "r%sc%s" % (row, col)
if value is None: if value is None:
@@ -646,36 +646,36 @@ class Sheet:
if isinstance(cells, dict): if isinstance(cells, dict):
for key, data in cells.iteritems(): for key, data in cells.iteritems():
r, c = self.position(key) r, c = self.position(key)
key = "r%sc%s" % (r+starts_r, c+starts_c) key = "r%sc%s" % (r + starts_r, c + starts_c)
active, onchange, readonly, cell_value = \ active, onchange, readonly, cell_value = \
self.get_cell_arguments(data, default=kwarg) self.get_cell_arguments(data, default=kwarg)
if value is None: if value is None:
v = cell_value v = cell_value
else: else:
v = value v = value
if (ends is None) or ((ends_r >= r+starts_r) and\ if (ends is None) or ((ends_r >= r + starts_r) and
(ends_c >= c+starts_c)): (ends_c >= c + starts_c)):
self.cell(key, v, active=active, self.cell(key, v, active=active,
readonly=readonly, readonly=readonly,
onchange=onchange, **attributes) onchange=onchange, **attributes)
else: else:
active, onchange, readonly, all_value = \ active, onchange, readonly, all_value = \
self.get_cell_arguments(kwarg) self.get_cell_arguments(kwarg)
for r, row in enumerate(cells): for r, row in enumerate(cells):
for c, cell_value in enumerate(row): for c, cell_value in enumerate(row):
if value is None: if value is None:
v = cell_value v = cell_value
else: else:
v = value v = value
key = "r%sc%s" % (r+starts_r, c+starts_c) key = "r%sc%s" % (r + starts_r, c + starts_c)
if (ends is None) or \ if (ends is None) or \
((ends_r >= r+starts_r) and\ ((ends_r >= r + starts_r) and
(ends_c >= c+starts_c)): (ends_c >= c + starts_c)):
self.cell(key, v, self.cell(key, v,
active=active, active=active,
onchange=onchange, onchange=onchange,
readonly=readonly, readonly=readonly,
**attributes) **attributes)
def __setitem__(self, key, value): def __setitem__(self, key, value):
key = str(key) key = str(key)
@@ -694,7 +694,7 @@ class Sheet:
other_key = match.group() other_key = match.group()
if other_key == key: if other_key == key:
self.computed_value = self.error % dict(error='cycle') self.computed_value = self.error % dict(error='cycle')
self.modified={} self.modified = {}
break break
if other_key in self.nodes: if other_key in self.nodes:
other_node = self.nodes[other_key] other_node = self.nodes[other_key]
@@ -704,7 +704,8 @@ class Sheet:
not other_key in self.environment: not other_key in self.environment:
node.locked = True node.locked = True
node.computed_value = \ node.computed_value = \
self.error % dict(error='invalid keyword: ' + other_key) self.error % dict(
error='invalid keyword: ' + other_key)
self.modified = {} self.modified = {}
break break
self.compute(node) self.compute(node)
@@ -737,7 +738,7 @@ class Sheet:
output = {node.name: node.computed_value} output = {node.name: node.computed_value}
changed_nodes = self.changed(node) changed_nodes = self.changed(node)
while changed_nodes: while changed_nodes:
ok=False ok = False
set_changed_nodes = set(changed_nodes) set_changed_nodes = set(changed_nodes)
for (k, other_node) in enumerate(changed_nodes): for (k, other_node) in enumerate(changed_nodes):
#print other_node, changed_nodes #print other_node, changed_nodes
@@ -758,7 +759,7 @@ class Sheet:
return self.nodes[str(key)] return self.nodes[str(key)]
def get_computed_values(self): def get_computed_values(self):
d={} d = {}
for key in self.nodes: for key in self.nodes:
node = self.nodes[key] node = self.nodes[key]
if node.value[:1] != '=' or not node.active: if node.value[:1] != '=' or not node.active:
@@ -780,21 +781,21 @@ class Sheet:
gluon.html.TH, gluon.html.BR, gluon.html.SCRIPT) gluon.html.TH, gluon.html.BR, gluon.html.SCRIPT)
regex = re.compile('r\d+c\d+') regex = re.compile('r\d+c\d+')
header = TR(TH(), *[TH('c%s' % c) \ header = TR(TH(), *[TH('c%s' % c)
for c in range(self.cols)]) for c in range(self.cols)])
rows = [] rows = []
for r in range(self.rows): for r in range(self.rows):
tds = [TH('r%s' % r),] tds = [TH('r%s' % r), ]
for c in range(self.cols): for c in range(self.cols):
key = 'r%sc%s'%(r, c) key = 'r%sc%s' % (r, c)
attributes = {"_class": "w2p_spreadsheet_col_%s" % \ attributes = {"_class": "w2p_spreadsheet_col_%s" %
self.even_or_odd(c)} self.even_or_odd(c)}
if key in self.td_attributes: if key in self.td_attributes:
attributes.update(self.td_attributes[key]) attributes.update(self.td_attributes[key])
td = TD(self.nodes[key], **attributes) td = TD(self.nodes[key], **attributes)
tds.append(td) tds.append(td)
attributes = {"_class": "w2p_spreadsheet_row_%s" % \ attributes = {"_class": "w2p_spreadsheet_row_%s" %
self.even_or_odd(r)} self.even_or_odd(r)}
if str(r) in self.tr_attributes: if str(r) in self.tr_attributes:
attributes.update(self.tr_attributes[str(r)]) attributes.update(self.tr_attributes[str(r)])
rows.append(TR(*tds, **attributes)) rows.append(TR(*tds, **attributes))
@@ -805,8 +806,8 @@ class Sheet:
table = TABLE(header, *rows, **self.attributes) table = TABLE(header, *rows, **self.attributes)
if len(self.client["cells"]) >= 1: if len(self.client["cells"]) >= 1:
data = SCRIPT(\ data = SCRIPT(
""" """
var w2p_spreadsheet_data = %(data)s; var w2p_spreadsheet_data = %(data)s;
var w2p_spreadsheet_update_button = "%(update_button)s"; var w2p_spreadsheet_update_button = "%(update_button)s";
var w2p_spreadsheet_update_result = null; var w2p_spreadsheet_update_result = null;
@@ -835,7 +836,8 @@ class Sheet:
if (w2p_spreadsheet_update_button != ""){ if (w2p_spreadsheet_update_button != ""){
jQuery(function(){ jQuery(function(){
jQuery("#" + w2p_spreadsheet_update_button).click(w2p_spreadsheet_update_db); jQuery("#" + w2p_spreadsheet_update_button).click(
w2p_spreadsheet_update_db);
}); });
} }
""" % dict(data=simplejson.dumps(self.client), """ % dict(data=simplejson.dumps(self.client),
@@ -848,22 +850,22 @@ class Sheet:
if self.client["headers"] is not None: if self.client["headers"] is not None:
for fieldname, name in self.client["headers"].iteritems(): for fieldname, name in self.client["headers"].iteritems():
unsorted_headers.append((self.client["columns"][fieldname], unsorted_headers.append((self.client["columns"][fieldname],
name)) name))
else: else:
for fieldname, c in self.client["columns"].iteritems(): for fieldname, c in self.client["columns"].iteritems():
unsorted_headers.append((c, fieldname)) unsorted_headers.append((c, fieldname))
sorted_headers = [TH(),] + \ sorted_headers = [TH(), ] + \
[TH(header[1]) for header in sorted(unsorted_headers)] [TH(header[1]) for header in sorted(unsorted_headers)]
table.insert(0, TR(*sorted_headers, table.insert(0, TR(*sorted_headers,
**{_class:"%s_fieldnames" % \ **{_class: "%s_fieldnames" %
attributes["_class"]})) attributes["_class"]}))
else: else:
data = SCRIPT(""" // web2py Spreadsheets: no db data.""") data = SCRIPT(""" // web2py Spreadsheets: no db data.""")
return DIV(table, return DIV(table,
BR(), BR(),
TABLE(*[TR(TH(key), TD(self.nodes[key])) \ TABLE(*[TR(TH(key), TD(self.nodes[key]))
for key in self.nodes if not regex.match(key)]), for key in self.nodes if not regex.match(key)]),
data, **attributes) data, **attributes)
+23 -21
View File
@@ -1,6 +1,7 @@
import urllib import urllib
import simplejson import simplejson
class Stripe: class Stripe:
""" """
Usage: Usage:
@@ -22,8 +23,9 @@ class Stripe:
if paid is True than transaction was processed if paid is True than transaction was processed
""" """
def __init__(self,key): def __init__(self, key):
self.key = key self.key = key
def charge(self, def charge(self,
amount, amount,
currency='usd', currency='usd',
@@ -32,33 +34,33 @@ class Stripe:
card_exp_year='2012', card_exp_year='2012',
card_cvc_check='123', card_cvc_check='123',
description='test charge'): description='test charge'):
params = urllib.urlencode({'amount':amount, params = urllib.urlencode({'amount': amount,
'currency':currency, 'currency': currency,
'card[number]':card_number, 'card[number]': card_number,
'card[exp_month]':card_exp_month, 'card[exp_month]': card_exp_month,
'card[exp_year]':card_exp_year, 'card[exp_year]': card_exp_year,
'card[cvc_check]':card_cvc_check, 'card[cvc_check]': card_cvc_check,
'description':description}) 'description': description})
u = urllib.urlopen('https://%s:@api.stripe.com/v1/charges' % \ u = urllib.urlopen('https://%s:@api.stripe.com/v1/charges' %
self.key,params) self.key, params)
return simplejson.loads(u.read()) return simplejson.loads(u.read())
def check(self,charge_id): def check(self, charge_id):
u = urllib.urlopen('https://%s:@api.stripe.com/v1/charges/%s' % \ u = urllib.urlopen('https://%s:@api.stripe.com/v1/charges/%s' %
(self.key,charge_id)) (self.key, charge_id))
return simplejson.loads(u.read()) return simplejson.loads(u.read())
def refund(self,charge_id): def refund(self, charge_id):
params = urllib.urlencode({}) params = urllib.urlencode({})
u = urllib.urlopen('https://%s:@api.stripe.com/v1/charges/%s/refund'%\ u = urllib.urlopen('https://%s:@api.stripe.com/v1/charges/%s/refund' %
(self.key,charge_id),params) (self.key, charge_id), params)
return simplejson.loads(u.read()) return simplejson.loads(u.read())
if __name__=='__main__': if __name__ == '__main__':
key = raw_input('user>') key = raw_input('user>')
d=Stripe(key).charge(100) d = Stripe(key).charge(100)
print 'charged',d['paid'] print 'charged', d['paid']
s = Stripe(key).check(d[u'id']) s = Stripe(key).check(d[u'id'])
print 'paid',s['paid'],s['amount'] ,s['currency'] print 'paid', s['paid'], s['amount'], s['currency']
s = Stripe(key).refund(d[u'id']) s = Stripe(key).refund(d[u'id'])
print 'refunded',s['refunded'] print 'refunded', s['refunded']
+13 -13
View File
@@ -28,7 +28,7 @@ class TaskBarIcon:
win32con.WM_DESTROY: self.OnDestroy, win32con.WM_DESTROY: self.OnDestroy,
win32con.WM_COMMAND: self.OnCommand, win32con.WM_COMMAND: self.OnCommand,
win32con.WM_USER + 20: self.OnTaskbarNotify, win32con.WM_USER + 20: self.OnTaskbarNotify,
} }
# Register the Window class. # Register the Window class.
@@ -56,7 +56,7 @@ class TaskBarIcon:
0, 0,
hinst, hinst,
None, None,
) )
win32gui.UpdateWindow(self.hwnd) win32gui.UpdateWindow(self.hwnd)
self.SetServerStopped() self.SetServerStopped()
@@ -82,7 +82,7 @@ class TaskBarIcon:
hicon = win32gui.LoadIcon(0, win32con.IDI_APPLICATION) hicon = win32gui.LoadIcon(0, win32con.IDI_APPLICATION)
flags = win32gui.NIF_ICON | win32gui.NIF_MESSAGE\ flags = win32gui.NIF_ICON | win32gui.NIF_MESSAGE\
| win32gui.NIF_TIP | win32gui.NIF_TIP
nid = ( nid = (
self.hwnd, self.hwnd,
0, 0,
@@ -90,7 +90,7 @@ class TaskBarIcon:
win32con.WM_USER + 20, win32con.WM_USER + 20,
hicon, hicon,
'web2py Framework', 'web2py Framework',
) )
try: try:
win32gui.Shell_NotifyIcon(win32gui.NIM_MODIFY, nid) win32gui.Shell_NotifyIcon(win32gui.NIM_MODIFY, nid)
except: except:
@@ -111,7 +111,7 @@ class TaskBarIcon:
msg, msg,
wparam, wparam,
lparam, lparam,
): ):
self._DoCreateIcons() self._DoCreateIcons()
def OnDestroy( def OnDestroy(
@@ -120,7 +120,7 @@ class TaskBarIcon:
msg, msg,
wparam, wparam,
lparam, lparam,
): ):
nid = (self.hwnd, 0) nid = (self.hwnd, 0)
win32gui.Shell_NotifyIcon(win32gui.NIM_DELETE, nid) win32gui.Shell_NotifyIcon(win32gui.NIM_DELETE, nid)
@@ -130,7 +130,7 @@ class TaskBarIcon:
msg, msg,
wparam, wparam,
lparam, lparam,
): ):
if lparam == win32con.WM_LBUTTONUP: if lparam == win32con.WM_LBUTTONUP:
pass pass
elif lparam == win32con.WM_LBUTTONDBLCLK: elif lparam == win32con.WM_LBUTTONDBLCLK:
@@ -144,14 +144,14 @@ class TaskBarIcon:
win32gui.AppendMenu(menu, win32con.MF_STRING, 1024, win32gui.AppendMenu(menu, win32con.MF_STRING, 1024,
'Start Server') 'Start Server')
win32gui.AppendMenu(menu, win32con.MF_STRING win32gui.AppendMenu(menu, win32con.MF_STRING
| win32con.MF_GRAYED, 1025, | win32con.MF_GRAYED, 1025,
'Restart Server') 'Restart Server')
win32gui.AppendMenu(menu, win32con.MF_STRING win32gui.AppendMenu(menu, win32con.MF_STRING
| win32con.MF_GRAYED, 1026, | win32con.MF_GRAYED, 1026,
'Stop Server') 'Stop Server')
else: else:
win32gui.AppendMenu(menu, win32con.MF_STRING win32gui.AppendMenu(menu, win32con.MF_STRING
| win32con.MF_GRAYED, 1024, | win32con.MF_GRAYED, 1024,
'Start Server') 'Start Server')
win32gui.AppendMenu(menu, win32con.MF_STRING, 1025, win32gui.AppendMenu(menu, win32con.MF_STRING, 1025,
'Restart Server') 'Restart Server')
@@ -173,7 +173,7 @@ class TaskBarIcon:
0, 0,
self.hwnd, self.hwnd,
None, None,
) )
win32api.PostMessage(self.hwnd, win32con.WM_NULL, 0, 0) win32api.PostMessage(self.hwnd, win32con.WM_NULL, 0, 0)
return 1 return 1
@@ -183,7 +183,7 @@ class TaskBarIcon:
msg, msg,
wparam, wparam,
lparam, lparam,
): ):
id = win32api.LOWORD(wparam) id = win32api.LOWORD(wparam)
if id == 1023: if id == 1023:
self.status.append(self.EnumStatus.TOGGLE) self.status.append(self.EnumStatus.TOGGLE)
@@ -226,7 +226,7 @@ class TaskBarIcon:
0, 0,
0, 0,
icon_flags, icon_flags,
) )
return hicon return hicon
class EnumStatus: class EnumStatus:
+20 -13
View File
@@ -3,16 +3,20 @@ import time
from collections import namedtuple from collections import namedtuple
Score = namedtuple('Score', ['tag', 'stamp']) Score = namedtuple('Score', ['tag', 'stamp'])
class TimeCollector(object): class TimeCollector(object):
def __init__(self): def __init__(self):
'''The first time stamp is created here''' '''The first time stamp is created here'''
self.scores = [Score(tag='start',stamp=time.clock())] self.scores = [Score(tag='start', stamp=time.clock())]
def addStamp(self, description): def addStamp(self, description):
'''Adds a new time stamp, with a description.''' '''Adds a new time stamp, with a description.'''
self.scores.append(Score(tag=description, stamp=time.clock())) self.scores.append(Score(tag=description, stamp=time.clock()))
def _stampDelta(self, index1, index2): def _stampDelta(self, index1, index2):
'''Private utility function to clean up this common calculation.''' '''Private utility function to clean up this common calculation.'''
return self.scores[index1].stamp - self.scores[index2].stamp return self.scores[index1].stamp - self.scores[index2].stamp
def getReportItems(self, orderByCost=True): def getReportItems(self, orderByCost=True):
'''Returns a list of dicts. Each dict has '''Returns a list of dicts. Each dict has
start (ms), start (ms),
@@ -25,23 +29,24 @@ class TimeCollector(object):
total_time = self._stampDelta(-1, 0) total_time = self._stampDelta(-1, 0)
data = [] data = []
for i in range(1, len(self.scores)): for i in range(1, len(self.scores)):
delta = self._stampDelta(i, i-1) delta = self._stampDelta(i, i - 1)
if abs(total_time) < 1e-6: if abs(total_time) < 1e-6:
perc = 0 perc = 0
else: else:
perc = delta / total_time * 100 perc = delta / total_time * 100
data.append( data.append(
dict( dict(
start = self._stampDelta(i-1, 0) * 1000, start=self._stampDelta(i - 1, 0) * 1000,
end = self._stampDelta(i, 0) * 1000, end=self._stampDelta(i, 0) * 1000,
delta = delta * 1000, delta=delta * 1000,
perc = perc, perc=perc,
tag = self.scores[i].tag tag=self.scores[i].tag
)
) )
)
if orderByCost: if orderByCost:
data.sort(key=lambda x: x['perc'], reverse=True) data.sort(key=lambda x: x['perc'], reverse=True)
return data return data
def getReportLines(self, orderByCost=True): def getReportLines(self, orderByCost=True):
'''Produces a report of logged time-stamps as a list of strings. '''Produces a report of logged time-stamps as a list of strings.
if orderByCost is False, then the order of the stamps is if orderByCost is False, then the order of the stamps is
@@ -51,14 +56,16 @@ class TimeCollector(object):
headerData = ('Start(ms)', 'End(ms)', 'Delta(ms)', 'Time Cost', headerData = ('Start(ms)', 'End(ms)', 'Delta(ms)', 'Time Cost',
'Description') 'Description')
bodyTemplate = '%(start)10.0f | %(end)10.0f | %(delta)10.0f |' \ bodyTemplate = '%(start)10.0f | %(end)10.0f | %(delta)10.0f |' \
+ ' %(perc)10.0f%% | %(tag)-30s' + ' %(perc)10.0f%% | %(tag)-30s'
return [headerTemplate % headerData] + [bodyTemplate % d for d in data] return [headerTemplate % headerData] + [bodyTemplate % d for d in data]
def getReportText(self, **kwargs): def getReportText(self, **kwargs):
return '\n'.join(self.getReportLines(**kwargs)) return '\n'.join(self.getReportLines(**kwargs))
def restart(self):
self.scores = [Score(tag='start',stamp=time.clock())]
if __name__=='__main__': def restart(self):
self.scores = [Score(tag='start', stamp=time.clock())]
if __name__ == '__main__':
print('') print('')
print('Testing:') print('Testing:')
print('') print('')
@@ -87,7 +94,7 @@ if __name__=='__main__':
print(t.getReportText()) print(t.getReportText())
t.restart() t.restart()
for y in range(1, 200, 20): for y in range(1, 200, 20):
x = [i for i in range(10000)*y] x = [i for i in range(10000) * y]
t.addStamp('Iteration when y = ' + str(y)) t.addStamp('Iteration when y = ' + str(y))
print('') print('')
+117 -69
View File
@@ -15,6 +15,7 @@ Symbian.
""" """
import sys import sys
class DetectorsHub(dict): class DetectorsHub(dict):
_known_types = ['os', 'dist', 'flavor', 'browser'] _known_types = ['os', 'dist', 'flavor', 'browser']
@@ -38,6 +39,7 @@ class DetectorsHub(dict):
return detectors return detectors
else: else:
prefs.insert(0, '') prefs.insert(0, '')
def key_name(d): def key_name(d):
return d.name in prefs and prefs.index(d.name) or sys.maxint return d.name in prefs and prefs.index(d.name) or sys.maxint
return sorted(detectors, key=key_name) return sorted(detectors, key=key_name)
@@ -46,24 +48,24 @@ class DetectorsHub(dict):
return iter(self._known_types) return iter(self._known_types)
def registerDetectors(self): def registerDetectors(self):
detectors = [v() for v in globals().values() \ detectors = [v() for v in globals().values()
if DetectorBase in getattr(v, '__mro__', [])] if DetectorBase in getattr(v, '__mro__', [])]
for d in detectors: for d in detectors:
if d.can_register: if d.can_register:
self.register(d) self.register(d)
class DetectorBase(object): class DetectorBase(object):
name = "" # "to perform match in DetectorsHub object" name = "" # "to perform match in DetectorsHub object"
info_type = '' #override me info_type = '' # override me
result_key = '' #override me result_key = '' # override me
order = 10 # 0 is highest order = 10 # 0 is highest
look_for = [] # list of words to look for look_for = [] # list of words to look for
skip_if_found = [] # strings if present stop processin skip_if_found = [] # strings if present stop processin
can_register = False can_register = False
is_mobile = False is_mobile = False
is_tablet = False is_tablet = False
prefs = dict() # dict(info_type = [name1, name2], ..) prefs = dict() # dict(info_type = [name1, name2], ..)
version_splitters = ["/", " "] version_splitters = ["/", " "]
_suggested_detectors = None _suggested_detectors = None
@@ -75,8 +77,8 @@ class DetectorBase(object):
def detect(self, agent, result): def detect(self, agent, result):
if agent and self.checkWords(agent): if agent and self.checkWords(agent):
result[self.info_type] = dict(name=self.name) result[self.info_type] = dict(name=self.name)
is_mobile=self.is_mobile is_mobile = self.is_mobile
is_tablet=self.is_tablet is_tablet = self.is_tablet
if is_mobile: if is_mobile:
result['is_mobile'] = is_mobile result['is_mobile'] = is_mobile
if is_tablet: if is_tablet:
@@ -131,6 +133,7 @@ class Browser(DetectorBase):
class Macintosh(OS): class Macintosh(OS):
look_for = ['Macintosh'] look_for = ['Macintosh']
prefs = dict(dist=None) prefs = dict(dist=None)
def getVersion(self, agent): def getVersion(self, agent):
pass pass
@@ -146,12 +149,15 @@ class Konqueror(Browser):
class Opera(Browser): class Opera(Browser):
look_for = ["Opera"] look_for = ["Opera"]
def getVersion(self, agent): def getVersion(self, agent):
return agent.partition(self.look_for[0])[2][1:].partition(' ')[0] return agent.partition(self.look_for[0])[2][1:].partition(' ')[0]
class Netscape(Browser): class Netscape(Browser):
look_for = ["Netscape"] look_for = ["Netscape"]
class MSIE(Browser): class MSIE(Browser):
look_for = ["MSIE"] look_for = ["MSIE"]
skip_if_found = ["Opera"] skip_if_found = ["Opera"]
@@ -171,6 +177,7 @@ class Safari(Browser):
if "Version/" in agent: if "Version/" in agent:
return agent.partition('Version/')[2].partition(' ')[0].strip() return agent.partition('Version/')[2].partition(' ')[0].strip()
class SafariTablet(Browser): class SafariTablet(Browser):
name = "Safari" name = "Safari"
look_for = ['Safari', 'Android'] look_for = ['Safari', 'Android']
@@ -182,6 +189,7 @@ class SafariTablet(Browser):
if "Version/" in agent: if "Version/" in agent:
return agent.partition('Version/')[2].partition(' ')[0].strip() return agent.partition('Version/')[2].partition(' ')[0].strip()
class SafariMobile(Browser): class SafariMobile(Browser):
name = "Safari" name = "Safari"
look_for = ["Safari", "Mobile"] look_for = ["Safari", "Mobile"]
@@ -191,6 +199,7 @@ class SafariMobile(Browser):
if "Version/" in agent: if "Version/" in agent:
return agent.partition('Version/')[2].partition(' ')[0].strip() return agent.partition('Version/')[2].partition(' ')[0].strip()
class SafariNokia(Browser): class SafariNokia(Browser):
name = "Safari" name = "Safari"
look_for = ["Safari", "SymbianOS"] look_for = ["Safari", "SymbianOS"]
@@ -199,6 +208,7 @@ class SafariNokia(Browser):
def getVersion(self, agent): def getVersion(self, agent):
pass pass
class SafariiPad(Browser): class SafariiPad(Browser):
name = "Safari" name = "Safari"
look_for = ["Safari", "iPad"] look_for = ["Safari", "iPad"]
@@ -218,9 +228,10 @@ class Linux(OS):
def getVersion(self, agent): def getVersion(self, agent):
pass pass
class BlackBerry(OS): class BlackBerry(OS):
look_for = ['BlackBerry'] look_for = ['BlackBerry']
prefs = dict( flavor=['PlayBook']) prefs = dict(flavor=['PlayBook'])
is_mobile = True is_mobile = True
# Manual check for tablet # Manual check for tablet
@@ -232,14 +243,16 @@ class BlackBerry(OS):
def getVersion(self, agent): def getVersion(self, agent):
pass pass
class PlayBook(Flavor): class PlayBook(Flavor):
look_for = ['PlayBook'] look_for = ['PlayBook']
is_mobile=True is_mobile = True
is_tablet=True is_tablet = True
def getVersion(self, agent): def getVersion(self, agent):
return agent.partition('Tablet OS')[2].partition(';')[0].strip() return agent.partition('Tablet OS')[2].partition(';')[0].strip()
class Macintosh(OS): class Macintosh(OS):
look_for = ['Macintosh'] look_for = ['Macintosh']
prefs = dict(dist=None, flavor=['MacOS']) prefs = dict(dist=None, flavor=['MacOS'])
@@ -250,7 +263,8 @@ class Macintosh(OS):
class MacOS(Flavor): class MacOS(Flavor):
look_for = ['Mac OS'] look_for = ['Mac OS']
prefs = dict(browser=['Safari','SafariMobile', 'SafariIpad', 'Firefox', 'Opera', "Microsoft Internet Explorer"]) prefs = dict(browser=['Safari', 'SafariMobile', 'SafariIpad',
'Firefox', 'Opera', "Microsoft Internet Explorer"])
def getVersion(self, agent): def getVersion(self, agent):
version_end_chars = [';', ')'] version_end_chars = [';', ')']
@@ -269,7 +283,8 @@ class Windows(OS):
def getVersion(self, agent): def getVersion(self, agent):
v = agent.partition('NT') v = agent.partition('NT')
return v[1]+' '+v[2].replace(')',';').partition(';')[0].strip() return v[1] + ' ' + v[2].replace(')', ';').partition(';')[0].strip()
class WindowsMobile(Dist): class WindowsMobile(Dist):
name = 'Phone' name = 'Phone'
@@ -277,7 +292,8 @@ class WindowsMobile(Dist):
is_mobile = True is_mobile = True
def getVersion(self, agent): def getVersion(self, agent):
return agent.partition('Windows Phone')[2].replace(')','').partition(';')[0].strip() return agent.partition('Windows Phone')[2].replace(')', '').partition(';')[0].strip()
class Ubuntu(Dist): class Ubuntu(Dist):
look_for = ['Ubuntu'] look_for = ['Ubuntu']
@@ -295,13 +311,16 @@ class Chrome(Browser):
look_for = ['Chrome'] look_for = ['Chrome']
version_splitters = ["/", " "] version_splitters = ["/", " "]
class ChromeOS(OS): class ChromeOS(OS):
look_for = ['CrOS'] look_for = ['CrOS']
version_splitters = [" ", ")"] version_splitters = [" ", ")"]
prefs = dict(browser=['Chrome']) prefs = dict(browser=['Chrome'])
def getVersion(self, agent): def getVersion(self, agent):
vs = self.version_splitters vs = self.version_splitters
return agent.partition(self.look_for[0]+vs[0])[2].partition(vs[1])[0].partition(" ")[2].strip() return agent.partition(self.look_for[0] + vs[0])[2].partition(vs[1])[0].partition(" ")[2].strip()
class Android(Dist): class Android(Dist):
look_for = ['Android'] look_for = ['Android']
@@ -311,23 +330,27 @@ class Android(Dist):
def getVersion(self, agent): def getVersion(self, agent):
return agent.partition('Android')[2].partition(';')[0].strip() return agent.partition('Android')[2].partition(';')[0].strip()
class SymbianOS(OS): class SymbianOS(OS):
look_for = ['SymbianOS'] look_for = ['SymbianOS']
prefs = dict(dist = ['Series'], browser = ['Safari', 'Opera']) prefs = dict(dist=['Series'], browser=['Safari', 'Opera'])
is_mobile = True is_mobile = True
version_splitters = ['/', '; '] version_splitters = ['/', '; ']
class Series(Flavor): class Series(Flavor):
look_for = ['SymbianOS', 'Series'] look_for = ['SymbianOS', 'Series']
version_splitters = ['/', ';'] version_splitters = ['/', ';']
def getVersion(self, agent): def getVersion(self, agent):
return agent.partition('Series')[2].partition(' ')[0].replace('/',' ') return agent.partition('Series')[2].partition(' ')[0].replace('/', ' ')
class BrowserNG(Browser): class BrowserNG(Browser):
look_for = ['BrowserNG'] look_for = ['BrowserNG']
version_splitters = ['/', ';'] version_splitters = ['/', ';']
class iPhone(Dist): class iPhone(Dist):
look_for = ['iPhone'] look_for = ['iPhone']
is_mobile = True is_mobile = True
@@ -344,6 +367,7 @@ class iPhone(Dist):
break break
return version.replace('_', '.') return version.replace('_', '.')
class iPad(Dist): class iPad(Dist):
look_for = ['iPad'] look_for = ['iPad']
is_mobile = True is_mobile = True
@@ -362,6 +386,7 @@ class iPad(Dist):
detectorshub = DetectorsHub() detectorshub = DetectorsHub()
def detect(agent): def detect(agent):
result = dict() result = dict()
prefs = dict() prefs = dict()
@@ -385,21 +410,26 @@ class Result(dict):
def __missing__(self, k): def __missing__(self, k):
return "" return ""
def simple_detect(agent): def simple_detect(agent):
""" """
-> (os, browser, is_mobile) # tuple of strings -> (os, browser, is_mobile) # tuple of strings
""" """
result = detect(agent) result = detect(agent)
os_list = [] os_list = []
if 'flavor' in result: os_list.append(result['flavor']['name']) if 'flavor' in result:
if 'dist' in result: os_list.append(result['dist']['name']) os_list.append(result['flavor']['name'])
if 'os' in result: os_list.append(result['os']['name']) if 'dist' in result:
os_list.append(result['dist']['name'])
if 'os' in result:
os_list.append(result['os']['name'])
os = os_list and " ".join(os_list) or "Unknown OS" os = os_list and " ".join(os_list) or "Unknown OS"
os_version = os_list and ('flavor' in result and result['flavor'] and result['flavor'].get( os_version = os_list and ('flavor' in result and result['flavor'] and result['flavor'].get(
'version')) or ('dist' in result and result['dist'] and result['dist'].get('version')) \ 'version')) or ('dist' in result and result['dist'] and result['dist'].get('version')) \
or ('os' in result and result['os'] and result['os'].get('version')) or "" or ('os' in result and result['os'] and result['os'].get('version')) or ""
browser = 'browser' in result and result['browser']['name'] or 'Unknown Browser' browser = 'browser' in result and result['browser'][
'name'] or 'Unknown Browser'
browser_version = 'browser' in result \ browser_version = 'browser' in result \
and result['browser'].get('version') or "" and result['browser'].get('version') or ""
if browser_version: if browser_version:
@@ -417,64 +447,82 @@ if __name__ == '__main__':
data = ( data = (
('Mozilla/5.0 (SymbianOS/9.2; U; Series60/3.1 Nokia6120c/3.83; Profile/MIDP-2.0 Configuration/CLDC-1.1) AppleWebKit/413 (KHTML, like Gecko) Safari/413', (
('Series SymbianOS 60 3.1', 'Safari', True), 'Mozilla/5.0 (SymbianOS/9.2; U; Series60/3.1 Nokia6120c/3.83; Profile/MIDP-2.0 Configuration/CLDC-1.1) AppleWebKit/413 (KHTML, like Gecko) Safari/413',
{'is_mobile': True, 'is_tablet': False, 'flavor': {'name': 'Series', 'version': '60 3.1'}, 'os': {'name': 'SymbianOS', 'version': '9.2'}, 'browser': {'name': 'Safari'}},), ('Series SymbianOS 60 3.1', 'Safari', True),
('Mozilla/5.0 (SymbianOS/9.4; Series60/5.0 NokiaN97-1/20.0.019; Profile/MIDP-2.1 Configuration/CLDC-1.1) AppleWebKit/525 (KHTML, like Gecko) BrowserNG/7.1.18124', {'is_mobile': True, 'is_tablet': False, 'flavor': {'name': 'Series', 'version': '60 3.1'}, 'os': {'name': 'SymbianOS', 'version': '9.2'}, 'browser': {'name': 'Safari'}},),
('Series SymbianOS 60 5.0', 'BrowserNG 7.1.18124', True), (
'Mozilla/5.0 (SymbianOS/9.4; Series60/5.0 NokiaN97-1/20.0.019; Profile/MIDP-2.1 Configuration/CLDC-1.1) AppleWebKit/525 (KHTML, like Gecko) BrowserNG/7.1.18124',
('Series SymbianOS 60 5.0', 'BrowserNG 7.1.18124', True),
{'is_mobile': True, 'is_tablet': False, 'flavor': {'name': 'Series', 'version': '60 5.0'}, 'os': {'name': 'SymbianOS', 'version': '9.4'}, 'browser': {'name': 'BrowserNG', 'version': '7.1.18124'}},), {'is_mobile': True, 'is_tablet': False, 'flavor': {'name': 'Series', 'version': '60 5.0'}, 'os': {'name': 'SymbianOS', 'version': '9.4'}, 'browser': {'name': 'BrowserNG', 'version': '7.1.18124'}},),
('Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; Windows Phone 6.5.3.5)', (
('Phone Windows 6.5.3.5', 'Microsoft Internet Explorer 6.0', True), 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; Windows Phone 6.5.3.5)',
{'is_mobile': True, 'is_tablet': False, 'dist': {'name': 'Phone', 'version': '6.5.3.5'}, 'os': {'name': 'Windows', 'version': 'NT 5.1'}, 'browser': {'name': 'Microsoft Internet Explorer', 'version': '6.0'}},), ('Phone Windows 6.5.3.5', 'Microsoft Internet Explorer 6.0', True),
('Mozilla/5.0 (PlayBook; U; RIM Tablet OS 1.0.0; en-US) AppleWebKit/534.8+ (KHTML, like Gecko) Version/0.0.1 Safari/534.8+', {'is_mobile': True, 'is_tablet': False, 'dist': {'name': 'Phone', 'version': '6.5.3.5'}, 'os': {'name': 'Windows', 'version': 'NT 5.1'}, 'browser': {'name': 'Microsoft Internet Explorer', 'version': '6.0'}},),
('PlayBook BlackBerry 1.0.0', 'Safari 0.0.1', True), (
'Mozilla/5.0 (PlayBook; U; RIM Tablet OS 1.0.0; en-US) AppleWebKit/534.8+ (KHTML, like Gecko) Version/0.0.1 Safari/534.8+',
('PlayBook BlackBerry 1.0.0', 'Safari 0.0.1', True),
{'is_mobile': True, 'is_tablet': True, 'flavor': {'name': 'PlayBook', 'version': '1.0.0'}, 'os': {'name': 'BlackBerry'}, 'browser': {'name': 'Safari', 'version': '0.0.1'}},), {'is_mobile': True, 'is_tablet': True, 'flavor': {'name': 'PlayBook', 'version': '1.0.0'}, 'os': {'name': 'BlackBerry'}, 'browser': {'name': 'Safari', 'version': '0.0.1'}},),
('Mozilla/5.0 (BlackBerry; U; BlackBerry 9800; en-US) AppleWebKit/534.1+ (KHTML, like Gecko) Version/6.0.0.246 Mobile Safari/534.1+', (
('BlackBerry', 'Safari 6.0.0.246', True), 'Mozilla/5.0 (BlackBerry; U; BlackBerry 9800; en-US) AppleWebKit/534.1+ (KHTML, like Gecko) Version/6.0.0.246 Mobile Safari/534.1+',
('BlackBerry', 'Safari 6.0.0.246', True),
{'is_mobile': True, 'is_tablet': False, 'os': {'name': 'BlackBerry'}, 'browser': {'name': 'Safari', 'version': '6.0.0.246'}},), {'is_mobile': True, 'is_tablet': False, 'os': {'name': 'BlackBerry'}, 'browser': {'name': 'Safari', 'version': '6.0.0.246'}},),
('Mozilla/5.0 (BlackBerry; U; BlackBerry 9800; en-US) AppleWebKit/534.8+ (KHTML, like Gecko) Version/6.0.0.600 Mobile Safari/534.8+', (
('BlackBerry', 'Safari 6.0.0.600', True), 'Mozilla/5.0 (BlackBerry; U; BlackBerry 9800; en-US) AppleWebKit/534.8+ (KHTML, like Gecko) Version/6.0.0.600 Mobile Safari/534.8+',
('BlackBerry', 'Safari 6.0.0.600', True),
{'is_mobile': True, 'is_tablet': False, 'os': {'name': 'BlackBerry'}, 'browser': {'name': 'Safari', 'version': '6.0.0.600'}},), {'is_mobile': True, 'is_tablet': False, 'os': {'name': 'BlackBerry'}, 'browser': {'name': 'Safari', 'version': '6.0.0.600'}},),
('Mozilla/5.0 (iPad; U; CPU OS 4_2_1 like Mac OS X; en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8C148 Safari/6533.18.5', (
('MacOS iPad X', 'Safari 5.0.2', True), 'Mozilla/5.0 (iPad; U; CPU OS 4_2_1 like Mac OS X; en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8C148 Safari/6533.18.5',
{'is_mobile': True, 'is_tablet': True, 'flavor': {'version': 'X', 'name': 'MacOS'}, 'dist': {'version': 'iOS 4.2.1', 'name': 'iPad'}, 'browser': {'name': 'Safari', 'version': '5.0.2'}},), ('MacOS iPad X', 'Safari 5.0.2', True),
('Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.5) Gecko/20060127 Netscape/8.1', {'is_mobile': True, 'is_tablet': True, 'flavor': {'version': 'X', 'name': 'MacOS'}, 'dist': {'version': 'iOS 4.2.1', 'name': 'iPad'}, 'browser': {'name': 'Safari', 'version': '5.0.2'}},),
('Windows NT 5.1', 'Netscape 8.1', False), (
'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.5) Gecko/20060127 Netscape/8.1',
('Windows NT 5.1', 'Netscape 8.1', False),
{'is_mobile': False, 'is_tablet': False, 'os': {'name': 'Windows', 'version': 'NT 5.1'}, 'browser': {'name': 'Netscape', 'version': '8.1'}},), {'is_mobile': False, 'is_tablet': False, 'os': {'name': 'Windows', 'version': 'NT 5.1'}, 'browser': {'name': 'Netscape', 'version': '8.1'}},),
('Mozilla/5.0 (Linux; U; Android 3.0.1; en-us; A500 Build/HRI66) AppleWebKit/534.13 (KHTML, like Gecko) Version/4.0 Safari/534.13', (
('Android Linux 3.0.1', 'Safari 4.0', True), 'Mozilla/5.0 (Linux; U; Android 3.0.1; en-us; A500 Build/HRI66) AppleWebKit/534.13 (KHTML, like Gecko) Version/4.0 Safari/534.13',
('Android Linux 3.0.1', 'Safari 4.0', True),
{'is_mobile': True, 'is_tablet': True, 'dist': {'version': '3.0.1', 'name': 'Android'}, 'os': {'name': 'Linux'}, 'browser': {'version': '4.0', 'name': 'Safari'}},), {'is_mobile': True, 'is_tablet': True, 'dist': {'version': '3.0.1', 'name': 'Android'}, 'os': {'name': 'Linux'}, 'browser': {'version': '4.0', 'name': 'Safari'}},),
('Mozilla/5.0 (Linux; U; Android 2.3.7; it-it; Dream/Sapphire Build/FRG83) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1', (
('Android Linux 2.3.7', 'Safari 4.0', True), 'Mozilla/5.0 (Linux; U; Android 2.3.7; it-it; Dream/Sapphire Build/FRG83) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1',
{'is_mobile': True, 'is_tablet': False, 'dist': {'version': '2.3.7', 'name': 'Android'}, 'os': {'name': 'Linux'}, 'browser': {'version': '4.0', 'name': 'Safari'}},), ('Android Linux 2.3.7', 'Safari 4.0', True),
('Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-GB; rv:1.9.0.10) Gecko/2009042315 Firefox/3.0.10', {'is_mobile': True, 'is_tablet': False, 'dist': {'version': '2.3.7', 'name': 'Android'}, 'os': {'name': 'Linux'}, 'browser': {'version': '4.0', 'name': 'Safari'}},),
('MacOS Macintosh X 10.5', 'Firefox 3.0.10', False), (
{ 'is_mobile': False, 'is_tablet': False, 'flavor': {'version': 'X 10.5', 'name': 'MacOS'}, 'os': {'name': 'Macintosh'}, 'browser': {'version': '3.0.10', 'name': 'Firefox'}},), 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-GB; rv:1.9.0.10) Gecko/2009042315 Firefox/3.0.10',
('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_6) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.3 Safari/534.24,gzip(gfe)', ('MacOS Macintosh X 10.5', 'Firefox 3.0.10', False),
('MacOS Macintosh X 10.6.6', 'Chrome 11.0.696.3', False), {'is_mobile': False, 'is_tablet': False, 'flavor': {'version': 'X 10.5', 'name': 'MacOS'}, 'os': {'name': 'Macintosh'}, 'browser': {'version': '3.0.10', 'name': 'Firefox'}},),
(
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_6) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.3 Safari/534.24,gzip(gfe)',
('MacOS Macintosh X 10.6.6', 'Chrome 11.0.696.3', False),
{'is_mobile': False, 'is_tablet': False, 'flavor': {'version': 'X 10.6.6', 'name': 'MacOS'}, 'os': {'name': 'Macintosh'}, 'browser': {'version': '11.0.696.3', 'name': 'Chrome'}},), {'is_mobile': False, 'is_tablet': False, 'flavor': {'version': 'X 10.6.6', 'name': 'MacOS'}, 'os': {'name': 'Macintosh'}, 'browser': {'version': '11.0.696.3', 'name': 'Chrome'}},),
('Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2) Gecko/20100308 Ubuntu/10.04 (lucid) Firefox/3.6 GTB7.1', (
('Ubuntu Linux 10.04', 'Firefox 3.6', False), 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2) Gecko/20100308 Ubuntu/10.04 (lucid) Firefox/3.6 GTB7.1',
('Ubuntu Linux 10.04', 'Firefox 3.6', False),
{'is_mobile': False, 'is_tablet': False, 'dist': {'version': '10.04', 'name': 'Ubuntu'}, 'os': {'name': 'Linux'}, 'browser': {'version': '3.6', 'name': 'Firefox'}},), {'is_mobile': False, 'is_tablet': False, 'dist': {'version': '10.04', 'name': 'Ubuntu'}, 'os': {'name': 'Linux'}, 'browser': {'version': '3.6', 'name': 'Firefox'}},),
('Mozilla/5.0 (Linux; U; Android 2.2.1; fr-ch; A43 Build/FROYO) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1', (
('Android Linux 2.2.1', 'Safari 4.0', True), 'Mozilla/5.0 (Linux; U; Android 2.2.1; fr-ch; A43 Build/FROYO) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1',
('Android Linux 2.2.1', 'Safari 4.0', True),
{'is_mobile': True, 'is_tablet': False, 'dist': {'version': '2.2.1', 'name': 'Android'}, 'os': {'name': 'Linux'}, 'browser': {'version': '4.0', 'name': 'Safari'}},), {'is_mobile': True, 'is_tablet': False, 'dist': {'version': '2.2.1', 'name': 'Android'}, 'os': {'name': 'Linux'}, 'browser': {'version': '4.0', 'name': 'Safari'}},),
('Mozilla/5.0 (Linux; U; Android 2.3.4; it-it; LG-P990 Build/GRJ22) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1 MMS/LG-Android-MMS-V1.0/1.2', (
('Android Linux 2.3.4', 'Safari 4.0', True), 'Mozilla/5.0 (Linux; U; Android 2.3.4; it-it; LG-P990 Build/GRJ22) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1 MMS/LG-Android-MMS-V1.0/1.2',
('Android Linux 2.3.4', 'Safari 4.0', True),
{'is_mobile': True, 'is_tablet': False, 'dist': {'version': '2.3.4', 'name': 'Android'}, 'os': {'name': 'Linux'}, 'browser': {'version': '4.0', 'name': 'Safari'}},), {'is_mobile': True, 'is_tablet': False, 'dist': {'version': '2.3.4', 'name': 'Android'}, 'os': {'name': 'Linux'}, 'browser': {'version': '4.0', 'name': 'Safari'}},),
('Mozilla/5.0 (iPhone; U; CPU like Mac OS X; en) AppleWebKit/420+ (KHTML, like Gecko) Version/3.0 Mobile/1A543a Safari/419.3', (
('MacOS iPhone X', 'Safari 3.0', True), 'Mozilla/5.0 (iPhone; U; CPU like Mac OS X; en) AppleWebKit/420+ (KHTML, like Gecko) Version/3.0 Mobile/1A543a Safari/419.3',
('MacOS iPhone X', 'Safari 3.0', True),
{'is_mobile': True, 'is_tablet': False, 'flavor': {'version': 'X', 'name': 'MacOS'}, 'dist': {'version': 'X', 'name': 'iPhone'}, 'browser': {'version': '3.0', 'name': 'Safari'}},), {'is_mobile': True, 'is_tablet': False, 'flavor': {'version': 'X', 'name': 'MacOS'}, 'dist': {'version': 'X', 'name': 'iPhone'}, 'browser': {'version': '3.0', 'name': 'Safari'}},),
('Mozilla/5.0 (X11; CrOS i686 0.0.0) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.27 Safari/534.24,gzip(gfe)', (
('ChromeOS 0.0.0', 'Chrome 11.0.696.27', False), 'Mozilla/5.0 (X11; CrOS i686 0.0.0) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.27 Safari/534.24,gzip(gfe)',
('ChromeOS 0.0.0', 'Chrome 11.0.696.27', False),
{'is_mobile': False, 'is_tablet': False, 'os': {'name': 'ChromeOS', 'version': '0.0.0'}, 'browser': {'name': 'Chrome', 'version': '11.0.696.27'}},), {'is_mobile': False, 'is_tablet': False, 'os': {'name': 'ChromeOS', 'version': '0.0.0'}, 'browser': {'name': 'Chrome', 'version': '11.0.696.27'}},),
('Mozilla/4.0 (compatible; MSIE 6.0; MSIE 5.5; Windows NT 5.1) Opera 7.02 [en]', (
('Windows NT 5.1', 'Opera 7.02', False), 'Mozilla/4.0 (compatible; MSIE 6.0; MSIE 5.5; Windows NT 5.1) Opera 7.02 [en]',
('Windows NT 5.1', 'Opera 7.02', False),
{'is_mobile': False, 'is_tablet': False, 'os': {'name': 'Windows', 'version': 'NT 5.1'}, 'browser': {'name': 'Opera', 'version': '7.02'}},), {'is_mobile': False, 'is_tablet': False, 'os': {'name': 'Windows', 'version': 'NT 5.1'}, 'browser': {'name': 'Opera', 'version': '7.02'}},),
('Opera/9.80 (X11; Linux i686; U; en) Presto/2.9.168 Version/11.50', ('Opera/9.80 (X11; Linux i686; U; en) Presto/2.9.168 Version/11.50',
('Linux', 'Opera 9.80', False), ('Linux', 'Opera 9.80', False),
{'is_mobile': False, 'is_tablet': False, 'os': {'name': 'Linux'}, 'browser': {'name': 'Opera', 'version': '9.80'}},), {'is_mobile': False, 'is_tablet': False, 'os': {'name': 'Linux'}, 'browser': {'name': 'Opera', 'version': '9.80'}},),
) )
class TestHAP(unittest.TestCase): class TestHAP(unittest.TestCase):
def setUp(self): def setUp(self):
@@ -513,6 +561,6 @@ class mobilize(object):
user_agent = current.request.user_agent() user_agent = current.request.user_agent()
if user_agent.is_mobile: if user_agent.is_mobile:
items = current.response.view.split('.') items = current.response.view.split('.')
items.insert(-1,'mobile') items.insert(-1, 'mobile')
current.response.view = '.'.join(items) current.response.view = '.'.join(items)
return self.func() return self.func()
+48 -45
View File
@@ -22,21 +22,22 @@ import urllib
import urllib2 import urllib2
DEFAULT_HEADERS = { DEFAULT_HEADERS = {
'user-agent': 'Mozilla/4.0', # some servers are picky 'user-agent': 'Mozilla/4.0', # some servers are picky
'accept-language': 'en', 'accept-language': 'en',
} }
FORM_REGEX = re.compile('(\<input name\="_formkey" type\="hidden" value\="(?P<formkey>.+?)" \/\>)?\<input name\="_formname" type\="hidden" value\="(?P<formname>.+?)" \/\>') FORM_REGEX = re.compile('(\<input name\="_formkey" type\="hidden" value\="(?P<formkey>.+?)" \/\>)?\<input name\="_formname" type\="hidden" value\="(?P<formname>.+?)" \/\>')
SESSION_REGEX = 'session_id_(?P<name>.+)' SESSION_REGEX = 'session_id_(?P<name>.+)'
class WebClient(object): class WebClient(object):
def __init__(self, def __init__(self,
app = '', app='',
postbacks = True, postbacks=True,
default_headers = DEFAULT_HEADERS, default_headers=DEFAULT_HEADERS,
session_regex = SESSION_REGEX): session_regex=SESSION_REGEX):
self.app = app self.app = app
self.postbacks = postbacks self.postbacks = postbacks
self.forms = {} self.forms = {}
@@ -46,18 +47,18 @@ class WebClient(object):
self.sessions = {} self.sessions = {}
self.session_regex = session_regex and re.compile(session_regex) self.session_regex = session_regex and re.compile(session_regex)
def get(self,url,cookies=None,headers=None,auth=None): def get(self, url, cookies=None, headers=None, auth=None):
return self.post(url,data=None,cookies=cookies,headers=headers) return self.post(url, data=None, cookies=cookies, headers=headers)
def post(self,url,data=None,cookies=None,headers=None,auth=None): def post(self, url, data=None, cookies=None, headers=None, auth=None):
self.url = self.app+url self.url = self.app + url
# if this POST form requires a postback do it # if this POST form requires a postback do it
if data and '_formname' in data and self.postbacks and \ if data and '_formname' in data and self.postbacks and \
self.history and self.history[-1][1]!=self.url: self.history and self.history[-1][1] != self.url:
# to bypass the web2py CSRF need to get formkey # to bypass the web2py CSRF need to get formkey
# before submitting the form # before submitting the form
self.get(url,cookies=cookies,headers=headers,auth=auth) self.get(url, cookies=cookies, headers=headers, auth=auth)
# unless cookies are specified, recycle cookies # unless cookies are specified, recycle cookies
if cookies is None: if cookies is None:
@@ -75,22 +76,23 @@ class WebClient(object):
# copy headers from dict to list of key,value # copy headers from dict to list of key,value
headers_list = [] headers_list = []
for key,value in self.default_headers.iteritems(): for key, value in self.default_headers.iteritems():
if not key in headers: if not key in headers:
headers[key] = value headers[key] = value
for key,value in headers.iteritems(): for key, value in headers.iteritems():
if isinstance(value,(list,tuple)): if isinstance(value, (list, tuple)):
for v in value: headers_list.append((key,v)) for v in value:
headers_list.append((key, v))
else: else:
headers_list.append((key,value)) headers_list.append((key, value))
# move cookies to headers # move cookies to headers
for key,value in cookies.iteritems(): for key, value in cookies.iteritems():
headers_list.append(('Cookie','%s=%s' % (key,value))) headers_list.append(('Cookie', '%s=%s' % (key, value)))
# add headers to request # add headers to request
for key,value in headers_list: for key, value in headers_list:
opener.addheaders.append((key,str(value))) opener.addheaders.append((key, str(value)))
# assume everything is ok and make http request # assume everything is ok and make http request
error = None error = None
@@ -99,7 +101,7 @@ class WebClient(object):
self.method = 'POST' self.method = 'POST'
# if there is only one form, set _formname automatically # if there is only one form, set _formname automatically
if not '_formname' in data and len(self.forms)==1: if not '_formname' in data and len(self.forms) == 1:
data['_formname'] = self.forms.keys()[0] data['_formname'] = self.forms.keys()[0]
# if there is no formkey but it is known, set it # if there is no formkey but it is known, set it
@@ -110,18 +112,18 @@ class WebClient(object):
# time the POST request # time the POST request
data = urllib.urlencode(data) data = urllib.urlencode(data)
t0 = time.time() t0 = time.time()
self.response = opener.open(self.url,data) self.response = opener.open(self.url, data)
self.time = time.time()-t0 self.time = time.time() - t0
else: else:
self.method = 'GET' self.method = 'GET'
# time the GET request # time the GET request
t0 = time.time() t0 = time.time()
self.response = opener.open(self.url) self.response = opener.open(self.url)
self.time = time.time()-t0 self.time = time.time() - t0
except urllib2.HTTPError, error: except urllib2.HTTPError, error:
# catch HTTP errors # catch HTTP errors
self.time = time.time()-t0 self.time = time.time() - t0
self.response = error self.response = error
self.status = self.response.getcode() self.status = self.response.getcode()
@@ -131,7 +133,7 @@ class WebClient(object):
# treat web2py tickets as special types of errors # treat web2py tickets as special types of errors
if error is not None: if error is not None:
if 'web2py_error' in self.headers: if 'web2py_error' in self.headers:
raise RuntimeError, self.headers['web2py_error'] raise RuntimeError(self.headers['web2py_error'])
else: else:
raise error raise error
@@ -139,8 +141,8 @@ class WebClient(object):
self.cookies = {} self.cookies = {}
if 'set-cookie' in self.headers: if 'set-cookie' in self.headers:
for item in self.headers['set-cookie'].split(','): for item in self.headers['set-cookie'].split(','):
key,value = item[:item.find(';')].split('=') key, value = item[:item.find(';')].split('=')
self.cookies[key.strip()]=value.strip() self.cookies[key.strip()] = value.strip()
# check is a new session id has been issued, symptom of broken session # check is a new session id has been issued, symptom of broken session
if self.session_regex is not None: if self.session_regex is not None:
@@ -148,8 +150,8 @@ class WebClient(object):
match = self.session_regex.match(cookie) match = self.session_regex.match(cookie)
if match: if match:
name = match.group('name') name = match.group('name')
if name in self.sessions and self.sessions[name]!=value: if name in self.sessions and self.sessions[name] != value:
raise RuntimeError, 'Broken sessions %s' % name raise RuntimeError('Broken sessions %s' % name)
self.sessions[name] = value self.sessions[name] = value
# find all forms and formkeys in page # find all forms and formkeys in page
@@ -158,7 +160,8 @@ class WebClient(object):
self.forms[match.group('formname')] = match.group('formkey') self.forms[match.group('formname')] = match.group('formkey')
# log this request # log this request
self.history.append((self.method,self.url,self.status,self.time)) self.history.append((self.method, self.url, self.status, self.time))
def test_web2py_registration_and_login(): def test_web2py_registration_and_login():
# from gluon.contrib.webclient import WebClient # from gluon.contrib.webclient import WebClient
@@ -168,13 +171,13 @@ def test_web2py_registration_and_login():
client.get('index') client.get('index')
# register # register
data = dict(first_name = 'Homer', data = dict(first_name='Homer',
last_name = 'Simpson', last_name='Simpson',
email = 'homer@web2py.com', email='homer@web2py.com',
password = 'test', password='test',
password_two = 'test', password_two='test',
_formname = 'register') _formname='register')
client.post('user/register',data = data) client.post('user/register', data=data)
# logout # logout
client.get('user/logout') client.get('user/logout')
@@ -182,18 +185,18 @@ def test_web2py_registration_and_login():
# login # login
data = dict(email='homer@web2py.com', data = dict(email='homer@web2py.com',
password='test', password='test',
_formname = 'login') _formname='login')
client.post('user/login',data = data) client.post('user/login', data=data)
# check registration and login were successful # check registration and login were successful
client.get('user/profile') client.get('user/profile')
assert 'Welcome Homer' in client.text assert 'Welcome Homer' in client.text
# print some variables # print some variables
print '\nsessions:\n',client.sessions print '\nsessions:\n', client.sessions
print '\nheaders:\n',client.headers print '\nheaders:\n', client.headers
print '\ncookies:\n',client.cookies print '\ncookies:\n', client.cookies
print '\nforms:\n',client.forms print '\nforms:\n', client.forms
print print
for method, url, status, t in client.history: for method, url, status, t in client.history:
print method, url, status, t print method, url, status, t
+37 -20
View File
@@ -61,7 +61,8 @@ Here is a complete sample web2py action:
form=SQLFORM.factory(Field('message')) form=SQLFORM.factory(Field('message'))
if form.accepts(request,session): if form.accepts(request,session):
from gluon.contrib.websocket_messaging import websocket_send from gluon.contrib.websocket_messaging import websocket_send
websocket_send('http://127.0.0.1:8888',form.vars.message,'mykey','mygroup') websocket_send(
'http://127.0.0.1:8888',form.vars.message,'mykey','mygroup')
return form return form
Acknowledgements: Acknowledgements:
@@ -83,31 +84,38 @@ listeners = {}
names = {} names = {}
tokens = {} tokens = {}
def websocket_send(url,message,hmac_key=None,group='default'):
sig = hmac_key and hmac.new(hmac_key,message).hexdigest() or '' def websocket_send(url, message, hmac_key=None, group='default'):
params = urllib.urlencode({'message': message, 'signature': sig, 'group':group}) sig = hmac_key and hmac.new(hmac_key, message).hexdigest() or ''
params = urllib.urlencode(
{'message': message, 'signature': sig, 'group': group})
f = urllib.urlopen(url, params) f = urllib.urlopen(url, params)
data= f.read() data = f.read()
f.close() f.close()
return data return data
class PostHandler(tornado.web.RequestHandler): class PostHandler(tornado.web.RequestHandler):
""" """
only authorized parties can post messages only authorized parties can post messages
""" """
def post(self): def post(self):
if hmac_key and not 'signature' in self.request.arguments: return 'false' if hmac_key and not 'signature' in self.request.arguments:
return 'false'
if 'message' in self.request.arguments: if 'message' in self.request.arguments:
message = self.request.arguments['message'][0] message = self.request.arguments['message'][0]
group = self.request.arguments.get('group',['default'])[0] group = self.request.arguments.get('group', ['default'])[0]
print '%s:MESSAGE to %s:%s' % (time.time(), group, message) print '%s:MESSAGE to %s:%s' % (time.time(), group, message)
if hmac_key: if hmac_key:
signature = self.request.arguments['signature'][0] signature = self.request.arguments['signature'][0]
if not hmac.new(hmac_key,message).hexdigest()==signature: return 'false' if not hmac.new(hmac_key, message).hexdigest() == signature:
for client in listeners.get(group,[]): client.write_message(message) return 'false'
for client in listeners.get(group, []):
client.write_message(message)
return 'true' return 'true'
return 'false' return 'false'
class TokenHandler(tornado.web.RequestHandler): class TokenHandler(tornado.web.RequestHandler):
""" """
if running with -t post a token to allow a client to join using the token if running with -t post a token to allow a client to join using the token
@@ -115,46 +123,55 @@ class TokenHandler(tornado.web.RequestHandler):
allows only authorized parties to joins, for example, a chat allows only authorized parties to joins, for example, a chat
""" """
def post(self): def post(self):
if hmac_key and not 'message' in self.request.arguments: return 'false' if hmac_key and not 'message' in self.request.arguments:
return 'false'
if 'message' in self.request.arguments: if 'message' in self.request.arguments:
message = self.request.arguments['message'][0] message = self.request.arguments['message'][0]
if hmac_key: if hmac_key:
signature = self.request.arguments['signature'][0] signature = self.request.arguments['signature'][0]
if not hmac.new(hmac_key,message).hexdigest()==signature: return 'false' if not hmac.new(hmac_key, message).hexdigest() == signature:
return 'false'
tokens[message] = None tokens[message] = None
return 'true' return 'true'
return 'false' return 'false'
class DistributeHandler(tornado.websocket.WebSocketHandler): class DistributeHandler(tornado.websocket.WebSocketHandler):
def open(self,params): def open(self, params):
group,token,name = params.split('/')+[None,None] group, token, name = params.split('/') + [None, None]
self.group = group or 'default' self.group = group or 'default'
self.token = token or 'none' self.token = token or 'none'
self.name = name or 'anonymous' self.name = name or 'anonymous'
# only authorized parties can join # only authorized parties can join
if DistributeHandler.tokens: if DistributeHandler.tokens:
if not self.token in tokens or not token[self.token]==None: if not self.token in tokens or not token[self.token] is None:
self.close() self.close()
else: else:
tokens[self.token] = self tokens[self.token] = self
if not self.group in listeners: listeners[self.group]=[] if not self.group in listeners:
listeners[self.group] = []
# notify clients that a member has joined the groups # notify clients that a member has joined the groups
for client in listeners.get(self.group,[]): client.write_message('+'+self.name) for client in listeners.get(self.group, []):
client.write_message('+' + self.name)
listeners[self.group].append(self) listeners[self.group].append(self)
names[self] = self.name names[self] = self.name
print '%s:CONNECT to %s' % (time.time(), self.group) print '%s:CONNECT to %s' % (time.time(), self.group)
def on_message(self, message): def on_message(self, message):
pass pass
def on_close(self): def on_close(self):
if self.group in listeners: listeners[self.group].remove(self) if self.group in listeners:
listeners[self.group].remove(self)
del names[self] del names[self]
# notify clients that a member has left the groups # notify clients that a member has left the groups
for client in listeners.get(self.group,[]): client.write_message('-'+self.name) for client in listeners.get(self.group, []):
client.write_message('-' + self.name)
print '%s:DISCONNECT from %s' % (time.time(), self.group) print '%s:DISCONNECT from %s' % (time.time(), self.group)
if __name__ == "__main__": if __name__ == "__main__":
usage = __doc__ usage = __doc__
version= "" version = ""
parser = optparse.OptionParser(usage, None, optparse.Option, version) parser = optparse.OptionParser(usage, None, optparse.Option, version)
parser.add_option('-p', parser.add_option('-p',
'--port', '--port',
@@ -180,7 +197,7 @@ if __name__ == "__main__":
(options, args) = parser.parse_args() (options, args) = parser.parse_args()
hmac_key = options.hmac_key hmac_key = options.hmac_key
DistributeHandler.tokens = options.tokens DistributeHandler.tokens = options.tokens
urls=[ urls = [
(r'/', PostHandler), (r'/', PostHandler),
(r'/token', TokenHandler), (r'/token', TokenHandler),
(r'/realtime/(.*)', DistributeHandler)] (r'/realtime/(.*)', DistributeHandler)]
+30 -23
View File
@@ -10,24 +10,30 @@ import traceback
from gluon import current from gluon import current
NATIVE_IMPORTER = __builtin__.__import__ NATIVE_IMPORTER = __builtin__.__import__
INVALID_MODULES = set(('','gluon','applications','custom_import')) INVALID_MODULES = set(('', 'gluon', 'applications', 'custom_import'))
# backward compatibility API # backward compatibility API
def custom_import_install(): def custom_import_install():
if __builtin__.__import__ != custom_importer: if __builtin__.__import__ != custom_importer:
INVALID_MODULES.update(sys.modules.keys()) INVALID_MODULES.update(sys.modules.keys())
__builtin__.__import__ = custom_importer __builtin__.__import__ = custom_importer
def track_changes(track=True): def track_changes(track=True):
assert track in (True,False), "must be True or False" assert track in (True, False), "must be True or False"
current.request._custom_import_track_changes = track current.request._custom_import_track_changes = track
def is_tracking_changes(): def is_tracking_changes():
return current.request._custom_import_track_changes return current.request._custom_import_track_changes
class CustomImportException(ImportError): class CustomImportException(ImportError):
pass pass
def custom_importer(name, globals=None, locals=None, fromlist=None, level=-1): def custom_importer(name, globals=None, locals=None, fromlist=None, level=-1):
""" """
The web2py custom importer. Like the standard Python importer but it The web2py custom importer. Like the standard Python importer but it
@@ -45,25 +51,26 @@ def custom_importer(name, globals=None, locals=None, fromlist=None, level=-1):
base_importer = TRACK_IMPORTER base_importer = TRACK_IMPORTER
else: else:
base_importer = NATIVE_IMPORTER base_importer = NATIVE_IMPORTER
except: # there is no current.request (should never happen) except: # there is no current.request (should never happen)
base_importer = NATIVE_IMPORTER base_importer = NATIVE_IMPORTER
# if not relative and not from applications: # if not relative and not from applications:
if hasattr(current,'request') \ if hasattr(current, 'request') \
and level<=0 \ and level <= 0 \
and not name.split('.')[0] in INVALID_MODULES \ and not name.split('.')[0] in INVALID_MODULES \
and isinstance(globals, dict): and isinstance(globals, dict):
import_tb = None import_tb = None
try: try:
items = current.request.folder.split(os.path.sep) items = current.request.folder.split(os.path.sep)
if not items[-1]: items = items[:-1] if not items[-1]:
modules_prefix = '.'.join(items[-2:])+'.modules' items = items[:-1]
modules_prefix = '.'.join(items[-2:]) + '.modules'
if not fromlist: if not fromlist:
# import like "import x" or "import x.y" # import like "import x" or "import x.y"
result = None result = None
for itemname in name.split("."): for itemname in name.split("."):
new_mod = base_importer( new_mod = base_importer(
modules_prefix, globals,locals, [itemname], level) modules_prefix, globals, locals, [itemname], level)
try: try:
result = result or new_mod.__dict__[itemname] result = result or new_mod.__dict__[itemname]
except KeyError, e: except KeyError, e:
@@ -77,16 +84,16 @@ def custom_importer(name, globals=None, locals=None, fromlist=None, level=-1):
except ImportError, e1: except ImportError, e1:
import_tb = sys.exc_info()[2] import_tb = sys.exc_info()[2]
try: try:
return NATIVE_IMPORTER(name,globals,locals,fromlist,level) return NATIVE_IMPORTER(name, globals, locals, fromlist, level)
except ImportError, e3: except ImportError, e3:
raise ImportError, e1, import_tb # there an import error in the module raise ImportError, e1, import_tb # there an import error in the module
except Exception, e2: except Exception, e2:
raise e2 # there is an error in the module raise e2 # there is an error in the module
finally: finally:
if import_tb: if import_tb:
import_tb = None import_tb = None
return NATIVE_IMPORTER(name,globals,locals,fromlist,level) return NATIVE_IMPORTER(name, globals, locals, fromlist, level)
class TrackImporter(object): class TrackImporter(object):
@@ -96,19 +103,19 @@ class TrackImporter(object):
""" """
THREAD_LOCAL = threading.local() THREAD_LOCAL = threading.local()
PACKAGE_PATH_SUFFIX = os.path.sep+"__init__.py" PACKAGE_PATH_SUFFIX = os.path.sep + "__init__.py"
def __init__(self): def __init__(self):
self._import_dates = {} # Import dates of the files of the modules self._import_dates = {} # Import dates of the files of the modules
def __call__(self,name,globals=None,locals=None,fromlist=None,level=-1): def __call__(self, name, globals=None, locals=None, fromlist=None, level=-1):
""" """
The import method itself. The import method itself.
""" """
globals = globals or {} globals = globals or {}
locals = locals or {} locals = locals or {}
fromlist = fromlist or [] fromlist = fromlist or []
if not hasattr(self.THREAD_LOCAL,'_modules_loaded'): if not hasattr(self.THREAD_LOCAL, '_modules_loaded'):
self.THREAD_LOCAL._modules_loaded = set() self.THREAD_LOCAL._modules_loaded = set()
try: try:
# Check the date and reload if needed: # Check the date and reload if needed:
@@ -119,7 +126,7 @@ class TrackImporter(object):
self._update_dates(name, globals, locals, fromlist, level) self._update_dates(name, globals, locals, fromlist, level)
return result return result
except Exception, e: except Exception, e:
raise # Don't hide something that went wrong raise # Don't hide something that went wrong
def _update_dates(self, name, globals, locals, fromlist, level): def _update_dates(self, name, globals, locals, fromlist, level):
""" """
@@ -143,7 +150,7 @@ class TrackImporter(object):
date = self._import_dates.get(file) date = self._import_dates.get(file)
new_date = None new_date = None
reload_mod = False reload_mod = False
mod_to_pack = False # Module turning into a package? (special case) mod_to_pack = False # Module turning into a package? (special case)
try: try:
new_date = os.path.getmtime(file) new_date = os.path.getmtime(file)
except: except:
@@ -154,13 +161,13 @@ class TrackImporter(object):
# Get path without file ext: # Get path without file ext:
file = os.path.splitext(file)[0] file = os.path.splitext(file)[0]
reload_mod = os.path.isdir(file) \ reload_mod = os.path.isdir(file) \
and os.path.isfile(file+self.PACKAGE_PATH_SUFFIX) and os.path.isfile(file + self.PACKAGE_PATH_SUFFIX)
mod_to_pack = reload_mod mod_to_pack = reload_mod
else: # Package turning into module? else: # Package turning into module?
file += ".py" file += ".py"
reload_mod = os.path.isfile(file) reload_mod = os.path.isfile(file)
if reload_mod: if reload_mod:
new_date = os.path.getmtime(file) # Refresh file date new_date = os.path.getmtime(file) # Refresh file date
if reload_mod or not date or new_date > date: if reload_mod or not date or new_date > date:
self._import_dates[file] = new_date self._import_dates[file] = new_date
if reload_mod or (date and new_date > date): if reload_mod or (date and new_date > date):
@@ -168,7 +175,7 @@ class TrackImporter(object):
if mod_to_pack: if mod_to_pack:
# Module turning into a package: # Module turning into a package:
mod_name = module.__name__ mod_name = module.__name__
del sys.modules[mod_name] # Delete the module del sys.modules[mod_name] # Delete the module
# Reload the module: # Reload the module:
NATIVE_IMPORTER(mod_name, globals, locals, [], level) NATIVE_IMPORTER(mod_name, globals, locals, [], level)
else: else:
@@ -182,7 +189,7 @@ class TrackImporter(object):
file = getattr(module, "__file__", None) file = getattr(module, "__file__", None)
if file: if file:
# Make path absolute if not: # Make path absolute if not:
file = os.path.splitext(file)[0]+".py" # Change .pyc for .py file = os.path.splitext(file)[0] + ".py" # Change .pyc for .py
if file.endswith(self.PACKAGE_PATH_SUFFIX): if file.endswith(self.PACKAGE_PATH_SUFFIX):
file = os.path.dirname(file) # Track dir for packages file = os.path.dirname(file) # Track dir for packages
return file return file
+2
View File
@@ -61,6 +61,7 @@ Example of usage:
### update one record ### update one record
>>> james.update_record(name='Jim') >>> james.update_record(name='Jim')
<Row {'id': 1, 'name': 'Jim'}>
### update multiple records by query ### update multiple records by query
>>> db(person.name.like('J%')).update(name='James') >>> db(person.name.like('J%')).update(name='James')
@@ -9390,6 +9391,7 @@ def test_all():
Update a single record Update a single record
>>> me.update_record(name=\"Max\") >>> me.update_record(name=\"Max\")
<Row {'name': 'Max', 'birth': datetime.date(1971, 12, 21), 'id': 2}>
>>> me.name >>> me.name
'Max' 'Max'
+6 -1
View File
@@ -17,6 +17,7 @@ import sys
logger = logging.getLogger("web2py") logger = logging.getLogger("web2py")
class Pipe(Queue.Queue): class Pipe(Queue.Queue):
def __init__(self, name, mode='r', *args, **kwargs): def __init__(self, name, mode='r', *args, **kwargs):
self.__name = name self.__name = name
@@ -52,6 +53,7 @@ pipe_out = Pipe('out')
debugger = pdb.Pdb(completekey=None, stdin=pipe_in, stdout=pipe_out,) debugger = pdb.Pdb(completekey=None, stdin=pipe_in, stdout=pipe_out,)
def set_trace(): def set_trace():
"breakpoint shortcut (like pdb)" "breakpoint shortcut (like pdb)"
logger.info("DEBUG: set_trace!") logger.info("DEBUG: set_trace!")
@@ -67,6 +69,7 @@ def stop_trace():
pipe_out.write(None) pipe_out.write(None)
#pipe_out.flush() #pipe_out.flush()
def communicate(command=None): def communicate(command=None):
"send command to debbuger, wait result" "send command to debbuger, wait result"
if command is not None: if command is not None:
@@ -91,6 +94,7 @@ from threading import RLock
interact_lock = RLock() interact_lock = RLock()
run_lock = RLock() run_lock = RLock()
def check_interaction(fn): def check_interaction(fn):
"Decorator to clean and prevent interaction when not available" "Decorator to clean and prevent interaction when not available"
def check_fn(self, *args, **kwargs): def check_fn(self, *args, **kwargs):
@@ -181,7 +185,8 @@ front_conn = qdb.QueuePipe("parent", parent_queue, child_queue)
child_conn = qdb.QueuePipe("child", child_queue, parent_queue) child_conn = qdb.QueuePipe("child", child_queue, parent_queue)
web_debugger = WebDebugger(front_conn) # frontend web_debugger = WebDebugger(front_conn) # frontend
qdb_debugger = qdb.Qdb(pipe=child_conn, redirect_stdio=False, skip=None) # backend qdb_debugger = qdb.Qdb(
pipe=child_conn, redirect_stdio=False, skip=None) # backend
dbg = qdb_debugger dbg = qdb_debugger
# enable getting context (stack, globals/locals) at interaction # enable getting context (stack, globals/locals) at interaction
+27 -24
View File
@@ -1,4 +1,5 @@
import codecs, encodings import codecs
import encodings
"""Caller will hand this library a buffer and ask it to either convert """Caller will hand this library a buffer and ask it to either convert
it or auto-detect the type. it or auto-detect the type.
@@ -9,16 +10,17 @@ Licensed under the PSF License
""" """
# None represents a potentially variable byte. "##" in the XML spec... # None represents a potentially variable byte. "##" in the XML spec...
autodetect_dict={ # bytepattern : ("name", autodetect_dict = { # bytepattern : ("name",
(0x00, 0x00, 0xFE, 0xFF) : ("ucs4_be"), (0x00, 0x00, 0xFE, 0xFF): ("ucs4_be"),
(0xFF, 0xFE, 0x00, 0x00) : ("ucs4_le"), (0xFF, 0xFE, 0x00, 0x00): ("ucs4_le"),
(0xFE, 0xFF, None, None) : ("utf_16_be"), (0xFE, 0xFF, None, None): ("utf_16_be"),
(0xFF, 0xFE, None, None) : ("utf_16_le"), (0xFF, 0xFE, None, None): ("utf_16_le"),
(0x00, 0x3C, 0x00, 0x3F) : ("utf_16_be"), (0x00, 0x3C, 0x00, 0x3F): ("utf_16_be"),
(0x3C, 0x00, 0x3F, 0x00) : ("utf_16_le"), (0x3C, 0x00, 0x3F, 0x00): ("utf_16_le"),
(0x3C, 0x3F, 0x78, 0x6D): ("utf_8"), (0x3C, 0x3F, 0x78, 0x6D): ("utf_8"),
(0x4C, 0x6F, 0xA7, 0x94): ("EBCDIC") (0x4C, 0x6F, 0xA7, 0x94): ("EBCDIC")
} }
def autoDetectXMLEncoding(buffer): def autoDetectXMLEncoding(buffer):
""" buffer -> encoding_name """ buffer -> encoding_name
@@ -31,14 +33,14 @@ def autoDetectXMLEncoding(buffer):
# buffer at once but otherwise we'd have to decode a character at # buffer at once but otherwise we'd have to decode a character at
# a time looking for the quote character...that's a pain # a time looking for the quote character...that's a pain
encoding = "utf_8" # according to the XML spec, this is the default encoding = "utf_8" # according to the XML spec, this is the default
# this code successively tries to refine the default # this code successively tries to refine the default
# whenever it fails to refine, it falls back to # whenever it fails to refine, it falls back to
# the last place encoding was set. # the last place encoding was set.
if len(buffer)>=4: if len(buffer) >= 4:
bytes = (byte1, byte2, byte3, byte4) = tuple(map(ord, buffer[0:4])) bytes = (byte1, byte2, byte3, byte4) = tuple(map(ord, buffer[0:4]))
enc_info = autodetect_dict.get(bytes, None) enc_info = autodetect_dict.get(bytes, None)
if not enc_info: # try autodetection again removing potentially if not enc_info: # try autodetection again removing potentially
# variable bytes # variable bytes
bytes = (byte1, byte2, None, None) bytes = (byte1, byte2, None, None)
enc_info = autodetect_dict.get(bytes) enc_info = autodetect_dict.get(bytes)
@@ -46,29 +48,30 @@ def autoDetectXMLEncoding(buffer):
enc_info = None enc_info = None
if enc_info: if enc_info:
encoding = enc_info # we've got a guess... these are encoding = enc_info # we've got a guess... these are
#the new defaults #the new defaults
# try to find a more precise encoding using xml declaration # try to find a more precise encoding using xml declaration
secret_decoder_ring = codecs.lookup(encoding)[1] secret_decoder_ring = codecs.lookup(encoding)[1]
(decoded,length) = secret_decoder_ring(buffer) (decoded, length) = secret_decoder_ring(buffer)
first_line = decoded.split("\n")[0] first_line = decoded.split("\n")[0]
if first_line and first_line.startswith(u"<?xml"): if first_line and first_line.startswith(u"<?xml"):
encoding_pos = first_line.find(u"encoding") encoding_pos = first_line.find(u"encoding")
if encoding_pos!=-1: if encoding_pos != -1:
# look for double quote # look for double quote
quote_pos=first_line.find('"', encoding_pos) quote_pos = first_line.find('"', encoding_pos)
if quote_pos==-1: # look for single quote if quote_pos == -1: # look for single quote
quote_pos=first_line.find("'", encoding_pos) quote_pos = first_line.find("'", encoding_pos)
if quote_pos>-1: if quote_pos > -1:
quote_char,rest=(first_line[quote_pos], quote_char, rest = (first_line[quote_pos],
first_line[quote_pos+1:]) first_line[quote_pos + 1:])
encoding=rest[:rest.find(quote_char)] encoding = rest[:rest.find(quote_char)]
return encoding return encoding
def decoder(buffer): def decoder(buffer):
encoding = autoDetectXMLEncoding(buffer) encoding = autoDetectXMLEncoding(buffer)
return buffer.decode(encoding).encode('utf8') return buffer.decode(encoding).encode('utf8')
+40 -24
View File
@@ -40,15 +40,17 @@ __all__ = [
'w2p_unpack_plugin', 'w2p_unpack_plugin',
'fix_newlines', 'fix_newlines',
'make_fake_file_like_object', 'make_fake_file_like_object',
] ]
def parse_version(version = "Version 1.99.0 (2011-09-19 08:23:26)"):
def parse_version(version="Version 1.99.0 (2011-09-19 08:23:26)"):
re_version = re.compile('[^\d]+ (\d+)\.(\d+)\.(\d+)\s*\((?P<datetime>.+?)\)\s*(?P<type>[a-z]+)?') re_version = re.compile('[^\d]+ (\d+)\.(\d+)\.(\d+)\s*\((?P<datetime>.+?)\)\s*(?P<type>[a-z]+)?')
m = re_version.match(version) m = re_version.match(version)
a,b,c = int(m.group(1)),int(m.group(2)),int(m.group(3)), a, b, c = int(m.group(1)), int(m.group(2)), int(m.group(3)),
s = m.group('type') or 'dev' s = m.group('type') or 'dev'
d = datetime.datetime.strptime(m.group('datetime'),'%Y-%m-%d %H:%M:%S') d = datetime.datetime.strptime(m.group('datetime'), '%Y-%m-%d %H:%M:%S')
return (a,b,c,d,s) return (a, b, c, d, s)
def read_file(filename, mode='r'): def read_file(filename, mode='r'):
"returns content from filename, making sure to close the file explicitly on exit." "returns content from filename, making sure to close the file explicitly on exit."
@@ -58,6 +60,7 @@ def read_file(filename, mode='r'):
finally: finally:
f.close() f.close()
def write_file(filename, value, mode='w'): def write_file(filename, value, mode='w'):
"writes <value> to filename, making sure to close the file explicitly on exit." "writes <value> to filename, making sure to close the file explicitly on exit."
f = open(filename, mode) f = open(filename, mode)
@@ -66,25 +69,28 @@ def write_file(filename, value, mode='w'):
finally: finally:
f.close() f.close()
def readlines_file(filename, mode='r'): def readlines_file(filename, mode='r'):
"applies .split('\n') to the output of read_file()" "applies .split('\n') to the output of read_file()"
return read_file(filename, mode).split('\n') return read_file(filename, mode).split('\n')
def mktree(path): def mktree(path):
head,tail =os.path.split(path) head, tail = os.path.split(path)
if head: if head:
if tail: mktree(head) if tail:
mktree(head)
if not os.path.exists(head): if not os.path.exists(head):
os.mkdir(head) os.mkdir(head)
def listdir( def listdir(
path, path,
expression='^.+$', expression='^.+$',
drop=True, drop=True,
add_dirs=False, add_dirs=False,
sort=True, sort=True,
): ):
""" """
like os.listdir() but you can specify a regex pattern to filter files. like os.listdir() but you can specify a regex pattern to filter files.
if add_dirs is True, the returned items will have the full path. if add_dirs is True, the returned items will have the full path.
@@ -115,7 +121,7 @@ def listdir(
def recursive_unlink(f): def recursive_unlink(f):
if os.path.isdir(f): if os.path.isdir(f):
for s in os.listdir(f): for s in os.listdir(f):
recursive_unlink(os.path.join(f,s)) recursive_unlink(os.path.join(f, s))
os.rmdir(f) os.rmdir(f)
elif os.path.isfile(f): elif os.path.isfile(f):
os.unlink(f) os.unlink(f)
@@ -130,7 +136,7 @@ def cleanpath(path):
items = path.split('.') items = path.split('.')
if len(items) > 1: if len(items) > 1:
path = re.sub('[^\w\.]+', '_', '_'.join(items[:-1]) + '.' path = re.sub('[^\w\.]+', '_', '_'.join(items[:-1]) + '.'
+ ''.join(items[-1:])) + ''.join(items[-1:]))
else: else:
path = re.sub('[^\w\.]+', '_', ''.join(items[-1:])) path = re.sub('[^\w\.]+', '_', ''.join(items[-1:]))
return path return path
@@ -161,7 +167,7 @@ def _extractall(filename, path='.', members=None):
try: try:
os.makedirs(os.path.join(path, os.makedirs(os.path.join(path,
tarinfo.name), 0777) tarinfo.name), 0777)
except EnvironmentError: except EnvironmentError:
pass pass
directories.append(tarinfo) directories.append(tarinfo)
@@ -187,7 +193,6 @@ def _extractall(filename, path='.', members=None):
else: else:
self._dbg(1, 'tarfile: %s' % e) self._dbg(1, 'tarfile: %s' % e)
_cls = TarFile _cls = TarFile
else: else:
_cls = tarfile.TarFile _cls = tarfile.TarFile
@@ -197,6 +202,7 @@ def _extractall(filename, path='.', members=None):
tar.close() tar.close()
return ret return ret
def tar(file, dir, expression='^.+$'): def tar(file, dir, expression='^.+$'):
""" """
tars dir into file, only tars file that match expression tars dir into file, only tars file that match expression
@@ -209,6 +215,7 @@ def tar(file, dir, expression='^.+$'):
finally: finally:
tar.close() tar.close()
def untar(file, dir): def untar(file, dir):
""" """
untar file into dir untar file into dir
@@ -232,6 +239,7 @@ def w2p_pack(filename, path, compiled=False):
tarfp.close() tarfp.close()
os.unlink(tarname) os.unlink(tarname)
def w2p_unpack(filename, path, delete_tar=True): def w2p_unpack(filename, path, delete_tar=True):
filename = abspath(filename) filename = abspath(filename)
path = abspath(path) path = abspath(path)
@@ -261,16 +269,18 @@ def w2p_pack_plugin(filename, path, plugin_name):
filename = abspath(filename) filename = abspath(filename)
path = abspath(path) path = abspath(path)
if not filename.endswith('web2py.plugin.%s.w2p' % plugin_name): if not filename.endswith('web2py.plugin.%s.w2p' % plugin_name):
raise Exception, "Not a web2py plugin name" raise Exception("Not a web2py plugin name")
plugin_tarball = tarfile.open(filename, 'w:gz') plugin_tarball = tarfile.open(filename, 'w:gz')
try: try:
app_dir = path app_dir = path
while app_dir[-1]=='/': while app_dir[-1] == '/':
app_dir = app_dir[:-1] app_dir = app_dir[:-1]
files1=glob.glob(os.path.join(app_dir,'*/plugin_%s.*' % plugin_name)) files1 = glob.glob(
files2=glob.glob(os.path.join(app_dir,'*/plugin_%s/*' % plugin_name)) os.path.join(app_dir, '*/plugin_%s.*' % plugin_name))
for file in files1+files2: files2 = glob.glob(
plugin_tarball.add(file, arcname=file[len(app_dir)+1:]) os.path.join(app_dir, '*/plugin_%s/*' % plugin_name))
for file in files1 + files2:
plugin_tarball.add(file, arcname=file[len(app_dir) + 1:])
finally: finally:
plugin_tarball.close() plugin_tarball.close()
@@ -279,8 +289,8 @@ def w2p_unpack_plugin(filename, path, delete_tar=True):
filename = abspath(filename) filename = abspath(filename)
path = abspath(path) path = abspath(path)
if not os.path.basename(filename).startswith('web2py.plugin.'): if not os.path.basename(filename).startswith('web2py.plugin.'):
raise Exception, "Not a web2py plugin" raise Exception("Not a web2py plugin")
w2p_unpack(filename,path,delete_tar) w2p_unpack(filename, path, delete_tar)
def tar_compiled(file, dir, expression='^.+$'): def tar_compiled(file, dir, expression='^.+$'):
@@ -306,6 +316,7 @@ def tar_compiled(file, dir, expression='^.+$'):
tar.add(filename, file, False) tar.add(filename, file, False)
tar.close() tar.close()
def up(path): def up(path):
return os.path.dirname(os.path.normpath(path)) return os.path.dirname(os.path.normpath(path))
@@ -317,13 +328,13 @@ def get_session(request, other_application='admin'):
try: try:
session_id = request.cookies['session_id_' + other_application].value session_id = request.cookies['session_id_' + other_application].value
osession = storage.load_storage(os.path.join( osession = storage.load_storage(os.path.join(
up(request.folder), other_application, 'sessions', session_id)) up(request.folder), other_application, 'sessions', session_id))
except Exception, e: except Exception, e:
osession = storage.Storage() osession = storage.Storage()
return osession return osession
def check_credentials(request, other_application='admin', expiration = 60*60): def check_credentials(request, other_application='admin', expiration=60 * 60):
""" checks that user is authorized to access other_application""" """ checks that user is authorized to access other_application"""
if request.env.web2py_runtime_gae: if request.env.web2py_runtime_gae:
from google.appengine.api import users from google.appengine.api import users
@@ -338,6 +349,7 @@ def check_credentials(request, other_application='admin', expiration = 60*60):
s = get_session(request, other_application) s = get_session(request, other_application)
return (s.authorized and s.last_time and s.last_time > dt) return (s.authorized and s.last_time and s.last_time > dt)
def fix_newlines(path): def fix_newlines(path):
regex = re.compile(r'''(\r regex = re.compile(r'''(\r
|\r| |\r|
@@ -348,12 +360,13 @@ def fix_newlines(path):
if wdata != rdata: if wdata != rdata:
write_file(filename, wdata, 'wb') write_file(filename, wdata, 'wb')
def copystream( def copystream(
src, src,
dest, dest,
size, size,
chunk_size=10 ** 5, chunk_size=10 ** 5,
): ):
""" """
this is here because I think there is a bug in shutil.copyfileobj this is here because I think there is a bug in shutil.copyfileobj
""" """
@@ -379,13 +392,16 @@ def make_fake_file_like_object():
class LogFile(object): class LogFile(object):
def write(self, value): def write(self, value):
pass pass
def close(self): def close(self):
pass pass
return LogFile() return LogFile()
from settings import global_settings # we need to import settings here because from settings import global_settings # we need to import settings here because
# settings imports fileutils too # settings imports fileutils too
def abspath(*relpath, **base): def abspath(*relpath, **base):
"convert relative path to absolute path based (by default) on applications_parent" "convert relative path to absolute path based (by default) on applications_parent"
path = os.path.join(*relpath) path = os.path.join(*relpath)
+113 -87
View File
@@ -61,6 +61,7 @@ less_template = '<link href="%s" rel="stylesheet/less" type="text/css" />'
css_inline = '<style type="text/css">\n%s\n</style>' css_inline = '<style type="text/css">\n%s\n</style>'
js_inline = '<script type="text/javascript">\n%s\n</script>' js_inline = '<script type="text/javascript">\n%s\n</script>'
class Request(Storage): class Request(Storage):
""" """
@@ -82,7 +83,7 @@ class Request(Storage):
def __init__(self): def __init__(self):
Storage.__init__(self) Storage.__init__(self)
self.wsgi = Storage() # hooks to environ and start_response self.wsgi = Storage() # hooks to environ and start_response
self.env = Storage() self.env = Storage()
self.cookies = Cookie.SimpleCookie() self.cookies = Cookie.SimpleCookie()
self.get_vars = Storage() self.get_vars = Storage()
@@ -116,8 +117,8 @@ class Request(Storage):
if session: if session:
session._user_agent = user_agent session._user_agent = user_agent
user_agent = Storage(user_agent) user_agent = Storage(user_agent)
for key,value in user_agent.items(): for key, value in user_agent.items():
if isinstance(value,dict): if isinstance(value, dict):
user_agent[key] = Storage(value) user_agent[key] = Storage(value)
return user_agent return user_agent
@@ -133,25 +134,26 @@ class Request(Storage):
current.session.secure() current.session.secure()
def restful(self): def restful(self):
def wrapper(action,self=self): def wrapper(action, self=self):
def f(_action=action,_self=self,*a,**b): def f(_action=action, _self=self, *a, **b):
self.is_restful = True self.is_restful = True
method = _self.env.request_method method = _self.env.request_method
if len(_self.args) and '.' in _self.args[-1]: if len(_self.args) and '.' in _self.args[-1]:
_self.args[-1],_self.extension = _self.args[-1].rsplit('.',1) _self.args[-
1], _self.extension = _self.args[-1].rsplit('.', 1)
current.response.headers['Content-Type'] = \ current.response.headers['Content-Type'] = \
contenttype(_self.extension.lower()) contenttype(_self.extension.lower())
if not method in ['GET','POST','DELETE','PUT']: if not method in ['GET', 'POST', 'DELETE', 'PUT']:
raise HTTP(400,"invalid method") raise HTTP(400, "invalid method")
rest_action = _action().get(method,None) rest_action = _action().get(method, None)
if not rest_action: if not rest_action:
raise HTTP(400,"method not supported") raise HTTP(400, "method not supported")
try: try:
return rest_action(*_self.args,**_self.vars) return rest_action(*_self.args, **_self.vars)
except TypeError, e: except TypeError, e:
exc_type, exc_value, exc_traceback = sys.exc_info() exc_type, exc_value, exc_traceback = sys.exc_info()
if len(traceback.extract_tb(exc_traceback))==1: if len(traceback.extract_tb(exc_traceback)) == 1:
raise HTTP(400,"invalid arguments") raise HTTP(400, "invalid arguments")
else: else:
raise e raise e
f.__doc__ = action.__doc__ f.__doc__ = action.__doc__
@@ -180,8 +182,8 @@ class Response(Storage):
self.meta = Storage() # used by web2py_ajax.html self.meta = Storage() # used by web2py_ajax.html
self.menu = [] # used by the default view layout self.menu = [] # used by the default view layout
self.files = [] # used by web2py_ajax.html self.files = [] # used by web2py_ajax.html
self.generic_patterns = [] # patterns to allow generic views self.generic_patterns = [] # patterns to allow generic views
self.delimiters = ('{{','}}') self.delimiters = ('{{', '}}')
self._vars = None self._vars = None
self._caller = lambda f: f() self._caller = lambda f: f()
self._view_environment = None self._view_environment = None
@@ -197,7 +199,8 @@ class Response(Storage):
def render(self, *a, **b): def render(self, *a, **b):
from compileapp import run_view_in from compileapp import run_view_in
if len(a) > 2: if len(a) > 2:
raise SyntaxError, 'Response.render can be called with two arguments, at most' raise SyntaxError(
'Response.render can be called with two arguments, at most')
elif len(a) == 2: elif len(a) == 2:
(view, self._vars) = (a[0], a[1]) (view, self._vars) = (a[0], a[1])
elif len(a) == 1 and isinstance(a[0], str): elif len(a) == 1 and isinstance(a[0], str):
@@ -225,9 +228,9 @@ class Response(Storage):
def include_meta(self): def include_meta(self):
s = '\n'.join( s = '\n'.join(
'<meta name="%s" content="%s" />\n' % (k,xmlescape(v)) '<meta name="%s" content="%s" />\n' % (k, xmlescape(v))
for k,v in (self.meta or {}).iteritems()) for k, v in (self.meta or {}).iteritems())
self.write(s,escape=False) self.write(s, escape=False)
def include_files(self, extensions=None): def include_files(self, extensions=None):
@@ -256,44 +259,52 @@ class Response(Storage):
# cache for 5 minutes by default # cache for 5 minutes by default
key = hashlib.md5(repr(files)).hexdigest() key = hashlib.md5(repr(files)).hexdigest()
cache = self.cache_includes or (current.cache.ram, 60*5) cache = self.cache_includes or (current.cache.ram, 60 * 5)
def call_minify(files=files): def call_minify(files=files):
return minify.minify(files, return minify.minify(files,
URL('static','temp'), URL('static', 'temp'),
current.request.folder, current.request.folder,
self.optimize_css, self.optimize_css,
self.optimize_js) self.optimize_js)
if cache: if cache:
cache_model, time_expire = cache cache_model, time_expire = cache
files = cache_model('response.files.minified/'+key, files = cache_model('response.files.minified/' + key,
call_minify, call_minify,
time_expire) time_expire)
else: else:
files = call_minify() files = call_minify()
s = '' s = ''
for item in files: for item in files:
if isinstance(item,str): if isinstance(item, str):
f = item.lower().split('?')[0] f = item.lower().split('?')[0]
if self.static_version: if self.static_version:
item = item.replace('/static/', '/static/_%s/' % self.static_version, 1) item = item.replace(
if f.endswith('.css'): s += css_template % item '/static/', '/static/_%s/' % self.static_version, 1)
elif f.endswith('.js'): s += js_template % item if f.endswith('.css'):
elif f.endswith('.coffee'): s += coffee_template % item s += css_template % item
elif f.endswith('.less'): s += less_template % item elif f.endswith('.js'):
elif isinstance(item,(list,tuple)): s += js_template % item
elif f.endswith('.coffee'):
s += coffee_template % item
elif f.endswith('.less'):
s += less_template % item
elif isinstance(item, (list, tuple)):
f = item[0] f = item[0]
if f=='css:inline': s += css_inline % item[1] if f == 'css:inline':
elif f=='js:inline': s += js_inline % item[1] s += css_inline % item[1]
elif f == 'js:inline':
s += js_inline % item[1]
self.write(s, escape=False) self.write(s, escape=False)
def stream( def stream(
self, self,
stream, stream,
chunk_size = DEFAULT_CHUNK_SIZE, chunk_size=DEFAULT_CHUNK_SIZE,
request=None, request=None,
attachment=False, attachment=False,
filename=None, filename=None,
): ):
""" """
if a controller function:: if a controller function::
@@ -347,7 +358,7 @@ class Response(Storage):
env = request.env env = request.env
# Internet Explorer < 9.0 will not allow downloads over SSL unless caching is enabled # Internet Explorer < 9.0 will not allow downloads over SSL unless caching is enabled
if request.is_https and isinstance(env.http_user_agent,str) and \ if request.is_https and isinstance(env.http_user_agent, str) and \
not re.search(r'Opera', env.http_user_agent) and \ not re.search(r'Opera', env.http_user_agent) and \
re.search(r'MSIE [5-8][^0-9]', env.http_user_agent): re.search(r'MSIE [5-8][^0-9]', env.http_user_agent):
headers['Pragma'] = 'cache' headers['Pragma'] = 'cache'
@@ -359,7 +370,7 @@ class Response(Storage):
wrapped = streamer(stream, chunk_size=chunk_size) wrapped = streamer(stream, chunk_size=chunk_size)
return wrapped return wrapped
def download(self, request, db, chunk_size = DEFAULT_CHUNK_SIZE, attachment=True): def download(self, request, db, chunk_size=DEFAULT_CHUNK_SIZE, attachment=True):
""" """
example of usage in controller:: example of usage in controller::
@@ -373,7 +384,7 @@ class Response(Storage):
raise HTTP(404) raise HTTP(404)
name = request.args[-1] name = request.args[-1]
items = re.compile('(?P<table>.*?)\.(?P<field>.*?)\..*')\ items = re.compile('(?P<table>.*?)\.(?P<field>.*?)\..*')\
.match(name) .match(name)
if not items: if not items:
raise HTTP(404) raise HTTP(404)
(t, f) = (items.group('table'), items.group('field')) (t, f) = (items.group('table'), items.group('field'))
@@ -389,9 +400,8 @@ class Response(Storage):
'attachment; filename=%s' % filename 'attachment; filename=%s' % filename
return self.stream(stream, chunk_size=chunk_size, request=request) return self.stream(stream, chunk_size=chunk_size, request=request)
def json(self, data, default=None): def json(self, data, default=None):
return json(data, default = default or custom_json) return json(data, default=default or custom_json)
def xmlrpc(self, request, methods): def xmlrpc(self, request, methods):
""" """
@@ -408,7 +418,8 @@ class Response(Storage):
the add function. Example:: the add function. Example::
import xmlrpclib import xmlrpclib
connection = xmlrpclib.ServerProxy('http://hostname/app/contr/func') connection = xmlrpclib.ServerProxy(
'http://hostname/app/contr/func')
print connection.add(3, 4) print connection.add(3, 4)
""" """
@@ -418,14 +429,14 @@ class Response(Storage):
def toolbar(self): def toolbar(self):
from html import DIV, SCRIPT, BEAUTIFY, TAG, URL, A from html import DIV, SCRIPT, BEAUTIFY, TAG, URL, A
BUTTON = TAG.button BUTTON = TAG.button
admin = URL("admin","default","design", admin = URL("admin", "default", "design",
args=current.request.application) args=current.request.application)
from gluon.dal import THREAD_LOCAL from gluon.dal import THREAD_LOCAL
if hasattr(THREAD_LOCAL,'instances'): if hasattr(THREAD_LOCAL, 'instances'):
dbstats = [TABLE(*[TR(PRE(row[0]),'%.2fms' % (row[1]*1000)) \ dbstats = [TABLE(*[TR(PRE(row[0]), '%.2fms' % (row[1] * 1000))
for row in i.db._timings]) \ for row in i.db._timings])
for i in THREAD_LOCAL.instances] for i in THREAD_LOCAL.instances]
dbtables = dict([(regex_nopasswd.sub('******',i.uri), dbtables = dict([(regex_nopasswd.sub('******', i.uri),
{'defined': {'defined':
sorted(list(set(i.db.tables) - sorted(list(set(i.db.tables) -
set(i.db._LAZY_TABLES.keys()))) or set(i.db._LAZY_TABLES.keys()))) or
@@ -434,25 +445,35 @@ class Response(Storage):
'[no lazy tables]'}) '[no lazy tables]'})
for i in THREAD_LOCAL.instances]) for i in THREAD_LOCAL.instances])
else: else:
dbstats = [] # if no db or on GAE dbstats = [] # if no db or on GAE
dbtables = {} dbtables = {}
u = web2py_uuid() u = web2py_uuid()
backtotop = A('Back to top', _href="#totop-%s" % u) backtotop = A('Back to top', _href="#totop-%s" % u)
return DIV( return DIV(
BUTTON('design',_onclick="document.location='%s'" % admin), BUTTON('design', _onclick="document.location='%s'" % admin),
BUTTON('request',_onclick="jQuery('#request-%s').slideToggle()"%u), BUTTON('request',
BUTTON('response',_onclick="jQuery('#response-%s').slideToggle()"%u), _onclick="jQuery('#request-%s').slideToggle()" % u),
BUTTON('session',_onclick="jQuery('#session-%s').slideToggle()"%u), BUTTON('response',
BUTTON('db tables',_onclick="jQuery('#db-tables-%s').slideToggle()"%u), _onclick="jQuery('#response-%s').slideToggle()" % u),
BUTTON('db stats',_onclick="jQuery('#db-stats-%s').slideToggle()"%u), BUTTON('session',
DIV(BEAUTIFY(current.request), backtotop,_class="hidden",_id="request-%s"%u), _onclick="jQuery('#session-%s').slideToggle()" % u),
DIV(BEAUTIFY(current.session), backtotop, _class="hidden",_id="session-%s"%u), BUTTON('db tables',
DIV(BEAUTIFY(current.response), backtotop, _class="hidden",_id="response-%s"%u), _onclick="jQuery('#db-tables-%s').slideToggle()" % u),
DIV(BEAUTIFY(dbtables), backtotop, _class="hidden",_id="db-tables-%s"%u), BUTTON('db stats',
DIV(BEAUTIFY(dbstats), backtotop, _class="hidden",_id="db-stats-%s"%u), _onclick="jQuery('#db-stats-%s').slideToggle()" % u),
SCRIPT("jQuery('.hidden').hide()") DIV(BEAUTIFY(current.request), backtotop,
,_id="totop-%s" % u _class="hidden", _id="request-%s" % u),
) DIV(BEAUTIFY(current.session), backtotop,
_class="hidden", _id="session-%s" % u),
DIV(BEAUTIFY(current.response), backtotop,
_class="hidden", _id="response-%s" % u),
DIV(BEAUTIFY(dbtables), backtotop, _class="hidden",
_id="db-tables-%s" % u),
DIV(BEAUTIFY(
dbstats), backtotop, _class="hidden", _id="db-stats-%s" % u),
SCRIPT("jQuery('.hidden').hide()"), _id="totop-%s" % u
)
class Session(Storage): class Session(Storage):
@@ -468,19 +489,21 @@ class Session(Storage):
tablename='web2py_session', tablename='web2py_session',
masterapp=None, masterapp=None,
migrate=True, migrate=True,
separate = None, separate=None,
check_client=False, check_client=False,
cookie_key=None, cookie_key=None,
cookie_expires=None, cookie_expires=None,
compression_level=None compression_level=None
): ):
""" """
separate can be separate=lambda(session_name): session_name[-2:] separate can be separate=lambda(session_name): session_name[-2:]
and it is used to determine a session prefix. and it is used to determine a session prefix.
separate can be True and it is set to session_name[-2:] separate can be True and it is set to session_name[-2:]
""" """
if request is None: request = current.request if request is None:
if response is None: response = current.response request = current.request
if response is None:
response = current.response
if separate == True: if separate == True:
separate = lambda session_name: session_name[-2:] separate = lambda session_name: session_name[-2:]
self._unlock(response) self._unlock(response)
@@ -512,7 +535,8 @@ class Session(Storage):
response.session_cookie_key = cookie_key response.session_cookie_key = cookie_key
response.session_cookie_compression_level = compression_level response.session_cookie_compression_level = compression_level
if session_cookie_data: if session_cookie_data:
data = secure_loads(session_cookie_data,cookie_key,compression_level=compression_level) data = secure_loads(session_cookie_data, cookie_key,
compression_level=compression_level)
if data: if data:
self.update(data) self.update(data)
# else if we are supposed to use file based sessions # else if we are supposed to use file based sessions
@@ -527,7 +551,7 @@ class Session(Storage):
if regex_session_id.match(response.session_id): if regex_session_id.match(response.session_id):
response.session_filename = \ response.session_filename = \
os.path.join(up(request.folder), masterapp, os.path.join(up(request.folder), masterapp,
'sessions', response.session_id) 'sessions', response.session_id)
else: else:
response.session_id = None response.session_id = None
# do not try load the data from file is these was data in cookie # do not try load the data from file is these was data in cookie
@@ -543,8 +567,8 @@ class Session(Storage):
response.session_file.seek(0) response.session_file.seek(0)
oc = response.session_filename.split('/')[-1]\ oc = response.session_filename.split('/')[-1]\
.split('-')[0] .split('-')[0]
if check_client and client!=oc: if check_client and client != oc:
raise Exception, "cookie attack" raise Exception("cookie attack")
finally: finally:
pass pass
#This causes admin login to break. Must find out why. #This causes admin login to break. Must find out why.
@@ -557,7 +581,7 @@ class Session(Storage):
if separate: if separate:
prefix = separate(response.session_id) prefix = separate(response.session_id)
response.session_id = '%s/%s' % \ response.session_id = '%s/%s' % \
(prefix,response.session_id) (prefix, response.session_id)
response.session_filename = \ response.session_filename = \
os.path.join(up(request.folder), masterapp, os.path.join(up(request.folder), masterapp,
'sessions', response.session_id) 'sessions', response.session_id)
@@ -585,25 +609,25 @@ class Session(Storage):
Field('locked', 'boolean', default=False), Field('locked', 'boolean', default=False),
Field('client_ip', length=64), Field('client_ip', length=64),
Field('created_datetime', 'datetime', Field('created_datetime', 'datetime',
default=request.now), default=request.now),
Field('modified_datetime', 'datetime'), Field('modified_datetime', 'datetime'),
Field('unique_key', length=64), Field('unique_key', length=64),
Field('session_data', 'blob'), Field('session_data', 'blob'),
migrate=table_migrate, migrate=table_migrate,
) )
table = db[tname] # to allow for lazy table table = db[tname] # to allow for lazy table
try: try:
# Get session data out of the database # Get session data out of the database
(record_id, unique_key) = response.session_id.split(':') (record_id, unique_key) = response.session_id.split(':')
if record_id == '0': if record_id == '0':
raise Exception, 'record_id == 0' raise Exception('record_id == 0')
# Select from database # Select from database
if not session_cookie_data: if not session_cookie_data:
rows = db(table.id == record_id).select() rows = db(table.id == record_id).select()
# Make sure the session data exists in the database # Make sure the session data exists in the database
if len(rows) == 0 or rows[0].unique_key != unique_key: if len(rows) == 0 or rows[0].unique_key != unique_key:
raise Exception, 'No record' raise Exception('No record')
# rows[0].update_record(locked=True) # rows[0].update_record(locked=True)
# Unpickle the data # Unpickle the data
session_data = cPickle.loads(rows[0].session_data) session_data = cPickle.loads(rows[0].session_data)
@@ -620,7 +644,8 @@ class Session(Storage):
rcookies[response.session_id_name] = response.session_id rcookies[response.session_id_name] = response.session_id
rcookies[response.session_id_name]['path'] = '/' rcookies[response.session_id_name]['path'] = '/'
if cookie_expires: if cookie_expires:
rcookies[response.session_id_name]['expires'] = cookie_expires.strftime(FMT) rcookies[response.session_id_name][
'expires'] = cookie_expires.strftime(FMT)
# if not cookie_key, but session_data_name in cookies # if not cookie_key, but session_data_name in cookies
# expire session_data_name from cookies # expire session_data_name from cookies
if session_cookie_data: if session_cookie_data:
@@ -637,10 +662,10 @@ class Session(Storage):
self._start_timestamp = datetime.datetime.today() self._start_timestamp = datetime.datetime.today()
return True return True
def is_expired(self, seconds = 3600): def is_expired(self, seconds=3600):
now = datetime.datetime.today() now = datetime.datetime.today()
if not self._last_timestamp or \ if not self._last_timestamp or \
self._last_timestamp + datetime.timedelta(seconds = seconds) > now: self._last_timestamp + datetime.timedelta(seconds=seconds) > now:
self._last_timestamp = now self._last_timestamp = now
return False return False
else: else:
@@ -654,12 +679,13 @@ class Session(Storage):
self._forget = True self._forget = True
def _try_store_in_cookie(self, request, response): def _try_store_in_cookie(self, request, response):
if response.session_storage_type!='cookie': return False if response.session_storage_type != 'cookie':
return False
name = response.session_data_name name = response.session_data_name
value = secure_dumps(dict(self),response.session_cookie_key, compression_level=response.session_cookie_compression_level) value = secure_dumps(dict(self), response.session_cookie_key, compression_level=response.session_cookie_compression_level)
expires = response.session_cookie_expires expires = response.session_cookie_expires
rcookies = response.cookies rcookies = response.cookies
rcookies.pop(name,None) rcookies.pop(name, None)
rcookies[name] = value rcookies[name] = value
rcookies[name]['path'] = '/' rcookies[name]['path'] = '/'
if expires: if expires:
@@ -667,7 +693,7 @@ class Session(Storage):
return True return True
def _unchanged(self): def _unchanged(self):
previous_session_hash = self.pop('_session_hash',None) previous_session_hash = self.pop('_session_hash', None)
if not previous_session_hash and not \ if not previous_session_hash and not \
any(value is not None for value in self.itervalues()): any(value is not None for value in self.itervalues()):
return True return True
@@ -683,7 +709,7 @@ class Session(Storage):
# don't save if file-based sessions, # don't save if file-based sessions,
# no session id, or session being forgotten # no session id, or session being forgotten
# or no changes to session # or no changes to session
if response.session_storage_type!='db' or not response.session_id \ if response.session_storage_type != 'db' or not response.session_id \
or self._forget or self._unchanged(): or self._forget or self._unchanged():
return False return False
@@ -692,7 +718,7 @@ class Session(Storage):
unique_key = response.session_db_unique_key unique_key = response.session_db_unique_key
dd = dict(locked=False, dd = dict(locked=False,
client_ip=request.client.replace(':','.'), client_ip=request.client.replace(':', '.'),
modified_datetime=request.now, modified_datetime=request.now,
session_data=cPickle.dumps(dict(self)), session_data=cPickle.dumps(dict(self)),
unique_key=unique_key) unique_key=unique_key)
@@ -708,11 +734,11 @@ class Session(Storage):
def _try_store_in_cookie_or_file(self, request, response): def _try_store_in_cookie_or_file(self, request, response):
return \ return \
self._try_store_in_cookie(request,response) or \ self._try_store_in_cookie(request, response) or \
self._try_store_in_file(request,response) self._try_store_in_file(request, response)
def _try_store_in_file(self, request, response): def _try_store_in_file(self, request, response):
if response.session_storage_type!='file': if response.session_storage_type != 'file':
return False return False
try: try:
@@ -740,7 +766,7 @@ class Session(Storage):
try: try:
portalocker.unlock(response.session_file) portalocker.unlock(response.session_file)
response.session_locked = False response.session_locked = False
except: ### this should never happen but happens in Windows except: # this should never happen but happens in Windows
pass pass
def _close(self, response): def _close(self, response):
+41 -39
View File
@@ -24,7 +24,7 @@ class Highlighter(object):
mode, mode,
link=None, link=None,
styles=None, styles=None,
): ):
""" """
Initialise highlighter: Initialise highlighter:
mode = language (PYTHON, WEB2PY,C, CPP, HTML, HTML_PLAIN) mode = language (PYTHON, WEB2PY,C, CPP, HTML, HTML_PLAIN)
@@ -50,7 +50,7 @@ class Highlighter(object):
elif mode == 'HTML': elif mode == 'HTML':
self.suppress_tokens = [] self.suppress_tokens = []
else: else:
raise SyntaxError, 'Unknown mode: %s' % mode raise SyntaxError('Unknown mode: %s' % mode)
self.mode = mode self.mode = mode
def c_tokenizer( def c_tokenizer(
@@ -58,7 +58,7 @@ class Highlighter(object):
token, token,
match, match,
style, style,
): ):
""" """
Callback for C specific highlighting. Callback for C specific highlighting.
""" """
@@ -72,7 +72,7 @@ class Highlighter(object):
token, token,
match, match,
style, style,
): ):
""" """
Callback for python specific highlighting. Callback for python specific highlighting.
""" """
@@ -93,10 +93,10 @@ class Highlighter(object):
(url, style) = style[5:].split(';', 1) (url, style) = style[5:].split(';', 1)
if url == 'None' or url == '': if url == 'None' or url == '':
self.output.append('<span style="%s">%s</span>' self.output.append('<span style="%s">%s</span>'
% (style, value)) % (style, value))
else: else:
self.output.append('<a href="%s%s" style="%s">%s</a>' self.output.append('<a href="%s%s" style="%s">%s</a>'
% (url, value, style, value)) % (url, value, style, value))
else: else:
self.change_style(token, style) self.change_style(token, style)
self.output.append(value) self.output.append(value)
@@ -109,7 +109,7 @@ class Highlighter(object):
token, token,
match, match,
style, style,
): ):
""" """
Callback for HTML specific highlighting. Callback for HTML specific highlighting.
""" """
@@ -135,51 +135,51 @@ class Highlighter(object):
re.compile(r'0x[0-9a-fA-F]+|[+-]?\d+(\.\d+)?([eE][+-]\d+)?|\d+'), re.compile(r'0x[0-9a-fA-F]+|[+-]?\d+(\.\d+)?([eE][+-]\d+)?|\d+'),
'color: red'), 'color: red'),
('KEYWORD', re.compile(r'(sizeof|int|long|short|char|void|' ('KEYWORD', re.compile(r'(sizeof|int|long|short|char|void|'
+ r'signed|unsigned|float|double|' + r'signed|unsigned|float|double|'
+ r'goto|break|return|continue|asm|' + r'goto|break|return|continue|asm|'
+ r'case|default|if|else|switch|while|for|do|' + r'case|default|if|else|switch|while|for|do|'
+ r'struct|union|enum|typedef|' + r'struct|union|enum|typedef|'
+ r'static|register|auto|volatile|extern|const)(?![a-zA-Z0-9_])'), + r'static|register|auto|volatile|extern|const)(?![a-zA-Z0-9_])'),
'color:#185369; font-weight: bold'), 'color:#185369; font-weight: bold'),
('CPPKEYWORD', ('CPPKEYWORD',
re.compile(r'(class|private|protected|public|template|new|delete|' re.compile(r'(class|private|protected|public|template|new|delete|'
+ r'this|friend|using|inline|export|bool|throw|try|catch|' + r'this|friend|using|inline|export|bool|throw|try|catch|'
+ r'operator|typeid|virtual)(?![a-zA-Z0-9_])'), + r'operator|typeid|virtual)(?![a-zA-Z0-9_])'),
'color: blue; font-weight: bold'), 'color: blue; font-weight: bold'),
('STRING', re.compile(r'r?u?\'(.*?)(?<!\\)\'|"(.*?)(?<!\\)"'), ('STRING', re.compile(r'r?u?\'(.*?)(?<!\\)\'|"(.*?)(?<!\\)"'),
'color: #FF9966'), 'color: #FF9966'),
('IDENTIFIER', re.compile(r'[a-zA-Z_][a-zA-Z0-9_]*'), ('IDENTIFIER', re.compile(r'[a-zA-Z_][a-zA-Z0-9_]*'),
None), None),
('WHITESPACE', re.compile(r'[ \r\n]+'), 'Keep'), ('WHITESPACE', re.compile(r'[ \r\n]+'), 'Keep'),
)), )),
'PYTHON': (python_tokenizer, ( 'PYTHON': (python_tokenizer, (
('GOTOHTML', re.compile(r'\}\}'), 'color: red'), ('GOTOHTML', re.compile(r'\}\}'), 'color: red'),
('PUNC', re.compile(r'[-+*!|&^~/%\=<>\[\]{}(),.:]'), ('PUNC', re.compile(r'[-+*!|&^~/%\=<>\[\]{}(),.:]'),
'font-weight: bold'), 'font-weight: bold'),
('NUMBER', ('NUMBER',
re.compile(r'0x[0-9a-fA-F]+|[+-]?\d+(\.\d+)?([eE][+-]\d+)?|\d+' re.compile(r'0x[0-9a-fA-F]+|[+-]?\d+(\.\d+)?([eE][+-]\d+)?|\d+'
), 'color: red'), ), 'color: red'),
('KEYWORD', ('KEYWORD',
re.compile(r'(def|class|break|continue|del|exec|finally|pass|' re.compile(r'(def|class|break|continue|del|exec|finally|pass|'
+ r'print|raise|return|try|except|global|assert|lambda|' + r'print|raise|return|try|except|global|assert|lambda|'
+ r'yield|for|while|if|elif|else|and|in|is|not|or|import|' + r'yield|for|while|if|elif|else|and|in|is|not|or|import|'
+ r'from|True|False)(?![a-zA-Z0-9_])'), + r'from|True|False)(?![a-zA-Z0-9_])'),
'color:#185369; font-weight: bold'), 'color:#185369; font-weight: bold'),
('WEB2PY', ('WEB2PY',
re.compile(r'(request|response|session|cache|redirect|local_import|HTTP|TR|XML|URL|BEAUTIFY|A|BODY|BR|B|CAT|CENTER|CODE|COL|COLGROUP|DIV|EM|EMBED|FIELDSET|LEGEND|FORM|H1|H2|H3|H4|H5|H6|IFRAME|HEAD|HR|HTML|I|IMG|INPUT|LABEL|LI|LINK|MARKMIN|MENU|META|OBJECT|OL|ON|OPTION|P|PRE|SCRIPT|SELECT|SPAN|STYLE|TABLE|THEAD|TBODY|TFOOT|TAG|TD|TEXTAREA|TH|TITLE|TT|T|UL|XHTML|IS_SLUG|IS_STRONG|IS_LOWER|IS_UPPER|IS_ALPHANUMERIC|IS_DATETIME|IS_DATETIME_IN_RANGE|IS_DATE|IS_DATE_IN_RANGE|IS_DECIMAL_IN_RANGE|IS_EMAIL|IS_EXPR|IS_FLOAT_IN_RANGE|IS_IMAGE|IS_INT_IN_RANGE|IS_IN_SET|IS_IPV4|IS_LIST_OF|IS_LENGTH|IS_MATCH|IS_EQUAL_TO|IS_EMPTY_OR|IS_NULL_OR|IS_NOT_EMPTY|IS_TIME|IS_UPLOAD_FILENAME|IS_URL|CLEANUP|CRYPT|IS_IN_DB|IS_NOT_IN_DB|DAL|Field|SQLFORM|SQLTABLE|xmlescape|embed64)(?![a-zA-Z0-9_])' re.compile(r'(request|response|session|cache|redirect|local_import|HTTP|TR|XML|URL|BEAUTIFY|A|BODY|BR|B|CAT|CENTER|CODE|COL|COLGROUP|DIV|EM|EMBED|FIELDSET|LEGEND|FORM|H1|H2|H3|H4|H5|H6|IFRAME|HEAD|HR|HTML|I|IMG|INPUT|LABEL|LI|LINK|MARKMIN|MENU|META|OBJECT|OL|ON|OPTION|P|PRE|SCRIPT|SELECT|SPAN|STYLE|TABLE|THEAD|TBODY|TFOOT|TAG|TD|TEXTAREA|TH|TITLE|TT|T|UL|XHTML|IS_SLUG|IS_STRONG|IS_LOWER|IS_UPPER|IS_ALPHANUMERIC|IS_DATETIME|IS_DATETIME_IN_RANGE|IS_DATE|IS_DATE_IN_RANGE|IS_DECIMAL_IN_RANGE|IS_EMAIL|IS_EXPR|IS_FLOAT_IN_RANGE|IS_IMAGE|IS_INT_IN_RANGE|IS_IN_SET|IS_IPV4|IS_LIST_OF|IS_LENGTH|IS_MATCH|IS_EQUAL_TO|IS_EMPTY_OR|IS_NULL_OR|IS_NOT_EMPTY|IS_TIME|IS_UPLOAD_FILENAME|IS_URL|CLEANUP|CRYPT|IS_IN_DB|IS_NOT_IN_DB|DAL|Field|SQLFORM|SQLTABLE|xmlescape|embed64)(?![a-zA-Z0-9_])'
), 'link:%(link)s;text-decoration:None;color:#FF5C1F;'), ), 'link:%(link)s;text-decoration:None;color:#FF5C1F;'),
('MAGIC', re.compile(r'self|None'), ('MAGIC', re.compile(r'self|None'),
'color:#185369; font-weight: bold'), 'color:#185369; font-weight: bold'),
('MULTILINESTRING', re.compile(r'r?u?(\'\'\'|""")'), ('MULTILINESTRING', re.compile(r'r?u?(\'\'\'|""")'),
'color: #FF9966'), 'color: #FF9966'),
('STRING', re.compile(r'r?u?\'(.*?)(?<!\\)\'|"(.*?)(?<!\\)"' ('STRING', re.compile(r'r?u?\'(.*?)(?<!\\)\'|"(.*?)(?<!\\)"'
), 'color: #FF9966'), ), 'color: #FF9966'),
('IDENTIFIER', re.compile(r'[a-zA-Z_][a-zA-Z0-9_]*'), ('IDENTIFIER', re.compile(r'[a-zA-Z_][a-zA-Z0-9_]*'),
None), None),
('COMMENT', re.compile(r'\#.*\r?\n'), ('COMMENT', re.compile(r'\#.*\r?\n'),
'color: green; font-style: italic'), 'color: green; font-style: italic'),
('WHITESPACE', re.compile(r'[ \r\n]+'), 'Keep'), ('WHITESPACE', re.compile(r'[ \r\n]+'), 'Keep'),
)), )),
'PYTHONMultilineString': (python_tokenizer, 'PYTHONMultilineString': (python_tokenizer,
(('ENDMULTILINESTRING', (('ENDMULTILINESTRING',
re.compile(r'.*?("""|\'\'\')', re.compile(r'.*?("""|\'\'\')',
@@ -191,13 +191,13 @@ class Highlighter(object):
('XMLCRAP', re.compile(r'<![^>]*>'), ('XMLCRAP', re.compile(r'<![^>]*>'),
'color: blue; font-style: italic'), 'color: blue; font-style: italic'),
('SCRIPT', re.compile(r'<script .*?</script>', re.IGNORECASE ('SCRIPT', re.compile(r'<script .*?</script>', re.IGNORECASE
+ re.DOTALL), 'color: black'), + re.DOTALL), 'color: black'),
('TAG', re.compile(r'</?\s*[a-zA-Z0-9]+'), ('TAG', re.compile(r'</?\s*[a-zA-Z0-9]+'),
'color: darkred; font-weight: bold'), 'color: darkred; font-weight: bold'),
('ENDTAG', re.compile(r'/?>'), ('ENDTAG', re.compile(r'/?>'),
'color: darkred; font-weight: bold'), 'color: darkred; font-weight: bold'),
)), )),
} }
def highlight(self, data): def highlight(self, data):
""" """
@@ -215,12 +215,12 @@ class Highlighter(object):
if style: if style:
new_mode = \ new_mode = \
Highlighter.all_styles[mode][0](self, Highlighter.all_styles[mode][0](self,
token, match, style token, match, style
% dict(link=self.link)) % dict(link=self.link))
else: else:
new_mode = \ new_mode = \
Highlighter.all_styles[mode][0](self, Highlighter.all_styles[mode][0](self,
token, match, style) token, match, style)
if not new_mode is None: if not new_mode is None:
mode = new_mode mode = new_mode
i += max(1, len(match.group())) i += max(1, len(match.group()))
@@ -257,7 +257,7 @@ def highlight(
highlight_line=None, highlight_line=None,
context_lines=None, context_lines=None,
attributes=None, attributes=None,
): ):
styles = styles or {} styles = styles or {}
attributes = attributes or {} attributes = attributes or {}
if not 'CODE' in styles: if not 'CODE' in styles:
@@ -289,7 +289,7 @@ def highlight(
linehighlight_style = styles['LINEHIGHLIGHT'] linehighlight_style = styles['LINEHIGHLIGHT']
if language and language.upper() in ['PYTHON', 'C', 'CPP', 'HTML', if language and language.upper() in ['PYTHON', 'C', 'CPP', 'HTML',
'WEB2PY']: 'WEB2PY']:
code = Highlighter(language, link, styles).highlight(code) code = Highlighter(language, link, styles).highlight(code)
else: else:
code = cgi.escape(code) code = cgi.escape(code)
@@ -301,22 +301,24 @@ def highlight(
linenumbers = [cgi.escape(counter)] * len(lines) linenumbers = [cgi.escape(counter)] * len(lines)
else: else:
linenumbers = [str(i + counter) + '.' for i in linenumbers = [str(i + counter) + '.' for i in
xrange(len(lines))] xrange(len(lines))]
if highlight_line: if highlight_line:
if counter and not isinstance(counter, str): if counter and not isinstance(counter, str):
lineno = highlight_line - counter lineno = highlight_line - counter
else: else:
lineno = highlight_line lineno = highlight_line
if lineno<len(lines): if lineno < len(lines):
lines[lineno] = '<div style="%s">%s</div>' % (linehighlight_style, lines[lineno]) lines[lineno] = '<div style="%s">%s</div>' % (
linenumbers[lineno] = '<div style="%s">%s</div>' % (linehighlight_style, linenumbers[lineno]) linehighlight_style, lines[lineno])
linenumbers[lineno] = '<div style="%s">%s</div>' % (
linehighlight_style, linenumbers[lineno])
if context_lines: if context_lines:
if lineno + context_lines < len(lines): if lineno + context_lines < len(lines):
del lines[lineno + context_lines:] del lines[lineno + context_lines:]
del linenumbers[lineno + context_lines:] del linenumbers[lineno + context_lines:]
if lineno -context_lines > 0: if lineno - context_lines > 0:
del lines[0:lineno - context_lines] del lines[0:lineno - context_lines]
del linenumbers[0:lineno - context_lines] del linenumbers[0:lineno - context_lines]
@@ -326,13 +328,13 @@ def highlight(
items = attributes.items() items = attributes.items()
fa = ' '.join([key[1:].lower() for (key, value) in items if key[:1] fa = ' '.join([key[1:].lower() for (key, value) in items if key[:1]
== '_' and value is None] + ['%s="%s"' == '_' and value is None] + ['%s="%s"'
% (key[1:].lower(), str(value).replace('"', "'")) % (key[1:].lower(), str(value).replace('"', "'"))
for (key, value) in attributes.items() if key[:1] for (key, value) in attributes.items() if key[:1]
== '_' and value]) == '_' and value])
if fa: if fa:
fa = ' ' + fa fa = ' ' + fa
return '<table%s><tr valign="top"><td style="width:40px; text-align: right;"><pre style="%s">%s</pre></td><td><pre style="%s">%s</pre></td></tr></table>'\ return '<table%s><tr valign="top"><td style="width:40px; text-align: right;"><pre style="%s">%s</pre></td><td><pre style="%s">%s</pre></td></tr></table>'\
% (fa, linenumbers_style, numbers, code_style, code) % (fa, linenumbers_style, numbers, code_style, code)
if __name__ == '__main__': if __name__ == '__main__':
@@ -341,4 +343,4 @@ if __name__ == '__main__':
data = argfp.read() data = argfp.read()
argfp.close() argfp.close()
print '<html><body>' + highlight(data, sys.argv[2])\ print '<html><body>' + highlight(data, sys.argv[2])\
+ '</body></html>' + '</body></html>'
+298 -223
View File
File diff suppressed because it is too large. Load diff
+10 -9
View File
@@ -49,7 +49,7 @@ defined_status = {
503: 'SERVICE UNAVAILABLE', 503: 'SERVICE UNAVAILABLE',
504: 'GATEWAY TIMEOUT', 504: 'GATEWAY TIMEOUT',
505: 'HTTP VERSION NOT SUPPORTED', 505: 'HTTP VERSION NOT SUPPORTED',
} }
# If web2py is executed with python2.4 we need # If web2py is executed with python2.4 we need
# to use Exception instead of BaseException # to use Exception instead of BaseException
@@ -61,6 +61,7 @@ except NameError:
regex_status = re.compile('^\d{3} \w+$') regex_status = re.compile('^\d{3} \w+$')
class HTTP(BaseException): class HTTP(BaseException):
def __init__( def __init__(
@@ -69,14 +70,14 @@ class HTTP(BaseException):
body='', body='',
cookies=None, cookies=None,
**headers **headers
): ):
self.status = status self.status = status
self.body = body self.body = body
self.headers = headers self.headers = headers
self.cookies2headers(cookies) self.cookies2headers(cookies)
def cookies2headers(self,cookies): def cookies2headers(self, cookies):
if cookies and len(cookies)>0: if cookies and len(cookies) > 0:
self.headers['Set-Cookie'] = [ self.headers['Set-Cookie'] = [
str(cookie)[11:] for cookie in cookies.values()] str(cookie)[11:] for cookie in cookies.values()]
@@ -90,15 +91,15 @@ class HTTP(BaseException):
status = str(status) status = str(status)
if not regex_status.match(status): if not regex_status.match(status):
status = '500 %s' % (defined_status[500]) status = '500 %s' % (defined_status[500])
headers.setdefault('Content-Type','text/html; charset=UTF-8') headers.setdefault('Content-Type', 'text/html; charset=UTF-8')
body = self.body body = self.body
if status[:1] == '4': if status[:1] == '4':
if not body: if not body:
body = status body = status
if isinstance(body, str): if isinstance(body, str):
if len(body)<512 and \ if len(body) < 512 and \
headers['Content-Type'].startswith('text/html'): headers['Content-Type'].startswith('text/html'):
body += '<!-- %s //-->' % ('x'*512) ### trick IE body += '<!-- %s //-->' % ('x' * 512) # trick IE
headers['Content-Length'] = len(body) headers['Content-Length'] = len(body)
rheaders = [] rheaders = []
for k, v in headers.iteritems(): for k, v in headers.iteritems():
@@ -107,9 +108,9 @@ class HTTP(BaseException):
elif not v is None: elif not v is None:
rheaders.append((k, str(v))) rheaders.append((k, str(v)))
responder(status, rheaders) responder(status, rheaders)
if env.get('request_method','')=='HEAD': if env.get('request_method', '') == 'HEAD':
return [''] return ['']
elif isinstance(body,str): elif isinstance(body, str):
return [body] return [body]
elif hasattr(body, '__iter__'): elif hasattr(body, '__iter__'):
return body return body
+7 -7
View File
@@ -69,10 +69,10 @@ base_modules = ['aifc', 'anydbm', 'array', 'asynchat', 'asyncore', 'atexit',
contributed_modules = [] contributed_modules = []
for root, dirs, files in os.walk('gluon'): for root, dirs, files in os.walk('gluon'):
for candidate in ['.'.join( for candidate in ['.'.join(
os.path.join(root, os.path.splitext(name)[0]).split(os.sep)) os.path.join(root, os.path.splitext(name)[0]).split(os.sep))
for name in files if name.endswith('.py') for name in files if name.endswith('.py')
and root.split(os.sep) != ['gluon', 'tests'] and root.split(os.sep) != ['gluon', 'tests']
]: ]:
contributed_modules.append(candidate) contributed_modules.append(candidate)
# Python base version # Python base version
@@ -86,7 +86,7 @@ alert_dependency = ['hashlib', 'uuid']
# #
# List of modules deprecated in Python 2.6 or 2.7 that are in the above set # List of modules deprecated in Python 2.6 or 2.7 that are in the above set
py26_deprecated = ['mhlib', 'multifile', 'mimify', 'sets', 'MimeWriter'] py26_deprecated = ['mhlib', 'multifile', 'mimify', 'sets', 'MimeWriter']
py27_deprecated = [] # ['optparse'] but we need it for now py27_deprecated = [] # ['optparse'] but we need it for now
if python_version >= '2.6': if python_version >= '2.6':
base_modules += ['json', 'multiprocessing'] base_modules += ['json', 'multiprocessing']
@@ -99,11 +99,11 @@ if python_version >= '2.7':
# Now iterate in the base_modules, trying to do the import # Now iterate in the base_modules, trying to do the import
for module in base_modules + contributed_modules: for module in base_modules + contributed_modules:
try: try:
__import__(module, globals(), locals(), []) __import__(module, globals(), locals(), [])
except: except:
# Raise an exception if the current module is a dependency # Raise an exception if the current module is a dependency
if module in alert_dependency: if module in alert_dependency:
msg = "Missing dependency: %(module)s\n" % locals() msg = "Missing dependency: %(module)s\n" % locals()
msg += "Try the following command: " msg += "Try the following command: "
msg += "easy_install-%(python_version)s -U %(module)s" % locals() msg += "easy_install-%(python_version)s -U %(module)s" % locals()
raise ImportError, msg raise ImportError(msg)
+141 -107
View File
@@ -48,16 +48,16 @@ DEFAULT_GET_PLURAL_ID = lambda n: 0
# word is unchangeable # word is unchangeable
DEFAULT_CONSTRUCT_PLURAL_FORM = lambda word, plural_id: word DEFAULT_CONSTRUCT_PLURAL_FORM = lambda word, plural_id: word
NUMBERS = (int,long,float) NUMBERS = (int, long, float)
# pattern to find T(blah blah blah) expressions # pattern to find T(blah blah blah) expressions
PY_STRING_LITERAL_RE = r'(?<=[^\w]T\()(?P<name>'\ PY_STRING_LITERAL_RE = r'(?<=[^\w]T\()(?P<name>'\
+ r"[uU]?[rR]?(?:'''(?:[^']|'{1,2}(?!'))*''')|"\ + r"[uU]?[rR]?(?:'''(?:[^']|'{1,2}(?!'))*''')|"\
+ r"(?:'(?:[^'\\]|\\.)*')|" + r'(?:"""(?:[^"]|"{1,2}(?!"))*""")|'\ + r"(?:'(?:[^'\\]|\\.)*')|" + r'(?:"""(?:[^"]|"{1,2}(?!"))*""")|'\
+ r'(?:"(?:[^"\\]|\\.)*"))' + r'(?:"(?:[^"\\]|\\.)*"))'
regex_translate = re.compile(PY_STRING_LITERAL_RE, re.DOTALL) regex_translate = re.compile(PY_STRING_LITERAL_RE, re.DOTALL)
regex_param=re.compile(r'{(?P<s>.+?)}') regex_param = re.compile(r'{(?P<s>.+?)}')
# pattern for a valid accept_language # pattern for a valid accept_language
regex_language = \ regex_language = \
@@ -66,33 +66,43 @@ regex_langfile = re.compile('^[a-z]{2}(-[a-z]{2})?\.py$')
regex_backslash = re.compile(r"\\([\\{}%])") regex_backslash = re.compile(r"\\([\\{}%])")
regex_plural = re.compile('%({.+?})') regex_plural = re.compile('%({.+?})')
regex_plural_dict = re.compile('^{(?P<w>[^()[\]][^()[\]]*?)\((?P<n>[^()\[\]]+)\)}$') # %%{word(varname or number)} regex_plural_dict = re.compile('^{(?P<w>[^()[\]][^()[\]]*?)\((?P<n>[^()\[\]]+)\)}$') # %%{word(varname or number)}
regex_plural_tuple = re.compile('^{(?P<w>[^[\]()]+)(?:\[(?P<i>\d+)\])?}$') # %%{word[index]} or %%{word} regex_plural_tuple = re.compile(
'^{(?P<w>[^[\]()]+)(?:\[(?P<i>\d+)\])?}$') # %%{word[index]} or %%{word}
regex_plural_file = re.compile('^plural-[a-zA-Z]{2}(-[a-zA-Z]{2})?\.py$') regex_plural_file = re.compile('^plural-[a-zA-Z]{2}(-[a-zA-Z]{2})?\.py$')
def safe_eval(text): def safe_eval(text):
if text.strip(): if text.strip():
try: try:
import ast import ast
return ast.literal_eval(text) return ast.literal_eval(text)
except ImportError: except ImportError:
return eval(text,{},{}) return eval(text, {}, {})
return None return None
# used as default filter in translator.M() # used as default filter in translator.M()
def markmin(s): def markmin(s):
def markmin_aux(m): def markmin_aux(m):
return '{%s}' % markmin_escape(m.group('s')) return '{%s}' % markmin_escape(m.group('s'))
return render(regex_param.sub(markmin_aux,s), return render(regex_param.sub(markmin_aux, s),
sep='br', autolinks=None, id_prefix='') sep='br', autolinks=None, id_prefix='')
# UTF8 helper functions # UTF8 helper functions
def upper_fun(s): def upper_fun(s):
return unicode(s,'utf-8').upper().encode('utf-8') return unicode(s, 'utf-8').upper().encode('utf-8')
def title_fun(s): def title_fun(s):
return unicode(s,'utf-8').title().encode('utf-8') return unicode(s, 'utf-8').title().encode('utf-8')
def cap_fun(s): def cap_fun(s):
return unicode(s,'utf-8').capitalize().encode('utf-8') return unicode(s, 'utf-8').capitalize().encode('utf-8')
ttab_in = maketrans("\\%{}", '\x1c\x1d\x1e\x1f') ttab_in = maketrans("\\%{}", '\x1c\x1d\x1e\x1f')
ttab_out = maketrans('\x1c\x1d\x1e\x1f', "\\%{}") ttab_out = maketrans('\x1c\x1d\x1e\x1f', "\\%{}")
# cache of translated messages: # cache of translated messages:
@@ -105,13 +115,14 @@ ttab_out = maketrans('\x1c\x1d\x1e\x1f', "\\%{}")
# ... # ...
# } # }
global_language_cache={} global_language_cache = {}
def get_from_cache(cache, val, fun): def get_from_cache(cache, val, fun):
lang_dict, lock = cache lang_dict, lock = cache
lock.acquire() lock.acquire()
try: try:
result = lang_dict.get(val); result = lang_dict.get(val)
finally: finally:
lock.release() lock.release()
if result: if result:
@@ -123,16 +134,18 @@ def get_from_cache(cache, val, fun):
lock.release() lock.release()
return result return result
def clear_cache(filename): def clear_cache(filename):
cache = global_language_cache.setdefault( cache = global_language_cache.setdefault(
filename, ({}, allocate_lock())) filename, ({}, allocate_lock()))
lang_dict, lock = cache lang_dict, lock = cache
lock.acquire() lock.acquire()
try: try:
lang_dict.clear(); lang_dict.clear()
finally: finally:
lock.release() lock.release()
def read_dict_aux(filename): def read_dict_aux(filename):
lang_text = portalocker.read_locked(filename).replace('\r\n', '\n') lang_text = portalocker.read_locked(filename).replace('\r\n', '\n')
clear_cache(filename) clear_cache(filename)
@@ -141,13 +154,14 @@ def read_dict_aux(filename):
except Exception, e: except Exception, e:
status = 'Syntax error in %s (%s)' % (filename, e) status = 'Syntax error in %s (%s)' % (filename, e)
logging.error(status) logging.error(status)
return {'__corrupted__':status} return {'__corrupted__': status}
def read_dict(filename): def read_dict(filename):
""" return dictionary with translation messages """ return dictionary with translation messages
""" """
return getcfs('lang:'+filename, filename, return getcfs('lang:' + filename, filename,
lambda: read_dict_aux(filename)) lambda: read_dict_aux(filename))
def read_possible_plural_rules(): def read_possible_plural_rules():
@@ -159,17 +173,17 @@ def read_possible_plural_rules():
try: try:
import contrib.plural_rules as package import contrib.plural_rules as package
for importer, modname, ispkg in pkgutil.iter_modules(package.__path__): for importer, modname, ispkg in pkgutil.iter_modules(package.__path__):
if len(modname)==2: if len(modname) == 2:
module = __import__(package.__name__+'.'+modname, module = __import__(package.__name__ + '.' + modname,
fromlist=[modname]) fromlist=[modname])
lang = modname lang = modname
pname = modname+'.py' pname = modname + '.py'
nplurals = getattr(module,'nplurals', DEFAULT_NPLURALS) nplurals = getattr(module, 'nplurals', DEFAULT_NPLURALS)
get_plural_id = getattr( get_plural_id = getattr(
module,'get_plural_id', module, 'get_plural_id',
DEFAULT_GET_PLURAL_ID) DEFAULT_GET_PLURAL_ID)
construct_plural_form = getattr( construct_plural_form = getattr(
module,'construct_plural_form', module, 'construct_plural_form',
DEFAULT_CONSTRUCT_PLURAL_FORM) DEFAULT_CONSTRUCT_PLURAL_FORM)
plurals[lang] = (lang, nplurals, get_plural_id, plurals[lang] = (lang, nplurals, get_plural_id,
construct_plural_form) construct_plural_form)
@@ -179,6 +193,7 @@ def read_possible_plural_rules():
PLURAL_RULES = read_possible_plural_rules() PLURAL_RULES = read_possible_plural_rules()
def read_possible_languages_aux(langdir): def read_possible_languages_aux(langdir):
def get_lang_struct(lang, langcode, langname, langfile_mtime): def get_lang_struct(lang, langcode, langname, langfile_mtime):
if lang == 'default': if lang == 'default':
@@ -189,27 +204,28 @@ def read_possible_languages_aux(langdir):
nplurals, nplurals,
get_plural_id, get_plural_id,
construct_plural_form construct_plural_form
) = PLURAL_RULES.get(real_lang[:2],('default', ) = PLURAL_RULES.get(real_lang[:2], ('default',
DEFAULT_NPLURALS, DEFAULT_NPLURALS,
DEFAULT_GET_PLURAL_ID, DEFAULT_GET_PLURAL_ID,
DEFAULT_CONSTRUCT_PLURAL_FORM)) DEFAULT_CONSTRUCT_PLURAL_FORM))
if prules_langcode != 'default': if prules_langcode != 'default':
(pluraldict_fname, (pluraldict_fname,
pluraldict_mtime) = plurals.get(real_lang, pluraldict_mtime) = plurals.get(real_lang,
plurals.get(real_lang[:2], plurals.get(real_lang[:2],
('plural-%s.py'%real_lang,0))) ('plural-%s.py' % real_lang, 0)))
else: else:
pluraldict_fname = None pluraldict_fname = None
pluraldict_mtime = 0 pluraldict_mtime = 0
return (langcode, # language code from !langcode! return (langcode, # language code from !langcode!
langname, # language name in national spelling from !langname! langname,
# language name in national spelling from !langname!
langfile_mtime, # m_time of language file langfile_mtime, # m_time of language file
pluraldict_fname,# name of plural dictionary file or None (when default.py is not exist) pluraldict_fname, # name of plural dictionary file or None (when default.py is not exist)
pluraldict_mtime,# m_time of plural dictionary file or 0 if file is not exist pluraldict_mtime, # m_time of plural dictionary file or 0 if file is not exist
prules_langcode, # code of plural rules language or 'default' prules_langcode, # code of plural rules language or 'default'
nplurals, # nplurals for current language nplurals, # nplurals for current language
get_plural_id, # get_plural_id() for current language get_plural_id, # get_plural_id() for current language
construct_plural_form) # construct_plural_form() for current language construct_plural_form) # construct_plural_form() for current language
plurals = {} plurals = {}
flist = oslistdir(langdir) flist = oslistdir(langdir)
@@ -217,17 +233,17 @@ def read_possible_languages_aux(langdir):
for pname in flist: for pname in flist:
if regex_plural_file.match(pname): if regex_plural_file.match(pname):
plurals[pname[7:-3]] = (pname, plurals[pname[7:-3]] = (pname,
ostat(pjoin(langdir,pname)).st_mtime) ostat(pjoin(langdir, pname)).st_mtime)
langs = {} langs = {}
# scan languages directory for langfiles: # scan languages directory for langfiles:
for fname in flist: for fname in flist:
if regex_langfile.match(fname) or fname == 'default.py': if regex_langfile.match(fname) or fname == 'default.py':
fname_with_path = pjoin(langdir,fname) fname_with_path = pjoin(langdir, fname)
d = read_dict(fname_with_path) d = read_dict(fname_with_path)
lang = fname[:-3] lang = fname[:-3]
langcode = d.get('!langcode!', lang if lang != 'default' langcode = d.get('!langcode!', lang if lang != 'default'
else DEFAULT_LANGUAGE) else DEFAULT_LANGUAGE)
langname = d.get('!langname!',langcode) langname = d.get('!langname!', langcode)
langfile_mtime = ostat(fname_with_path).st_mtime langfile_mtime = ostat(fname_with_path).st_mtime
langs[lang] = get_lang_struct(lang, langcode, langs[lang] = get_lang_struct(lang, langcode,
langname, langfile_mtime) langname, langfile_mtime)
@@ -240,27 +256,31 @@ def read_possible_languages_aux(langdir):
deflangcode = deflang[0] deflangcode = deflang[0]
if deflangcode not in langs: if deflangcode not in langs:
# create language from default.py: # create language from default.py:
langs[deflangcode] = deflang[:2]+(0,)+deflang[3:] langs[deflangcode] = deflang[:2] + (0,) + deflang[3:]
return langs return langs
def read_possible_languages(appdir): def read_possible_languages(appdir):
langdir = pjoin(appdir,'languages') langdir = pjoin(appdir, 'languages')
return getcfs('langs:'+langdir, langdir, return getcfs('langs:' + langdir, langdir,
lambda: read_possible_languages_aux(langdir)) lambda: read_possible_languages_aux(langdir))
def read_plural_dict_aux(filename): def read_plural_dict_aux(filename):
lang_text = portalocker.read_locked(filename).replace('\r\n', '\n') lang_text = portalocker.read_locked(filename).replace('\r\n', '\n')
try: try:
return eval(lang_text) or {} return eval(lang_text) or {}
except Exception, e: except Exception, e:
status='Syntax error in %s (%s)' % (filename, e) status = 'Syntax error in %s (%s)' % (filename, e)
logging.error(status) logging.error(status)
return {'__corrupted__':status} return {'__corrupted__': status}
def read_plural_dict(filename): def read_plural_dict(filename):
return getcfs('plurals:'+filename, filename, return getcfs('plurals:' + filename, filename,
lambda: read_plural_dict_aux(filename)) lambda: read_plural_dict_aux(filename))
def write_plural_dict(filename, contents): def write_plural_dict(filename, contents):
if '__corrupted__' in contents: if '__corrupted__' in contents:
@@ -269,8 +289,9 @@ def write_plural_dict(filename, contents):
fp = portalocker.LockedFile(filename, 'w') fp = portalocker.LockedFile(filename, 'w')
fp.write('#!/usr/bin/env python\n{\n# "singular form (0)": ["first plural form (1)", "second plural form (2)", ...],\n') fp.write('#!/usr/bin/env python\n{\n# "singular form (0)": ["first plural form (1)", "second plural form (2)", ...],\n')
# coding: utf8\n{\n') # coding: utf8\n{\n')
for key in sorted(contents,lambda x,y: cmp(unicode(x,'utf-8').lower(), unicode(y,'utf-8').lower())): for key in sorted(contents, lambda x, y: cmp(unicode(x, 'utf-8').lower(), unicode(y, 'utf-8').lower())):
forms = '['+','.join([repr(Utf8(form)) for form in contents[key]])+']' forms = '[' + ','.join([repr(Utf8(form))
for form in contents[key]]) + ']'
fp.write('%s: %s,\n' % (repr(Utf8(key)), forms)) fp.write('%s: %s,\n' % (repr(Utf8(key)), forms))
fp.write('}\n') fp.write('}\n')
except (IOError, OSError): except (IOError, OSError):
@@ -291,13 +312,12 @@ def write_dict(filename, contents):
logging.warning('Unable to write to file %s' % filename) logging.warning('Unable to write to file %s' % filename)
return return
fp.write('# coding: utf8\n{\n') fp.write('# coding: utf8\n{\n')
for key in sorted(contents,lambda x,y: cmp(unicode(x,'utf-8').lower(), unicode(y,'utf-8').lower())): for key in sorted(contents, lambda x, y: cmp(unicode(x, 'utf-8').lower(), unicode(y, 'utf-8').lower())):
fp.write('%s: %s,\n' % (repr(Utf8(key)), repr(Utf8(contents[key])))) fp.write('%s: %s,\n' % (repr(Utf8(key)), repr(Utf8(contents[key]))))
fp.write('}\n') fp.write('}\n')
fp.close() fp.close()
class lazyT(object): class lazyT(object):
""" """
never to be called explicitly, returned by never to be called explicitly, returned by
@@ -309,12 +329,12 @@ class lazyT(object):
def __init__( def __init__(
self, self,
message, message,
symbols = {}, symbols={},
T = None, T=None,
filter = None, filter=None,
ftag = None, ftag=None,
M = False M=False
): ):
if isinstance(message, lazyT): if isinstance(message, lazyT):
self.m = message.m self.m = message.m
self.s = message.s self.s = message.s
@@ -354,7 +374,7 @@ class lazyT(object):
def __mul__(self, other): def __mul__(self, other):
return str(self) * other return str(self) * other
def __cmp__(self,other): def __cmp__(self, other):
return cmp(str(self), str(other)) return cmp(str(self), str(other))
def __hash__(self): def __hash__(self):
@@ -370,7 +390,8 @@ class lazyT(object):
return str(self)[i:j] return str(self)[i:j]
def __iter__(self): def __iter__(self):
for c in str(self): yield c for c in str(self):
yield c
def __len__(self): def __len__(self):
return len(str(self)) return len(str(self))
@@ -388,9 +409,11 @@ class lazyT(object):
return str(self) return str(self)
def __mod__(self, symbols): def __mod__(self, symbols):
if self.is_copy: return lazyT(self) if self.is_copy:
return lazyT(self)
return lazyT(self.m, symbols, self.T, self.f, self.t, self.M) return lazyT(self.m, symbols, self.T, self.f, self.t, self.M)
class translator(object): class translator(object):
""" """
this class is instantiated by gluon.compileapp.build_environment this class is instantiated by gluon.compileapp.build_environment
@@ -459,7 +482,8 @@ class translator(object):
returns dictionary with all possible languages: returns dictionary with all possible languages:
{ langcode(from filename): { langcode(from filename):
( langcode, # language code from !langcode! ( langcode, # language code from !langcode!
langname, # language name in national spelling from !langname! langname,
# language name in national spelling from !langname!
langfile_mtime, # m_time of language file langfile_mtime, # m_time of language file
pluraldict_fname,# name of plural dictionary file or None (when default.py is not exist) pluraldict_fname,# name of plural dictionary file or None (when default.py is not exist)
pluraldict_mtime,# m_time of plural dictionary file or 0 if file is not exist pluraldict_mtime,# m_time of plural dictionary file or 0 if file is not exist
@@ -470,14 +494,15 @@ class translator(object):
} }
""" """
info = read_possible_languages(self.folder) info = read_possible_languages(self.folder)
if lang: info = info.get(lang) if lang:
info = info.get(lang)
return info return info
def get_possible_languages(self): def get_possible_languages(self):
""" get list of all possible languages for current applications """ """ get list of all possible languages for current applications """
return list(set(self.current_languages + return list(set(self.current_languages +
[lang for lang in read_possible_languages(self.folder).iterkeys() [lang for lang in read_possible_languages(self.folder).iterkeys()
if lang != 'default'])) if lang != 'default']))
def set_current_languages(self, *languages): def set_current_languages(self, *languages):
""" """
@@ -486,12 +511,12 @@ class translator(object):
turn translation off to use default language turn translation off to use default language
""" """
if len(languages) == 1 and isinstance( if len(languages) == 1 and isinstance(
languages[0], (tuple, list)): languages[0], (tuple, list)):
languages = languages[0] languages = languages[0]
if not languages or languages[0] is None: if not languages or languages[0] is None:
# set default language from default.py/DEFAULT_LANGUAGE # set default language from default.py/DEFAULT_LANGUAGE
pl_info = self.get_possible_languages_info('default') pl_info = self.get_possible_languages_info('default')
if pl_info[2]==0: # langfile_mtime if pl_info[2] == 0: # langfile_mtime
# if languages/default.py is not found # if languages/default.py is not found
self.default_language_file = self.langpath self.default_language_file = self.langpath
self.default_t = {} self.default_t = {}
@@ -500,12 +525,11 @@ class translator(object):
self.default_language_file = pjoin(self.langpath, self.default_language_file = pjoin(self.langpath,
'default.py') 'default.py')
self.default_t = read_dict(self.default_language_file) self.default_t = read_dict(self.default_language_file)
self.current_languages = [pl_info[0]] # !langcode! self.current_languages = [pl_info[0]] # !langcode!
else: else:
self.current_languages = list(languages) self.current_languages = list(languages)
self.force(self.http_accept_language) self.force(self.http_accept_language)
def plural(self, word, n): def plural(self, word, n):
""" get plural form of word for number *n* """ get plural form of word for number *n*
NOTE: *word" MUST be defined in current language NOTE: *word" MUST be defined in current language
@@ -529,14 +553,14 @@ class translator(object):
# etc. # etc.
if id != 0: if id != 0:
forms = self.plural_dict.get(word, []) forms = self.plural_dict.get(word, [])
if len(forms)>=id: if len(forms) >= id:
# have this plural form: # have this plural form:
return forms[id-1] return forms[id - 1]
else: else:
# guessing this plural form # guessing this plural form
forms += ['']*(self.nplurals-len(forms)-1) forms += [''] * (self.nplurals - len(forms) - 1)
form = self.construct_plural_form(word, id) form = self.construct_plural_form(word, id)
forms[id-1] = form forms[id - 1] = form
self.plural_dict[word] = forms self.plural_dict[word] = forms
if self.is_writable and self.plural_file: if self.is_writable and self.plural_file:
write_plural_dict(self.plural_file, write_plural_dict(self.plural_file,
@@ -558,6 +582,7 @@ class translator(object):
of them matches possible_languages. of them matches possible_languages.
""" """
pl_info = read_possible_languages(self.folder) pl_info = read_possible_languages(self.folder)
def set_plural(language): def set_plural(language):
""" """
initialize plural forms subsystem initialize plural forms subsystem
@@ -570,7 +595,7 @@ class translator(object):
self.nplurals, self.nplurals,
self.get_plural_id, self.get_plural_id,
self.construct_plural_form self.construct_plural_form
) = lang_info[3:] ) = lang_info[3:]
pdict = {} pdict = {}
if pname: if pname:
pname = pjoin(self.langpath, pname) pname = pjoin(self.langpath, pname)
@@ -586,7 +611,7 @@ class translator(object):
self.plural_file = None self.plural_file = None
self.plural_dict = {} self.plural_dict = {}
language = '' language = ''
if len(languages)==1 and isinstance(languages[0],str): if len(languages) == 1 and isinstance(languages[0], str):
languages = regex_language.findall(languages[0].lower()) languages = regex_language.findall(languages[0].lower())
elif not languages or languages[0] is None: elif not languages or languages[0] is None:
languages = [] languages = []
@@ -594,7 +619,7 @@ class translator(object):
if languages: if languages:
all_languages = set(lang for lang in pl_info.iterkeys() all_languages = set(lang for lang in pl_info.iterkeys()
if lang != 'default') \ if lang != 'default') \
| set(self.current_languages) | set(self.current_languages)
for lang in languages: for lang in languages:
# compare "aa-bb" | "aa" from *language* parameter # compare "aa-bb" | "aa" from *language* parameter
# with strings from langlist using such alghorythm: # with strings from langlist using such alghorythm:
@@ -604,20 +629,20 @@ class translator(object):
language = lang5 language = lang5
else: else:
lang2 = lang[:2] lang2 = lang[:2]
if len(lang5)>2 and lang2 in all_languages: if len(lang5) > 2 and lang2 in all_languages:
language = lang2 language = lang2
else: else:
for l in all_languages: for l in all_languages:
if l[:2]==lang2: if l[:2] == lang2:
language = l language = l
if language: if language:
if language in self.current_languages: if language in self.current_languages:
break break
self.language_file = pjoin(self.langpath, language+'.py') self.language_file = pjoin(self.langpath, language + '.py')
self.t = read_dict(self.language_file) self.t = read_dict(self.language_file)
self.cache = global_language_cache.setdefault( self.cache = global_language_cache.setdefault(
self.language_file, self.language_file,
({},allocate_lock())) ({}, allocate_lock()))
set_plural(language) set_plural(language)
self.accepted_language = language self.accepted_language = language
return languages return languages
@@ -637,7 +662,7 @@ class translator(object):
if lazy is None: if lazy is None:
lazy = self.lazy lazy = self.lazy
if not language: if not language:
if lazy : if lazy:
return lazyT(message, symbols, self) return lazyT(message, symbols, self)
else: else:
return self.translate(message, symbols) return self.translate(message, symbols)
@@ -654,18 +679,18 @@ class translator(object):
s = self.get_t(message, prefix) s = self.get_t(message, prefix)
return filter(s) if filter else self.filter(s) return filter(s) if filter else self.filter(s)
if filter: if filter:
prefix = '@'+(ftag or 'userdef')+'\x01' prefix = '@' + (ftag or 'userdef') + '\x01'
else: else:
prefix = '@'+self.ftag+'\x01' prefix = '@' + self.ftag + '\x01'
message = get_from_cache( message = get_from_cache(
self.cache, prefix+message, self.cache, prefix + message,
lambda: get_tr(message, prefix, filter)) lambda: get_tr(message, prefix, filter))
if symbols or symbols == 0 or symbols == "": if symbols or symbols == 0 or symbols == "":
if isinstance(symbols, dict): if isinstance(symbols, dict):
symbols.update( symbols.update(
(key, xmlescape(value).translate(ttab_in)) (key, xmlescape(value).translate(ttab_in))
for key, value in symbols.iteritems() for key, value in symbols.iteritems()
if not isinstance(value, NUMBERS) ) if not isinstance(value, NUMBERS))
else: else:
if not isinstance(symbols, tuple): if not isinstance(symbols, tuple):
symbols = (symbols,) symbols = (symbols,)
@@ -686,7 +711,7 @@ class translator(object):
lazy = self.lazy lazy = self.lazy
if not language: if not language:
if lazy: if lazy:
return lazyT(message, symbols, self, filter, ftag, True) return lazyT(message, symbols, self, filter, ftag, True)
else: else:
return self.apply_filter(message, symbols, filter, ftag) return self.apply_filter(message, symbols, filter, ftag)
else: else:
@@ -714,11 +739,12 @@ class translator(object):
message = message.encode('utf8') message = message.encode('utf8')
if isinstance(prefix, unicode): if isinstance(prefix, unicode):
prefix = prefix.encode('utf8') prefix = prefix.encode('utf8')
key = prefix+message key = prefix + message
mt = self.t.get(key, None) mt = self.t.get(key, None)
if mt is not None: return mt if mt is not None:
return mt
# we did not find a translation # we did not find a translation
if message.find('##')>0 and not '\n' in message: if message.find('##') > 0 and not '\n' in message:
# remove comments # remove comments
message = message.rsplit('##', 1)[0] message = message.rsplit('##', 1)[0]
# guess translation same as original # guess translation same as original
@@ -756,7 +782,7 @@ class translator(object):
word, !word, !!word, !!!word, ?word?number, ??number, ?number word, !word, !!word, !!!word, ?word?number, ??number, ?number
?word?word[number], ?word?[number], ??word[number] ?word?word[number], ?word?[number], ??word[number]
""" """
w,i = m.group('w','i') w, i = m.group('w', 'i')
c = w[0] c = w[0]
if c not in '!?': if c not in '!?':
return self.plural(w, symbols[int(i or 0)]) return self.plural(w, symbols[int(i or 0)])
@@ -764,15 +790,17 @@ class translator(object):
(p1, sep, p2) = w[1:].partition("?") (p1, sep, p2) = w[1:].partition("?")
part1 = p1 if sep else "" part1 = p1 if sep else ""
(part2, sep, part3) = (p2 if sep else p1).partition("?") (part2, sep, part3) = (p2 if sep else p1).partition("?")
if not sep: part3 = part2 if not sep:
part3 = part2
if i is None: if i is None:
# ?[word]?number[?number] or ?number # ?[word]?number[?number] or ?number
if not part2: return m.group(0) if not part2:
num = int(part2) return m.group(0)
num = int(part2)
else: else:
# ?[word]?word2[?word3][number] # ?[word]?word2[?word3][number]
num = int(symbols[int(i or 0)]) num = int(symbols[int(i or 0)])
return part1 if num==1 else part3 if num==0 else part2 return part1 if num == 1 else part3 if num == 0 else part2
elif w.startswith('!!!'): elif w.startswith('!!!'):
word = w[3:] word = w[3:]
fun = upper_fun fun = upper_fun
@@ -783,7 +811,7 @@ class translator(object):
word = w[1:] word = w[1:]
fun = cap_fun fun = cap_fun
if i is not None: if i is not None:
return fun(self.plural(word, symbols[int(i)])) return fun(self.plural(word, symbols[int(i)]))
return fun(word) return fun(word)
def sub_dict(m): def sub_dict(m):
@@ -792,7 +820,7 @@ class translator(object):
?word2(var), ?word1?word2(var), ?word1?word2?word0(var) ?word2(var), ?word1?word2(var), ?word1?word2?word0(var)
?word2(num), ?word1?word2(num), ?word1?word2?word0(num) ?word2(num), ?word1?word2(num), ?word1?word2?word0(num)
""" """
w,n = m.group('w','n') w, n = m.group('w', 'n')
c = w[0] c = w[0]
n = int(n) if n.isdigit() else symbols[n] n = int(n) if n.isdigit() else symbols[n]
if c not in '!?': if c not in '!?':
@@ -802,9 +830,10 @@ class translator(object):
(p1, sep, p2) = w[1:].partition("?") (p1, sep, p2) = w[1:].partition("?")
part1 = p1 if sep else "" part1 = p1 if sep else ""
(part2, sep, part3) = (p2 if sep else p1).partition("?") (part2, sep, part3) = (p2 if sep else p1).partition("?")
if not sep: part3 = part2 if not sep:
part3 = part2
num = int(n) num = int(n)
return part1 if num==1 else part3 if num==0 else part2 return part1 if num == 1 else part3 if num == 0 else part2
elif w.startswith('!!!'): elif w.startswith('!!!'):
word = w[3:] word = w[3:]
fun = upper_fun fun = upper_fun
@@ -824,7 +853,7 @@ class translator(object):
return m.group(0) return m.group(0)
return part return part
message = message % symbols message = message % symbols
message = regex_plural.sub(sub_plural, message ) message = regex_plural.sub(sub_plural, message)
return message return message
def translate(self, message, symbols): def translate(self, message, symbols):
@@ -838,7 +867,7 @@ class translator(object):
symbols.update( symbols.update(
(key, str(value).translate(ttab_in)) (key, str(value).translate(ttab_in))
for key, value in symbols.iteritems() for key, value in symbols.iteritems()
if not isinstance(value, NUMBERS) ) if not isinstance(value, NUMBERS))
else: else:
if not isinstance(symbols, tuple): if not isinstance(symbols, tuple):
symbols = (symbols,) symbols = (symbols,)
@@ -849,6 +878,7 @@ class translator(object):
message = self.params_substitution(message, symbols) message = self.params_substitution(message, symbols)
return message.translate(ttab_out) return message.translate(ttab_out)
def findT(path, language=DEFAULT_LANGUAGE): def findT(path, language=DEFAULT_LANGUAGE):
""" """
must be run by the admin app must be run by the admin app
@@ -860,22 +890,22 @@ def findT(path, language=DEFAULT_LANGUAGE):
vp = pjoin(path, 'views') vp = pjoin(path, 'views')
mop = pjoin(path, 'modules') mop = pjoin(path, 'modules')
for filename in \ for filename in \
listdir(mp, '^.+\.py$', 0)+listdir(cp, '^.+\.py$', 0)\ listdir(mp, '^.+\.py$', 0) + listdir(cp, '^.+\.py$', 0)\
+listdir(vp, '^.+\.html$', 0)+listdir(mop, '^.+\.py$', 0): + listdir(vp, '^.+\.html$', 0) + listdir(mop, '^.+\.py$', 0):
data = portalocker.read_locked(filename) data = portalocker.read_locked(filename)
items = regex_translate.findall(data) items = regex_translate.findall(data)
for item in items: for item in items:
try: try:
message = safe_eval(item) message = safe_eval(item)
except: except:
continue # silently ignore inproperly formatted strings continue # silently ignore inproperly formatted strings
if not message.startswith('#') and not '\n' in message: if not message.startswith('#') and not '\n' in message:
tokens = message.rsplit('##', 1) tokens = message.rsplit('##', 1)
else: else:
# this allows markmin syntax in translations # this allows markmin syntax in translations
tokens = [message] tokens = [message]
if len(tokens) == 2: if len(tokens) == 2:
message = tokens[0].strip()+'##'+tokens[1].strip() message = tokens[0].strip() + '##' + tokens[1].strip()
if message and not message in sentences: if message and not message in sentences:
sentences[message] = message sentences[message] = message
if not '!langcode!' in sentences: if not '!langcode!' in sentences:
@@ -888,8 +918,12 @@ def findT(path, language=DEFAULT_LANGUAGE):
write_dict(lang_file, sentences) write_dict(lang_file, sentences)
### important to allow safe session.flash=T(....) ### important to allow safe session.flash=T(....)
def lazyT_unpickle(data): def lazyT_unpickle(data):
return marshal.loads(data) return marshal.loads(data)
def lazyT_pickle(data): def lazyT_pickle(data):
return lazyT_unpickle, (marshal.dumps(str(data)),) return lazyT_unpickle, (marshal.dumps(str(data)),)
copy_reg.pickle(lazyT, lazyT_pickle, lazyT_unpickle) copy_reg.pickle(lazyT, lazyT_pickle, lazyT_unpickle)
+84 -75
View File
@@ -51,7 +51,7 @@ from globals import current
# The two are identical unless web2py_path is changed via the web2py.py -f folder option # The two are identical unless web2py_path is changed via the web2py.py -f folder option
# main.web2py_path is the same as applications_parent (for backward compatibility) # main.web2py_path is the same as applications_parent (for backward compatibility)
web2py_path = global_settings.applications_parent # backward compatibility web2py_path = global_settings.applications_parent # backward compatibility
create_missing_folders() create_missing_folders()
@@ -107,7 +107,7 @@ try:
version_info.close() version_info.close()
global_settings.web2py_version = parse_version(raw_version_string) global_settings.web2py_version = parse_version(raw_version_string)
except: except:
raise RuntimeError, "Cannot determine web2py version" raise RuntimeError("Cannot determine web2py version")
web2py_version = global_settings.web2py_version web2py_version = global_settings.web2py_version
@@ -119,7 +119,8 @@ except:
load() load()
HTTPS_SCHEMES = set(('https','HTTPS')) HTTPS_SCHEMES = set(('https', 'HTTPS'))
def get_client(env): def get_client(env):
""" """
@@ -138,10 +139,11 @@ def get_client(env):
else: else:
client = '127.0.0.1' client = '127.0.0.1'
if not is_valid_ip_address(client): if not is_valid_ip_address(client):
raise HTTP(400,"Bad Request (request.client=%s)" % client) raise HTTP(400, "Bad Request (request.client=%s)" % client)
return client return client
def copystream_progress(request, chunk_size= 10**5):
def copystream_progress(request, chunk_size=10 ** 5):
""" """
copies request.env.wsgi_input into request.body copies request.env.wsgi_input into request.body
and stores progress upload status in cache_ram and stores progress upload status in cache_ram
@@ -154,22 +156,22 @@ def copystream_progress(request, chunk_size= 10**5):
try: try:
size = int(env.content_length) size = int(env.content_length)
except ValueError: except ValueError:
raise HTTP(400,"Invalid Content-Length header") raise HTTP(400, "Invalid Content-Length header")
dest = tempfile.TemporaryFile() dest = tempfile.TemporaryFile()
if not 'X-Progress-ID' in request.vars: if not 'X-Progress-ID' in request.vars:
copystream(source, dest, size, chunk_size) copystream(source, dest, size, chunk_size)
return dest return dest
cache_key = 'X-Progress-ID:'+request.vars['X-Progress-ID'] cache_key = 'X-Progress-ID:' + request.vars['X-Progress-ID']
cache_ram = CacheInRam(request) # same as cache.ram because meta_storage cache_ram = CacheInRam(request) # same as cache.ram because meta_storage
cache_ram(cache_key+':length', lambda: size, 0) cache_ram(cache_key + ':length', lambda: size, 0)
cache_ram(cache_key+':uploaded', lambda: 0, 0) cache_ram(cache_key + ':uploaded', lambda: 0, 0)
while size > 0: while size > 0:
if size < chunk_size: if size < chunk_size:
data = source.read(size) data = source.read(size)
cache_ram.increment(cache_key+':uploaded', size) cache_ram.increment(cache_key + ':uploaded', size)
else: else:
data = source.read(chunk_size) data = source.read(chunk_size)
cache_ram.increment(cache_key+':uploaded', chunk_size) cache_ram.increment(cache_key + ':uploaded', chunk_size)
length = len(data) length = len(data)
if length > size: if length > size:
(data, length) = (data[:size], size) (data, length) = (data[:size], size)
@@ -180,8 +182,8 @@ def copystream_progress(request, chunk_size= 10**5):
if length < chunk_size: if length < chunk_size:
break break
dest.seek(0) dest.seek(0)
cache_ram(cache_key+':length', None) cache_ram(cache_key + ':length', None)
cache_ram(cache_key+':uploaded', None) cache_ram(cache_key + ':uploaded', None)
return dest return dest
@@ -224,8 +226,9 @@ def serve_controller(request, response, session):
page = response.body.getvalue() page = response.body.getvalue()
# logic to garbage collect after exec, not always, once every 100 requests # logic to garbage collect after exec, not always, once every 100 requests
global requests global requests
requests = ('requests' in globals()) and (requests+1) % 100 or 0 requests = ('requests' in globals()) and (requests + 1) % 100 or 0
if not requests: gc.collect() if not requests:
gc.collect()
# end garbage collection logic # end garbage collection logic
# ################################################## # ##################################################
@@ -233,13 +236,14 @@ def serve_controller(request, response, session):
# ################################################## # ##################################################
default_headers = [ default_headers = [
('Content-Type', contenttype('.'+request.extension)), ('Content-Type', contenttype('.' + request.extension)),
('Cache-Control','no-store, no-cache, must-revalidate, post-check=0, pre-check=0'), ('Cache-Control',
'no-store, no-cache, must-revalidate, post-check=0, pre-check=0'),
('Expires', time.strftime('%a, %d %b %Y %H:%M:%S GMT', ('Expires', time.strftime('%a, %d %b %Y %H:%M:%S GMT',
time.gmtime())), time.gmtime())),
('Pragma', 'no-cache')] ('Pragma', 'no-cache')]
for key,value in default_headers: for key, value in default_headers:
response.headers.setdefault(key,value) response.headers.setdefault(key, value)
raise HTTP(response.status, page, **response.headers) raise HTTP(response.status, page, **response.headers)
@@ -253,9 +257,9 @@ def start_response_aux(status, headers, exc_info, response=None):
to call third party WSGI applications to call third party WSGI applications
""" """
response.status = str(status).split(' ',1)[0] response.status = str(status).split(' ', 1)[0]
response.headers = dict(headers) response.headers = dict(headers)
return lambda *args, **kargs: response.write(escape=False,*args,**kargs) return lambda *args, **kargs: response.write(escape=False, *args, **kargs)
def middleware_aux(request, response, *middleware_apps): def middleware_aux(request, response, *middleware_apps):
@@ -270,24 +274,27 @@ def middleware_aux(request, response, *middleware_apps):
def middleware(f): def middleware(f):
def app(environ, start_response): def app(environ, start_response):
data = f() data = f()
start_response(response.status,response.headers.items()) start_response(response.status, response.headers.items())
if isinstance(data,list): if isinstance(data, list):
return data return data
return [data] return [data]
for item in middleware_apps: for item in middleware_apps:
app=item(app) app = item(app)
def caller(app): def caller(app):
wsgi = request.wsgi wsgi = request.wsgi
return app(wsgi.environ, wsgi.start_response) return app(wsgi.environ, wsgi.start_response)
return lambda caller=caller, app=app: caller(app) return lambda caller=caller, app=app: caller(app)
return middleware return middleware
def environ_aux(environ,request):
def environ_aux(environ, request):
new_environ = copy.copy(environ) new_environ = copy.copy(environ)
new_environ['wsgi.input'] = request.body new_environ['wsgi.input'] = request.body
new_environ['wsgi.version'] = 1 new_environ['wsgi.version'] = 1
return new_environ return new_environ
def parse_get_post_vars(request, environ): def parse_get_post_vars(request, environ):
# always parse variables in URL for GET, POST, PUT, DELETE, etc. in get_vars # always parse variables in URL for GET, POST, PUT, DELETE, etc. in get_vars
@@ -307,22 +314,23 @@ def parse_get_post_vars(request, environ):
try: try:
request.body = body = copystream_progress(request) request.body = body = copystream_progress(request)
except IOError: except IOError:
raise HTTP(400,"Bad Request - HTTP body is incomplete") raise HTTP(400, "Bad Request - HTTP body is incomplete")
if (body and env.request_method in ('POST', 'PUT', 'BOTH')): if (body and env.request_method in ('POST', 'PUT', 'BOTH')):
dpost = cgi.FieldStorage(fp=body,environ=environ,keep_blank_values=1) dpost = cgi.FieldStorage(fp=body, environ=environ, keep_blank_values=1)
# The same detection used by FieldStorage to detect multipart POSTs # The same detection used by FieldStorage to detect multipart POSTs
is_multipart = dpost.type[:10] == 'multipart/' is_multipart = dpost.type[:10] == 'multipart/'
body.seek(0) body.seek(0)
isle25 = sys.version_info[1] <= 5 isle25 = sys.version_info[1] <= 5
def listify(a): def listify(a):
return (not isinstance(a,list) and [a]) or a return (not isinstance(a, list) and [a]) or a
try: try:
keys = sorted(dpost) keys = sorted(dpost)
except TypeError: except TypeError:
keys = [] keys = []
for key in keys: for key in keys:
if key is None: continue # not sure why cgi.FieldStorage returns None key if key is None:
continue # not sure why cgi.FieldStorage returns None key
dpk = dpost[key] dpk = dpost[key]
# if en element is not a file replace it with its value else leave it alone # if en element is not a file replace it with its value else leave it alone
if isinstance(dpk, list): if isinstance(dpk, list):
@@ -347,7 +355,8 @@ def parse_get_post_vars(request, environ):
pvalue = pvalue[:-len(gvalue)] pvalue = pvalue[:-len(gvalue)]
request.vars[key] = value request.vars[key] = value
if len(pvalue): if len(pvalue):
request.post_vars[key] = (len(pvalue)>1 and pvalue) or pvalue[0] request.post_vars[key] = (len(pvalue) >
1 and pvalue) or pvalue[0]
def wsgibase(environ, responder): def wsgibase(environ, responder):
@@ -407,22 +416,23 @@ def wsgibase(environ, responder):
response.status = env.web2py_status_code or response.status response.status = env.web2py_status_code or response.status
if static_file: if static_file:
if environ.get('QUERY_STRING','').startswith( if environ.get('QUERY_STRING', '').startswith(
'attachment'): 'attachment'):
response.headers['Content-Disposition'] \ response.headers['Content-Disposition'] \
= 'attachment' = 'attachment'
if version: if version:
response.headers['Cache-Control'] = 'max-age=315360000' response.headers['Cache-Control'] = 'max-age=315360000'
response.headers['Expires'] = 'Thu, 31 Dec 2037 23:59:59 GMT' response.headers[
'Expires'] = 'Thu, 31 Dec 2037 23:59:59 GMT'
response.stream(static_file, request=request) response.stream(static_file, request=request)
# ################################################## # ##################################################
# fill in request items # fill in request items
# ################################################## # ##################################################
app = request.application ## must go after url_in! app = request.application # must go after url_in!
if not global_settings.local_hosts: if not global_settings.local_hosts:
local_hosts = ['127.0.0.1','::ffff:127.0.0.1'] local_hosts = ['127.0.0.1', '::ffff:127.0.0.1']
if not global_settings.web2py_runtime_gae: if not global_settings.web2py_runtime_gae:
try: try:
local_hosts.append(socket.gethostname()) local_hosts.append(socket.gethostname())
@@ -433,7 +443,7 @@ def wsgibase(environ, responder):
local_hosts += [ local_hosts += [
env.server_name, env.server_name,
socket.gethostbyname(env.server_name)] socket.gethostbyname(env.server_name)]
except (socket.gaierror,TypeError): except (socket.gaierror, TypeError):
pass pass
global_settings.local_hosts = local_hosts global_settings.local_hosts = local_hosts
else: else:
@@ -442,15 +452,15 @@ def wsgibase(environ, responder):
x_req_with = str(env.http_x_requested_with).lower() x_req_with = str(env.http_x_requested_with).lower()
request.update( request.update(
client = client, client=client,
folder = abspath('applications',app) + os.sep, folder=abspath('applications', app) + os.sep,
ajax = x_req_with == 'xmlhttprequest', ajax=x_req_with == 'xmlhttprequest',
cid = env.http_web2py_component_element, cid=env.http_web2py_component_element,
is_local = env.remote_addr in local_hosts, is_local=env.remote_addr in local_hosts,
is_https = env.wsgi_url_scheme in HTTPS_SCHEMES \ is_https=env.wsgi_url_scheme in HTTPS_SCHEMES
or request.env.http_x_forwarded_proto in HTTPS_SCHEMES \ or request.env.http_x_forwarded_proto in HTTPS_SCHEMES
or env.https=='on') or env.https == 'on')
request.uuid = request.compute_uuid() # requires client request.uuid = request.compute_uuid() # requires client
request.url = environ['PATH_INFO'] request.url = environ['PATH_INFO']
# ################################################## # ##################################################
@@ -460,7 +470,7 @@ def wsgibase(environ, responder):
if not exists(request.folder): if not exists(request.folder):
if app == rwthread.routes.default_application \ if app == rwthread.routes.default_application \
and app != 'welcome': and app != 'welcome':
redirect(URL('welcome','default','index')) redirect(URL('welcome', 'default', 'index'))
elif rwthread.routes.error_handler: elif rwthread.routes.error_handler:
_handler = rwthread.routes.error_handler _handler = rwthread.routes.error_handler
redirect(URL(_handler['application'], redirect(URL(_handler['application'],
@@ -468,11 +478,11 @@ def wsgibase(environ, responder):
_handler['function'], _handler['function'],
args=app)) args=app))
else: else:
raise HTTP(404, rwthread.routes.error_message \ raise HTTP(404, rwthread.routes.error_message
% 'invalid request', % 'invalid request',
web2py_error='invalid application') web2py_error='invalid application')
elif not request.is_local and \ elif not request.is_local and \
exists(pjoin(request.folder,'DISABLED')): exists(pjoin(request.folder, 'DISABLED')):
raise HTTP(503, "<html><body><h1>Temporarily down for maintenance</h1></body></html>") raise HTTP(503, "<html><body><h1>Temporarily down for maintenance</h1></body></html>")
# ################################################## # ##################################################
@@ -491,13 +501,13 @@ def wsgibase(environ, responder):
# expose wsgi hooks for convenience # expose wsgi hooks for convenience
# ################################################## # ##################################################
request.wsgi.environ = environ_aux(environ,request) request.wsgi.environ = environ_aux(environ, request)
request.wsgi.start_response = \ request.wsgi.start_response = \
lambda status='200', headers=[], \ lambda status='200', headers=[], \
exec_info=None, response=response: \ exec_info=None, response=response: \
start_response_aux(status, headers, exec_info, response) start_response_aux(status, headers, exec_info, response)
request.wsgi.middleware = \ request.wsgi.middleware = \
lambda *a: middleware_aux(request,response,*a) lambda *a: middleware_aux(request, response, *a)
# ################################################## # ##################################################
# load cookies # load cookies
@@ -507,7 +517,7 @@ def wsgibase(environ, responder):
try: try:
request.cookies.load(env.http_cookie) request.cookies.load(env.http_cookie)
except Cookie.CookieError, e: except Cookie.CookieError, e:
pass # invalid cookies pass # invalid cookies
# ################################################## # ##################################################
# try load session or create new session file # try load session or create new session file
@@ -530,7 +540,7 @@ def wsgibase(environ, responder):
except HTTP, http_response: except HTTP, http_response:
if static_file: if static_file:
return http_response.to(responder,env=env) return http_response.to(responder, env=env)
if request.body: if request.body:
request.body.close() request.body.close()
@@ -562,9 +572,9 @@ def wsgibase(environ, responder):
if request.cid: if request.cid:
if response.flash: if response.flash:
http_response.headers['web2py-component-flash'] = urllib2.quote(xmlescape(response.flash).replace('\n','')) http_response.headers['web2py-component-flash'] = urllib2.quote(xmlescape(response.flash).replace('\n', ''))
if response.js: if response.js:
http_response.headers['web2py-component-command'] = response.js.replace('\n','') http_response.headers['web2py-component-command'] = response.js.replace('\n', '')
# ################################################## # ##################################################
# store cookies in headers # store cookies in headers
@@ -576,7 +586,7 @@ def wsgibase(environ, responder):
elif session._secure: elif session._secure:
rcookies[response.session_id_name]['secure'] = True rcookies[response.session_id_name]['secure'] = True
http_response.cookies2headers(rcookies) http_response.cookies2headers(rcookies)
ticket=None ticket = None
except RestrictedError, e: except RestrictedError, e:
@@ -594,8 +604,8 @@ def wsgibase(environ, responder):
BaseAdapter.close_all_instances('rollback') BaseAdapter.close_all_instances('rollback')
http_response = \ http_response = \
HTTP(500, rwthread.routes.error_message_ticket % \ HTTP(500, rwthread.routes.error_message_ticket %
dict(ticket=ticket), dict(ticket=ticket),
web2py_error='ticket %s' % ticket) web2py_error='ticket %s' % ticket)
except: except:
@@ -617,8 +627,8 @@ def wsgibase(environ, responder):
e = RestrictedError('Framework', '', '', locals()) e = RestrictedError('Framework', '', '', locals())
ticket = e.log(request) or 'unrecoverable' ticket = e.log(request) or 'unrecoverable'
http_response = \ http_response = \
HTTP(500, rwthread.routes.error_message_ticket \ HTTP(500, rwthread.routes.error_message_ticket
% dict(ticket=ticket), % dict(ticket=ticket),
web2py_error='ticket %s' % ticket) web2py_error='ticket %s' % ticket)
finally: finally:
@@ -630,10 +640,10 @@ def wsgibase(environ, responder):
http_response, new_environ = try_rewrite_on_error( http_response, new_environ = try_rewrite_on_error(
http_response, request, environ, ticket) http_response, request, environ, ticket)
if not http_response: if not http_response:
return wsgibase(new_environ,responder) return wsgibase(new_environ, responder)
if global_settings.web2py_crontype == 'soft': if global_settings.web2py_crontype == 'soft':
newcron.softcron(global_settings.applications_parent).start() newcron.softcron(global_settings.applications_parent).start()
return http_response.to(responder,env=env) return http_response.to(responder, env=env)
def save_password(password, port): def save_password(password, port):
@@ -712,14 +722,14 @@ def appfactory(wsgiapp=wsgibase,
locker.acquire() locker.acquire()
cProfile.runctx('ret[0] = wsgiapp(environ, responder2)', cProfile.runctx('ret[0] = wsgiapp(environ, responder2)',
globals(), locals(), profilerfilename+'.tmp') globals(), locals(), profilerfilename + '.tmp')
stat = pstats.Stats(profilerfilename+'.tmp') stat = pstats.Stats(profilerfilename + '.tmp')
stat.stream = cStringIO.StringIO() stat.stream = cStringIO.StringIO()
stat.strip_dirs().sort_stats("time").print_stats(80) stat.strip_dirs().sort_stats("time").print_stats(80)
profile_out = stat.stream.getvalue() profile_out = stat.stream.getvalue()
profile_file = open(profilerfilename, 'a') profile_file = open(profilerfilename, 'a')
profile_file.write('%s\n%s\n%s\n%s\n\n' % \ profile_file.write('%s\n%s\n%s\n%s\n\n' %
('='*60, environ['PATH_INFO'], '='*60, profile_out)) ('=' * 60, environ['PATH_INFO'], '=' * 60, profile_out))
profile_file.close() profile_file.close()
locker.release() locker.release()
try: try:
@@ -731,7 +741,7 @@ def appfactory(wsgiapp=wsgibase,
environ['SERVER_PROTOCOL'], environ['SERVER_PROTOCOL'],
(status_headers[0])[:3], (status_headers[0])[:3],
time.time() - time_in, time.time() - time_in,
) )
if not logfilename: if not logfilename:
sys.stdout.write(line) sys.stdout.write(line)
elif isinstance(logfilename, str): elif isinstance(logfilename, str):
@@ -766,11 +776,11 @@ class HttpServer(object):
server_name=None, server_name=None,
request_queue_size=5, request_queue_size=5,
timeout=10, timeout=10,
socket_timeout = 1, socket_timeout=1,
shutdown_timeout=None, # Rocket does not use a shutdown timeout shutdown_timeout=None, # Rocket does not use a shutdown timeout
path=None, path=None,
interfaces=None # Rocket is able to use several interfaces - must be list of socket-tuples as string interfaces=None # Rocket is able to use several interfaces - must be list of socket-tuples as string
): ):
""" """
starts the web server. starts the web server.
""" """
@@ -779,9 +789,9 @@ class HttpServer(object):
# if interfaces is specified, it must be tested for rocket parameter correctness # if interfaces is specified, it must be tested for rocket parameter correctness
# not necessarily completely tested (e.g. content of tuples or ip-format) # not necessarily completely tested (e.g. content of tuples or ip-format)
import types import types
if isinstance(interfaces,types.ListType): if isinstance(interfaces, types.ListType):
for i in interfaces: for i in interfaces:
if not isinstance(i,types.TupleType): if not isinstance(i, types.TupleType):
raise "Wrong format for rocket interfaces parameter - see http://packages.python.org/rocket/" raise "Wrong format for rocket interfaces parameter - see http://packages.python.org/rocket/"
else: else:
raise "Wrong format for rocket interfaces parameter - see http://packages.python.org/rocket/" raise "Wrong format for rocket interfaces parameter - see http://packages.python.org/rocket/"
@@ -822,7 +832,7 @@ class HttpServer(object):
logger.info('SSL is ON') logger.info('SSL is ON')
app_info = {'wsgi_app': appfactory(wsgibase, app_info = {'wsgi_app': appfactory(wsgibase,
log_filename, log_filename,
profiler_filename) } profiler_filename)}
self.server = rocket.Rocket(interfaces or tuple(sock_list), self.server = rocket.Rocket(interfaces or tuple(sock_list),
method='wsgi', method='wsgi',
@@ -834,7 +844,6 @@ class HttpServer(object):
handle_signals=False, handle_signals=False,
) )
def start(self): def start(self):
""" """
start the web server start the web server
+1
View File
@@ -5,6 +5,7 @@ try:
except: except:
tkMessageBox = None tkMessageBox = None
class MessageBoxHandler(logging.Handler): class MessageBoxHandler(logging.Handler):
def __init__(self): def __init__(self):
logging.Handler.__init__(self) logging.Handler.__init__(self)
+5 -5
View File
@@ -11,18 +11,18 @@ import re
# pattern to find defined tables # pattern to find defined tables
regex_tables = re.compile(\ regex_tables = re.compile(
"""^[\w]+\.define_table\(\s*[\'\"](?P<name>\w+)[\'\"]""", """^[\w]+\.define_table\(\s*[\'\"](?P<name>\w+)[\'\"]""",
flags=re.M) flags=re.M)
# pattern to find exposed functions in controller # pattern to find exposed functions in controller
regex_expose = re.compile(\ regex_expose = re.compile(
'^def\s+(?P<name>(?:[a-zA-Z0-9]\w*)|(?:_[a-zA-Z0-9]\w*))\(\)\s*:', '^def\s+(?P<name>(?:[a-zA-Z0-9]\w*)|(?:_[a-zA-Z0-9]\w*))\(\)\s*:',
flags=re.M) flags=re.M)
regex_include = re.compile(\ regex_include = re.compile(
'(?P<all>\{\{\s*include\s+[\'"](?P<name>[^\'"]*)[\'"]\s*\}\})') '(?P<all>\{\{\s*include\s+[\'"](?P<name>[^\'"]*)[\'"]\s*\}\})')
regex_extend = re.compile(\ regex_extend = re.compile(
'^\s*(?P<all>\{\{\s*extend\s+[\'"](?P<name>[^\'"]+)[\'"]\s*\}\})',re.MULTILINE) '^\s*(?P<all>\{\{\s*extend\s+[\'"](?P<name>[^\'"]+)[\'"]\s*\}\})', re.MULTILINE)
+60 -51
View File
@@ -23,6 +23,7 @@ from settings import global_settings
logger = logging.getLogger("web2py.cron") logger = logging.getLogger("web2py.cron")
_cron_stopping = False _cron_stopping = False
def absolute_path_link(path): def absolute_path_link(path):
""" """
Return an absolute path for the destination of a symlink Return an absolute path for the destination of a symlink
@@ -36,11 +37,13 @@ def absolute_path_link(path):
link = os.path.abspath(path) link = os.path.abspath(path)
return link return link
def stopcron(): def stopcron():
"graceful shutdown of cron" "graceful shutdown of cron"
global _cron_stopping global _cron_stopping
_cron_stopping = True _cron_stopping = True
class extcron(threading.Thread): class extcron(threading.Thread):
def __init__(self, applications_parent, apps=None): def __init__(self, applications_parent, apps=None):
@@ -55,6 +58,7 @@ class extcron(threading.Thread):
logger.debug('external cron invocation') logger.debug('external cron invocation')
crondance(self.path, 'external', startup=False, apps=self.apps) crondance(self.path, 'external', startup=False, apps=self.apps)
class hardcron(threading.Thread): class hardcron(threading.Thread):
def __init__(self, applications_parent): def __init__(self, applications_parent):
@@ -66,7 +70,7 @@ class hardcron(threading.Thread):
def launch(self): def launch(self):
if not _cron_stopping: if not _cron_stopping:
logger.debug('hard cron invocation') logger.debug('hard cron invocation')
crondance(self.path, 'hard', startup = False) crondance(self.path, 'hard', startup=False)
def run(self): def run(self):
s = sched.scheduler(time.time, time.sleep) s = sched.scheduler(time.time, time.sleep)
@@ -76,6 +80,7 @@ class hardcron(threading.Thread):
s.enter(60 - now % 60, 1, self.launch, ()) s.enter(60 - now % 60, 1, self.launch, ())
s.run() s.run()
class softcron(threading.Thread): class softcron(threading.Thread):
def __init__(self, applications_parent): def __init__(self, applications_parent):
@@ -88,16 +93,17 @@ class softcron(threading.Thread):
logger.debug('soft cron invocation') logger.debug('soft cron invocation')
crondance(self.path, 'soft', startup=False) crondance(self.path, 'soft', startup=False)
class Token(object): class Token(object):
def __init__(self,path): def __init__(self, path):
self.path = os.path.join(path, 'cron.master') self.path = os.path.join(path, 'cron.master')
if not os.path.exists(self.path): if not os.path.exists(self.path):
fileutils.write_file(self.path, '', 'wb') fileutils.write_file(self.path, '', 'wb')
self.master = None self.master = None
self.now = time.time() self.now = time.time()
def acquire(self,startup=False): def acquire(self, startup=False):
""" """
returns the time when the lock is acquired or returns the time when the lock is acquired or
None if cron already running None if cron already running
@@ -112,12 +118,12 @@ class Token(object):
if portalocker.LOCK_EX is None: if portalocker.LOCK_EX is None:
logger.warning('WEB2PY CRON: Disabled because no file locking') logger.warning('WEB2PY CRON: Disabled because no file locking')
return None return None
self.master = open(self.path,'rb+') self.master = open(self.path, 'rb+')
try: try:
ret = None ret = None
portalocker.lock(self.master,portalocker.LOCK_EX) portalocker.lock(self.master, portalocker.LOCK_EX)
try: try:
(start, stop) = cPickle.load(self.master) (start, stop) = cPickle.load(self.master)
except: except:
(start, stop) = (0, 1) (start, stop) = (0, 1)
if startup or self.now - start > 59.99: if startup or self.now - start > 59.99:
@@ -127,7 +133,7 @@ class Token(object):
logger.warning('WEB2PY CRON: Stale cron.master detected') logger.warning('WEB2PY CRON: Stale cron.master detected')
logger.debug('WEB2PY CRON: Acquiring lock') logger.debug('WEB2PY CRON: Acquiring lock')
self.master.seek(0) self.master.seek(0)
cPickle.dump((self.now,0),self.master) cPickle.dump((self.now, 0), self.master)
finally: finally:
portalocker.unlock(self.master) portalocker.unlock(self.master)
if not ret: if not ret:
@@ -141,13 +147,13 @@ class Token(object):
was completed was completed
""" """
if not self.master.closed: if not self.master.closed:
portalocker.lock(self.master,portalocker.LOCK_EX) portalocker.lock(self.master, portalocker.LOCK_EX)
logger.debug('WEB2PY CRON: Releasing cron lock') logger.debug('WEB2PY CRON: Releasing cron lock')
self.master.seek(0) self.master.seek(0)
(start, stop) = cPickle.load(self.master) (start, stop) = cPickle.load(self.master)
if start == self.now: # if this is my lock if start == self.now: # if this is my lock
self.master.seek(0) self.master.seek(0)
cPickle.dump((self.now,time.time()),self.master) cPickle.dump((self.now, time.time()), self.master)
portalocker.unlock(self.master) portalocker.unlock(self.master)
self.master.close() self.master.close()
@@ -177,25 +183,26 @@ def rangetolist(s, period='min'):
def parsecronline(line): def parsecronline(line):
task = {} task = {}
if line.startswith('@reboot'): if line.startswith('@reboot'):
line=line.replace('@reboot', '-1 * * * *') line = line.replace('@reboot', '-1 * * * *')
elif line.startswith('@yearly'): elif line.startswith('@yearly'):
line=line.replace('@yearly', '0 0 1 1 *') line = line.replace('@yearly', '0 0 1 1 *')
elif line.startswith('@annually'): elif line.startswith('@annually'):
line=line.replace('@annually', '0 0 1 1 *') line = line.replace('@annually', '0 0 1 1 *')
elif line.startswith('@monthly'): elif line.startswith('@monthly'):
line=line.replace('@monthly', '0 0 1 * *') line = line.replace('@monthly', '0 0 1 * *')
elif line.startswith('@weekly'): elif line.startswith('@weekly'):
line=line.replace('@weekly', '0 0 * * 0') line = line.replace('@weekly', '0 0 * * 0')
elif line.startswith('@daily'): elif line.startswith('@daily'):
line=line.replace('@daily', '0 0 * * *') line = line.replace('@daily', '0 0 * * *')
elif line.startswith('@midnight'): elif line.startswith('@midnight'):
line=line.replace('@midnight', '0 0 * * *') line = line.replace('@midnight', '0 0 * * *')
elif line.startswith('@hourly'): elif line.startswith('@hourly'):
line=line.replace('@hourly', '0 * * * *') line = line.replace('@hourly', '0 * * * *')
params = line.strip().split(None, 6) params = line.strip().split(None, 6)
if len(params) < 7: if len(params) < 7:
return None return None
daysofweek={'sun':0,'mon':1,'tue':2,'wed':3,'thu':4,'fri':5,'sat':6} daysofweek = {'sun': 0, 'mon': 1, 'tue': 2, 'wed': 3, 'thu': 4,
'fri': 5, 'sat': 6}
for (s, id) in zip(params[:5], ['min', 'hr', 'dom', 'mon', 'dow']): for (s, id) in zip(params[:5], ['min', 'hr', 'dom', 'mon', 'dow']):
if not s in [None, '*']: if not s in [None, '*']:
task[id] = [] task[id] = []
@@ -205,9 +212,9 @@ def parsecronline(line):
val = '%s/1' % val val = '%s/1' % val
if '/' in val: if '/' in val:
task[id] += rangetolist(val, id) task[id] += rangetolist(val, id)
elif val.isdigit() or val=='-1': elif val.isdigit() or val == '-1':
task[id].append(int(val)) task[id].append(int(val))
elif id=='dow' and val[:3].lower() in daysofweek: elif id == 'dow' and val[:3].lower() in daysofweek:
task[id].append(daysofweek(val[:3].lower())) task[id].append(daysofweek(val[:3].lower()))
task['user'] = params[5] task['user'] = params[5]
task['cmd'] = params[6] task['cmd'] = params[6]
@@ -225,7 +232,7 @@ class cronlauncher(threading.Thread):
def run(self): def run(self):
import subprocess import subprocess
if isinstance(self.cmd, (list,tuple)): if isinstance(self.cmd, (list, tuple)):
cmd = self.cmd cmd = self.cmd
else: else:
cmd = self.cmd.split() cmd = self.cmd.split()
@@ -234,28 +241,29 @@ class cronlauncher(threading.Thread):
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, stderr=subprocess.PIPE,
shell=self.shell) shell=self.shell)
(stdoutdata,stderrdata) = proc.communicate() (stdoutdata, stderrdata) = proc.communicate()
if proc.returncode != 0: if proc.returncode != 0:
logger.warning( logger.warning(
'WEB2PY CRON Call returned code %s:\n%s' % \ 'WEB2PY CRON Call returned code %s:\n%s' %
(proc.returncode, stdoutdata+stderrdata)) (proc.returncode, stdoutdata + stderrdata))
else: else:
logger.debug('WEB2PY CRON Call returned success:\n%s' \ logger.debug('WEB2PY CRON Call returned success:\n%s'
% stdoutdata) % stdoutdata)
def crondance(applications_parent, ctype='soft', startup=False, apps=None): def crondance(applications_parent, ctype='soft', startup=False, apps=None):
apppath = os.path.join(applications_parent,'applications') apppath = os.path.join(applications_parent, 'applications')
cron_path = os.path.join(applications_parent) cron_path = os.path.join(applications_parent)
token = Token(cron_path) token = Token(cron_path)
cronmaster = token.acquire(startup=startup) cronmaster = token.acquire(startup=startup)
if not cronmaster: if not cronmaster:
return return
now_s = time.localtime() now_s = time.localtime()
checks=(('min',now_s.tm_min), checks = (('min', now_s.tm_min),
('hr',now_s.tm_hour), ('hr', now_s.tm_hour),
('mon',now_s.tm_mon), ('mon', now_s.tm_mon),
('dom',now_s.tm_mday), ('dom', now_s.tm_mday),
('dow',(now_s.tm_wday+1)%7)) ('dow', (now_s.tm_wday + 1) % 7))
if apps is None: if apps is None:
apps = [x for x in os.listdir(apppath) apps = [x for x in os.listdir(apppath)
@@ -265,8 +273,8 @@ def crondance(applications_parent, ctype='soft', startup=False, apps=None):
for app in apps: for app in apps:
if _cron_stopping: if _cron_stopping:
break; break
apath = os.path.join(apppath,app) apath = os.path.join(apppath, app)
# if app is a symbolic link to other app, skip it # if app is a symbolic link to other app, skip it
full_apath_link = absolute_path_link(apath) full_apath_link = absolute_path_link(apath)
@@ -281,7 +289,8 @@ def crondance(applications_parent, ctype='soft', startup=False, apps=None):
continue continue
try: try:
cronlines = fileutils.readlines_file(crontab, 'rt') cronlines = fileutils.readlines_file(crontab, 'rt')
lines = [x.strip() for x in cronlines if x.strip() and not x.strip().startswith('#')] lines = [x.strip() for x in cronlines if x.strip(
) and not x.strip().startswith('#')]
tasks = [parsecronline(cline) for cline in lines] tasks = [parsecronline(cline) for cline in lines]
except Exception, e: except Exception, e:
logger.error('WEB2PY CRON: crontab read error %s' % e) logger.error('WEB2PY CRON: crontab read error %s' % e)
@@ -289,42 +298,42 @@ def crondance(applications_parent, ctype='soft', startup=False, apps=None):
for task in tasks: for task in tasks:
if _cron_stopping: if _cron_stopping:
break; break
commands = [sys.executable] commands = [sys.executable]
w2p_path = fileutils.abspath('web2py.py', gluon=True) w2p_path = fileutils.abspath('web2py.py', gluon=True)
if os.path.exists(w2p_path): if os.path.exists(w2p_path):
commands.append(w2p_path) commands.append(w2p_path)
if global_settings.applications_parent != global_settings.gluon_parent: if global_settings.applications_parent != global_settings.gluon_parent:
commands.extend(('-f', global_settings.applications_parent)) commands.extend(('-f', global_settings.applications_parent))
citems = [(k in task and not v in task[k]) for k,v in checks] citems = [(k in task and not v in task[k]) for k, v in checks]
task_min= task.get('min',[]) task_min = task.get('min', [])
if not task: if not task:
continue continue
elif not startup and task_min == [-1]: elif not startup and task_min == [-1]:
continue continue
elif task_min != [-1] and reduce(lambda a,b: a or b, citems): elif task_min != [-1] and reduce(lambda a, b: a or b, citems):
continue continue
logger.info('WEB2PY CRON (%s): %s executing %s in %s at %s' \ logger.info('WEB2PY CRON (%s): %s executing %s in %s at %s'
% (ctype, app, task.get('cmd'), % (ctype, app, task.get('cmd'),
os.getcwd(), datetime.datetime.now())) os.getcwd(), datetime.datetime.now()))
action, command, models = False, task['cmd'], '' action, command, models = False, task['cmd'], ''
if command.startswith('**'): if command.startswith('**'):
(action,models,command) = (True,'',command[2:]) (action, models, command) = (True, '', command[2:])
elif command.startswith('*'): elif command.startswith('*'):
(action,models,command) = (True,'-M',command[1:]) (action, models, command) = (True, '-M', command[1:])
else: else:
action=False action = False
if action and command.endswith('.py'): if action and command.endswith('.py'):
commands.extend(('-J', # cron job commands.extend(('-J', # cron job
models, # import models? models, # import models?
'-S', app, # app name '-S', app, # app name
'-a', '"<recycle>"', # password '-a', '"<recycle>"', # password
'-R', command)) # command '-R', command)) # command
elif action: elif action:
commands.extend(('-J', # cron job commands.extend(('-J', # cron job
models, # import models? models, # import models?
'-S', app+'/'+command, # app name '-S', app + '/' + command, # app name
'-a', '"<recycle>"')) # password '-a', '"<recycle>"')) # password
else: else:
commands = command commands = command
@@ -338,6 +347,6 @@ def crondance(applications_parent, ctype='soft', startup=False, apps=None):
cronlauncher(commands, shell=shell).start() cronlauncher(commands, shell=shell).start()
except Exception, e: except Exception, e:
logger.warning( logger.warning(
'WEB2PY CRON: Execution error for %s: %s' \ 'WEB2PY CRON: Execution error for %s: %s'
% (task.get('cmd'), e)) % (task.get('cmd'), e))
token.release() token.release()
+20 -12
View File
@@ -110,54 +110,62 @@ else:
class LockedFile(object): class LockedFile(object):
def __init__(self,filename, mode='rb'): def __init__(self, filename, mode='rb'):
self.filename = filename self.filename = filename
self.mode = mode self.mode = mode
self.file = None self.file = None
if 'r' in mode: if 'r' in mode:
self.file = open(filename,mode) self.file = open(filename, mode)
lock(self.file,LOCK_SH) lock(self.file, LOCK_SH)
elif 'w' in mode or 'a' in mode: elif 'w' in mode or 'a' in mode:
self.file = open(filename,mode.replace('w','a')) self.file = open(filename, mode.replace('w', 'a'))
lock(self.file,LOCK_EX) lock(self.file, LOCK_EX)
if not 'a' in mode: if not 'a' in mode:
self.file.seek(0) self.file.seek(0)
self.file.truncate() self.file.truncate()
else: else:
raise RuntimeError, "invalid LockedFile(...,mode)" raise RuntimeError("invalid LockedFile(...,mode)")
def read(self,size=None):
def read(self, size=None):
return self.file.read() if size is None else self.file.read(size) return self.file.read() if size is None else self.file.read(size)
def readline(self): def readline(self):
return self.file.readline() return self.file.readline()
def readlines(self): def readlines(self):
return self.file.readlines() return self.file.readlines()
def write(self,data):
def write(self, data):
self.file.write(data) self.file.write(data)
self.file.flush() self.file.flush()
def close(self): def close(self):
if not self.file is None: if not self.file is None:
unlock(self.file) unlock(self.file)
self.file.close() self.file.close()
self.file = None self.file = None
def __del__(self): def __del__(self):
if not self.file is None: if not self.file is None:
self.close() self.close()
def read_locked(filename): def read_locked(filename):
fp = LockedFile(filename, 'r') fp = LockedFile(filename, 'r')
data = fp.read() data = fp.read()
fp.close() fp.close()
return data return data
def write_locked(filename,data):
def write_locked(filename, data):
fp = LockedFile(filename, 'w') fp = LockedFile(filename, 'w')
data = fp.write(data) data = fp.write(data)
fp.close() fp.close()
if __name__=='__main__': if __name__ == '__main__':
f = LockedFile('test.txt',mode='wb') f = LockedFile('test.txt', mode='wb')
f.write('test ok') f.write('test ok')
f.close() f.close()
f = LockedFile('test.txt',mode='rb') f = LockedFile('test.txt', mode='rb')
print f.read() print f.read()
f.close() f.close()
+11 -10
View File
@@ -419,7 +419,7 @@ POSTGRESQL_NONRESERVED = set((
'HOLD', 'HOLD',
'HOST', 'HOST',
'HOUR', 'HOUR',
# 'ID', # 'ID',
'IDENTITY', 'IDENTITY',
'IF', 'IF',
'IGNORE', 'IGNORE',
@@ -501,7 +501,7 @@ POSTGRESQL_NONRESERVED = set((
'MOVE', 'MOVE',
'MULTISET', 'MULTISET',
'MUMPS', 'MUMPS',
# 'NAME', # 'NAME',
'NAMES', 'NAMES',
'NAMESPACE', 'NAMESPACE',
'NCLOB', 'NCLOB',
@@ -563,7 +563,7 @@ POSTGRESQL_NONRESERVED = set((
'PARTITION', 'PARTITION',
'PASCAL', 'PASCAL',
'PASSING', 'PASSING',
# 'PASSWORD', # 'PASSWORD',
'PATH', 'PATH',
'PERCENT_RANK', 'PERCENT_RANK',
'PERCENTILE_CONT', 'PERCENTILE_CONT',
@@ -622,7 +622,7 @@ POSTGRESQL_NONRESERVED = set((
'RETURNED_SQLSTATE', 'RETURNED_SQLSTATE',
'RETURNS', 'RETURNS',
'REVOKE', 'REVOKE',
# 'ROLE', # 'ROLE',
'ROLLBACK', 'ROLLBACK',
'ROLLUP', 'ROLLUP',
'ROUTINE', 'ROUTINE',
@@ -697,7 +697,7 @@ POSTGRESQL_NONRESERVED = set((
'SYSTEM', 'SYSTEM',
'SYSTEM_USER', 'SYSTEM_USER',
'T', 'T',
# 'TABLE_NAME', # 'TABLE_NAME',
'TABLESAMPLE', 'TABLESAMPLE',
'TABLESPACE', 'TABLESPACE',
'TEMP', 'TEMP',
@@ -785,7 +785,7 @@ POSTGRESQL_NONRESERVED = set((
'YEAR', 'YEAR',
'YES', 'YES',
'ZONE', 'ZONE',
)) ))
#Thanks villas #Thanks villas
FIREBIRD = set(( FIREBIRD = set((
@@ -887,7 +887,7 @@ FIREBIRD = set((
'PAGES', 'PAGES',
'PAGE_SIZE', 'PAGE_SIZE',
'PARAMETER', 'PARAMETER',
# 'PASSWORD', # 'PASSWORD',
'PLAN', 'PLAN',
'POST_EVENT', 'POST_EVENT',
'QUIT', 'QUIT',
@@ -903,7 +903,7 @@ FIREBIRD = set((
'RETURN', 'RETURN',
'RETURNING_VALUES', 'RETURNING_VALUES',
'RETURNS', 'RETURNS',
# 'ROLE', # 'ROLE',
'ROW_COUNT', 'ROW_COUNT',
'ROWS', 'ROWS',
'RUNTIME', 'RUNTIME',
@@ -941,7 +941,7 @@ FIREBIRD = set((
'WEEKDAY', 'WEEKDAY',
'WHILE', 'WHILE',
'YEARDAY', 'YEARDAY',
)) ))
FIREBIRD_NONRESERVED = set(( FIREBIRD_NONRESERVED = set((
'BACKUP', 'BACKUP',
'BLOCK', 'BLOCK',
@@ -1710,4 +1710,5 @@ ADAPTERS = {
'common': COMMON, 'common': COMMON,
} }
ADAPTERS['all'] = reduce(lambda a,b:a.union(b),(x for x in ADAPTERS.values())) ADAPTERS['all'] = reduce(lambda a, b: a.union(b), (
x for x in ADAPTERS.values()))
+46 -29
View File
@@ -22,6 +22,7 @@ logger = logging.getLogger("web2py")
__all__ = ['RestrictedError', 'restricted', 'TicketStorage', 'compile2'] __all__ = ['RestrictedError', 'restricted', 'TicketStorage', 'compile2']
class TicketStorage(Storage): class TicketStorage(Storage):
""" """
@@ -32,7 +33,7 @@ class TicketStorage(Storage):
self, self,
db=None, db=None,
tablename='web2py_ticket' tablename='web2py_ticket'
): ):
Storage.__init__(self) Storage.__init__(self)
self.db = db self.db = db
self.tablename = tablename self.tablename = tablename
@@ -64,7 +65,8 @@ class TicketStorage(Storage):
root = request.folder root = request.folder
if app: if app:
root = os.path.join(os.path.join(root, '..'), app) root = os.path.join(os.path.join(root, '..'), app)
errors_folder = os.path.abspath(os.path.join(root, 'errors'))#.replace('\\', '/') errors_folder = os.path.abspath(
os.path.join(root, 'errors')) # .replace('\\', '/')
return open(os.path.join(errors_folder, ticket_id), mode) return open(os.path.join(errors_folder, ticket_id), mode)
def _get_table(self, db, tablename, app): def _get_table(self, db, tablename, app):
@@ -78,7 +80,7 @@ class TicketStorage(Storage):
db.Field('ticket_id', length=100), db.Field('ticket_id', length=100),
db.Field('ticket_data', 'text'), db.Field('ticket_data', 'text'),
db.Field('created_datetime', 'datetime'), db.Field('created_datetime', 'datetime'),
) )
return table return table
def load( def load(
@@ -86,7 +88,7 @@ class TicketStorage(Storage):
request, request,
app, app,
ticket_id, ticket_id,
): ):
if not self.db: if not self.db:
try: try:
ef = self._error_file(request, ticket_id, 'rb', app) ef = self._error_file(request, ticket_id, 'rb', app)
@@ -102,7 +104,6 @@ class TicketStorage(Storage):
return cPickle.loads(rows[0].ticket_data) if rows else {} return cPickle.loads(rows[0].ticket_data) if rows else {}
class RestrictedError(Exception): class RestrictedError(Exception):
""" """
class used to wrap an exception that occurs in the restricted environment class used to wrap an exception that occurs in the restricted environment
@@ -115,12 +116,13 @@ class RestrictedError(Exception):
code='', code='',
output='', output='',
environment=None, environment=None,
): ):
""" """
layer here is some description of where in the system the exception layer here is some description of where in the system the exception
occurred. occurred.
""" """
if environment is None: environment = {} if environment is None:
environment = {}
self.layer = layer self.layer = layer
self.code = code self.code = code
self.output = output self.output = output
@@ -131,7 +133,7 @@ class RestrictedError(Exception):
except: except:
self.traceback = 'no traceback because template parting error' self.traceback = 'no traceback because template parting error'
try: try:
self.snapshot = snapshot(context=10,code=code, self.snapshot = snapshot(context=10, code=code,
environment=self.environment) environment=self.environment)
except: except:
self.snapshot = {} self.snapshot = {}
@@ -151,15 +153,14 @@ class RestrictedError(Exception):
'output': str(self.output), 'output': str(self.output),
'traceback': str(self.traceback), 'traceback': str(self.traceback),
'snapshot': self.snapshot, 'snapshot': self.snapshot,
} }
ticket_storage = TicketStorage(db=request.tickets_db) ticket_storage = TicketStorage(db=request.tickets_db)
ticket_storage.store(request, request.uuid.split('/',1)[1], d) ticket_storage.store(request, request.uuid.split('/', 1)[1], d)
return request.uuid return request.uuid
except: except:
logger.error(self.traceback) logger.error(self.traceback)
return None return None
def load(self, request, app, ticket_id): def load(self, request, app, ticket_id):
""" """
loads a logged exception. loads a logged exception.
@@ -186,11 +187,12 @@ class RestrictedError(Exception):
return output return output
def compile2(code,layer): def compile2(code, layer):
""" """
The +'\n' is necessary else compile fails when code ends in a comment. The +'\n' is necessary else compile fails when code ends in a comment.
""" """
return compile(code.rstrip().replace('\r\n','\n')+'\n', layer, 'exec') return compile(code.rstrip().replace('\r\n', '\n') + '\n', layer, 'exec')
def restricted(code, environment=None, layer='Unknown'): def restricted(code, environment=None, layer='Unknown'):
""" """
@@ -198,14 +200,15 @@ def restricted(code, environment=None, layer='Unknown'):
in code it raises a RestrictedError containing the traceback. layer is in code it raises a RestrictedError containing the traceback. layer is
passed to RestrictedError to identify where the error occurred. passed to RestrictedError to identify where the error occurred.
""" """
if environment is None: environment = {} if environment is None:
environment = {}
environment['__file__'] = layer environment['__file__'] = layer
environment['__name__'] = '__restricted__' environment['__name__'] = '__restricted__'
try: try:
if type(code) == types.CodeType: if isinstance(code, types.CodeType):
ccode = code ccode = code
else: else:
ccode = compile2(code,layer) ccode = compile2(code, layer)
exec ccode in environment exec ccode in environment
except HTTP: except HTTP:
raise raise
@@ -221,14 +224,21 @@ def restricted(code, environment=None, layer='Unknown'):
output = "%s %s" % (etype, evalue) output = "%s %s" % (etype, evalue)
raise RestrictedError(layer, code, output, environment) raise RestrictedError(layer, code, output, environment)
def snapshot(info=None, context=5, code=None, environment=None): def snapshot(info=None, context=5, code=None, environment=None):
"""Return a dict describing a given traceback (based on cgitb.text).""" """Return a dict describing a given traceback (based on cgitb.text)."""
import os, types, time, linecache, inspect, pydoc, cgitb import os
import types
import time
import linecache
import inspect
import pydoc
import cgitb
# if no exception info given, get current: # if no exception info given, get current:
etype, evalue, etb = info or sys.exc_info() etype, evalue, etb = info or sys.exc_info()
if type(etype) is types.ClassType: if isinstance(etype, types.ClassType):
etype = etype.__name__ etype = etype.__name__
# create a snapshot dict with some basic information # create a snapshot dict with some basic information
@@ -245,22 +255,26 @@ def snapshot(info=None, context=5, code=None, environment=None):
call = '' call = ''
if func != '?': if func != '?':
call = inspect.formatargvalues(args, varargs, varkw, locals, call = inspect.formatargvalues(args, varargs, varkw, locals,
formatvalue=lambda value: '=' + pydoc.text.repr(value)) formatvalue=lambda value: '=' + pydoc.text.repr(value))
# basic frame information # basic frame information
f = {'file': file, 'func': func, 'call': call, 'lines': {}, 'lnum': lnum} f = {'file': file, 'func': func, 'call': call, 'lines': {},
'lnum': lnum}
highlight = {} highlight = {}
def reader(lnum=[lnum]): def reader(lnum=[lnum]):
highlight[lnum[0]] = 1 highlight[lnum[0]] = 1
try: return linecache.getline(file, lnum[0]) try:
finally: lnum[0] += 1 return linecache.getline(file, lnum[0])
finally:
lnum[0] += 1
vars = cgitb.scanvars(reader, frame, locals) vars = cgitb.scanvars(reader, frame, locals)
# if it is a view, replace with generated code # if it is a view, replace with generated code
if file.endswith('html'): if file.endswith('html'):
lmin = lnum>context and (lnum-context) or 0 lmin = lnum > context and (lnum - context) or 0
lmax = lnum+context lmax = lnum + context
lines = code.split("\n")[lmin:lmax] lines = code.split("\n")[lmin:lmax]
index = min(context, lnum) - 1 index = min(context, lnum) - 1
@@ -273,10 +287,13 @@ def snapshot(info=None, context=5, code=None, environment=None):
# dump local variables (referenced in current line only) # dump local variables (referenced in current line only)
f['dump'] = {} f['dump'] = {}
for name, where, value in vars: for name, where, value in vars:
if name in f['dump']: continue if name in f['dump']:
continue
if value is not cgitb.__UNDEF__: if value is not cgitb.__UNDEF__:
if where == 'global': name = 'global ' + name if where == 'global':
elif where != 'local': name = where + name.split('.')[-1] name = 'global ' + name
elif where != 'local':
name = where + name.split('.')[-1]
f['dump'][name] = pydoc.text.repr(value) f['dump'][name] = pydoc.text.repr(value)
else: else:
f['dump'][name] = 'undefined' f['dump'][name] = 'undefined'
@@ -290,7 +307,7 @@ def snapshot(info=None, context=5, code=None, environment=None):
if isinstance(evalue, BaseException): if isinstance(evalue, BaseException):
for name in dir(evalue): for name in dir(evalue):
# prevent py26 DeprecatedWarning: # prevent py26 DeprecatedWarning:
if name!='message' or sys.version_info<(2.6): if name != 'message' or sys.version_info < (2.6):
value = pydoc.text.repr(getattr(evalue, name)) value = pydoc.text.repr(getattr(evalue, name))
s['exception'][name] = value s['exception'][name] = value
@@ -300,7 +317,7 @@ def snapshot(info=None, context=5, code=None, environment=None):
s['locals'][name] = pydoc.text.repr(value) s['locals'][name] = pydoc.text.repr(value)
# add web2py environment variables # add web2py environment variables
for k,v in environment.items(): for k, v in environment.items():
if k in ('request', 'response', 'session'): if k in ('request', 'response', 'session'):
s[k] = BEAUTIFY(v) s[k] = BEAUTIFY(v)
+172 -127
View File
@@ -38,7 +38,8 @@ THREAD_LOCAL = threading.local() # thread-local storage for routing params
regex_at = re.compile(r'(?<!\\)\$[a-zA-Z]\w*') regex_at = re.compile(r'(?<!\\)\$[a-zA-Z]\w*')
regex_anything = re.compile(r'(?<!\\)\$anything') regex_anything = re.compile(r'(?<!\\)\$anything')
regex_redirect = re.compile(r'(\d+)->(.*)') regex_redirect = re.compile(r'(\d+)->(.*)')
regex_full_url = re.compile(r'^(?P<scheme>http|https|HTTP|HTTPS)\://(?P<host>[^/]*)(?P<uri>.*)') regex_full_url = re.compile(
r'^(?P<scheme>http|https|HTTP|HTTPS)\://(?P<host>[^/]*)(?P<uri>.*)')
regex_version = re.compile(r'^(_[\d]+\.[\d]+\.[\d]+)$') regex_version = re.compile(r'^(_[\d]+\.[\d]+\.[\d]+)$')
# pattern to replace spaces with underscore in URL # pattern to replace spaces with underscore in URL
# also the html escaped variants '+' and '%20' are covered # also the html escaped variants '+' and '%20' are covered
@@ -60,25 +61,26 @@ regex_url = re.compile('^/((?P<a>\w+)(/(?P<c>\w+)(/(?P<z>(?P<f>\w+)(\.(?P<e>[\w.
def _router_default(): def _router_default():
"return new copy of default base router" "return new copy of default base router"
router = Storage( router = Storage(
default_application = 'init', default_application='init',
applications = 'ALL', applications='ALL',
default_controller = 'default', default_controller='default',
controllers = 'DEFAULT', controllers='DEFAULT',
default_function = 'index', default_function='index',
functions = dict(), functions=dict(),
default_language = None, default_language=None,
languages = None, languages=None,
root_static = ['favicon.ico', 'robots.txt'], root_static=['favicon.ico', 'robots.txt'],
map_static = None, map_static=None,
domains = None, domains=None,
exclusive_domain = False, exclusive_domain=False,
map_hyphen = False, map_hyphen=False,
acfe_match = r'\w+$', # legal app/ctlr/fcn/ext acfe_match=r'\w+$', # legal app/ctlr/fcn/ext
file_match = r'([-+=@$%\w]+[./]?)+$', # legal static subpath file_match=r'([-+=@$%\w]+[./]?)+$', # legal static subpath
args_match = r'([\w@ -]+[=.]?)*$', # legal arg in args args_match=r'([\w@ -]+[=.]?)*$', # legal arg in args
) )
return router return router
def _params_default(app=None): def _params_default(app=None):
"return new copy of default parameters" "return new copy of default parameters"
p = Storage() p = Storage()
@@ -94,7 +96,7 @@ def _params_default(app=None):
p.error_handler = None p.error_handler = None
p.error_message = '<html><body><h1>%s</h1></body></html>' p.error_message = '<html><body><h1>%s</h1></body></html>'
p.error_message_ticket = \ p.error_message_ticket = \
'<html><body><h1>Internal error</h1>Ticket issued: <a href="/admin/default/ticket/%(ticket)s" target="_blank">%(ticket)s</a></body><!-- this is junk text else IE does not display the page: '+('x'*512)+' //--></html>' '<html><body><h1>Internal error</h1>Ticket issued: <a href="/admin/default/ticket/%(ticket)s" target="_blank">%(ticket)s</a></body><!-- this is junk text else IE does not display the page: ' + ('x' * 512) + ' //--></html>'
p.routers = None p.routers = None
p.logging = 'off' p.logging = 'off'
return p return p
@@ -104,6 +106,7 @@ params = _params_default(app=None) # regex rewrite parameters
THREAD_LOCAL.routes = params # default to base regex rewrite parameters THREAD_LOCAL.routes = params # default to base regex rewrite parameters
routers = None routers = None
def log_rewrite(string): def log_rewrite(string):
"Log rewrite activity under control of routes.py" "Log rewrite activity under control of routes.py"
if params.logging == 'debug': # catch common cases first if params.logging == 'debug': # catch common cases first
@@ -152,6 +155,7 @@ ROUTER_BASE_KEYS = set(
# filter_err: helper for doctest & unittest # filter_err: helper for doctest & unittest
# regex_filter_out: doctest # regex_filter_out: doctest
def fixup_missing_path_info(environ): def fixup_missing_path_info(environ):
eget = environ.get eget = environ.get
path_info = eget('PATH_INFO') path_info = eget('PATH_INFO')
@@ -165,12 +169,12 @@ def fixup_missing_path_info(environ):
elif not request_uri: elif not request_uri:
query_string = eget('QUERY_STRING') query_string = eget('QUERY_STRING')
if query_string: if query_string:
environ['REQUEST_URI'] = '%s?%s' % (path_info,query_string) environ['REQUEST_URI'] = '%s?%s' % (path_info, query_string)
else: else:
environ['REQUEST_URI'] = path_info environ['REQUEST_URI'] = path_info
if not eget('HTTP_HOST'): if not eget('HTTP_HOST'):
environ['HTTP_HOST'] = \ environ['HTTP_HOST'] = \
'%s:%s' % (eget('SERVER_NAME'),eget('SERVER_PORT')) '%s:%s' % (eget('SERVER_NAME'), eget('SERVER_PORT'))
def url_in(request, environ): def url_in(request, environ):
@@ -179,6 +183,7 @@ def url_in(request, environ):
return map_url_in(request, environ) return map_url_in(request, environ)
return regex_url_in(request, environ) return regex_url_in(request, environ)
def url_out(request, environ, application, controller, function, def url_out(request, environ, application, controller, function,
args, other, scheme, host, port): args, other, scheme, host, port):
"assemble and rewrite outgoing URL" "assemble and rewrite outgoing URL"
@@ -193,48 +198,51 @@ def url_out(request, environ, application, controller, function,
# fill in scheme and host if absolute URL is requested # fill in scheme and host if absolute URL is requested
# scheme can be a string, eg 'http', 'https', 'ws', 'wss' # scheme can be a string, eg 'http', 'https', 'ws', 'wss'
# #
if host is True or (host is None and (scheme or port!=None)): if host is True or (host is None and (scheme or port is not None)):
host = request.env.http_host host = request.env.http_host
if not scheme or scheme is True: if not scheme or scheme is True:
scheme = request.env.get('wsgi_url_scheme', 'http').lower() \ scheme = request.env.get('wsgi_url_scheme', 'http').lower() \
if request else 'http' if request else 'http'
if host: if host:
host_port = host if not port else host.split(':',1)[0]+':%s'%port host_port = host if not port else host.split(':', 1)[0] + ':%s' % port
url = '%s://%s%s' % (scheme, host_port, url) url = '%s://%s%s' % (scheme, host_port, url)
return url return url
def try_rewrite_on_error(http_response, request, environ, ticket=None): def try_rewrite_on_error(http_response, request, environ, ticket=None):
""" """
called from main.wsgibase to rewrite the http response. called from main.wsgibase to rewrite the http response.
""" """
status = int(str(http_response.status).split()[0]) status = int(str(http_response.status).split()[0])
if status>=399 and THREAD_LOCAL.routes.routes_onerror: if status >= 399 and THREAD_LOCAL.routes.routes_onerror:
keys=set(('%s/%s' % (request.application, status), keys = set(('%s/%s' % (request.application, status),
'%s/*' % (request.application), '%s/*' % (request.application),
'*/%s' % (status), '*/%s' % (status),
'*/*')) '*/*'))
for (key,uri) in THREAD_LOCAL.routes.routes_onerror: for (key, uri) in THREAD_LOCAL.routes.routes_onerror:
if key in keys: if key in keys:
if uri == '!': if uri == '!':
# do nothing! # do nothing!
return http_response, environ return http_response, environ
elif '?' in uri: elif '?' in uri:
path_info, query_string = uri.split('?',1) path_info, query_string = uri.split('?', 1)
query_string += '&' query_string += '&'
else: else:
path_info, query_string = uri, '' path_info, query_string = uri, ''
query_string += \ query_string += \
'code=%s&ticket=%s&requested_uri=%s&request_url=%s' % \ 'code=%s&ticket=%s&requested_uri=%s&request_url=%s' % \
(status,ticket,urllib.quote_plus(request.env.request_uri),request.url) (status, ticket, urllib.quote_plus(
request.env.request_uri), request.url)
if uri.startswith('http://') or uri.startswith('https://'): if uri.startswith('http://') or uri.startswith('https://'):
# make up a response # make up a response
url = path_info+'?'+query_string url = path_info + '?' + query_string
message = 'You are being redirected <a href="%s">here</a>' message = 'You are being redirected <a href="%s">here</a>'
return HTTP(303, message % url, Location=url), environ return HTTP(303, message % url, Location=url), environ
else: else:
error_raising_path = environ['PATH_INFO'] error_raising_path = environ['PATH_INFO']
# Rewrite routes_onerror path. # Rewrite routes_onerror path.
path_info = '/' + path_info.lstrip('/') # add leading '/' if missing path_info = '/' + path_info.lstrip(
'/') # add leading '/' if missing
environ['PATH_INFO'] = path_info environ['PATH_INFO'] = path_info
error_handling_path = \ error_handling_path = \
url_in(request, environ)[2]['PATH_INFO'] url_in(request, environ)[2]['PATH_INFO']
@@ -248,29 +256,30 @@ def try_rewrite_on_error(http_response, request, environ, ticket=None):
# do nothing! # do nothing!
return http_response, environ return http_response, environ
def try_redirect_on_error(http_object, request, ticket=None): def try_redirect_on_error(http_object, request, ticket=None):
"called from main.wsgibase to rewrite the http response" "called from main.wsgibase to rewrite the http response"
status = int(str(http_object.status).split()[0]) status = int(str(http_object.status).split()[0])
if status>399 and THREAD_LOCAL.routes.routes_onerror: if status > 399 and THREAD_LOCAL.routes.routes_onerror:
keys=set(('%s/%s' % (request.application, status), keys = set(('%s/%s' % (request.application, status),
'%s/*' % (request.application), '%s/*' % (request.application),
'*/%s' % (status), '*/%s' % (status),
'*/*')) '*/*'))
for (key,redir) in THREAD_LOCAL.routes.routes_onerror: for (key, redir) in THREAD_LOCAL.routes.routes_onerror:
if key in keys: if key in keys:
if redir == '!': if redir == '!':
break break
elif '?' in redir: elif '?' in redir:
url = '%s&code=%s&ticket=%s&requested_uri=%s&request_url=%s' % \ url = '%s&code=%s&ticket=%s&requested_uri=%s&request_url=%s' % \
(redir,status,ticket, (redir, status, ticket,
urllib.quote_plus(request.env.request_uri), urllib.quote_plus(request.env.request_uri),
request.url) request.url)
else: else:
url = '%s?code=%s&ticket=%s&requested_uri=%s&request_url=%s' % \ url = '%s?code=%s&ticket=%s&requested_uri=%s&request_url=%s' % \
(redir,status,ticket, (redir, status, ticket,
urllib.quote_plus(request.env.request_uri), urllib.quote_plus(request.env.request_uri),
request.url) request.url)
return HTTP(303,'You are being redirected <a href="%s">here</a>' % url,Location=url) return HTTP(303, 'You are being redirected <a href="%s">here</a>' % url, Location=url)
return http_object return http_object
@@ -305,7 +314,7 @@ def load(routes='routes.py', app=None, data=None, rdict=None):
path = abspath('applications', app, routes) path = abspath('applications', app, routes)
if not exists(path): if not exists(path):
return return
data = read_file(path).replace('\r\n','\n') data = read_file(path).replace('\r\n', '\n')
symbols = dict(app=app) symbols = dict(app=app)
try: try:
@@ -323,8 +332,8 @@ def load(routes='routes.py', app=None, data=None, rdict=None):
for items in symbols[sym]: for items in symbols[sym]:
p[sym].append(compile_regex(*items)) p[sym].append(compile_regex(*items))
for sym in ('routes_onerror', 'routes_apps_raw', for sym in ('routes_onerror', 'routes_apps_raw',
'error_handler','error_message', 'error_message_ticket', 'error_handler', 'error_message', 'error_message_ticket',
'default_application','default_controller', 'default_function', 'default_application', 'default_controller', 'default_function',
'logging'): 'logging'):
if sym in symbols: if sym in symbols:
p[sym] = symbols[sym] p[sym] = symbols[sym]
@@ -357,15 +366,15 @@ def load(routes='routes.py', app=None, data=None, rdict=None):
apppath = abspath('applications') apppath = abspath('applications')
for appname in os.listdir(apppath): for appname in os.listdir(apppath):
if not appname.startswith('.') and \ if not appname.startswith('.') and \
isdir(abspath(apppath,appname)) and \ isdir(abspath(apppath, appname)) and \
isdir(abspath(apppath,appname,'controllers')): isdir(abspath(apppath, appname, 'controllers')):
all_apps.append(appname) all_apps.append(appname)
if routers: if routers:
router = Storage(routers.BASE) # new copy router = Storage(routers.BASE) # new copy
if appname in routers: if appname in routers:
for key in routers[appname].keys(): for key in routers[appname].keys():
if key in ROUTER_BASE_KEYS: if key in ROUTER_BASE_KEYS:
raise SyntaxError, "BASE-only key '%s' in router '%s'" % (key, appname) raise SyntaxError("BASE-only key '%s' in router '%s'" % (key, appname))
router.update(routers[appname]) router.update(routers[appname])
routers[appname] = router routers[appname] = router
if exists(abspath('applications', appname, routes)): if exists(abspath('applications', appname, routes)):
@@ -374,7 +383,7 @@ def load(routes='routes.py', app=None, data=None, rdict=None):
if routers: if routers:
load_routers(all_apps) load_routers(all_apps)
else: # app else: # app
params_apps[app] = p params_apps[app] = p
if routers and p.routers: if routers and p.routers:
if app in p.routers: if app in p.routers:
@@ -406,8 +415,8 @@ def compile_regex(k, v, env=None):
if k.find('://') < 0: if k.find('://') < 0:
i = k.find(':/') i = k.find(':/')
if i < 0: if i < 0:
raise SyntaxError, "routes pattern syntax error: path needs leading '/' [%s]" % k0 raise SyntaxError("routes pattern syntax error: path needs leading '/' [%s]" % k0)
k = r'%s:https?://[^:/]+:[a-z]+ %s' % (k[:i], k[i+1:]) k = r'%s:https?://[^:/]+:[a-z]+ %s' % (k[:i], k[i + 1:])
# $anything -> ?P<anything>.* # $anything -> ?P<anything>.*
for item in regex_anything.findall(k): for item in regex_anything.findall(k):
k = k.replace(item, '(?P<anything>.*)') k = k.replace(item, '(?P<anything>.*)')
@@ -419,6 +428,7 @@ def compile_regex(k, v, env=None):
v = v.replace(item, r'\g<%s>' % item[1:]) v = v.replace(item, r'\g<%s>' % item[1:])
return (re.compile(k, re.DOTALL), v, env or {}) return (re.compile(k, re.DOTALL), v, env or {})
def load_routers(all_apps): def load_routers(all_apps):
"load-time post-processing of routers" "load-time post-processing of routers"
@@ -431,13 +441,15 @@ def load_routers(all_apps):
if app != 'BASE': if app != 'BASE':
keys = set(routers[app]).intersection(ROUTER_BASE_KEYS) keys = set(routers[app]).intersection(ROUTER_BASE_KEYS)
if keys: if keys:
raise SyntaxError, "BASE-only key(s) %s in router '%s'" % (tuple(keys), app) raise SyntaxError("BASE-only key(s) %s in router '%s'" % (
tuple(keys), app))
router.update(routers[app]) router.update(routers[app])
routers[app] = router routers[app] = router
router = routers[app] router = routers[app]
keys = set(router).difference(ROUTER_KEYS) keys = set(router).difference(ROUTER_KEYS)
if keys: if keys:
raise SyntaxError, "unknown key(s) %s in router '%s'" % (tuple(keys), app) raise SyntaxError("unknown key(s) %s in router '%s'" % (
tuple(keys), app))
if not router.controllers: if not router.controllers:
router.controllers = set() router.controllers = set()
elif not isinstance(router.controllers, str): elif not isinstance(router.controllers, str):
@@ -450,10 +462,12 @@ def load_routers(all_apps):
if isinstance(router.functions, (set, tuple, list)): if isinstance(router.functions, (set, tuple, list)):
functions = set(router.functions) functions = set(router.functions)
if isinstance(router.default_function, str): if isinstance(router.default_function, str):
functions.add(router.default_function) # legacy compatibility functions.add(
router.functions = { router.default_controller: functions } router.default_function) # legacy compatibility
router.functions = {router.default_controller: functions}
for controller in router.functions: for controller in router.functions:
router.functions[controller] = set(router.functions[controller]) router.functions[controller] = set(
router.functions[controller])
else: else:
router.functions = dict() router.functions = dict()
if app != 'BASE': if app != 'BASE':
@@ -516,19 +530,20 @@ def load_routers(all_apps):
else: else:
fcn = None fcn = None
if app not in all_apps and app not in routers: if app not in all_apps and app not in routers:
raise SyntaxError, "unknown app '%s' in domains" % app raise SyntaxError("unknown app '%s' in domains" % app)
domains[(domain, port)] = (app, ctlr, fcn) domains[(domain, port)] = (app, ctlr, fcn)
routers.BASE.domains = domains routers.BASE.domains = domains
def regex_uri(e, regexes, tag, default=None): def regex_uri(e, regexes, tag, default=None):
"filter incoming URI against a list of regexes" "filter incoming URI against a list of regexes"
path = e['PATH_INFO'] path = e['PATH_INFO']
host = e.get('http_host', e.get('SERVER_NAME','localhost')).lower() host = e.get('http_host', e.get('SERVER_NAME', 'localhost')).lower()
i = host.find(':') i = host.find(':')
if i > 0: if i > 0:
host = host[:i] host = host[:i]
key = '%s:%s://%s:%s %s' % \ key = '%s:%s://%s:%s %s' % \
(e.get('REMOTE_ADDR','localhost'), (e.get('REMOTE_ADDR', 'localhost'),
e.get('wsgi.url_scheme', 'http').lower(), host, e.get('wsgi.url_scheme', 'http').lower(), host,
e.get('REQUEST_METHOD', 'get').lower(), path) e.get('REQUEST_METHOD', 'get').lower(), path)
for (regex, value, custom_env) in regexes: for (regex, value, custom_env) in regexes:
@@ -540,6 +555,7 @@ def regex_uri(e, regexes, tag, default=None):
log_rewrite('%s: [%s] -> %s (not rewritten)' % (tag, key, default)) log_rewrite('%s: [%s] -> %s (not rewritten)' % (tag, key, default))
return default return default
def regex_select(env=None, app=None, request=None): def regex_select(env=None, app=None, request=None):
""" """
select a set of regex rewrite params for the current request select a set of regex rewrite params for the current request
@@ -553,10 +569,11 @@ def regex_select(env=None, app=None, request=None):
app = regex_uri(env, params.routes_app, "routes_app") app = regex_uri(env, params.routes_app, "routes_app")
THREAD_LOCAL.routes = params_apps.get(app, params) THREAD_LOCAL.routes = params_apps.get(app, params)
else: else:
THREAD_LOCAL.routes = params # default to base rewrite parameters THREAD_LOCAL.routes = params # default to base rewrite parameters
log_rewrite("select routing parameters: %s" % THREAD_LOCAL.routes.name) log_rewrite("select routing parameters: %s" % THREAD_LOCAL.routes.name)
return app # for doctest return app # for doctest
def regex_filter_in(e): def regex_filter_in(e):
"regex rewrite incoming URL" "regex rewrite incoming URL"
routes = THREAD_LOCAL.routes routes = THREAD_LOCAL.routes
@@ -567,7 +584,7 @@ def regex_filter_in(e):
"routes_in", e['PATH_INFO']) "routes_in", e['PATH_INFO'])
rmatch = regex_redirect.match(path) rmatch = regex_redirect.match(path)
if rmatch: if rmatch:
raise HTTP(int(rmatch.group(1)),location=rmatch.group(2)) raise HTTP(int(rmatch.group(1)), location=rmatch.group(2))
items = path.split('?', 1) items = path.split('?', 1)
e['PATH_INFO'] = items[0] e['PATH_INFO'] = items[0]
if len(items) > 1: if len(items) > 1:
@@ -581,7 +598,8 @@ def regex_filter_in(e):
def sluggify(key): def sluggify(key):
return key.lower().replace('.','_') return key.lower().replace('.', '_')
def regex_url_in(request, environ): def regex_url_in(request, environ):
"rewrite and parse incoming URL" "rewrite and parse incoming URL"
@@ -596,26 +614,28 @@ def regex_url_in(request, environ):
routes = THREAD_LOCAL.routes routes = THREAD_LOCAL.routes
if routes.routes_in: if routes.routes_in:
environ = regex_filter_in(environ) environ = regex_filter_in(environ)
request.env.update((k.lower().replace('.','_'),v) for k,v in environ.iteritems()) request.env.update(
(k.lower().replace('.', '_'), v) for k, v in environ.iteritems())
# ################################################## # ##################################################
# serve if a static file # serve if a static file
# ################################################## # ##################################################
path = request.env.path_info.replace('\\', '/') or '/' path = request.env.path_info.replace('\\', '/') or '/'
path = regex_space.sub('_',path) path = regex_space.sub('_', path)
if path.endswith('/') and len(path)>1: path = path[:-1] if path.endswith('/') and len(path) > 1:
path = path[:-1]
match = regex_url.match(path) match = regex_url.match(path)
if not match: if not match:
raise HTTP(400, raise HTTP(400,
routes.error_message % 'invalid request', routes.error_message % 'invalid request',
web2py_error='invalid path') web2py_error='invalid path')
elif match.group('c')=='static': elif match.group('c') == 'static':
application = match.group('a') application = match.group('a')
version, filename = None, match.group('z') version, filename = None, match.group('z')
items = filename.split('/',1) items = filename.split('/', 1)
if regex_version.match(items[0]): if regex_version.match(items[0]):
version,filename = items version, filename = items
static_file = pjoin(request.env.applications_parent, static_file = pjoin(request.env.applications_parent,
'applications', application, 'applications', application,
'static', filename) 'static', filename)
@@ -655,9 +675,9 @@ def regex_filter_out(url, e=None):
if i > 0: if i > 0:
host = host[:i] host = host[:i]
items[0] = '%s:%s://%s:%s %s' % \ items[0] = '%s:%s://%s:%s %s' % \
(e.get('remote_addr', ''), (e.get('remote_addr', ''),
e.get('wsgi_url_scheme', 'http').lower(), host, e.get('wsgi_url_scheme', 'http').lower(), host,
e.get('request_method', 'get').lower(), items[0]) e.get('request_method', 'get').lower(), items[0])
else: else:
items[0] = ':http://localhost:get %s' % items[0] items[0] = ':http://localhost:get %s' % items[0]
for (regex, value, tmp) in routes.routes_out: for (regex, value, tmp) in routes.routes_out:
@@ -671,7 +691,7 @@ def regex_filter_out(url, e=None):
def filter_url(url, method='get', remote='0.0.0.0', def filter_url(url, method='get', remote='0.0.0.0',
out=False, app=False, lang=None, out=False, app=False, lang=None,
domain=(None,None), env=False, scheme=None, domain=(None, None), env=False, scheme=None,
host=None, port=None): host=None, port=None):
""" """
doctest/unittest interface to regex_filter_in() and regex_filter_out() doctest/unittest interface to regex_filter_in() and regex_filter_out()
@@ -685,21 +705,21 @@ def filter_url(url, method='get', remote='0.0.0.0',
k = len(uri) k = len(uri)
if isinstance(domain, str): if isinstance(domain, str):
domain = (domain, None) domain = (domain, None)
(path_info, query_string) = (uri[:k], uri[k+1:]) (path_info, query_string) = (uri[:k], uri[k + 1:])
path_info = urllib.unquote(path_info) # simulate server path_info = urllib.unquote(path_info) # simulate server
e = { e = {
'REMOTE_ADDR': remote, 'REMOTE_ADDR': remote,
'REQUEST_METHOD': method, 'REQUEST_METHOD': method,
'wsgi.url_scheme': urlscheme, 'wsgi.url_scheme': urlscheme,
'HTTP_HOST': urlhost, 'HTTP_HOST': urlhost,
'REQUEST_URI': uri, 'REQUEST_URI': uri,
'PATH_INFO': path_info, 'PATH_INFO': path_info,
'QUERY_STRING': query_string, 'QUERY_STRING': query_string,
#for filter_out request.env use lowercase #for filter_out request.env use lowercase
'remote_addr': remote, 'remote_addr': remote,
'request_method': method, 'request_method': method,
'wsgi_url_scheme': urlscheme, 'wsgi_url_scheme': urlscheme,
'http_host': urlhost 'http_host': urlhost
} }
request = Storage() request = Storage()
@@ -717,17 +737,19 @@ def filter_url(url, method='get', remote='0.0.0.0',
# rewrite outbound URL # rewrite outbound URL
# #
if out: if out:
(request.env.domain_application, request.env.domain_controller) = domain (request.env.domain_application,
request.env.domain_controller) = domain
items = path_info.lstrip('/').split('/') items = path_info.lstrip('/').split('/')
if items[-1] == '': if items[-1] == '':
items.pop() # adjust trailing empty args items.pop() # adjust trailing empty args
assert len(items) >= 3, "at least /a/c/f is required" assert len(items) >= 3, "at least /a/c/f is required"
a = items.pop(0) a = items.pop(0)
c = items.pop(0) c = items.pop(0)
f = items.pop(0) f = items.pop(0)
if not routers: if not routers:
return regex_filter_out(uri, e) return regex_filter_out(uri, e)
acf = map_url_out(request, None, a, c, f, items, None, scheme, host, port) acf = map_url_out(
request, None, a, c, f, items, None, scheme, host, port)
if items: if items:
url = '%s/%s' % (acf, '/'.join(items)) url = '%s/%s' % (acf, '/'.join(items))
if items[-1] == '': if items[-1] == '':
@@ -743,7 +765,8 @@ def filter_url(url, method='get', remote='0.0.0.0',
(static, version, e) = url_in(request, e) (static, version, e) = url_in(request, e)
if static: if static:
return static return static
result = "/%s/%s/%s" % (request.application, request.controller, request.function) result = "/%s/%s/%s" % (
request.application, request.controller, request.function)
if request.extension and request.extension != 'html': if request.extension and request.extension != 'html':
result += ".%s" % request.extension result += ".%s" % request.extension
if request.args: if request.args:
@@ -762,22 +785,24 @@ def filter_err(status, application='app', ticket='tkt'):
routes = THREAD_LOCAL.routes routes = THREAD_LOCAL.routes
if status > 399 and routes.routes_onerror: if status > 399 and routes.routes_onerror:
keys = set(('%s/%s' % (application, status), keys = set(('%s/%s' % (application, status),
'%s/*' % (application), '%s/*' % (application),
'*/%s' % (status), '*/%s' % (status),
'*/*')) '*/*'))
for (key,redir) in routes.routes_onerror: for (key, redir) in routes.routes_onerror:
if key in keys: if key in keys:
if redir == '!': if redir == '!':
break break
elif '?' in redir: elif '?' in redir:
url = redir + '&' + 'code=%s&ticket=%s' % (status,ticket) url = redir + '&' + 'code=%s&ticket=%s' % (status, ticket)
else: else:
url = redir + '?' + 'code=%s&ticket=%s' % (status,ticket) url = redir + '?' + 'code=%s&ticket=%s' % (status, ticket)
return url # redirection return url # redirection
return status # no action return status # no action
# router support # router support
# #
class MapUrlIn(object): class MapUrlIn(object):
"logic for mapping incoming URLs" "logic for mapping incoming URLs"
@@ -804,7 +829,8 @@ class MapUrlIn(object):
self.query = self.env.get('QUERY_STRING', None) self.query = self.env.get('QUERY_STRING', None)
path = path.lstrip('/') path = path.lstrip('/')
self.env['PATH_INFO'] = '/' + path self.env['PATH_INFO'] = '/' + path
self.env['WEB2PY_ORIGINAL_URI'] = self.env['PATH_INFO'] + (self.query and ('?' + self.query) or '') self.env['WEB2PY_ORIGINAL_URI'] = self.env['PATH_INFO'] + (
self.query and ('?' + self.query) or '')
# to handle empty args, strip exactly one trailing slash, if present # to handle empty args, strip exactly one trailing slash, if present
# .../arg1// represents one trailing empty arg # .../arg1// represents one trailing empty arg
@@ -814,12 +840,13 @@ class MapUrlIn(object):
self.args = List(path and path.split('/') or []) self.args = List(path and path.split('/') or [])
# see http://www.python.org/dev/peps/pep-3333/#url-reconstruction for URL composition # see http://www.python.org/dev/peps/pep-3333/#url-reconstruction for URL composition
self.remote_addr = self.env.get('REMOTE_ADDR','localhost') self.remote_addr = self.env.get('REMOTE_ADDR', 'localhost')
self.scheme = self.env.get('wsgi.url_scheme', 'http').lower() self.scheme = self.env.get('wsgi.url_scheme', 'http').lower()
self.method = self.env.get('REQUEST_METHOD', 'get').lower() self.method = self.env.get('REQUEST_METHOD', 'get').lower()
(self.host, self.port) = (self.env.get('HTTP_HOST'), None) (self.host, self.port) = (self.env.get('HTTP_HOST'), None)
if not self.host: if not self.host:
(self.host, self.port) = (self.env.get('SERVER_NAME'), self.env.get('SERVER_PORT')) (self.host, self.port) = (
self.env.get('SERVER_NAME'), self.env.get('SERVER_PORT'))
if not self.host: if not self.host:
(self.host, self.port) = ('localhost', '80') (self.host, self.port) = ('localhost', '80')
if ':' in self.host: if ':' in self.host:
@@ -837,7 +864,7 @@ class MapUrlIn(object):
for i in xrange(prefixlen): for i in xrange(prefixlen):
if prefix[i] != self.args[i]: if prefix[i] != self.args[i]:
return # prefix didn't match return # prefix didn't match
self.args = List(self.args[prefixlen:]) # strip the prefix self.args = List(self.args[prefixlen:]) # strip the prefix
def map_app(self): def map_app(self):
"determine application name" "determine application name"
@@ -851,12 +878,14 @@ class MapUrlIn(object):
elif not base.exclusive_domain and arg0 and not base.applications: elif not base.exclusive_domain and arg0 and not base.applications:
self.application = arg0 self.application = arg0
elif (self.host, self.port) in base.domains: elif (self.host, self.port) in base.domains:
(self.application, self.domain_controller, self.domain_function) = base.domains[(self.host, self.port)] (self.application, self.domain_controller,
self.domain_function) = base.domains[(self.host, self.port)]
self.env['domain_application'] = self.application self.env['domain_application'] = self.application
self.env['domain_controller'] = self.domain_controller self.env['domain_controller'] = self.domain_controller
self.env['domain_function'] = self.domain_function self.env['domain_function'] = self.domain_function
elif (self.host, None) in base.domains: elif (self.host, None) in base.domains:
(self.application, self.domain_controller, self.domain_function) = base.domains[(self.host, None)] (self.application, self.domain_controller,
self.domain_function) = base.domains[(self.host, None)]
self.env['domain_application'] = self.application self.env['domain_application'] = self.application
self.env['domain_controller'] = self.domain_controller self.env['domain_controller'] = self.domain_controller
self.env['domain_function'] = self.domain_function self.env['domain_function'] = self.domain_function
@@ -869,12 +898,14 @@ class MapUrlIn(object):
self.pop_arg_if(self.application == arg0) self.pop_arg_if(self.application == arg0)
if not base._acfe_match.match(self.application): if not base._acfe_match.match(self.application):
raise HTTP(400, THREAD_LOCAL.routes.error_message % 'invalid request', raise HTTP(
web2py_error="invalid application: '%s'" % self.application) 400, THREAD_LOCAL.routes.error_message % 'invalid request',
web2py_error="invalid application: '%s'" % self.application)
if self.application not in routers and \ if self.application not in routers and \
(self.application != THREAD_LOCAL.routes.default_application or self.application == 'welcome'): (self.application != THREAD_LOCAL.routes.default_application or self.application == 'welcome'):
raise HTTP(400, THREAD_LOCAL.routes.error_message % 'invalid request', raise HTTP(
400, THREAD_LOCAL.routes.error_message % 'invalid request',
web2py_error="unknown application: '%s'" % self.application) web2py_error="unknown application: '%s'" % self.application)
# set the application router # set the application router
@@ -908,8 +939,8 @@ class MapUrlIn(object):
if len(self.args) == 1 and self.arg0 in self.router.root_static: if len(self.args) == 1 and self.arg0 in self.router.root_static:
self.controller = self.request.controller = 'static' self.controller = self.request.controller = 'static'
root_static_file = pjoin(self.request.env.applications_parent, root_static_file = pjoin(self.request.env.applications_parent,
'applications', self.application, 'applications', self.application,
self.controller, self.arg0) self.controller, self.arg0)
log_rewrite("route: root static=%s" % root_static_file) log_rewrite("route: root static=%s" % root_static_file)
return root_static_file, None return root_static_file, None
return None, None return None, None
@@ -938,8 +969,9 @@ class MapUrlIn(object):
self.pop_arg_if(arg0 == self.controller) self.pop_arg_if(arg0 == self.controller)
log_rewrite("route: controller=%s" % self.controller) log_rewrite("route: controller=%s" % self.controller)
if not self.router._acfe_match.match(self.controller): if not self.router._acfe_match.match(self.controller):
raise HTTP(400, THREAD_LOCAL.routes.error_message % 'invalid request', raise HTTP(
web2py_error='invalid controller') 400, THREAD_LOCAL.routes.error_message % 'invalid request',
web2py_error='invalid controller')
def map_static(self): def map_static(self):
''' '''
@@ -962,7 +994,8 @@ class MapUrlIn(object):
# match path elements # match path elements
bad_static = False bad_static = False
for name in self.args: for name in self.args:
bad_static = bad_static or name in ('', '.', '..') or not self.router._file_match.match(name) bad_static = bad_static or name in (
'', '.', '..') or not self.router._file_match.match(name)
if bad_static: if bad_static:
log_rewrite('bad static path=%s' % file) log_rewrite('bad static path=%s' % file)
raise HTTP(400, raise HTTP(400,
@@ -990,9 +1023,10 @@ class MapUrlIn(object):
arg0 = self.harg0 # map hyphens arg0 = self.harg0 # map hyphens
functions = self.functions.get(self.controller, set()) functions = self.functions.get(self.controller, set())
if isinstance(self.router.default_function, dict): if isinstance(self.router.default_function, dict):
default_function = self.router.default_function.get(self.controller, None) default_function = self.router.default_function.get(
self.controller, None)
else: else:
default_function = self.router.default_function # str or None default_function = self.router.default_function # str or None
default_function = self.domain_function or default_function default_function = self.domain_function or default_function
if not arg0 or functions and arg0 not in functions: if not arg0 or functions and arg0 not in functions:
self.function = default_function or "" self.function = default_function or ""
@@ -1005,14 +1039,17 @@ class MapUrlIn(object):
else: else:
self.function = arg0 self.function = arg0
self.pop_arg_if(True) self.pop_arg_if(True)
log_rewrite("route: function.ext=%s.%s" % (self.function, self.extension)) log_rewrite(
"route: function.ext=%s.%s" % (self.function, self.extension))
if not self.router._acfe_match.match(self.function): if not self.router._acfe_match.match(self.function):
raise HTTP(400, THREAD_LOCAL.routes.error_message % 'invalid request', raise HTTP(
web2py_error='invalid function') 400, THREAD_LOCAL.routes.error_message % 'invalid request',
web2py_error='invalid function')
if self.extension and not self.router._acfe_match.match(self.extension): if self.extension and not self.router._acfe_match.match(self.extension):
raise HTTP(400, THREAD_LOCAL.routes.error_message % 'invalid request', raise HTTP(
web2py_error='invalid extension') 400, THREAD_LOCAL.routes.error_message % 'invalid request',
web2py_error='invalid extension')
def validate_args(self): def validate_args(self):
''' '''
@@ -1020,13 +1057,14 @@ class MapUrlIn(object):
''' '''
for arg in self.args: for arg in self.args:
if not self.router._args_match.match(arg): if not self.router._args_match.match(arg):
raise HTTP(400, THREAD_LOCAL.routes.error_message % 'invalid request', raise HTTP(
web2py_error='invalid arg <%s>' % arg) 400, THREAD_LOCAL.routes.error_message % 'invalid request',
web2py_error='invalid arg <%s>' % arg)
def sluggify(self): def sluggify(self):
"" ""
self.request.env.update( self.request.env.update(
(k.lower().replace('.','_'),v) for k,v in self.env.iteritems()) (k.lower().replace('.', '_'), v) for k, v in self.env.iteritems())
def update_request(self): def update_request(self):
''' '''
@@ -1053,7 +1091,8 @@ class MapUrlIn(object):
uri = '/%s%s%s%s' % ( uri = '/%s%s%s%s' % (
app, app,
uri, uri,
urllib.quote('/'+'/'.join(str(x) for x in self.args)) if self.args else '', urllib.quote('/' + '/'.join(
str(x) for x in self.args)) if self.args else '',
('?' + self.query) if self.query else '') ('?' + self.query) if self.query else '')
self.env['REQUEST_URI'] = uri self.env['REQUEST_URI'] = uri
self.sluggify() self.sluggify()
@@ -1075,6 +1114,7 @@ class MapUrlIn(object):
if dopop: if dopop:
self.args.pop(0) self.args.pop(0)
class MapUrlOut(object): class MapUrlOut(object):
"logic for mapping outgoing URLs" "logic for mapping outgoing URLs"
@@ -1090,7 +1130,8 @@ class MapUrlOut(object):
self.env = env self.env = env
self.application = application self.application = application
self.controller = controller self.controller = controller
self.is_static = (controller == 'static' or controller.startswith('static/')) self.is_static = (
controller == 'static' or controller.startswith('static/'))
self.function = function self.function = function
self.args = args self.args = args
self.other = other self.other = other
@@ -1111,12 +1152,13 @@ class MapUrlOut(object):
self.domain_application = request and self.request.env.domain_application self.domain_application = request and self.request.env.domain_application
self.domain_controller = request and self.request.env.domain_controller self.domain_controller = request and self.request.env.domain_controller
if isinstance(self.router.default_function, dict): if isinstance(self.router.default_function, dict):
self.default_function = self.router.default_function.get(self.controller, None) self.default_function = self.router.default_function.get(
self.controller, None)
else: else:
self.default_function = self.router.default_function self.default_function = self.router.default_function
if (self.router.exclusive_domain and self.domain_application and self.domain_application != self.application and not self.host): if (self.router.exclusive_domain and self.domain_application and self.domain_application != self.application and not self.host):
raise SyntaxError, 'cross-domain conflict: must specify host' raise SyntaxError('cross-domain conflict: must specify host')
lang = request and request.uri_language lang = request and request.uri_language
if lang and self.languages and lang in self.languages: if lang and self.languages and lang in self.languages:
@@ -1237,7 +1279,7 @@ class MapUrlOut(object):
return None # use regex filter return None # use regex filter
self.omit_lang() # try to omit language self.omit_lang() # try to omit language
self.omit_acf() # try to omit a/c/f self.omit_acf() # try to omit a/c/f
return self.build_acf() # build and return the /a/lang/c/f string return self.build_acf() # build and return the /a/lang/c/f string
def map_url_in(request, env, app=False): def map_url_in(request, env, app=False):
@@ -1259,7 +1301,8 @@ def map_url_in(request, env, app=False):
if app: if app:
return map.application return map.application
root_static_file, version = map.map_root_static() # handle root-static files root_static_file, version = map.map_root_static(
) # handle root-static files
if root_static_file: if root_static_file:
map.update_request() map.update_request()
return (root_static_file, version, map.env) return (root_static_file, version, map.env)
@@ -1280,6 +1323,7 @@ def map_url_in(request, env, app=False):
map.update_request() map.update_request()
return (None, None, map.env) return (None, None, map.env)
def map_url_out(request, env, application, controller, def map_url_out(request, env, application, controller,
function, args, other, scheme, host, port): function, args, other, scheme, host, port):
''' '''
@@ -1311,6 +1355,7 @@ def map_url_out(request, env, application, controller,
function, args, other, scheme, host, port) function, args, other, scheme, host, port)
return map.acf() return map.acf()
def get_effective_router(appname): def get_effective_router(appname):
"return a private copy of the effective router for the specified application" "return a private copy of the effective router for the specified application"
if not routers or appname not in routers: if not routers or appname not in routers:
+121 -92
View File
@@ -16,21 +16,23 @@ import traceback
VERSION = '1.2.5' VERSION = '1.2.5'
SERVER_NAME = socket.gethostname() SERVER_NAME = socket.gethostname()
SERVER_SOFTWARE = 'Rocket %s' % VERSION SERVER_SOFTWARE = 'Rocket %s' % VERSION
HTTP_SERVER_SOFTWARE = '%s Python/%s' % (SERVER_SOFTWARE, sys.version.split(' ')[0]) HTTP_SERVER_SOFTWARE = '%s Python/%s' % (
SERVER_SOFTWARE, sys.version.split(' ')[0])
BUF_SIZE = 16384 BUF_SIZE = 16384
SOCKET_TIMEOUT = 10 # in secs SOCKET_TIMEOUT = 10 # in secs
THREAD_STOP_CHECK_INTERVAL = 1 # in secs, How often should threads check for a server stop message? THREAD_STOP_CHECK_INTERVAL = 1 # in secs, How often should threads check for a server stop message?
IS_JYTHON = platform.system() == 'Java' # Handle special cases for Jython IS_JYTHON = platform.system() == 'Java' # Handle special cases for Jython
IGNORE_ERRORS_ON_CLOSE = set([errno.ECONNABORTED, errno.ECONNRESET]) IGNORE_ERRORS_ON_CLOSE = set([errno.ECONNABORTED, errno.ECONNRESET])
DEFAULT_LISTEN_QUEUE_SIZE = 5 DEFAULT_LISTEN_QUEUE_SIZE = 5
DEFAULT_MIN_THREADS = 10 DEFAULT_MIN_THREADS = 10
DEFAULT_MAX_THREADS = 0 DEFAULT_MAX_THREADS = 0
DEFAULTS = dict(LISTEN_QUEUE_SIZE = DEFAULT_LISTEN_QUEUE_SIZE, DEFAULTS = dict(LISTEN_QUEUE_SIZE=DEFAULT_LISTEN_QUEUE_SIZE,
MIN_THREADS = DEFAULT_MIN_THREADS, MIN_THREADS=DEFAULT_MIN_THREADS,
MAX_THREADS = DEFAULT_MAX_THREADS) MAX_THREADS=DEFAULT_MAX_THREADS)
PY3K = sys.version_info[0] > 2 PY3K = sys.version_info[0] > 2
class NullHandler(logging.Handler): class NullHandler(logging.Handler):
"A Logging handler to prevent library errors." "A Logging handler to prevent library errors."
def emit(self, record): def emit(self, record):
@@ -94,6 +96,7 @@ except ImportError:
# TODO - This part is still very experimental. # TODO - This part is still very experimental.
#from .filelike import FileLikeSocket #from .filelike import FileLikeSocket
class Connection(object): class Connection(object):
__slots__ = [ __slots__ = [
'setblocking', 'setblocking',
@@ -170,6 +173,7 @@ except ImportError:
# Import Package Modules # Import Package Modules
# package imports removed in monolithic build # package imports removed in monolithic build
class FileLikeSocket(object): class FileLikeSocket(object):
def __init__(self, conn, buf_size=BUF_SIZE): def __init__(self, conn, buf_size=BUF_SIZE):
self.conn = conn self.conn = conn
@@ -317,13 +321,12 @@ class WSGIFuture(Future):
else: else:
return super(WSGIFuture, self).set_running_or_notify_cancel() return super(WSGIFuture, self).set_running_or_notify_cancel()
def remember(self, name, lifespan=None): def remember(self, name, lifespan=None):
self._lifespan = lifespan or self._lifespan self._lifespan = lifespan or self._lifespan
if name in self._mem_dict: if name in self._mem_dict:
raise NameError('Cannot remember future by name "%s". ' % name + \ raise NameError('Cannot remember future by name "%s". ' % name +
'A future already exists with that name.' ) 'A future already exists with that name.')
self._name = name self._name = name
self._mem_dict[name] = self self._mem_dict[name] = self
@@ -334,6 +337,7 @@ class WSGIFuture(Future):
del self._mem_dict[self._name] del self._mem_dict[self._name]
self._name = None self._name = None
class _WorkItem(object): class _WorkItem(object):
def __init__(self, future, fn, args, kwargs): def __init__(self, future, fn, args, kwargs):
self.future = future self.future = future
@@ -353,6 +357,7 @@ class _WorkItem(object):
else: else:
self.future.set_result(result) self.future.set_result(result)
class WSGIExecutor(ThreadPoolExecutor): class WSGIExecutor(ThreadPoolExecutor):
multithread = True multithread = True
multiprocess = False multiprocess = False
@@ -366,7 +371,8 @@ class WSGIExecutor(ThreadPoolExecutor):
if self._shutdown_lock.acquire(): if self._shutdown_lock.acquire():
if self._shutdown: if self._shutdown:
self._shutdown_lock.release() self._shutdown_lock.release()
raise RuntimeError('Cannot schedule new futures after shutdown') raise RuntimeError(
'Cannot schedule new futures after shutdown')
f = WSGIFuture(self.futures) f = WSGIFuture(self.futures)
w = _WorkItem(f, fn, args, kwargs) w = _WorkItem(f, fn, args, kwargs)
@@ -378,6 +384,7 @@ class WSGIExecutor(ThreadPoolExecutor):
else: else:
return False return False
class FuturesMiddleware(object): class FuturesMiddleware(object):
"Futures middleware that adds a Futures Executor to the environment" "Futures middleware that adds a Futures Executor to the environment"
def __init__(self, app, threads=5): def __init__(self, app, threads=5):
@@ -405,11 +412,13 @@ try:
has_ssl = True has_ssl = True
except ImportError: except ImportError:
has_ssl = False has_ssl = False
class SSLError(socket.error): class SSLError(socket.error):
pass pass
# Import Package Modules # Import Package Modules
# package imports removed in monolithic build # package imports removed in monolithic build
class Listener(Thread): class Listener(Thread):
"""The Listener class is a class responsible for accepting connections """The Listener class is a class responsible for accepting connections
and queuing them to be processed by a worker thread.""" and queuing them to be processed by a worker thread."""
@@ -446,18 +455,18 @@ class Listener(Thread):
elif not os.path.exists(interface[2]): elif not os.path.exists(interface[2]):
data = (interface[2], interface[0], interface[1]) data = (interface[2], interface[0], interface[1])
self.err_log.error("Cannot find key file " self.err_log.error("Cannot find key file "
"'%s'. Cannot bind to %s:%s" % data) "'%s'. Cannot bind to %s:%s" % data)
return return
elif not os.path.exists(interface[3]): elif not os.path.exists(interface[3]):
data = (interface[3], interface[0], interface[1]) data = (interface[3], interface[0], interface[1])
self.err_log.error("Cannot find certificate file " self.err_log.error("Cannot find certificate file "
"'%s'. Cannot bind to %s:%s" % data) "'%s'. Cannot bind to %s:%s" % data)
return return
if self.clientcert_req and not os.path.exists(interface[4]): if self.clientcert_req and not os.path.exists(interface[4]):
data = (interface[4], interface[0], interface[1]) data = (interface[4], interface[0], interface[1])
self.err_log.error("Cannot find root ca certificate file " self.err_log.error("Cannot find root ca certificate file "
"'%s'. Cannot bind to %s:%s" % data) "'%s'. Cannot bind to %s:%s" % data)
return return
# Set socket options # Set socket options
@@ -499,18 +508,18 @@ class Listener(Thread):
ca_certs = self.interface[4] ca_certs = self.interface[4]
cert_reqs = ssl.CERT_OPTIONAL cert_reqs = ssl.CERT_OPTIONAL
sock = ssl.wrap_socket(sock, sock = ssl.wrap_socket(sock,
keyfile = self.interface[2], keyfile=self.interface[2],
certfile = self.interface[3], certfile=self.interface[3],
server_side = True, server_side=True,
cert_reqs = cert_reqs, cert_reqs=cert_reqs,
ca_certs = ca_certs, ca_certs=ca_certs,
ssl_version = ssl.PROTOCOL_SSLv23) ssl_version=ssl.PROTOCOL_SSLv23)
else: else:
sock = ssl.wrap_socket(sock, sock = ssl.wrap_socket(sock,
keyfile = self.interface[2], keyfile=self.interface[2],
certfile = self.interface[3], certfile=self.interface[3],
server_side = True, server_side=True,
ssl_version = ssl.PROTOCOL_SSLv23) ssl_version=ssl.PROTOCOL_SSLv23)
except SSLError: except SSLError:
# Generally this happens when an HTTP request is received on a # Generally this happens when an HTTP request is received on a
@@ -598,19 +607,17 @@ except ImportError:
# package imports removed in monolithic build # package imports removed in monolithic build
# Setup Logging # Setup Logging
log = logging.getLogger('Rocket') log = logging.getLogger('Rocket')
log.addHandler(NullHandler()) log.addHandler(NullHandler())
class Rocket(object): class Rocket(object):
"""The Rocket class is responsible for handling threads and accepting and """The Rocket class is responsible for handling threads and accepting and
dispatching connections.""" dispatching connections."""
def __init__(self, def __init__(self,
interfaces = ('127.0.0.1', 8000), interfaces=('127.0.0.1', 8000),
method = 'wsgi', method = 'wsgi',
app_info = None, app_info = None,
min_threads = None, min_threads = None,
@@ -650,15 +657,16 @@ class Rocket(object):
self.active_queue = Queue() self.active_queue = Queue()
self._threadpool = ThreadPool(get_method(method), self._threadpool = ThreadPool(get_method(method),
app_info = app_info, app_info=app_info,
active_queue = self.active_queue, active_queue=self.active_queue,
monitor_queue = self.monitor_queue, monitor_queue=self.monitor_queue,
min_threads = min_threads, min_threads=min_threads,
max_threads = max_threads) max_threads=max_threads)
# Build our socket listeners # Build our socket listeners
self.listeners = [Listener(i, queue_size, self.active_queue) for i in self.interfaces] self.listeners = [Listener(
for ndx in range(len(self.listeners)-1, 0, -1): i, queue_size, self.active_queue) for i in self.interfaces]
for ndx in range(len(self.listeners) - 1, 0, -1):
if not self.listeners[ndx].ready: if not self.listeners[ndx].ready:
del self.listeners[ndx] del self.listeners[ndx]
@@ -705,7 +713,8 @@ class Rocket(object):
str_extract = lambda l: (l.addr, l.port, l.secure and '*' or '') str_extract = lambda l: (l.addr, l.port, l.secure and '*' or '')
msg = 'Listening on sockets: ' msg = 'Listening on sockets: '
msg += ', '.join(['%s:%i%s' % str_extract(l) for l in self.listeners]) msg += ', '.join(
['%s:%i%s' % str_extract(l) for l in self.listeners])
log.info(msg) log.info(msg)
for l in self.listeners: for l in self.listeners:
@@ -730,7 +739,7 @@ class Rocket(object):
return self.stop() return self.stop()
def stop(self, stoplogging = False): def stop(self, stoplogging=False):
log.info('Stopping %s' % SERVER_SOFTWARE) log.info('Stopping %s' % SERVER_SOFTWARE)
self.startstop_lock.acquire() self.startstop_lock.acquire()
@@ -773,23 +782,24 @@ class Rocket(object):
self.stop() self.stop()
self.start() self.start()
def CherryPyWSGIServer(bind_addr, def CherryPyWSGIServer(bind_addr,
wsgi_app, wsgi_app,
numthreads = 10, numthreads=10,
server_name = None, server_name=None,
max = -1, max=-1,
request_queue_size = 5, request_queue_size=5,
timeout = 10, timeout=10,
shutdown_timeout = 5): shutdown_timeout=5):
""" A Cherrypy wsgiserver-compatible wrapper. """ """ A Cherrypy wsgiserver-compatible wrapper. """
max_threads = max max_threads = max
if max_threads < 0: if max_threads < 0:
max_threads = 0 max_threads = 0
return Rocket(bind_addr, 'wsgi', {'wsgi_app': wsgi_app}, return Rocket(bind_addr, 'wsgi', {'wsgi_app': wsgi_app},
min_threads = numthreads, min_threads=numthreads,
max_threads = max_threads, max_threads=max_threads,
queue_size = request_queue_size, queue_size=request_queue_size,
timeout = timeout) timeout=timeout)
# Monolithic build...end of module: rocket\main.py # Monolithic build...end of module: rocket\main.py
# Monolithic build...start of module: rocket\monitor.py # Monolithic build...start of module: rocket\monitor.py
@@ -803,6 +813,7 @@ from threading import Thread
# Import Package Modules # Import Package Modules
# package imports removed in monolithic build # package imports removed in monolithic build
class Monitor(Thread): class Monitor(Thread):
# Monitor worker class. # Monitor worker class.
@@ -926,8 +937,10 @@ class Monitor(Thread):
for c in stale: for c in stale:
if __debug__: if __debug__:
# "EXPR and A or B" kept for Py2.4 compatibility # "EXPR and A or B" kept for Py2.4 compatibility
data = (c.client_addr, c.server_port, c.ssl and '*' or '') data = (
self.log.debug('Flushing stale connection: %s:%i%s' % data) c.client_addr, c.server_port, c.ssl and '*' or '')
self.log.debug(
'Flushing stale connection: %s:%i%s' % data)
self.connections.remove(c) self.connections.remove(c)
list_changed = True list_changed = True
@@ -940,7 +953,6 @@ class Monitor(Thread):
# Dynamically resize the threadpool to adapt to our changing needs. # Dynamically resize the threadpool to adapt to our changing needs.
self._threadpool.dynamic_resize() self._threadpool.dynamic_resize()
def stop(self): def stop(self):
self.active = False self.active = False
@@ -984,6 +996,7 @@ import logging
log = logging.getLogger('Rocket.Errors.ThreadPool') log = logging.getLogger('Rocket.Errors.ThreadPool')
log.addHandler(NullHandler()) log.addHandler(NullHandler())
class ThreadPool: class ThreadPool:
"""The ThreadPool class is a container class for all the worker threads. It """The ThreadPool class is a container class for all the worker threads. It
manages the number of actively running threads.""" manages the number of actively running threads."""
@@ -1011,7 +1024,7 @@ class ThreadPool:
self.alive = False self.alive = False
# TODO - Optimize this based on some real-world usage data # TODO - Optimize this based on some real-world usage data
self.grow_threshold = int(max_threads/10) + 2 self.grow_threshold = int(max_threads / 10) + 2
if not isinstance(app_info, dict): if not isinstance(app_info, dict):
app_info = dict() app_info = dict()
@@ -1193,7 +1206,9 @@ Content-Type: %s
%s %s
''' '''
if IS_JYTHON: if IS_JYTHON:
HTTP_METHODS = set(['OPTIONS', 'GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'TRACE', 'CONNECT']) HTTP_METHODS = set(['OPTIONS', 'GET', 'HEAD', 'POST', 'PUT',
'DELETE', 'TRACE', 'CONNECT'])
class Worker(Thread): class Worker(Thread):
"""The Worker class is a base class responsible for receiving connections """The Worker class is a base class responsible for receiving connections
@@ -1223,7 +1238,7 @@ class Worker(Thread):
self.req_log.addHandler(NullHandler()) self.req_log.addHandler(NullHandler())
# Error Log # Error Log
self.err_log = logging.getLogger('Rocket.Errors.'+self.getName()) self.err_log = logging.getLogger('Rocket.Errors.' + self.getName())
self.err_log.addHandler(NullHandler()) self.err_log.addHandler(NullHandler())
def _handleError(self, typ, val, tb): def _handleError(self, typ, val, tb):
@@ -1301,11 +1316,11 @@ class Worker(Thread):
self.err_log.debug('Serving a request') self.err_log.debug('Serving a request')
try: try:
self.run_app(conn) self.run_app(conn)
log_info = dict(client_ip = conn.client_addr, log_info = dict(client_ip=conn.client_addr,
time = datetime.now().strftime('%c'), time=datetime.now().strftime('%c'),
status = self.status.split(' ')[0], status=self.status.split(' ')[0],
size = self.size, size=self.size,
request_line = self.request_line) request_line=self.request_line)
self.req_log.info(LOG_LINE % log_info) self.req_log.info(LOG_LINE % log_info)
except: except:
exc = sys.exc_info() exc = sys.exc_info()
@@ -1314,11 +1329,12 @@ class Worker(Thread):
break break
else: else:
if self.request_line: if self.request_line:
log_info = dict(client_ip = conn.client_addr, log_info = dict(client_ip=conn.client_addr,
time = datetime.now().strftime('%c'), time=datetime.now(
status = self.status.split(' ')[0], ).strftime('%c'),
size = self.size, status=self.status.split(' ')[0],
request_line = self.request_line + ' - not stopping') size=self.size,
request_line=self.request_line + ' - not stopping')
self.req_log.info(LOG_LINE % log_info) self.req_log.info(LOG_LINE % log_info)
if self.closeConnection: if self.closeConnection:
@@ -1382,13 +1398,15 @@ class Worker(Thread):
except socket.timeout: except socket.timeout:
raise SocketTimeout("Socket timed out before request.") raise SocketTimeout("Socket timed out before request.")
except TypeError: except TypeError:
raise SocketClosed("ssl bug caused closer of socket, upgrade to python 2.7") raise SocketClosed(
"ssl bug caused closer of socket, upgrade to python 2.7")
d = d.strip() d = d.strip()
if not d: if not d:
if __debug__: if __debug__:
self.err_log.debug('Client did not send a recognizable request.') self.err_log.debug(
'Client did not send a recognizable request.')
raise SocketClosed('Client closed socket.') raise SocketClosed('Client closed socket.')
self.request_line = d self.request_line = d
@@ -1408,11 +1426,12 @@ class Worker(Thread):
raise BadRequest raise BadRequest
req = match.groupdict() req = match.groupdict()
for k,v in req.iteritems(): for k, v in req.iteritems():
if not v: if not v:
req[k] = "" req[k] = ""
if k == 'path': if k == 'path':
req['path'] = r'%2F'.join([unquote(x) for x in re_SLASH.split(v)]) req['path'] = r'%2F'.join(
[unquote(x) for x in re_SLASH.split(v)])
return req return req
@@ -1421,15 +1440,15 @@ class Worker(Thread):
try: try:
method, uri, proto = d.split(' ') method, uri, proto = d.split(' ')
if not proto.startswith('HTTP') or \ if not proto.startswith('HTTP') or \
proto[-3:] not in ('1.0', '1.1') or \ proto[-3:] not in ('1.0', '1.1') or \
method not in HTTP_METHODS: method not in HTTP_METHODS:
self.send_response('400 Bad Request') self.send_response('400 Bad Request')
raise BadRequest raise BadRequest
except ValueError: except ValueError:
self.send_response('400 Bad Request') self.send_response('400 Bad Request')
raise BadRequest raise BadRequest
req = dict(method=method, protocol = proto) req = dict(method=method, protocol=proto)
scheme = '' scheme = ''
host = '' host = ''
if uri == '*' or uri.startswith('/'): if uri == '*' or uri.startswith('/'):
@@ -1454,7 +1473,6 @@ class Worker(Thread):
host=host) host=host)
return req return req
def read_headers(self, sock_file, environ): def read_headers(self, sock_file, environ):
try: try:
lname = None lname = None
@@ -1464,9 +1482,10 @@ class Worker(Thread):
try: try:
l = str(l, 'ISO-8859-1') l = str(l, 'ISO-8859-1')
except UnicodeDecodeError: except UnicodeDecodeError:
self.err_log.warning('Invalid request header: '+repr(l)) self.err_log.warning(
'Invalid request header: ' + repr(l))
if l.strip().replace('\0','') == '': if l.strip().replace('\0', '') == '':
break break
elif l[0] in ' \t' and lname: elif l[0] in ' \t' and lname:
# Some headers take more than one line # Some headers take more than one line
@@ -1476,25 +1495,30 @@ class Worker(Thread):
l = l.split(':', 1) l = l.split(':', 1)
# HTTP header names are us-ascii encoded # HTTP header names are us-ascii encoded
lname = str('HTTP_'+l[0].strip().upper().replace('-', '_')) lname = str(
'HTTP_' + l[0].strip().upper().replace('-', '_'))
lval = str(l[-1].strip()) lval = str(l[-1].strip())
environ[lname] = lval environ[lname] = lval
except socket.timeout: except socket.timeout:
raise SocketTimeout("Socket timed out before request.") raise SocketTimeout("Socket timed out before request.")
class SocketTimeout(Exception): class SocketTimeout(Exception):
"Exception for when a socket times out between requests." "Exception for when a socket times out between requests."
pass pass
class BadRequest(Exception): class BadRequest(Exception):
"Exception for when a client sends an incomprehensible request." "Exception for when a client sends an incomprehensible request."
pass pass
class SocketClosed(Exception): class SocketClosed(Exception):
"Exception for when a socket is closed by the client." "Exception for when a socket is closed by the client."
pass pass
class ChunkedReader(object): class ChunkedReader(object):
def __init__(self, sock_file): def __init__(self, sock_file):
self.stream = sock_file self.stream = sock_file
@@ -1542,6 +1566,7 @@ class ChunkedReader(object):
def readlines(self): def readlines(self):
yield self.readline() yield self.readline()
def get_method(method): def get_method(method):
methods = dict(wsgi=WSGIWorker) methods = dict(wsgi=WSGIWorker)
return methods[method.lower()] return methods[method.lower()]
@@ -1559,7 +1584,6 @@ from wsgiref.util import FileWrapper
# package imports removed in monolithic build # package imports removed in monolithic build
if PY3K: if PY3K:
from email.utils import formatdate from email.utils import formatdate
else: else:
@@ -1578,6 +1602,7 @@ BASE_ENV = {'SERVER_NAME': SERVER_NAME,
'wsgi.file_wrapper': FileWrapper 'wsgi.file_wrapper': FileWrapper
} }
class WSGIWorker(Worker): class WSGIWorker(Worker):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
"""Builds some instance variables that will last the life of the """Builds some instance variables that will last the life of the
@@ -1588,9 +1613,10 @@ class WSGIWorker(Worker):
multithreaded = self.app_info.get('max_threads') != 1 multithreaded = self.app_info.get('max_threads') != 1
else: else:
multithreaded = False multithreaded = False
self.base_environ = dict({'SERVER_SOFTWARE': self.app_info['server_software'], self.base_environ = dict(
'wsgi.multithread': multithreaded, {'SERVER_SOFTWARE': self.app_info['server_software'],
}) 'wsgi.multithread': multithreaded,
})
self.base_environ.update(BASE_ENV) self.base_environ.update(BASE_ENV)
# Grab our application # Grab our application
@@ -1614,7 +1640,7 @@ class WSGIWorker(Worker):
environ = self.base_environ.copy() environ = self.base_environ.copy()
# Grab the headers # Grab the headers
self.read_headers(sock_file,environ) self.read_headers(sock_file, environ)
# Add CGI Variables # Add CGI Variables
environ['SERVER_PORT'] = str(conn.server_port) environ['SERVER_PORT'] = str(conn.server_port)
@@ -1644,7 +1670,7 @@ class WSGIWorker(Worker):
peercert = conn.socket.getpeercert(binary_form=True) peercert = conn.socket.getpeercert(binary_form=True)
environ['SSL_CLIENT_RAW_CERT'] = \ environ['SSL_CLIENT_RAW_CERT'] = \
peercert and ssl.DER_cert_to_PEM_cert(peercert) peercert and ssl.DER_cert_to_PEM_cert(peercert)
except Exception,e: except Exception, e:
print e print e
if environ.get('HTTP_TRANSFER_ENCODING', '') == 'chunked': if environ.get('HTTP_TRANSFER_ENCODING', '') == 'chunked':
@@ -1822,7 +1848,7 @@ class WSGIWorker(Worker):
if __debug__: if __debug__:
self.err_log.debug('Finally closing output and sock_file') self.err_log.debug('Finally closing output and sock_file')
if hasattr(output,'close'): if hasattr(output, 'close'):
output.close() output.close()
sock_file.close() sock_file.close()
@@ -1833,19 +1859,21 @@ class WSGIWorker(Worker):
# the following code is not part of Rocket but was added in web2py for testing purposes # the following code is not part of Rocket but was added in web2py for testing purposes
# #
def demo_app(environ, start_response): def demo_app(environ, start_response):
global static_folder global static_folder
import os import os
types = {'htm': 'text/html','html': 'text/html','gif': 'image/gif', types = {'htm': 'text/html', 'html': 'text/html', 'gif': 'image/gif',
'jpg': 'image/jpeg','png': 'image/png','pdf': 'applications/pdf'} 'jpg': 'image/jpeg', 'png': 'image/png', 'pdf': 'applications/pdf'}
if static_folder: if static_folder:
if not static_folder.startswith('/'): if not static_folder.startswith('/'):
static_folder = os.path.join(os.getcwd(),static_folder) static_folder = os.path.join(os.getcwd(), static_folder)
path = os.path.join(static_folder, environ['PATH_INFO'][1:] or 'index.html') path = os.path.join(
type = types.get(path.split('.')[-1],'text') static_folder, environ['PATH_INFO'][1:] or 'index.html')
type = types.get(path.split('.')[-1], 'text')
if os.path.exists(path): if os.path.exists(path):
try: try:
data = open(path,'rb').read() data = open(path, 'rb').read()
start_response('200 OK', [('Content-Type', type)]) start_response('200 OK', [('Content-Type', type)])
except IOError: except IOError:
start_response('404 NOT FOUND', []) start_response('404 NOT FOUND', [])
@@ -1858,21 +1886,22 @@ def demo_app(environ, start_response):
data = '<html><body><h1>Hello from Rocket Web Server</h1></body></html>' data = '<html><body><h1>Hello from Rocket Web Server</h1></body></html>'
return [data] return [data]
def demo(): def demo():
from optparse import OptionParser from optparse import OptionParser
parser = OptionParser() parser = OptionParser()
parser.add_option("-i", "--ip", dest="ip",default="127.0.0.1", parser.add_option("-i", "--ip", dest="ip", default="127.0.0.1",
help="ip address of the network interface") help="ip address of the network interface")
parser.add_option("-p", "--port", dest="port",default="8000", parser.add_option("-p", "--port", dest="port", default="8000",
help="post where to run web server") help="post where to run web server")
parser.add_option("-s", "--static", dest="static",default=None, parser.add_option("-s", "--static", dest="static", default=None,
help="folder containing static files") help="folder containing static files")
(options, args) = parser.parse_args() (options, args) = parser.parse_args()
global static_folder global static_folder
static_folder = options.static static_folder = options.static
print 'Rocket running on %s:%s' % (options.ip, options.port) print 'Rocket running on %s:%s' % (options.ip, options.port)
r=Rocket((options.ip,int(options.port)),'wsgi', {'wsgi_app':demo_app}) r = Rocket((options.ip, int(options.port)), 'wsgi', {'wsgi_app': demo_app})
r.start() r.start()
if __name__=='__main__': if __name__ == '__main__':
demo() demo()
+19 -17
View File
@@ -47,12 +47,12 @@ class XssCleaner(HTMLParser):
'code', 'code',
'pre', 'pre',
'img/', 'img/',
], ],
allowed_attributes={'a': ['href', 'title'], 'img': ['src', 'alt' allowed_attributes={'a': ['href', 'title'], 'img': ['src', 'alt'
], 'blockquote': ['type']}, ], 'blockquote': ['type']},
fmt=AbstractFormatter, fmt=AbstractFormatter,
strip_disallowed = False strip_disallowed=False
): ):
HTMLParser.__init__(self, fmt) HTMLParser.__init__(self, fmt)
self.result = '' self.result = ''
@@ -103,7 +103,7 @@ class XssCleaner(HTMLParser):
tag, tag,
method, method,
attrs, attrs,
): ):
if tag not in self.permitted_tags: if tag not in self.permitted_tags:
if self.strip_disallowed: if self.strip_disallowed:
self.in_disallowed = True self.in_disallowed = True
@@ -114,16 +114,16 @@ class XssCleaner(HTMLParser):
if tag in self.allowed_attributes: if tag in self.allowed_attributes:
attrs = dict(attrs) attrs = dict(attrs)
self.allowed_attributes_here = [x for x in self.allowed_attributes_here = [x for x in
self.allowed_attributes[tag] if x in attrs self.allowed_attributes[tag] if x in attrs
and len(attrs[x]) > 0] and len(attrs[x]) > 0]
for attribute in self.allowed_attributes_here: for attribute in self.allowed_attributes_here:
if attribute in ['href', 'src', 'background']: if attribute in ['href', 'src', 'background']:
if self.url_is_acceptable(attrs[attribute]): if self.url_is_acceptable(attrs[attribute]):
bt += ' %s="%s"' % (attribute, bt += ' %s="%s"' % (attribute,
attrs[attribute]) attrs[attribute])
else: else:
bt += ' %s=%s' % (xssescape(attribute), bt += ' %s=%s' % (xssescape(attribute),
quoteattr(attrs[attribute])) quoteattr(attrs[attribute]))
if bt == '<a' or bt == '<img': if bt == '<a' or bt == '<img':
return return
if tag in self.requires_no_close: if tag in self.requires_no_close:
@@ -168,7 +168,8 @@ class XssCleaner(HTMLParser):
content, otherwise remove it content, otherwise remove it
""" """
if not isinstance(rawstring, str): return str(rawstring) if not isinstance(rawstring, str):
return str(rawstring)
for tag in self.requires_no_close: for tag in self.requires_no_close:
rawstring = rawstring.replace("<%s/>" % tag, "<%s />" % tag) rawstring = rawstring.replace("<%s/>" % tag, "<%s />" % tag)
if not escape: if not escape:
@@ -209,16 +210,17 @@ def sanitize(text, permitted_tags=[
'code', 'code',
'pre', 'pre',
'img/', 'img/',
'h1','h2','h3','h4','h5','h6', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
'table','tr','td','div', 'table', 'tr', 'td', 'div',
], ],
allowed_attributes = { allowed_attributes={
'a': ['href', 'title'], 'a': ['href', 'title'],
'img': ['src', 'alt'], 'img': ['src', 'alt'],
'blockquote': ['type'], 'blockquote': ['type'],
'td': ['colspan'], 'td': ['colspan'],
}, },
escape=True): escape=True):
if not isinstance(text, str): return str(text) if not isinstance(text, str):
return str(text)
return XssCleaner(permitted_tags=permitted_tags, return XssCleaner(permitted_tags=permitted_tags,
allowed_attributes=allowed_attributes).strip(text, escape) allowed_attributes=allowed_attributes).strip(text, escape)
+244 -211
View File
@@ -106,7 +106,7 @@ DISABLED = 'DISABLED'
KILL = 'KILL' KILL = 'KILL'
EXPIRED = 'EXPIRED' EXPIRED = 'EXPIRED'
SECONDS = 1 SECONDS = 1
HEARTBEAT = 3*SECONDS HEARTBEAT = 3 * SECONDS
MAXHIBERNATION = 10 MAXHIBERNATION = 10
CLEAROUT = '!clear!' CLEAROUT = '!clear!'
@@ -114,20 +114,23 @@ CALLABLETYPES = (types.LambdaType, types.FunctionType,
types.BuiltinFunctionType, types.BuiltinFunctionType,
types.MethodType, types.BuiltinMethodType) types.MethodType, types.BuiltinMethodType)
class Task(object): class Task(object):
def __init__(self,app,function,timeout,args='[]',vars='{}',**kwargs): def __init__(self, app, function, timeout, args='[]', vars='{}', **kwargs):
logger.debug(' new task allocated: %s.%s' % (app,function)) logger.debug(' new task allocated: %s.%s' % (app, function))
self.app = app self.app = app
self.function = function self.function = function
self.timeout = timeout self.timeout = timeout
self.args = args # json self.args = args # json
self.vars = vars # json self.vars = vars # json
self.__dict__.update(kwargs) self.__dict__.update(kwargs)
def __str__(self): def __str__(self):
return '<Task: %s>' % self.function return '<Task: %s>' % self.function
class TaskReport(object): class TaskReport(object):
def __init__(self,status,result=None,output=None,tb=None): def __init__(self, status, result=None, output=None, tb=None):
logger.debug(' new task report: %s' % status) logger.debug(' new task report: %s' % status)
if tb: if tb:
logger.debug(' traceback: %s' % tb) logger.debug(' traceback: %s' % tb)
@@ -137,19 +140,23 @@ class TaskReport(object):
self.result = result self.result = result
self.output = output self.output = output
self.tb = tb self.tb = tb
def __str__(self): def __str__(self):
return '<TaskReport: %s>' % self.status return '<TaskReport: %s>' % self.status
def demo_function(*argv,**kwargs):
def demo_function(*argv, **kwargs):
""" test function """ """ test function """
for i in range(argv[0]): for i in range(argv[0]):
print 'click',i print 'click', i
time.sleep(1) time.sleep(1)
return 'done' return 'done'
#the two functions below deal with simplejson decoding as unicode, esp for the dict decode #the two functions below deal with simplejson decoding as unicode, esp for the dict decode
#and subsequent usage as function Keyword arguments unicode variable names won't work! #and subsequent usage as function Keyword arguments unicode variable names won't work!
#borrowed from http://stackoverflow.com/questions/956867/how-to-get-string-objects-instead-unicode-ones-from-json-in-python #borrowed from http://stackoverflow.com/questions/956867/how-to-get-string-objects-instead-unicode-ones-from-json-in-python
def _decode_list(lst): def _decode_list(lst):
newlist = [] newlist = []
for i in lst: for i in lst:
@@ -160,19 +167,21 @@ def _decode_list(lst):
newlist.append(i) newlist.append(i)
return newlist return newlist
def _decode_dict(dct): def _decode_dict(dct):
newdict = {} newdict = {}
for k, v in dct.iteritems(): for k, v in dct.iteritems():
if isinstance(k, unicode): if isinstance(k, unicode):
k = k.encode('utf-8') k = k.encode('utf-8')
if isinstance(v, unicode): if isinstance(v, unicode):
v = v.encode('utf-8') v = v.encode('utf-8')
elif isinstance(v, list): elif isinstance(v, list):
v = _decode_list(v) v = _decode_list(v)
newdict[k] = v newdict[k] = v
return newdict return newdict
def executor(queue,task, out):
def executor(queue, task, out):
""" the background process """ """ the background process """
logger.debug(' task started') logger.debug(' task started')
@@ -182,11 +191,14 @@ def executor(queue,task, out):
self.out_queue = out_queue self.out_queue = out_queue
self.stdout = sys.stdout self.stdout = sys.stdout
sys.stdout = self sys.stdout = self
def __del__(self): def __del__(self):
sys.stdout = self.stdout sys.stdout = self.stdout
def flush(self): def flush(self):
pass pass
def write(self,data):
def write(self, data):
self.out_queue.put(data) self.out_queue.put(data)
stdout = LogOutput(out) stdout = LogOutput(out)
@@ -200,8 +212,8 @@ def executor(queue,task, out):
logging.getLogger().setLevel(logging.WARN) logging.getLogger().setLevel(logging.WARN)
# Get controller-specific subdirectory if task.app is of # Get controller-specific subdirectory if task.app is of
# form 'app/controller' # form 'app/controller'
(a,c,f) = parse_path_info(task.app) (a, c, f) = parse_path_info(task.app)
_env = env(a=a,c=c,import_models=True) _env = env(a=a, c=c, import_models=True)
logging.getLogger().setLevel(level) logging.getLogger().setLevel(level)
scheduler = current._scheduler scheduler = current._scheduler
f = task.function f = task.function
@@ -212,22 +224,24 @@ def executor(queue,task, out):
else: else:
_function = functions.get(f) _function = functions.get(f)
if not isinstance(_function, CALLABLETYPES): if not isinstance(_function, CALLABLETYPES):
raise NameError("name '%s' not found in scheduler's environment" % f) raise NameError(
"name '%s' not found in scheduler's environment" % f)
globals().update(_env) globals().update(_env)
args = loads(task.args) args = loads(task.args)
vars = loads(task.vars, object_hook=_decode_dict) vars = loads(task.vars, object_hook=_decode_dict)
result = dumps(_function(*args,**vars)) result = dumps(_function(*args, **vars))
else: else:
### for testing purpose only ### for testing purpose only
result = eval(task.function)( result = eval(task.function)(
*loads(task.args, object_hook=_decode_dict), *loads(task.args, object_hook=_decode_dict),
**loads(task.vars, object_hook=_decode_dict)) **loads(task.vars, object_hook=_decode_dict))
queue.put(TaskReport(COMPLETED, result=result)) queue.put(TaskReport(COMPLETED, result=result))
except BaseException,e: except BaseException, e:
tb = traceback.format_exc() tb = traceback.format_exc()
queue.put(TaskReport(FAILED,tb=tb)) queue.put(TaskReport(FAILED, tb=tb))
del stdout del stdout
class MetaScheduler(threading.Thread): class MetaScheduler(threading.Thread):
def __init__(self): def __init__(self):
threading.Thread.__init__(self) threading.Thread.__init__(self)
@@ -235,7 +249,7 @@ class MetaScheduler(threading.Thread):
self.have_heartbeat = True # set to False to kill self.have_heartbeat = True # set to False to kill
self.empty_runs = 0 self.empty_runs = 0
def async(self,task): def async(self, task):
""" """
starts the background process and returns: starts the background process and returns:
('ok',result,output) ('ok',result,output)
@@ -247,7 +261,7 @@ class MetaScheduler(threading.Thread):
sr = db.scheduler_run sr = db.scheduler_run
out = multiprocessing.Queue() out = multiprocessing.Queue()
queue = multiprocessing.Queue(maxsize=1) queue = multiprocessing.Queue(maxsize=1)
p = multiprocessing.Process(target=executor,args=(queue,task,out)) p = multiprocessing.Process(target=executor, args=(queue, task, out))
self.process = p self.process = p
logger.debug(' task starting') logger.debug(' task starting')
p.start() p.start()
@@ -264,11 +278,11 @@ class MetaScheduler(threading.Thread):
start = time.time() start = time.time()
while p.is_alive() and ( while p.is_alive() and (
not task.timeout or time.time()-start < task.timeout): not task.timeout or time.time() - start < task.timeout):
if tout: if tout:
try: try:
logger.debug(' partial output saved') logger.debug(' partial output saved')
db(sr.id==task.run_id).update(output = task_output) db(sr.id == task.run_id).update(output=task_output)
db.commit() db.commit()
except: except:
pass pass
@@ -279,7 +293,8 @@ class MetaScheduler(threading.Thread):
if tout: if tout:
logger.debug(' partial output: "%s"' % str(tout)) logger.debug(' partial output: "%s"' % str(tout))
if CLEAROUT in tout: if CLEAROUT in tout:
task_output = tout[tout.rfind(CLEAROUT)+len(CLEAROUT):] task_output = tout[
tout.rfind(CLEAROUT) + len(CLEAROUT):]
else: else:
task_output += tout task_output += tout
except: except:
@@ -322,7 +337,7 @@ class MetaScheduler(threading.Thread):
try: try:
self.process.terminate() self.process.terminate()
except: except:
pass # no process to terminate pass # no process to terminate
def run(self): def run(self):
""" the thread that sends heartbeat """ """ the thread that sends heartbeat """
@@ -334,19 +349,19 @@ class MetaScheduler(threading.Thread):
def start_heartbeats(self): def start_heartbeats(self):
self.start() self.start()
def send_heartbeat(self,counter): def send_heartbeat(self, counter):
print 'thum' print 'thum'
time.sleep(1) time.sleep(1)
def pop_task(self): def pop_task(self):
return Task( return Task(
app = None, app=None,
function = 'demo_function', function='demo_function',
timeout = 7, timeout=7,
args = '[2]', args='[2]',
vars = '{}') vars='{}')
def report_task(self,task,task_report): def report_task(self, task, task_report):
print 'reporting task' print 'reporting task'
pass pass
@@ -361,14 +376,16 @@ class MetaScheduler(threading.Thread):
task = self.pop_task() task = self.pop_task()
if task: if task:
self.empty_runs = 0 self.empty_runs = 0
self.report_task(task,self.async(task)) self.report_task(task, self.async(task))
else: else:
self.empty_runs += 1 self.empty_runs += 1
logger.debug('sleeping...') logger.debug('sleeping...')
if self.max_empty_runs != 0: if self.max_empty_runs != 0:
logger.debug('empty runs %s/%s', self.empty_runs, self.max_empty_runs) logger.debug('empty runs %s/%s',
self.empty_runs, self.max_empty_runs)
if self.empty_runs >= self.max_empty_runs: if self.empty_runs >= self.max_empty_runs:
logger.info('empty runs limit reached, killing myself') logger.info(
'empty runs limit reached, killing myself')
self.die() self.die()
self.sleep() self.sleep()
except KeyboardInterrupt: except KeyboardInterrupt:
@@ -379,33 +396,35 @@ TASK_STATUS = (QUEUED, RUNNING, COMPLETED, FAILED, TIMEOUT, STOPPED, EXPIRED)
RUN_STATUS = (RUNNING, COMPLETED, FAILED, TIMEOUT, STOPPED) RUN_STATUS = (RUNNING, COMPLETED, FAILED, TIMEOUT, STOPPED)
WORKER_STATUS = (ACTIVE, DISABLED, TERMINATE, KILL) WORKER_STATUS = (ACTIVE, DISABLED, TERMINATE, KILL)
class TYPE(object): class TYPE(object):
""" """
validator that check whether field is valid json and validate its type validator that check whether field is valid json and validate its type
""" """
def __init__(self,myclass=list,parse=False): def __init__(self, myclass=list, parse=False):
self.myclass = myclass self.myclass = myclass
self.parse = parse self.parse = parse
def __call__(self,value): def __call__(self, value):
from gluon import current from gluon import current
try: try:
obj = loads(value) obj = loads(value)
except: except:
return (value,current.T('invalid json')) return (value, current.T('invalid json'))
else: else:
if isinstance(obj,self.myclass): if isinstance(obj, self.myclass):
if self.parse: if self.parse:
return (obj,None) return (obj, None)
else: else:
return (value,None) return (value, None)
else: else:
return (value,current.T('Not of type: %s') % self.myclass) return (value, current.T('Not of type: %s') % self.myclass)
class Scheduler(MetaScheduler): class Scheduler(MetaScheduler):
def __init__(self,db,tasks=None,migrate=True, def __init__(self, db, tasks=None, migrate=True,
worker_name=None,group_names=['main'],heartbeat=HEARTBEAT, worker_name=None, group_names=['main'], heartbeat=HEARTBEAT,
max_empty_runs=0, discard_results=False, utc_time=False): max_empty_runs=0, discard_results=False, utc_time=False):
MetaScheduler.__init__(self) MetaScheduler.__init__(self)
@@ -415,8 +434,9 @@ class Scheduler(MetaScheduler):
self.tasks = tasks self.tasks = tasks
self.group_names = group_names self.group_names = group_names
self.heartbeat = heartbeat self.heartbeat = heartbeat
self.worker_name = worker_name or socket.gethostname()+'#'+str(os.getpid()) self.worker_name = worker_name or socket.gethostname(
self.worker_status = RUNNING, 1 #tuple containing status as recorded in ) + '#' + str(os.getpid())
self.worker_status = RUNNING, 1 # tuple containing status as recorded in
#the table, plus a boost parameter for #the table, plus a boost parameter for
#hibernation (i.e. when someone stop the #hibernation (i.e. when someone stop the
#worker acting on the scheduler_worker table) #worker acting on the scheduler_worker table)
@@ -429,86 +449,85 @@ class Scheduler(MetaScheduler):
from gluon import current from gluon import current
current._scheduler = self current._scheduler = self
self.define_tables(db,migrate=migrate) self.define_tables(db, migrate=migrate)
def now(self): def now(self):
return self.utc_time and datetime.datetime.utcnow() or datetime.datetime.now() return self.utc_time and datetime.datetime.utcnow() or datetime.datetime.now()
def set_requirements(self, scheduler_task): def set_requirements(self, scheduler_task):
from gluon import current from gluon import current
if hasattr(current,'request'): if hasattr(current, 'request'):
scheduler_task.application_name.default= '%s/%s' % ( scheduler_task.application_name.default = '%s/%s' % (
current.request.application, current.request.controller current.request.application, current.request.controller
) )
def define_tables(self,db,migrate): def define_tables(self, db, migrate):
from gluon.dal import DEFAULT from gluon.dal import DEFAULT
logger.debug('defining tables (migrate=%s)' % migrate) logger.debug('defining tables (migrate=%s)' % migrate)
now = self.now now = self.now
db.define_table( db.define_table(
'scheduler_task', 'scheduler_task',
Field('application_name',requires=IS_NOT_EMPTY(), Field('application_name', requires=IS_NOT_EMPTY(),
default=None,writable=False), default=None, writable=False),
Field('task_name',default=None), Field('task_name', default=None),
Field('group_name',default='main'), Field('group_name', default='main'),
Field('status',requires=IS_IN_SET(TASK_STATUS), Field('status', requires=IS_IN_SET(TASK_STATUS),
default=QUEUED,writable=False), default=QUEUED, writable=False),
Field('function_name', Field('function_name',
requires=IS_IN_SET(sorted(self.tasks.keys())) requires=IS_IN_SET(sorted(self.tasks.keys()))
if self.tasks else DEFAULT), if self.tasks else DEFAULT),
Field('uuid', requires=IS_NOT_IN_DB(db, 'scheduler_task.uuid'), Field('uuid', requires=IS_NOT_IN_DB(db, 'scheduler_task.uuid'),
unique=True, default=web2py_uuid), unique=True, default=web2py_uuid),
Field('args','text',default='[]',requires=TYPE(list)), Field('args', 'text', default='[]', requires=TYPE(list)),
Field('vars','text',default='{}',requires=TYPE(dict)), Field('vars', 'text', default='{}', requires=TYPE(dict)),
Field('enabled','boolean',default=True), Field('enabled', 'boolean', default=True),
Field('start_time','datetime',default=now, Field('start_time', 'datetime', default=now,
requires = IS_DATETIME()), requires=IS_DATETIME()),
Field('next_run_time','datetime',default=now), Field('next_run_time', 'datetime', default=now),
Field('stop_time','datetime'), Field('stop_time', 'datetime'),
Field('repeats','integer',default=1,comment="0=unlimited", Field('repeats', 'integer', default=1, comment="0=unlimited",
requires=IS_INT_IN_RANGE(0, None)), requires=IS_INT_IN_RANGE(0, None)),
Field('retry_failed', 'integer', default=0, comment="-1=unlimited", Field('retry_failed', 'integer', default=0, comment="-1=unlimited",
requires=IS_INT_IN_RANGE(-1, None)), requires=IS_INT_IN_RANGE(-1, None)),
Field('period','integer',default=60,comment='seconds', Field('period', 'integer', default=60, comment='seconds',
requires=IS_INT_IN_RANGE(0, None)), requires=IS_INT_IN_RANGE(0, None)),
Field('timeout','integer',default=60,comment='seconds', Field('timeout', 'integer', default=60, comment='seconds',
requires=IS_INT_IN_RANGE(0, None)), requires=IS_INT_IN_RANGE(0, None)),
Field('sync_output', 'integer', default=0, Field('sync_output', 'integer', default=0,
comment="update output every n sec: 0=never", comment="update output every n sec: 0=never",
requires=IS_INT_IN_RANGE(0, None)), requires=IS_INT_IN_RANGE(0, None)),
Field('times_run','integer',default=0,writable=False), Field('times_run', 'integer', default=0, writable=False),
Field('times_failed','integer',default=0,writable=False), Field('times_failed', 'integer', default=0, writable=False),
Field('last_run_time','datetime',writable=False,readable=False), Field('last_run_time', 'datetime', writable=False, readable=False),
Field('assigned_worker_name',default='',writable=False), Field('assigned_worker_name', default='', writable=False),
on_define=self.set_requirements, on_define=self.set_requirements,
migrate=migrate,format='%(task_name)s') migrate=migrate, format='%(task_name)s')
db.define_table( db.define_table(
'scheduler_run', 'scheduler_run',
Field('scheduler_task','reference scheduler_task'), Field('scheduler_task', 'reference scheduler_task'),
Field('status',requires=IS_IN_SET(RUN_STATUS)), Field('status', requires=IS_IN_SET(RUN_STATUS)),
Field('start_time','datetime'), Field('start_time', 'datetime'),
Field('stop_time','datetime'), Field('stop_time', 'datetime'),
Field('output','text'), Field('output', 'text'),
Field('result','text'), Field('result', 'text'),
Field('traceback','text'), Field('traceback', 'text'),
Field('worker_name',default=self.worker_name), Field('worker_name', default=self.worker_name),
migrate=migrate) migrate=migrate)
db.define_table( db.define_table(
'scheduler_worker', 'scheduler_worker',
Field('worker_name', unique=True), Field('worker_name', unique=True),
Field('first_heartbeat','datetime'), Field('first_heartbeat', 'datetime'),
Field('last_heartbeat','datetime'), Field('last_heartbeat', 'datetime'),
Field('status',requires=IS_IN_SET(WORKER_STATUS)), Field('status', requires=IS_IN_SET(WORKER_STATUS)),
Field('is_ticker', 'boolean', default=False, writable=False), Field('is_ticker', 'boolean', default=False, writable=False),
Field('group_names', 'list:string', default=self.group_names), Field('group_names', 'list:string', default=self.group_names),
migrate=migrate) migrate=migrate)
if migrate: if migrate:
db.commit() db.commit()
def loop(self,worker_name=None): def loop(self, worker_name=None):
signal.signal(signal.SIGTERM, lambda signum, stack_frame: sys.exit(1)) signal.signal(signal.SIGTERM, lambda signum, stack_frame: sys.exit(1))
try: try:
self.start_heartbeats() self.start_heartbeats()
@@ -521,14 +540,16 @@ class Scheduler(MetaScheduler):
task = self.pop_task() task = self.pop_task()
if task: if task:
self.empty_runs = 0 self.empty_runs = 0
self.report_task(task,self.async(task)) self.report_task(task, self.async(task))
else: else:
self.empty_runs += 1 self.empty_runs += 1
logger.debug('sleeping...') logger.debug('sleeping...')
if self.max_empty_runs != 0: if self.max_empty_runs != 0:
logger.debug('empty runs %s/%s', self.empty_runs, self.max_empty_runs) logger.debug('empty runs %s/%s',
self.empty_runs, self.max_empty_runs)
if self.empty_runs >= self.max_empty_runs: if self.empty_runs >= self.max_empty_runs:
logger.info('empty runs limit reached, killing myself') logger.info(
'empty runs limit reached, killing myself')
self.die() self.die()
self.sleep() self.sleep()
except (KeyboardInterrupt, SystemExit): except (KeyboardInterrupt, SystemExit):
@@ -541,7 +562,7 @@ class Scheduler(MetaScheduler):
if self.is_a_ticker and self.do_assign_tasks: if self.is_a_ticker and self.do_assign_tasks:
#I'm a ticker, and 5 loops passed without reassigning tasks, let's do #I'm a ticker, and 5 loops passed without reassigning tasks, let's do
#that and loop again #that and loop again
db.commit() #?don't know if it's useful, let's be completely sure db.commit() # ?don't know if it's useful, let's be completely sure
while True: while True:
try: try:
self.assign_tasks() self.assign_tasks()
@@ -552,21 +573,22 @@ class Scheduler(MetaScheduler):
logger.error('TICKER: error assigning tasks') logger.error('TICKER: error assigning tasks')
return None return None
db.commit() db.commit()
grabbed = db(ts.assigned_worker_name==self.worker_name)\ grabbed = db(ts.assigned_worker_name == self.worker_name)(
(ts.status==ASSIGNED) ts.status == ASSIGNED)
task = grabbed.select(limitby=(0,1), orderby=ts.next_run_time).first() task = grabbed.select(limitby=(0, 1), orderby=ts.next_run_time).first()
if task: if task:
task.update_record(status=RUNNING,last_run_time=now) task.update_record(status=RUNNING, last_run_time=now)
#noone will touch my task! #noone will touch my task!
db.commit() db.commit()
logger.debug(' work to do %s' % task.id) logger.debug(' work to do %s' % task.id)
else: else:
logger.debug('nothing to do') logger.debug('nothing to do')
return None return None
next_run_time = task.last_run_time + datetime.timedelta(seconds=task.period) next_run_time = task.last_run_time + datetime.timedelta(
seconds=task.period)
times_run = task.times_run + 1 times_run = task.times_run + 1
if times_run < task.repeats or task.repeats==0: if times_run < task.repeats or task.repeats == 0:
run_again = True run_again = True
else: else:
run_again = False run_again = False
@@ -575,7 +597,7 @@ class Scheduler(MetaScheduler):
logger.debug(' new scheduler_run record') logger.debug(' new scheduler_run record')
try: try:
run_id = db.scheduler_run.insert( run_id = db.scheduler_run.insert(
scheduler_task = task.id, scheduler_task=task.id,
status=RUNNING, status=RUNNING,
start_time=now, start_time=now,
worker_name=self.worker_name) worker_name=self.worker_name)
@@ -585,22 +607,22 @@ class Scheduler(MetaScheduler):
db.rollback() db.rollback()
logger.info('new task %(id)s "%(task_name)s" %(application_name)s.%(function_name)s' % task) logger.info('new task %(id)s "%(task_name)s" %(application_name)s.%(function_name)s' % task)
return Task( return Task(
app = task.application_name, app=task.application_name,
function = task.function_name, function=task.function_name,
timeout = task.timeout, timeout=task.timeout,
args = task.args, #in json args=task.args, # in json
vars = task.vars, #in json vars=task.vars, # in json
task_id = task.id, task_id=task.id,
run_id = run_id, run_id=run_id,
run_again = run_again, run_again=run_again,
next_run_time=next_run_time, next_run_time=next_run_time,
times_run = times_run, times_run=times_run,
stop_time = task.stop_time, stop_time=task.stop_time,
retry_failed = task.retry_failed, retry_failed=task.retry_failed,
times_failed = task.times_failed, times_failed=task.times_failed,
sync_output = task.sync_output) sync_output=task.sync_output)
def report_task(self,task,task_report): def report_task(self, task, task_report):
db = self.db db = self.db
now = self.now() now = self.now()
while True: while True:
@@ -610,16 +632,17 @@ class Scheduler(MetaScheduler):
#result is 'null' as a string if task completed #result is 'null' as a string if task completed
#if it's stopped it's None as NoneType, so we record #if it's stopped it's None as NoneType, so we record
#the STOPPED "run" anyway #the STOPPED "run" anyway
logger.debug(' recording task report in db (%s)' % task_report.status) logger.debug(' recording task report in db (%s)' %
db(db.scheduler_run.id==task.run_id).update( task_report.status)
status = task_report.status, db(db.scheduler_run.id == task.run_id).update(
stop_time = now, status=task_report.status,
result = task_report.result, stop_time=now,
output = task_report.output, result=task_report.result,
traceback = task_report.tb) output=task_report.output,
traceback=task_report.tb)
else: else:
logger.debug(' deleting task report in db because of no result') logger.debug(' deleting task report in db because of no result')
db(db.scheduler_run.id==task.run_id).delete() db(db.scheduler_run.id == task.run_id).delete()
is_expired = (task.stop_time is_expired = (task.stop_time
and task.next_run_time > task.stop_time and task.next_run_time > task.stop_time
and True or False) and True or False)
@@ -627,26 +650,25 @@ class Scheduler(MetaScheduler):
or task.run_again and not is_expired or task.run_again and not is_expired
and QUEUED or COMPLETED) and QUEUED or COMPLETED)
if task_report.status == COMPLETED: if task_report.status == COMPLETED:
d = dict(status = status, d = dict(status=status,
next_run_time = task.next_run_time, next_run_time=task.next_run_time,
times_run = task.times_run, times_run=task.times_run,
times_failed = 0 times_failed=0
) )
db(db.scheduler_task.id==task.task_id)\ db(db.scheduler_task.id == task.task_id)(
(db.scheduler_task.status==RUNNING).update(**d) db.scheduler_task.status == RUNNING).update(**d)
else: else:
st_mapping = {'FAILED':'FAILED', st_mapping = {'FAILED': 'FAILED',
'TIMEOUT':'TIMEOUT', 'TIMEOUT': 'TIMEOUT',
'STOPPED':'QUEUED'}[task_report.status] 'STOPPED': 'QUEUED'}[task_report.status]
status = (task.retry_failed status = (task.retry_failed
and task.times_failed < task.retry_failed and task.times_failed < task.retry_failed
and QUEUED or task.retry_failed==-1 and QUEUED or task.retry_failed == -1
and QUEUED or st_mapping) and QUEUED or st_mapping)
db(db.scheduler_task.id==task.task_id)\ db(db.scheduler_task.id == task.task_id)(db.scheduler_task.status == RUNNING).update(
(db.scheduler_task.status==RUNNING).update( times_failed=db.scheduler_task.times_failed + 1,
times_failed=db.scheduler_task.times_failed+1, next_run_time=task.next_run_time,
next_run_time = task.next_run_time, status=status)
status=status)
db.commit() db.commit()
logger.info('task completed (%s)' % task_report.status) logger.info('task completed (%s)' % task_report.status)
break break
@@ -655,34 +677,39 @@ class Scheduler(MetaScheduler):
def adj_hibernation(self): def adj_hibernation(self):
if self.worker_status[0] == DISABLED: if self.worker_status[0] == DISABLED:
hibernation = self.worker_status[1] + 1 if self.worker_status[1] < MAXHIBERNATION else MAXHIBERNATION hibernation = self.worker_status[1] + 1 if self.worker_status[
1] < MAXHIBERNATION else MAXHIBERNATION
self.worker_status = DISABLED, hibernation self.worker_status = DISABLED, hibernation
def send_heartbeat(self,counter): def send_heartbeat(self, counter):
if not self.db_thread: if not self.db_thread:
logger.debug('thread building own DAL object') logger.debug('thread building own DAL object')
self.db_thread = DAL(self.db._uri,folder = self.db._adapter.folder) self.db_thread = DAL(
self.define_tables(self.db_thread,migrate=False) self.db._uri, folder=self.db._adapter.folder)
self.define_tables(self.db_thread, migrate=False)
try: try:
db = self.db_thread db = self.db_thread
sw, st = db.scheduler_worker, db.scheduler_task sw, st = db.scheduler_worker, db.scheduler_task
now = self.now() now = self.now()
expiration = now-datetime.timedelta(seconds=self.heartbeat*3) expiration = now - datetime.timedelta(seconds=self.heartbeat * 3)
departure = now-datetime.timedelta(seconds=self.heartbeat*3*MAXHIBERNATION) departure = now - datetime.timedelta(
seconds=self.heartbeat * 3 * MAXHIBERNATION)
# record heartbeat # record heartbeat
mybackedstatus = db(sw.worker_name==self.worker_name).select().first() mybackedstatus = db(
sw.worker_name == self.worker_name).select().first()
if not mybackedstatus: if not mybackedstatus:
sw.insert(status = ACTIVE,worker_name = self.worker_name, sw.insert(status=ACTIVE, worker_name=self.worker_name,
first_heartbeat = now,last_heartbeat = now, first_heartbeat=now, last_heartbeat=now,
group_names = self.group_names) group_names=self.group_names)
self.worker_status = ACTIVE, 1 #activating the process self.worker_status = ACTIVE, 1 # activating the process
else: else:
if mybackedstatus.status == DISABLED: if mybackedstatus.status == DISABLED:
self.worker_status = DISABLED, self.worker_status[1]#keep sleeping self.worker_status = DISABLED, self.worker_status[
1] # keep sleeping
if self.worker_status[1] == MAXHIBERNATION: if self.worker_status[1] == MAXHIBERNATION:
logger.debug('........recording heartbeat') logger.debug('........recording heartbeat')
db(sw.worker_name==self.worker_name).update( db(sw.worker_name == self.worker_name).update(
last_heartbeat = now) last_heartbeat=now)
elif mybackedstatus.status == TERMINATE: elif mybackedstatus.status == TERMINATE:
self.worker_status = TERMINATE, self.worker_status[1] self.worker_status = TERMINATE, self.worker_status[1]
@@ -695,23 +722,24 @@ class Scheduler(MetaScheduler):
else: else:
logger.debug('........recording heartbeat') logger.debug('........recording heartbeat')
db(sw.worker_name==self.worker_name).update( db(sw.worker_name == self.worker_name).update(
last_heartbeat = now, status = ACTIVE) last_heartbeat=now, status=ACTIVE)
self.worker_status = ACTIVE, 1 #re-activating the process self.worker_status = ACTIVE, 1 # re-activating the process
self.do_assign_tasks = False self.do_assign_tasks = False
if counter % 5 == 0: if counter % 5 == 0:
try: try:
# delete inactive workers # delete inactive workers
logger.debug(' freeing workers that have not sent heartbeat') logger.debug(
' freeing workers that have not sent heartbeat')
inactive_workers = db( inactive_workers = db(
((sw.last_heartbeat<expiration) & (sw.status == ACTIVE)) | ((sw.last_heartbeat < expiration) & (sw.status == ACTIVE)) |
((sw.last_heartbeat<departure) & (sw.status != ACTIVE)) ((sw.last_heartbeat <
) departure) & (sw.status != ACTIVE))
)
db(st.assigned_worker_name.belongs( db(st.assigned_worker_name.belongs(
inactive_workers._select(sw.worker_name)))\ inactive_workers._select(sw.worker_name)))(st.status == RUNNING)\
(st.status == RUNNING)\ .update(assigned_worker_name='', status=QUEUED)
.update(assigned_worker_name='',status=QUEUED)
inactive_workers.delete() inactive_workers.delete()
self.is_a_ticker = self.being_a_ticker() self.is_a_ticker = self.being_a_ticker()
if self.worker_status[0] == ACTIVE: if self.worker_status[0] == ACTIVE:
@@ -727,15 +755,17 @@ class Scheduler(MetaScheduler):
def being_a_ticker(self): def being_a_ticker(self):
db = self.db_thread db = self.db_thread
sw = db.scheduler_worker sw = db.scheduler_worker
ticker = db((sw.worker_name != self.worker_name) & (sw.is_ticker == True) & (sw.status == ACTIVE)).select().first() ticker = db((sw.worker_name != self.worker_name) & (
sw.is_ticker == True) & (sw.status == ACTIVE)).select().first()
if not ticker: if not ticker:
db(sw.worker_name == self.worker_name).update(is_ticker = True) db(sw.worker_name == self.worker_name).update(is_ticker=True)
db(sw.worker_name != self.worker_name).update(is_ticker = False) db(sw.worker_name != self.worker_name).update(is_ticker=False)
logger.info("TICKER: I'm a ticker (%s)" % self.worker_name) logger.info("TICKER: I'm a ticker (%s)" % self.worker_name)
db.commit() db.commit()
return True return True
else: else:
logger.info("%s is a ticker, I'm a poor worker" % ticker.worker_name) logger.info(
"%s is a ticker, I'm a poor worker" % ticker.worker_name)
return False return False
def assign_tasks(self): def assign_tasks(self):
@@ -749,19 +779,17 @@ class Scheduler(MetaScheduler):
group_names = w.group_names group_names = w.group_names
for gname in group_names: for gname in group_names:
if gname not in wkgroups: if gname not in wkgroups:
wkgroups[gname] = dict(workers=[{'name' : w.worker_name, 'c' : 0}]) wkgroups[gname] = dict(
workers=[{'name': w.worker_name, 'c': 0}])
else: else:
wkgroups[gname]['workers'].append({'name' : w.worker_name, 'c' : 0}) wkgroups[gname]['workers'].append(
{'name': w.worker_name, 'c': 0})
#set queued tasks that expired between "runs" (i.e., you turned off) #set queued tasks that expired between "runs" (i.e., you turned off)
#the scheduler and then it wasn't expired, but now it is #the scheduler and then it wasn't expired, but now it is
db(ts.status.belongs((QUEUED,ASSIGNED)))(ts.stop_time<now).update(status=EXPIRED) db(ts.status.belongs(
(QUEUED, ASSIGNED)))(ts.stop_time < now).update(status=EXPIRED)
all_available = db(ts.status.belongs((QUEUED,ASSIGNED)))\ all_available = db(ts.status.belongs((QUEUED, ASSIGNED)))((ts.times_run < ts.repeats) | (ts.repeats == 0))(ts.start_time <= now)((ts.stop_time is None) | (ts.stop_time > now))(ts.next_run_time <= now)(ts.enabled == True)
((ts.times_run<ts.repeats)|(ts.repeats==0))\
(ts.start_time<=now)\
((ts.stop_time==None) | (ts.stop_time>now))\
(ts.next_run_time<=now)\
(ts.enabled==True)
limit = len(all_workers) * 50 limit = len(all_workers) * 50
#if there are a moltitude of tasks, let's assign a maximum of 50 tasks per worker. #if there are a moltitude of tasks, let's assign a maximum of 50 tasks per worker.
@@ -770,7 +798,8 @@ class Scheduler(MetaScheduler):
#50 is quite a sweet spot also for fast tasks, with sane heartbeat values #50 is quite a sweet spot also for fast tasks, with sane heartbeat values
#NB: ticker reassign tasks every 5 cycles, so if a worker completes his 50 tasks in less #NB: ticker reassign tasks every 5 cycles, so if a worker completes his 50 tasks in less
#than heartbeat*5 seconds, it won't pick new tasks until heartbeat*5 seconds pass. #than heartbeat*5 seconds, it won't pick new tasks until heartbeat*5 seconds pass.
tasks = all_available.select(limitby=(0,limit), orderby=ts.next_run_time) tasks = all_available.select(
limitby=(0, limit), orderby=ts.next_run_time)
#everything until now is going fine. If a worker is currently elaborating a long task, #everything until now is going fine. If a worker is currently elaborating a long task,
#all other tasks assigned to him needs to be reassigned "freely" to other workers, that may be free. #all other tasks assigned to him needs to be reassigned "freely" to other workers, that may be free.
#this shuffles up things a bit, in order to maintain the idea of a semi-linear scalability #this shuffles up things a bit, in order to maintain the idea of a semi-linear scalability
@@ -803,7 +832,8 @@ class Scheduler(MetaScheduler):
logger.info('TICKER: tasks are %s' % len(tasks)) logger.info('TICKER: tasks are %s' % len(tasks))
def sleep(self): def sleep(self):
time.sleep(self.heartbeat*self.worker_status[1]) # should only sleep until next available task time.sleep(self.heartbeat * self.worker_status[1])
# should only sleep until next available task
def queue_task(self, function, pargs=[], pvars={}, **kwargs): def queue_task(self, function, pargs=[], pvars={}, **kwargs):
""" """
@@ -829,12 +859,12 @@ class Scheduler(MetaScheduler):
tname = 'task_name' in kwargs and kwargs.pop('task_name') or function tname = 'task_name' in kwargs and kwargs.pop('task_name') or function
print 'a', targs print 'a', targs
rtn = self.db.scheduler_task.validate_and_insert( rtn = self.db.scheduler_task.validate_and_insert(
function_name=function, function_name=function,
task_name=tname, task_name=tname,
args=targs, args=targs,
vars=tvars, vars=tvars,
uuid=tuuid, uuid=tuuid,
**kwargs) **kwargs)
if not rtn.errors: if not rtn.errors:
rtn.uuid = tuuid rtn.uuid = tuuid
else: else:
@@ -866,25 +896,28 @@ class Scheduler(MetaScheduler):
elif isinstance(ref, Query): elif isinstance(ref, Query):
q = ref q = ref
else: else:
raise SyntaxError, "You can retrieve results only by id, uuid or Query" raise SyntaxError(
"You can retrieve results only by id, uuid or Query")
fields = st.ALL fields = st.ALL
left = False left = False
orderby = ~st.id orderby = ~st.id
if output: if output:
fields = st.ALL, sr.ALL fields = st.ALL, sr.ALL
left = sr.on(sr.scheduler_task == st.id) left = sr.on(sr.scheduler_task == st.id)
orderby = ~st.id|~sr.id orderby = ~st.id | ~sr.id
row = self.db(q).select( row = self.db(q).select(
*fields, *fields,
orderby=orderby, orderby=orderby,
left=left, left=left,
limitby=(0,1) limitby=(0, 1)
).first() ).first()
if output: if output:
row.result = row.scheduler_run.result and \ row.result = row.scheduler_run.result and \
loads(row.scheduler_run.result, object_hook=_decode_dict) or None loads(row.scheduler_run.result,
object_hook=_decode_dict) or None
return row return row
def main(): def main():
""" """
allows to run worker without python web2py.py .... by simply python this.py allows to run worker without python web2py.py .... by simply python this.py
@@ -894,34 +927,34 @@ def main():
"-w", "--worker_name", dest="worker_name", default=None, "-w", "--worker_name", dest="worker_name", default=None,
help="start a worker with name") help="start a worker with name")
parser.add_option( parser.add_option(
"-b", "--heartbeat",dest="heartbeat", default = 10, "-b", "--heartbeat", dest="heartbeat", default=10,
type='int', help="heartbeat time in seconds (default 10)") type='int', help="heartbeat time in seconds (default 10)")
parser.add_option( parser.add_option(
"-L", "--logger_level",dest="logger_level", "-L", "--logger_level", dest="logger_level",
default=30, default=30,
type='int', type='int',
help="set debug output level (0-100, 0 means all, 100 means none;default is 30)") help="set debug output level (0-100, 0 means all, 100 means none;default is 30)")
parser.add_option("-E", "--empty-runs", parser.add_option("-E", "--empty-runs",
dest="max_empty_runs", dest="max_empty_runs",
type='int', type='int',
default = 0, default=0,
help="max loops with no grabbed tasks permitted (0 for never check)") help="max loops with no grabbed tasks permitted (0 for never check)")
parser.add_option( parser.add_option(
"-g", "--group_names",dest="group_names", "-g", "--group_names", dest="group_names",
default = 'main', default='main',
help="comma separated list of groups to be picked by the worker") help="comma separated list of groups to be picked by the worker")
parser.add_option( parser.add_option(
"-f", "--db_folder",dest="db_folder", "-f", "--db_folder", dest="db_folder",
default = '/Users/mdipierro/web2py/applications/scheduler/databases', default='/Users/mdipierro/web2py/applications/scheduler/databases',
help="location of the dal database folder") help="location of the dal database folder")
parser.add_option( parser.add_option(
"-u", "--db_uri",dest="db_uri", "-u", "--db_uri", dest="db_uri",
default = 'sqlite://storage.sqlite', default='sqlite://storage.sqlite',
help="database URI string (web2py DAL syntax)") help="database URI string (web2py DAL syntax)")
parser.add_option( parser.add_option(
"-t", "--tasks",dest="tasks",default=None, "-t", "--tasks", dest="tasks", default=None,
help="file containing task files, must define" + \ help="file containing task files, must define" +
"tasks = {'task_name':(lambda: 'output')} or similar set of tasks") "tasks = {'task_name':(lambda: 'output')} or similar set of tasks")
parser.add_option( parser.add_option(
"-U", "--utc-time", dest="utc_time", default=False, "-U", "--utc-time", dest="utc_time", default=False,
help="work with UTC timestamps" help="work with UTC timestamps"
@@ -930,35 +963,35 @@ def main():
if not options.tasks or not options.db_uri: if not options.tasks or not options.db_uri:
print USAGE print USAGE
if options.tasks: if options.tasks:
path,filename = os.path.split(options.tasks) path, filename = os.path.split(options.tasks)
if filename.endswith('.py'): if filename.endswith('.py'):
filename = filename[:-3] filename = filename[:-3]
sys.path.append(path) sys.path.append(path)
print 'importing tasks...' print 'importing tasks...'
tasks = __import__(filename, globals(), locals(), [], -1).tasks tasks = __import__(filename, globals(), locals(), [], -1).tasks
print 'tasks found: '+', '.join(tasks.keys()) print 'tasks found: ' + ', '.join(tasks.keys())
else: else:
tasks = {} tasks = {}
group_names = [x.strip() for x in options.group_names.split(',')] group_names = [x.strip() for x in options.group_names.split(',')]
logging.getLogger().setLevel(options.logger_level) logging.getLogger().setLevel(options.logger_level)
print 'groups for this worker: '+', '.join(group_names) print 'groups for this worker: ' + ', '.join(group_names)
print 'connecting to database in folder: ' + options.db_folder or './' print 'connecting to database in folder: ' + options.db_folder or './'
print 'using URI: '+options.db_uri print 'using URI: ' + options.db_uri
db = DAL(options.db_uri,folder=options.db_folder) db = DAL(options.db_uri, folder=options.db_folder)
print 'instantiating scheduler...' print 'instantiating scheduler...'
scheduler=Scheduler(db = db, scheduler = Scheduler(db=db,
worker_name = options.worker_name, worker_name=options.worker_name,
tasks = tasks, tasks=tasks,
migrate = True, migrate=True,
group_names = group_names, group_names=group_names,
heartbeat = options.heartbeat, heartbeat=options.heartbeat,
max_empty_runs = options.max_empty_runs, max_empty_runs=options.max_empty_runs,
utc_time = options.utc_time) utc_time=options.utc_time)
signal.signal(signal.SIGTERM, lambda signum, stack_frame: sys.exit(1)) signal.signal(signal.SIGTERM, lambda signum, stack_frame: sys.exit(1))
print 'starting main worker loop...' print 'starting main worker loop...'
scheduler.loop() scheduler.loop()
if __name__=='__main__': if __name__ == '__main__':
main() main()
+36 -32
View File
@@ -19,61 +19,64 @@ except ImportError:
except: except:
import contrib.simplejson as json_parser # fallback to pure-Python module import contrib.simplejson as json_parser # fallback to pure-Python module
def custom_json(o): def custom_json(o):
if hasattr(o,'custom_json') and callable(o.custom_json): if hasattr(o, 'custom_json') and callable(o.custom_json):
return o.custom_json() return o.custom_json()
if isinstance(o, (datetime.date, if isinstance(o, (datetime.date,
datetime.datetime, datetime.datetime,
datetime.time)): datetime.time)):
return o.isoformat()[:19].replace('T',' ') return o.isoformat()[:19].replace('T', ' ')
elif isinstance(o, (int, long)): elif isinstance(o, (int, long)):
return int(o) return int(o)
elif isinstance(o, decimal.Decimal): elif isinstance(o, decimal.Decimal):
return str(o) return str(o)
elif isinstance(o, lazyT): elif isinstance(o, lazyT):
return str(o) return str(o)
elif isinstance(o,XmlComponent): elif isinstance(o, XmlComponent):
return str(o) return str(o)
elif hasattr(o,'as_list') and callable(o.as_list): elif hasattr(o, 'as_list') and callable(o.as_list):
return o.as_list() return o.as_list()
elif hasattr(o,'as_dict') and callable(o.as_dict): elif hasattr(o, 'as_dict') and callable(o.as_dict):
return o.as_dict() return o.as_dict()
else: else:
raise TypeError(repr(o) + " is not JSON serializable") raise TypeError(repr(o) + " is not JSON serializable")
def xml_rec(value, key, quote=True): def xml_rec(value, key, quote=True):
if hasattr(value,'custom_xml') and callable(value.custom_xml): if hasattr(value, 'custom_xml') and callable(value.custom_xml):
return value.custom_xml() return value.custom_xml()
elif isinstance(value, (dict, Storage)): elif isinstance(value, (dict, Storage)):
return TAG[key](*[TAG[k](xml_rec(v, '',quote)) \ return TAG[key](*[TAG[k](xml_rec(v, '', quote))
for k, v in value.items()]) for k, v in value.items()])
elif isinstance(value, list): elif isinstance(value, list):
return TAG[key](*[TAG.item(xml_rec(item, '',quote)) for item in value]) return TAG[key](*[TAG.item(xml_rec(item, '', quote)) for item in value])
elif hasattr(value,'as_list') and callable(value.as_list): elif hasattr(value, 'as_list') and callable(value.as_list):
return str(xml_rec(value.as_list(),'',quote)) return str(xml_rec(value.as_list(), '', quote))
elif hasattr(value,'as_dict') and callable(value.as_dict): elif hasattr(value, 'as_dict') and callable(value.as_dict):
return str(xml_rec(value.as_dict(),'',quote)) return str(xml_rec(value.as_dict(), '', quote))
else: else:
return xmlescape(value,quote) return xmlescape(value, quote)
def xml(value, encoding='UTF-8', key='document', quote=True): def xml(value, encoding='UTF-8', key='document', quote=True):
return ('<?xml version="1.0" encoding="%s"?>' % encoding) + str(xml_rec(value,key,quote)) return ('<?xml version="1.0" encoding="%s"?>' % encoding) + str(xml_rec(value, key, quote))
def json(value,default=custom_json): def json(value, default=custom_json):
return json_parser.dumps(value,default=default) return json_parser.dumps(value, default=default)
def csv(value): def csv(value):
return '' return ''
def ics(events, title=None, link=None, timeshift=0, **ignored): def ics(events, title=None, link=None, timeshift=0, **ignored):
import datetime import datetime
title = title or '(unkown)' title = title or '(unkown)'
if link and not callable(link): if link and not callable(link):
link = lambda item,prefix=link: prefix.replace('[id]',str(item['id'])) link = lambda item, prefix=link: prefix.replace(
'[id]', str(item['id']))
s = 'BEGIN:VCALENDAR' s = 'BEGIN:VCALENDAR'
s += '\nVERSION:2.0' s += '\nVERSION:2.0'
s += '\nX-WR-CALNAME:%s' % title s += '\nX-WR-CALNAME:%s' % title
@@ -86,9 +89,9 @@ def ics(events, title=None, link=None, timeshift=0, **ignored):
s += '\nUID:%s' % item['id'] s += '\nUID:%s' % item['id']
if link: if link:
s += '\nURL:%s' % link(item) s += '\nURL:%s' % link(item)
shift = datetime.timedelta(seconds=3600*timeshift) shift = datetime.timedelta(seconds=3600 * timeshift)
start = item['start_datetime']+shift start = item['start_datetime'] + shift
stop = item['stop_datetime']+shift stop = item['stop_datetime'] + shift
s += '\nDTSTART:%s' % start.strftime('%Y%m%dT%H%M%S') s += '\nDTSTART:%s' % start.strftime('%Y%m%dT%H%M%S')
s += '\nDTEND:%s' % stop.strftime('%Y%m%dT%H%M%S') s += '\nDTEND:%s' % stop.strftime('%Y%m%dT%H%M%S')
s += '\nSUMMARY:%s' % item['title'] s += '\nSUMMARY:%s' % item['title']
@@ -96,18 +99,19 @@ def ics(events, title=None, link=None, timeshift=0, **ignored):
s += '\nEND:VCALENDAR' s += '\nEND:VCALENDAR'
return s return s
def rss(feed): def rss(feed):
if not 'entries' in feed and 'items' in feed: if not 'entries' in feed and 'items' in feed:
feed['entries'] = feed['items'] feed['entries'] = feed['items']
now=datetime.datetime.now() now = datetime.datetime.now()
rss = rss2.RSS2(title = str(feed.get('title','(notitle)')), rss = rss2.RSS2(title=str(feed.get('title', '(notitle)')),
link = str(feed.get('link',None)), link=str(feed.get('link', None)),
description = str(feed.get('description','')), description=str(feed.get('description', '')),
lastBuildDate = feed.get('created_on', now), lastBuildDate=feed.get('created_on', now),
items = [rss2.RSSItem( items=[rss2.RSSItem(
title=str(entry.get('title','(notitle)')), title=str(entry.get('title', '(notitle)')),
link=str(entry.get('link',None)), link=str(entry.get('link', None)),
description=str(entry.get('description','')), description=str(entry.get('description', '')),
pubDate=entry.get('created_on', now) pubDate=entry.get('created_on', now)
) for entry in feed.get('entries',[])]) ) for entry in feed.get('entries', [])])
return rss2.dumps(rss) return rss2.dumps(rss)
+1 -1
View File
@@ -29,7 +29,7 @@ global_settings.app_folders = set()
global_settings.debugging = False global_settings.debugging = False
global_settings.is_pypy = \ global_settings.is_pypy = \
hasattr(platform,'python_implementation') and \ hasattr(platform, 'python_implementation') and \
platform.python_implementation() == 'PyPy' platform.python_implementation() == 'PyPy'
global_settings.is_jython = \ global_settings.is_jython = \
+47 -35
View File
@@ -30,12 +30,13 @@ from dal import BaseAdapter
logger = logging.getLogger("web2py") logger = logging.getLogger("web2py")
def exec_environment( def exec_environment(
pyfile='', pyfile='',
request=None, request=None,
response=None, response=None,
session=None, session=None,
): ):
""" """
.. function:: gluon.shell.exec_environment([pyfile=''[, request=Request() .. function:: gluon.shell.exec_environment([pyfile=''[, request=Request()
[, response=Response[, session=Session()]]]]) [, response=Response[, session=Session()]]]])
@@ -50,9 +51,12 @@ def exec_environment(
""" """
if request is None: request = Request() if request is None:
if response is None: response = Response() request = Request()
if session is None: session = Session() if response is None:
response = Response()
if session is None:
session = Session()
if request.folder is None: if request.folder is None:
mo = re.match(r'(|.*/)applications/(?P<appname>[^/]+)', pyfile) mo = re.match(r'(|.*/)applications/(?P<appname>[^/]+)', pyfile)
@@ -78,7 +82,7 @@ def env(
f=None, f=None,
dir='', dir='',
extra_request={}, extra_request={},
): ):
""" """
Return web2py execution environment for application (a), controller (c), Return web2py execution environment for application (a), controller (c),
function (f). function (f).
@@ -113,7 +117,7 @@ def env(
request.env.remote_addr = '127.0.0.1' request.env.remote_addr = '127.0.0.1'
request.env.web2py_runtime_gae = global_settings.web2py_runtime_gae request.env.web2py_runtime_gae = global_settings.web2py_runtime_gae
for k,v in extra_request.items(): for k, v in extra_request.items():
request[k] = v request[k] = v
# Monkey patch so credentials checks pass. # Monkey patch so credentials checks pass.
@@ -129,7 +133,7 @@ def env(
try: try:
run_models_in(environment) run_models_in(environment)
except RestrictedError, e: except RestrictedError, e:
sys.stderr.write(e.traceback+'\n') sys.stderr.write(e.traceback + '\n')
sys.exit(1) sys.exit(1)
environment['__name__'] = '__main__' environment['__name__'] = '__main__'
@@ -156,7 +160,7 @@ def run(
startfile=None, startfile=None,
bpython=False, bpython=False,
python_code=False python_code=False
): ):
""" """
Start interactive shell or run Python script (startfile) in web2py Start interactive shell or run Python script (startfile) in web2py
controller environment. appname is formatted like: controller environment. appname is formatted like:
@@ -172,7 +176,8 @@ def run(
adir = os.path.join('applications', a) adir = os.path.join('applications', a)
if not os.path.exists(adir): if not os.path.exists(adir):
if sys.stdin and not sys.stdin.name == '/dev/null': if sys.stdin and not sys.stdin.name == '/dev/null':
confirm = raw_input('application %s does not exist, create (y/n)?' % a) confirm = raw_input(
'application %s does not exist, create (y/n)?' % a)
else: else:
logging.warn('application does not exist and will not be created') logging.warn('application does not exist and will not be created')
return return
@@ -180,16 +185,17 @@ def run(
os.mkdir(adir) os.mkdir(adir)
w2p_unpack('welcome.w2p', adir) w2p_unpack('welcome.w2p', adir)
for subfolder in ['models','views','controllers', 'databases', for subfolder in ['models', 'views', 'controllers', 'databases',
'modules','cron','errors','sessions', 'modules', 'cron', 'errors', 'sessions',
'languages','static','private','uploads']: 'languages', 'static', 'private', 'uploads']:
subpath = os.path.join(adir,subfolder) subpath = os.path.join(adir, subfolder)
if not os.path.exists(subpath): if not os.path.exists(subpath):
os.mkdir(subpath) os.mkdir(subpath)
db = os.path.join(adir,'models/db.py') db = os.path.join(adir, 'models/db.py')
if os.path.exists(db): if os.path.exists(db):
data = fileutils.read_file(db) data = fileutils.read_file(db)
data = data.replace('<your secret key>','sha512:'+web2py_uuid()) data = data.replace(
'<your secret key>', 'sha512:' + web2py_uuid())
fileutils.write_file(db, data) fileutils.write_file(db, data)
if c: if c:
@@ -198,7 +204,8 @@ def run(
if c: if c:
cfile = os.path.join('applications', a, 'controllers', c + '.py') cfile = os.path.join('applications', a, 'controllers', c + '.py')
if not os.path.isfile(cfile): if not os.path.isfile(cfile):
cfile = os.path.join('applications', a, 'compiled', "controllers_%s_%s.pyc" % (c,f)) cfile = os.path.join('applications', a, 'compiled',
"controllers_%s_%s.pyc" % (c, f))
if not os.path.isfile(cfile): if not os.path.isfile(cfile):
die(errmsg) die(errmsg)
else: else:
@@ -214,17 +221,21 @@ def run(
if startfile: if startfile:
try: try:
execfile(startfile, _env) execfile(startfile, _env)
if import_models: BaseAdapter.close_all_instances('commit') if import_models:
BaseAdapter.close_all_instances('commit')
except Exception, e: except Exception, e:
print traceback.format_exc() print traceback.format_exc()
if import_models: BaseAdapter.close_all_instances('rollback') if import_models:
BaseAdapter.close_all_instances('rollback')
elif python_code: elif python_code:
try: try:
exec(python_code, _env) exec(python_code, _env)
if import_models: BaseAdapter.close_all_instances('commit') if import_models:
BaseAdapter.close_all_instances('commit')
except Exception, e: except Exception, e:
print traceback.format_exc() print traceback.format_exc()
if import_models: BaseAdapter.close_all_instances('rollback') if import_models:
BaseAdapter.close_all_instances('rollback')
else: else:
if not plain: if not plain:
if bpython: if bpython:
@@ -248,7 +259,7 @@ def run(
# IPython; thanks Michael Toomim # IPython; thanks Michael Toomim
if '__builtins__' in _env: if '__builtins__' in _env:
del _env['__builtins__'] del _env['__builtins__']
shell = IPython.Shell.IPShell(argv=[],user_ns=_env) shell = IPython.Shell.IPShell(argv=[], user_ns=_env)
shell.mainloop() shell.mainloop()
return return
except: except:
@@ -304,7 +315,7 @@ def test(testpath, import_models=True, verbose=False):
mo = re.match(r'(|.*/)applications/(?P<a>[^/]+)', testpath) mo = re.match(r'(|.*/)applications/(?P<a>[^/]+)', testpath)
if not mo: if not mo:
die('test file is not in application directory: %s' die('test file is not in application directory: %s'
% testpath) % testpath)
a = mo.group('a') a = mo.group('a')
c = f = None c = f = None
files = [testpath] files = [testpath]
@@ -340,8 +351,8 @@ def test(testpath, import_models=True, verbose=False):
globs = env(a, c=c, f=f, import_models=import_models) globs = env(a, c=c, f=f, import_models=import_models)
execfile(testfile, globs) execfile(testfile, globs)
doctest.run_docstring_examples(obj, globs=globs, doctest.run_docstring_examples(obj, globs=globs,
name='%s: %s' % (os.path.basename(testfile), name='%s: %s' % (os.path.basename(testfile),
name), verbose=verbose) name), verbose=verbose)
if type(obj) in (types.TypeType, types.ClassType): if type(obj) in (types.TypeType, types.ClassType):
for attr_name in dir(obj): for attr_name in dir(obj):
@@ -369,8 +380,8 @@ def execute_from_command_line(argv=None):
parser = optparse.OptionParser(usage=get_usage()) parser = optparse.OptionParser(usage=get_usage())
parser.add_option('-S', '--shell', dest='shell', metavar='APPNAME', parser.add_option('-S', '--shell', dest='shell', metavar='APPNAME',
help='run web2py in interactive shell or IPython(if installed) ' + \ help='run web2py in interactive shell or IPython(if installed) ' +
'with specified appname') 'with specified appname')
msg = 'run web2py in interactive shell or bpython (if installed) with' msg = 'run web2py in interactive shell or bpython (if installed) with'
msg += ' specified appname (if app does not exist it will be created).' msg += ' specified appname (if app does not exist it will be created).'
msg += '\n Use combined with --shell' msg += '\n Use combined with --shell'
@@ -381,7 +392,7 @@ def execute_from_command_line(argv=None):
default=False, default=False,
dest='bpython', dest='bpython',
help=msg, help=msg,
) )
parser.add_option( parser.add_option(
'-P', '-P',
'--plain', '--plain',
@@ -389,25 +400,25 @@ def execute_from_command_line(argv=None):
default=False, default=False,
dest='plain', dest='plain',
help='only use plain python shell, should be used with --shell option', help='only use plain python shell, should be used with --shell option',
) )
parser.add_option( parser.add_option(
'-M', '-M',
'--import_models', '--import_models',
action='store_true', action='store_true',
default=False, default=False,
dest='import_models', dest='import_models',
help='auto import model files, default is False, ' + \ help='auto import model files, default is False, ' +
' should be used with --shell option', ' should be used with --shell option',
) )
parser.add_option( parser.add_option(
'-R', '-R',
'--run', '--run',
dest='run', dest='run',
metavar='PYTHON_FILE', metavar='PYTHON_FILE',
default='', default='',
help='run PYTHON_FILE in web2py environment, ' + \ help='run PYTHON_FILE in web2py environment, ' +
'should be used with --shell option', 'should be used with --shell option',
) )
(options, args) = parser.parse_args(argv[1:]) (options, args) = parser.parse_args(argv[1:])
@@ -419,7 +430,8 @@ def execute_from_command_line(argv=None):
startfile = args[0] startfile = args[0]
else: else:
startfile = '' startfile = ''
run(options.shell, options.plain, startfile=startfile, bpython=options.bpython) run(options.shell, options.plain, startfile=startfile,
bpython=options.bpython)
if __name__ == '__main__': if __name__ == '__main__':
+1 -1
View File
@@ -1,5 +1,5 @@
# this file exists for backward compatibility # this file exists for backward compatibility
__all__ = ['DAL','Field','DRIVERS'] __all__ = ['DAL', 'Field', 'DRIVERS']
from dal import DAL, Field, Table, Query, Set, Expression, Row, Rows, DRIVERS, BaseAdapter, SQLField, SQLTable, SQLXorable, SQLQuery, SQLSet, SQLRows, SQLStorage, SQLDB, GQLDB, SQLALL, SQLCustomType from dal import DAL, Field, Table, Query, Set, Expression, Row, Rows, DRIVERS, BaseAdapter, SQLField, SQLTable, SQLXorable, SQLQuery, SQLSet, SQLRows, SQLStorage, SQLDB, GQLDB, SQLALL, SQLCustomType
+630 -564
View File
File diff suppressed because it is too large. Load diff
+43 -24
View File
@@ -18,6 +18,7 @@ import portalocker
__all__ = ['List', 'Storage', 'Settings', 'Messages', __all__ = ['List', 'Storage', 'Settings', 'Messages',
'StorageList', 'load_storage', 'save_storage'] 'StorageList', 'load_storage', 'save_storage']
class Storage(dict): class Storage(dict):
""" """
A Storage object is like a dictionary except `obj.foo` can be used A Storage object is like a dictionary except `obj.foo` can be used
@@ -38,7 +39,7 @@ class Storage(dict):
>>> print o.a >>> print o.a
None None
""" """
__slots__=() __slots__ = ()
__setattr__ = dict.__setitem__ __setattr__ = dict.__setitem__
__delattr__ = dict.__delitem__ __delattr__ = dict.__delitem__
__getitem__ = dict.get __getitem__ = dict.get
@@ -48,7 +49,7 @@ class Storage(dict):
__getstate__ = lambda self: None __getstate__ = lambda self: None
__copy__ = lambda self: Storage(self) __copy__ = lambda self: Storage(self)
def getlist(self,key): def getlist(self, key):
""" """
Return a Storage value as a list. Return a Storage value as a list.
@@ -68,11 +69,11 @@ class Storage(dict):
>>> request.vars.getlist('z') >>> request.vars.getlist('z')
[] []
""" """
value = self.get(key,[]) value = self.get(key, [])
return value if not value else \ return value if not value else \
value if isinstance(value,(list,tuple)) else [value] value if isinstance(value, (list, tuple)) else [value]
def getfirst(self,key,default=None): def getfirst(self, key, default=None):
""" """
Return the first or only value when given a request.vars-style key. Return the first or only value when given a request.vars-style key.
@@ -93,7 +94,7 @@ class Storage(dict):
values = self.getlist(key) values = self.getlist(key)
return values[0] if values else default return values[0] if values else default
def getlast(self,key,default=None): def getlast(self, key, default=None):
""" """
Returns the last or only single value when Returns the last or only single value when
given a request.vars-style key. given a request.vars-style key.
@@ -115,56 +116,66 @@ class Storage(dict):
values = self.getlist(key) values = self.getlist(key)
return values[-1] if values else default return values[-1] if values else default
PICKABLE = (str,int,long,float,bool,list,dict,tuple,set) PICKABLE = (str, int, long, float, bool, list, dict, tuple, set)
class StorageList(Storage): class StorageList(Storage):
""" """
like Storage but missing elements default to [] instead of None like Storage but missing elements default to [] instead of None
""" """
def __getitem__(self,key): def __getitem__(self, key):
return self.__getattr__(key) return self.__getattr__(key)
def __getattr__(self, key): def __getattr__(self, key):
if key in self: if key in self:
return getattr(self,key) return getattr(self, key)
else: else:
r = [] r = []
setattr(self,key,r) setattr(self, key, r)
return r return r
def load_storage(filename): def load_storage(filename):
fp = None fp = None
try: try:
fp = portalocker.LockedFile(filename, 'rb') fp = portalocker.LockedFile(filename, 'rb')
storage = cPickle.load(fp) storage = cPickle.load(fp)
finally: finally:
if fp: fp.close() if fp:
fp.close()
return Storage(storage) return Storage(storage)
def save_storage(storage, filename): def save_storage(storage, filename):
fp = None fp = None
try: try:
fp = portalocker.LockedFile(filename, 'wb') fp = portalocker.LockedFile(filename, 'wb')
cPickle.dump(dict(storage), fp) cPickle.dump(dict(storage), fp)
finally: finally:
if fp: fp.close() if fp:
fp.close()
class Settings(Storage): class Settings(Storage):
def __setattr__(self, key, value): def __setattr__(self, key, value):
if key != 'lock_keys' and self['lock_keys'] and key not in self: if key != 'lock_keys' and self['lock_keys'] and key not in self:
raise SyntaxError, 'setting key \'%s\' does not exist' % key raise SyntaxError('setting key \'%s\' does not exist' % key)
if key != 'lock_values' and self['lock_values']: if key != 'lock_values' and self['lock_values']:
raise SyntaxError, 'setting value cannot be changed: %s' % key raise SyntaxError('setting value cannot be changed: %s' % key)
self[key] = value self[key] = value
class Messages(Settings): class Messages(Settings):
def __init__(self, T): def __init__(self, T):
Storage.__init__(self,T=T) Storage.__init__(self, T=T)
def __getattr__(self, key): def __getattr__(self, key):
value = self[key] value = self[key]
if isinstance(value, str): if isinstance(value, str):
return str(self.T(value)) return str(self.T(value))
return value return value
class FastStorage(dict): class FastStorage(dict):
""" """
Eventually this should replace class Storage but causes memory leak Eventually this should replace class Storage but causes memory leak
@@ -203,25 +214,33 @@ class FastStorage(dict):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
dict.__init__(self, *args, **kwargs) dict.__init__(self, *args, **kwargs)
self.__dict__ = self self.__dict__ = self
def __getattr__(self,key):
return getattr(self,key) if key in self else None def __getattr__(self, key):
def __getitem__(self,key): return getattr(self, key) if key in self else None
return dict.get(self,key,None)
def __getitem__(self, key):
return dict.get(self, key, None)
def copy(self): def copy(self):
self.__dict__ = {} self.__dict__ = {}
s = FastStorage(self) s = FastStorage(self)
self.__dict__ = self self.__dict__ = self
return s return s
def __repr__(self): def __repr__(self):
return '<Storage %s>' % dict.__repr__(self) return '<Storage %s>' % dict.__repr__(self)
def __getstate__(self): def __getstate__(self):
return dict(self) return dict(self)
def __setstate__(self, sdict): def __setstate__(self, sdict):
dict.__init__(self, sdict) dict.__init__(self, sdict)
self.__dict__=self self.__dict__ = self
def update(self, *args, **kwargs): def update(self, *args, **kwargs):
dict.__init__(self, *args, **kwargs) dict.__init__(self, *args, **kwargs)
self.__dict__=self self.__dict__ = self
class List(list): class List(list):
""" """
@@ -235,7 +254,7 @@ class List(list):
request.args(0,default=0,cast=int,otherwise=lambda:...) request.args(0,default=0,cast=int,otherwise=lambda:...)
""" """
n = len(self) n = len(self)
if 0<=i<n or -n<=i<0: if 0 <= i < n or -n <= i < 0:
value = self[i] value = self[i]
else: else:
value = default value = default
@@ -246,12 +265,12 @@ class List(list):
from http import HTTP, redirect from http import HTTP, redirect
if otherwise is None: if otherwise is None:
raise HTTP(404) raise HTTP(404)
elif isinstance(otherwise,str): elif isinstance(otherwise, str):
redirect(otherwise) redirect(otherwise)
elif callable(otherwise): elif callable(otherwise):
return otherwise() return otherwise()
else: else:
raise RuntimeError, "invalid otherwise" raise RuntimeError("invalid otherwise")
return value return value
+13 -11
View File
@@ -20,9 +20,10 @@ from contenttype import contenttype
regex_start_range = re.compile('\d+(?=\-)') regex_start_range = re.compile('\d+(?=\-)')
regex_stop_range = re.compile('(?<=\-)\d+') regex_stop_range = re.compile('(?<=\-)\d+')
DEFAULT_CHUNK_SIZE = 64*1024 DEFAULT_CHUNK_SIZE = 64 * 1024
def streamer(stream, chunk_size = DEFAULT_CHUNK_SIZE, bytes = None):
def streamer(stream, chunk_size=DEFAULT_CHUNK_SIZE, bytes=None):
offset = 0 offset = 0
while bytes is None or offset < bytes: while bytes is None or offset < bytes:
if not bytes is None and bytes - offset < chunk_size: if not bytes is None and bytes - offset < chunk_size:
@@ -38,14 +39,15 @@ def streamer(stream, chunk_size = DEFAULT_CHUNK_SIZE, bytes = None):
offset += length offset += length
stream.close() stream.close()
def stream_file_or_304_or_206( def stream_file_or_304_or_206(
static_file, static_file,
chunk_size = DEFAULT_CHUNK_SIZE, chunk_size=DEFAULT_CHUNK_SIZE,
request = None, request=None,
headers = {}, headers={},
status = 200, status=200,
error_message = None, error_message=None,
): ):
if error_message is None: if error_message is None:
error_message = rewrite.THREAD_LOCAL.routes.error_message % 'invalid request' error_message = rewrite.THREAD_LOCAL.routes.error_message % 'invalid request'
try: try:
@@ -62,7 +64,7 @@ def stream_file_or_304_or_206(
stat_file = os.stat(static_file) stat_file = os.stat(static_file)
fsize = stat_file[stat.ST_SIZE] fsize = stat_file[stat.ST_SIZE]
modified = stat_file[stat.ST_MTIME] modified = stat_file[stat.ST_MTIME]
mtime = time.strftime('%a, %d %b %Y %H:%M:%S GMT',time.gmtime(modified)) mtime = time.strftime('%a, %d %b %Y %H:%M:%S GMT', time.gmtime(modified))
headers.setdefault('Content-Type', contenttype(static_file)) headers.setdefault('Content-Type', contenttype(static_file))
headers.setdefault('Last-Modified', mtime) headers.setdefault('Last-Modified', mtime)
headers.setdefault('Pragma', 'cache') headers.setdefault('Pragma', 'cache')
@@ -95,10 +97,10 @@ def stream_file_or_304_or_206(
status = 206 status = 206
# in all the other cases (not 304, not 206, but 200 or error page) # in all the other cases (not 304, not 206, but 200 or error page)
if status != 206: if status != 206:
enc = request.env.http_accept_encoding enc = request.env.http_accept_encoding
if enc and 'gzip' in enc and not 'Content-Encoding' in headers: if enc and 'gzip' in enc and not 'Content-Encoding' in headers:
gzipped = static_file + '.gz' gzipped = static_file + '.gz'
if os.path.isfile(gzipped) and os.path.getmtime(gzipped)>modified: if os.path.isfile(gzipped) and os.path.getmtime(gzipped) > modified:
static_file = gzipped static_file = gzipped
fsize = os.path.getsize(gzipped) fsize = os.path.getsize(gzipped)
headers['Content-Encoding'] = 'gzip' headers['Content-Encoding'] = 'gzip'
+99 -77
View File
@@ -26,23 +26,26 @@ try:
except ImportError: except ImportError:
# do not have web2py # do not have web2py
current = None current = None
def RestrictedError(a,b,c):
logging.error(str(a)+':'+str(b)+':'+str(c)) def RestrictedError(a, b, c):
logging.error(str(a) + ':' + str(b) + ':' + str(c))
return RuntimeError return RuntimeError
class Node(object): class Node(object):
""" """
Basic Container Object Basic Container Object
""" """
def __init__(self, value = None, pre_extend = False): def __init__(self, value=None, pre_extend=False):
self.value = value self.value = value
self.pre_extend = pre_extend self.pre_extend = pre_extend
def __str__(self): def __str__(self):
return str(self.value) return str(self.value)
class SuperNode(Node): class SuperNode(Node):
def __init__(self, name = '', pre_extend = False): def __init__(self, name='', pre_extend=False):
self.name = name self.name = name
self.value = None self.value = None
self.pre_extend = pre_extend self.pre_extend = pre_extend
@@ -57,7 +60,8 @@ class SuperNode(Node):
def __repr__(self): def __repr__(self):
return "%s->%s" % (self.name, self.value) return "%s->%s" % (self.name, self.value)
def output_aux(node,blocks):
def output_aux(node, blocks):
# If we have a block level # If we have a block level
# If we can override this block. # If we can override this block.
# Override block from vars. # Override block from vars.
@@ -66,8 +70,9 @@ def output_aux(node,blocks):
return (blocks[node.name].output(blocks) return (blocks[node.name].output(blocks)
if node.name in blocks else if node.name in blocks else
node.output(blocks)) \ node.output(blocks)) \
if isinstance(node, BlockNode) \ if isinstance(node, BlockNode) \
else str(node) else str(node)
class BlockNode(Node): class BlockNode(Node):
""" """
@@ -82,7 +87,7 @@ class BlockNode(Node):
This is default block test This is default block test
{{ end }} {{ end }}
""" """
def __init__(self, name = '', pre_extend = False, delimiters = ('{{','}}')): def __init__(self, name='', pre_extend=False, delimiters=('{{', '}}')):
""" """
name - Name of this Node. name - Name of this Node.
""" """
@@ -92,7 +97,7 @@ class BlockNode(Node):
self.left, self.right = delimiters self.left, self.right = delimiters
def __repr__(self): def __repr__(self):
lines = ['%sblock %s%s' % (self.left,self.name,self.right)] lines = ['%sblock %s%s' % (self.left, self.name, self.right)]
lines += [str(node) for node in self.nodes] lines += [str(node) for node in self.nodes]
lines.append('%send%s' % (self.left, self.right)) lines.append('%send%s' % (self.left, self.right))
return ''.join(lines) return ''.join(lines)
@@ -101,8 +106,8 @@ class BlockNode(Node):
""" """
Get this BlockNodes content, not including child Nodes Get this BlockNodes content, not including child Nodes
""" """
return ''.join(str(node) for node in self.nodes \ return ''.join(str(node) for node in self.nodes
if not isinstance(node, BlockNode)) if not isinstance(node, BlockNode))
def append(self, node): def append(self, node):
""" """
@@ -128,8 +133,8 @@ class BlockNode(Node):
if isinstance(other, BlockNode): if isinstance(other, BlockNode):
self.nodes.extend(other.nodes) self.nodes.extend(other.nodes)
else: else:
raise TypeError("Invalid type; must be instance of ``BlockNode``. %s" % other) raise TypeError(
"Invalid type; must be instance of ``BlockNode``. %s" % other)
def output(self, blocks): def output(self, blocks):
""" """
@@ -137,7 +142,8 @@ class BlockNode(Node):
blocks -- Dictionary of blocks that are extending blocks -- Dictionary of blocks that are extending
from this template. from this template.
""" """
return ''.join(output_aux(node,blocks) for node in self.nodes) return ''.join(output_aux(node, blocks) for node in self.nodes)
class Content(BlockNode): class Content(BlockNode):
""" """
@@ -145,7 +151,7 @@ class Content(BlockNode):
Contains functions that operate as such. Contains functions that operate as such.
""" """
def __init__(self, name = "ContentBlock", pre_extend = False): def __init__(self, name="ContentBlock", pre_extend=False):
""" """
Keyword Arguments Keyword Arguments
@@ -157,18 +163,19 @@ class Content(BlockNode):
self.pre_extend = pre_extend self.pre_extend = pre_extend
def __str__(self): def __str__(self):
return ''.join(output_aux(node,self.blocks) for node in self.nodes) return ''.join(output_aux(node, self.blocks) for node in self.nodes)
def _insert(self, other, index = 0): def _insert(self, other, index=0):
""" """
Inserts object at index. Inserts object at index.
""" """
if isinstance(other, (str, Node)): if isinstance(other, (str, Node)):
self.nodes.insert(index, other) self.nodes.insert(index, other)
else: else:
raise TypeError("Invalid type, must be instance of ``str`` or ``Node``.") raise TypeError(
"Invalid type, must be instance of ``str`` or ``Node``.")
def insert(self, other, index = 0): def insert(self, other, index=0):
""" """
Inserts object at index. Inserts object at index.
@@ -201,21 +208,23 @@ class Content(BlockNode):
self.nodes.extend(other.nodes) self.nodes.extend(other.nodes)
self.blocks.update(other.blocks) self.blocks.update(other.blocks)
else: else:
raise TypeError("Invalid type; must be instance of ``BlockNode``. %s" % other) raise TypeError(
"Invalid type; must be instance of ``BlockNode``. %s" % other)
def clear_content(self): def clear_content(self):
self.nodes = [] self.nodes = []
class TemplateParser(object): class TemplateParser(object):
default_delimiters = ('{{','}}') default_delimiters = ('{{', '}}')
r_tag = compile(r'(\{\{.*?\}\})', DOTALL) r_tag = compile(r'(\{\{.*?\}\})', DOTALL)
r_multiline = compile(r'(""".*?""")|(\'\'\'.*?\'\'\')', DOTALL) r_multiline = compile(r'(""".*?""")|(\'\'\'.*?\'\'\')', DOTALL)
# These are used for re-indentation. # These are used for re-indentation.
# Indent + 1 # Indent + 1
re_block = compile('^(elif |else:|except:|except |finally:).*$',DOTALL) re_block = compile('^(elif |else:|except:|except |finally:).*$', DOTALL)
# Indent - 1 # Indent - 1
re_unblock = compile('^(return|continue|break|raise)( .*)?$', DOTALL) re_unblock = compile('^(return|continue|break|raise)( .*)?$', DOTALL)
@@ -223,12 +232,12 @@ class TemplateParser(object):
re_pass = compile('^pass( .*)?$', DOTALL) re_pass = compile('^pass( .*)?$', DOTALL)
def __init__(self, text, def __init__(self, text,
name = "ParserContainer", name="ParserContainer",
context = dict(), context=dict(),
path = 'views/', path='views/',
writer = 'response.write', writer='response.write',
lexers = {}, lexers={},
delimiters = ('{{','}}'), delimiters=('{{', '}}'),
_super_nodes = [], _super_nodes = [],
): ):
""" """
@@ -270,7 +279,7 @@ class TemplateParser(object):
escaped_delimiters = (escape(delimiters[0]), escaped_delimiters = (escape(delimiters[0]),
escape(delimiters[1])) escape(delimiters[1]))
self.r_tag = compile(r'(%s.*?%s)' % escaped_delimiters, DOTALL) self.r_tag = compile(r'(%s.*?%s)' % escaped_delimiters, DOTALL)
elif hasattr(context.get('response',None),'delimiters'): elif hasattr(context.get('response', None), 'delimiters'):
if context['response'].delimiters != self.default_delimiters: if context['response'].delimiters != self.default_delimiters:
escaped_delimiters = ( escaped_delimiters = (
escape(context['response'].delimiters[0]), escape(context['response'].delimiters[0]),
@@ -359,10 +368,10 @@ class TemplateParser(object):
k = k + credit - 1 k = k + credit - 1
# We obviously can't have a negative indentation # We obviously can't have a negative indentation
k = max(k,0) k = max(k, 0)
# Add the indentation! # Add the indentation!
new_lines.append(' '*(4*k)+line) new_lines.append(' ' * (4 * k) + line)
# Bank account back to 0 again :( # Bank account back to 0 again :(
credit = 0 credit = 0
@@ -416,7 +425,7 @@ class TemplateParser(object):
# Allow Views to include other views dynamically # Allow Views to include other views dynamically
context = self.context context = self.context
if current and not "response" in context: if current and not "response" in context:
context["response"] = getattr(current,'response',None) context["response"] = getattr(current, 'response', None)
# Get the filename; filename looks like ``"template.html"``. # Get the filename; filename looks like ``"template.html"``.
# We need to eval to remove the quotes and get the string type. # We need to eval to remove the quotes and get the string type.
@@ -442,11 +451,11 @@ class TemplateParser(object):
text = self._get_file_text(filename) text = self._get_file_text(filename)
t = TemplateParser(text, t = TemplateParser(text,
name = filename, name=filename,
context = self.context, context=self.context,
path = self.path, path=self.path,
writer = self.writer, writer=self.writer,
delimiters = self.delimiters) delimiters=self.delimiters)
content.append(t.content) content.append(t.content)
@@ -465,16 +474,17 @@ class TemplateParser(object):
super_nodes.extend(self.super_nodes) super_nodes.extend(self.super_nodes)
t = TemplateParser(text, t = TemplateParser(text,
name = filename, name=filename,
context = self.context, context=self.context,
path = self.path, path=self.path,
writer = self.writer, writer=self.writer,
delimiters = self.delimiters, delimiters=self.delimiters,
_super_nodes = super_nodes) _super_nodes=super_nodes)
# Make a temporary buffer that is unique for parent # Make a temporary buffer that is unique for parent
# template. # template.
buf = BlockNode(name='__include__' + filename, delimiters=self.delimiters) buf = BlockNode(
name='__include__' + filename, delimiters=self.delimiters)
pre = [] pre = []
# Iterate through each of our nodes # Iterate through each of our nodes
@@ -601,22 +611,22 @@ class TemplateParser(object):
# You can define custom names such as # You can define custom names such as
# '{{<<variable}}' which could potentially # '{{<<variable}}' which could potentially
# write unescaped version of the variable. # write unescaped version of the variable.
self.lexers[name](parser = self, self.lexers[name](parser=self,
value = value, value=value,
top = top, top=top,
stack = stack) stack=stack)
elif name == '=': elif name == '=':
# So we have a variable to insert into # So we have a variable to insert into
# the template # the template
buf = "\n%s(%s)" % (self.writer, value) buf = "\n%s(%s)" % (self.writer, value)
top.append(Node(buf, pre_extend = pre_extend)) top.append(Node(buf, pre_extend=pre_extend))
elif name == 'block' and not value.startswith('='): elif name == 'block' and not value.startswith('='):
# Make a new node with name. # Make a new node with name.
node = BlockNode(name = value.strip(), node = BlockNode(name=value.strip(),
pre_extend = pre_extend, pre_extend=pre_extend,
delimiters = self.delimiters) delimiters=self.delimiters)
# Append this node to our active node # Append this node to our active node
top.append(node) top.append(node)
@@ -646,8 +656,8 @@ class TemplateParser(object):
target_node = top.name target_node = top.name
# Create a SuperNode instance # Create a SuperNode instance
node = SuperNode(name = target_node, node = SuperNode(name=target_node,
pre_extend = pre_extend) pre_extend=pre_extend)
# Add this to our list to be taken care of # Add this to our list to be taken care of
self.super_nodes.append(node) self.super_nodes.append(node)
@@ -663,9 +673,10 @@ class TemplateParser(object):
# Otherwise, make a temporary include node # Otherwise, make a temporary include node
# That the child node will know to hook into. # That the child node will know to hook into.
else: else:
include_node = BlockNode(name = '__include__' + self.name, include_node = BlockNode(
pre_extend = pre_extend, name='__include__' + self.name,
delimiters = self.delimiters) pre_extend=pre_extend,
delimiters=self.delimiters)
top.append(include_node) top.append(include_node)
elif name == 'extend' and not value.startswith('='): elif name == 'extend' and not value.startswith('='):
@@ -697,21 +708,22 @@ class TemplateParser(object):
if token.startswith('='): if token.startswith('='):
if token.endswith('\\'): if token.endswith('\\'):
continuation = True continuation = True
tokens[k] = "\n%s(%s" % (self.writer, token[1:].strip()) tokens[k] = "\n%s(%s" % (
self.writer, token[1:].strip())
else: else:
tokens[k] = "\n%s(%s)" % (self.writer, token[1:].strip()) tokens[k] = "\n%s(%s)" % (
self.writer, token[1:].strip())
elif continuation: elif continuation:
tokens[k] += ')' tokens[k] += ')'
continuation = False continuation = False
buf = "\n%s" % '\n'.join(tokens) buf = "\n%s" % '\n'.join(tokens)
top.append(Node(buf, pre_extend = pre_extend)) top.append(Node(buf, pre_extend=pre_extend))
else: else:
# It is HTML so just include it. # It is HTML so just include it.
buf = "\n%s(%r, escape=False)" % (self.writer, i) buf = "\n%s(%r, escape=False)" % (self.writer, i)
top.append(Node(buf, pre_extend = pre_extend)) top.append(Node(buf, pre_extend=pre_extend))
# Remember: tag, not tag, tag, not tag # Remember: tag, not tag, tag, not tag
in_tag = not in_tag in_tag = not in_tag
@@ -740,11 +752,13 @@ class TemplateParser(object):
self.extend(extend) self.extend(extend)
# We need this for integration with gluon # We need this for integration with gluon
def parse_template(filename, def parse_template(filename,
path = 'views/', path='views/',
context = dict(), context=dict(),
lexers = {}, lexers={},
delimiters = ('{{','}}') delimiters=('{{', '}}')
): ):
""" """
filename can be a view filename in the views folder or an input stream filename can be a view filename in the views folder or an input stream
@@ -766,6 +780,7 @@ def parse_template(filename,
# Use the file contents to get a parsed template and return it. # Use the file contents to get a parsed template and return it.
return str(TemplateParser(text, context=context, path=path, lexers=lexers, delimiters=delimiters)) return str(TemplateParser(text, context=context, path=path, lexers=lexers, delimiters=delimiters))
def get_parsed(text): def get_parsed(text):
""" """
Returns the indented python code of text. Useful for unit testing. Returns the indented python code of text. Useful for unit testing.
@@ -773,13 +788,15 @@ def get_parsed(text):
""" """
return str(TemplateParser(text)) return str(TemplateParser(text))
class DummyResponse(): class DummyResponse():
def __init__(self): def __init__(self):
self.body = cStringIO.StringIO() self.body = cStringIO.StringIO()
def write(self, data, escape=True): def write(self, data, escape=True):
if not escape: if not escape:
self.body.write(str(data)) self.body.write(str(data))
elif hasattr(data,'xml') and callable(data.xml): elif hasattr(data, 'xml') and callable(data.xml):
self.body.write(data.xml()) self.body.write(data.xml())
else: else:
# make it a string # make it a string
@@ -787,27 +804,31 @@ class DummyResponse():
data = str(data) data = str(data)
elif isinstance(data, unicode): elif isinstance(data, unicode):
data = data.encode('utf8', 'xmlcharrefreplace') data = data.encode('utf8', 'xmlcharrefreplace')
data = cgi.escape(data, True).replace("'","&#x27;") data = cgi.escape(data, True).replace("'", "&#x27;")
self.body.write(data) self.body.write(data)
class NOESCAPE(): class NOESCAPE():
""" """
A little helper to avoid escaping. A little helper to avoid escaping.
""" """
def __init__(self, text): def __init__(self, text):
self.text = text self.text = text
def xml(self): def xml(self):
return self.text return self.text
# And this is a generic render function. # And this is a generic render function.
# Here for integration with gluon. # Here for integration with gluon.
def render(content = "hello world",
stream = None,
filename = None, def render(content="hello world",
path = None, stream=None,
context = {}, filename=None,
lexers = {}, path=None,
delimiters = ('{{','}}') context={},
lexers={},
delimiters=('{{', '}}')
): ):
""" """
>>> render() >>> render()
@@ -856,7 +877,7 @@ def render(content = "hello world",
# If we don't have anything to render, why bother? # If we don't have anything to render, why bother?
if not content and not stream and not filename: if not content and not stream and not filename:
raise SyntaxError, "Must specify a stream or filename or content" raise SyntaxError("Must specify a stream or filename or content")
# Here for legacy purposes, probably can be reduced to # Here for legacy purposes, probably can be reduced to
# something more simple. # something more simple.
@@ -869,7 +890,8 @@ def render(content = "hello world",
stream = cStringIO.StringIO(content) stream = cStringIO.StringIO(content)
# Execute the template. # Execute the template.
code = str(TemplateParser(stream.read(), context=context, path=path, lexers=lexers, delimiters=delimiters)) code = str(TemplateParser(stream.read(
), context=context, path=path, lexers=lexers, delimiters=delimiters))
try: try:
exec(code) in context exec(code) in context
except Exception: except Exception:
+3 -2
View File
@@ -18,6 +18,7 @@ from cache import CacheInRam, CacheOnDisk
oldcwd = None oldcwd = None
def setUpModule(): def setUpModule():
global oldcwd global oldcwd
if oldcwd is None: if oldcwd is None:
@@ -25,12 +26,14 @@ def setUpModule():
if not os.path.isdir('gluon'): if not os.path.isdir('gluon'):
os.chdir(os.path.realpath('../../')) os.chdir(os.path.realpath('../../'))
def tearDownModule(): def tearDownModule():
global oldcwd global oldcwd
if oldcwd: if oldcwd:
os.chdir(oldcwd) os.chdir(oldcwd)
oldcwd = None oldcwd = None
class TestCache(unittest.TestCase): class TestCache(unittest.TestCase):
def testCacheInRam(self): def testCacheInRam(self):
@@ -70,5 +73,3 @@ if __name__ == '__main__':
setUpModule() # pre-python-2.7 setUpModule() # pre-python-2.7
unittest.main() unittest.main()
tearDownModule() tearDownModule()
+3 -3
View File
@@ -22,13 +22,14 @@ class TestContribs(unittest.TestCase):
def test_fpdf(self): def test_fpdf(self):
""" Basic PDF test and sanity checks """ """ Basic PDF test and sanity checks """
self.assertEqual(fpdf.FPDF_VERSION, pyfpdf.FPDF_VERSION, 'version mistmatch') self.assertEqual(
fpdf.FPDF_VERSION, pyfpdf.FPDF_VERSION, 'version mistmatch')
self.assertEqual(fpdf.FPDF, pyfpdf.FPDF, 'class mistmatch') self.assertEqual(fpdf.FPDF, pyfpdf.FPDF, 'class mistmatch')
pdf = fpdf.FPDF() pdf = fpdf.FPDF()
pdf.add_page() pdf.add_page()
pdf.compress = False pdf.compress = False
pdf.set_font('Arial', '',14) pdf.set_font('Arial', '', 14)
pdf.ln(10) pdf.ln(10)
pdf.write(5, 'hello world') pdf.write(5, 'hello world')
pdf_out = pdf.output('', 'S') pdf_out = pdf.output('', 'S')
@@ -39,4 +40,3 @@ class TestContribs(unittest.TestCase):
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()
+5 -6
View File
@@ -130,7 +130,7 @@ class TestBareHelpers(unittest.TestCase):
def testOPTION(self): def testOPTION(self):
self.assertEqual(OPTION('<>', _a='1', _b='2').xml(), self.assertEqual(OPTION('<>', _a='1', _b='2').xml(),
'<option a="1" b="2" value="&lt;&gt;">&lt;&gt;' + \ '<option a="1" b="2" value="&lt;&gt;">&lt;&gt;' +
'</option>') '</option>')
def testP(self): def testP(self):
@@ -149,7 +149,7 @@ class TestBareHelpers(unittest.TestCase):
def testSELECT(self): def testSELECT(self):
self.assertEqual(SELECT('<>', _a='1', _b='2').xml(), self.assertEqual(SELECT('<>', _a='1', _b='2').xml(),
'<select a="1" b="2">'+ \ '<select a="1" b="2">' +
'<option value="&lt;&gt;">&lt;&gt;</option></select>') '<option value="&lt;&gt;">&lt;&gt;</option></select>')
def testSPAN(self): def testSPAN(self):
@@ -162,7 +162,7 @@ class TestBareHelpers(unittest.TestCase):
def testTABLE(self): def testTABLE(self):
self.assertEqual(TABLE('<>', _a='1', _b='2').xml(), self.assertEqual(TABLE('<>', _a='1', _b='2').xml(),
'<table a="1" b="2"><tr><td>&lt;&gt;</td></tr>' + \ '<table a="1" b="2"><tr><td>&lt;&gt;</td></tr>' +
'</table>') '</table>')
def testTBODY(self): def testTBODY(self):
@@ -175,8 +175,8 @@ class TestBareHelpers(unittest.TestCase):
def testTEXTAREA(self): def testTEXTAREA(self):
self.assertEqual(TEXTAREA('<>', _a='1', _b='2').xml(), self.assertEqual(TEXTAREA('<>', _a='1', _b='2').xml(),
'<textarea a="1" b="2" cols="40" rows="10">&lt;&gt;' + \ '<textarea a="1" b="2" cols="40" rows="10">&lt;&gt;' +
'</textarea>') '</textarea>')
def testTFOOT(self): def testTFOOT(self):
self.assertEqual(TFOOT('<>', _a='1', _b='2').xml(), self.assertEqual(TFOOT('<>', _a='1', _b='2').xml(),
@@ -209,4 +209,3 @@ class TestBareHelpers(unittest.TestCase):
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()
+93 -63
View File
@@ -31,7 +31,8 @@ class TestIsUrl(unittest.TestCase):
self.assertEqual(x('unreal.blargg'), ('unreal.blargg', self.assertEqual(x('unreal.blargg'), ('unreal.blargg',
'enter a valid URL')) 'enter a valid URL'))
self.assertEqual(x('google..ca'), ('google..ca', 'enter a valid URL')) self.assertEqual(x('google..ca'), ('google..ca', 'enter a valid URL'))
self.assertEqual(x('google.ca..'), ('google.ca..', 'enter a valid URL')) self.assertEqual(
x('google.ca..'), ('google.ca..', 'enter a valid URL'))
# explicit use of 'http' mode # explicit use of 'http' mode
@@ -116,9 +117,9 @@ class TestIsUrl(unittest.TestCase):
# 'generic' mode # 'generic' mode
x = IS_URL(mode='generic') x = IS_URL(mode='generic')
self.assertEqual(x('http://google.ca'), ('http://google.ca',None)) self.assertEqual(x('http://google.ca'), ('http://google.ca', None))
self.assertEqual(x('google.ca'), ('google.ca', None)) self.assertEqual(x('google.ca'), ('google.ca', None))
self.assertEqual(x('google.ca:80'), ('http://google.ca:80',None)) self.assertEqual(x('google.ca:80'), ('http://google.ca:80', None))
self.assertEqual(x('blargg://unreal'), ('blargg://unreal', self.assertEqual(x('blargg://unreal'), ('blargg://unreal',
'enter a valid URL')) 'enter a valid URL'))
@@ -152,7 +153,7 @@ class TestIsUrl(unittest.TestCase):
# 'generic' mode with overriden allowed_schemes and prepend_scheme # 'generic' mode with overriden allowed_schemes and prepend_scheme
x = IS_URL(mode='generic', allowed_schemes=[None, 'ftp', 'ftps' x = IS_URL(mode='generic', allowed_schemes=[None, 'ftp', 'ftps'
], prepend_scheme='ftp') ], prepend_scheme='ftp')
self.assertEqual(x('http://google.ca'), ('http://google.ca', self.assertEqual(x('http://google.ca'), ('http://google.ca',
'enter a valid URL')) 'enter a valid URL'))
self.assertEqual(x('google.ca'), ('google.ca', None)) self.assertEqual(x('google.ca'), ('google.ca', None))
@@ -191,10 +192,11 @@ class TestIsUrl(unittest.TestCase):
# error at calling time # error at calling time
except Exception, e: except Exception, e:
if str(e)\ if str(e)\
!= "allowed_scheme value 'ftp' is not in [None, 'http', 'https']": != "allowed_scheme value 'ftp' is not in [None, 'http', 'https']":
self.fail('Wrong exception: ' + str(e)) self.fail('Wrong exception: ' + str(e))
else: else:
self.fail("Accepted invalid allowed_schemes: [None, 'ftp', 'ftps']") self.fail(
"Accepted invalid allowed_schemes: [None, 'ftp', 'ftps']")
# prepend_scheme's value must be in allowed_schemes (default for 'http' # prepend_scheme's value must be in allowed_schemes (default for 'http'
# mode is [None, 'http', 'https']) # mode is [None, 'http', 'https'])
@@ -205,7 +207,7 @@ class TestIsUrl(unittest.TestCase):
# error at calling time # error at calling time
except Exception, e: except Exception, e:
if str(e)\ if str(e)\
!= "prepend_scheme='ftp' is not in allowed_schemes=[None, 'http', 'https']": != "prepend_scheme='ftp' is not in allowed_schemes=[None, 'http', 'https']":
self.fail('Wrong exception: ' + str(e)) self.fail('Wrong exception: ' + str(e))
else: else:
self.fail("Accepted invalid prepend_scheme: 'ftp'") self.fail("Accepted invalid prepend_scheme: 'ftp'")
@@ -217,7 +219,7 @@ class TestIsUrl(unittest.TestCase):
x = IS_URL(allowed_schemes=[None, 'https']) x = IS_URL(allowed_schemes=[None, 'https'])
except Exception, e: except Exception, e:
if str(e)\ if str(e)\
!= "prepend_scheme='http' is not in allowed_schemes=[None, 'https']": != "prepend_scheme='http' is not in allowed_schemes=[None, 'https']":
self.fail('Wrong exception: ' + str(e)) self.fail('Wrong exception: ' + str(e))
else: else:
self.fail("Accepted invalid prepend_scheme: 'http'") self.fail("Accepted invalid prepend_scheme: 'http'")
@@ -229,7 +231,7 @@ class TestIsUrl(unittest.TestCase):
prepend_scheme='https') prepend_scheme='https')
except Exception, e: except Exception, e:
if str(e)\ if str(e)\
!= "prepend_scheme='https' is not in allowed_schemes=[None, 'http']": != "prepend_scheme='https' is not in allowed_schemes=[None, 'http']":
self.fail('Wrong exception: ' + str(e)) self.fail('Wrong exception: ' + str(e))
else: else:
self.fail("Accepted invalid prepend_scheme: 'https'") self.fail("Accepted invalid prepend_scheme: 'https'")
@@ -241,7 +243,7 @@ class TestIsUrl(unittest.TestCase):
'ftps']) 'ftps'])
except Exception, e: except Exception, e:
if str(e)\ if str(e)\
!= "prepend_scheme='http' is not in allowed_schemes=[None, 'ftp', 'ftps']": != "prepend_scheme='http' is not in allowed_schemes=[None, 'ftp', 'ftps']":
self.fail('Wrong exception: ' + str(e)) self.fail('Wrong exception: ' + str(e))
else: else:
self.fail("Accepted invalid prepend_scheme: 'http'") self.fail("Accepted invalid prepend_scheme: 'http'")
@@ -251,7 +253,8 @@ class TestIsUrl(unittest.TestCase):
try: try:
x = IS_URL(mode='generic', prepend_scheme='blargg') x = IS_URL(mode='generic', prepend_scheme='blargg')
x('http://www.google.ca') # we can only reasonably know about the error at calling time x('http://www.google.ca')
# we can only reasonably know about the error at calling time
except Exception, e: except Exception, e:
if not str(e).startswith( if not str(e).startswith(
"prepend_scheme='blargg' is not in allowed_schemes="): "prepend_scheme='blargg' is not in allowed_schemes="):
@@ -266,7 +269,7 @@ class TestIsUrl(unittest.TestCase):
prepend_scheme='blargg') prepend_scheme='blargg')
except Exception, e: except Exception, e:
if str(e)\ if str(e)\
!= "prepend_scheme='blargg' is not in allowed_schemes=[None, 'http']": != "prepend_scheme='blargg' is not in allowed_schemes=[None, 'http']":
self.fail('Wrong exception: ' + str(e)) self.fail('Wrong exception: ' + str(e))
else: else:
self.fail("Accepted invalid prepend_scheme: 'blargg'") self.fail("Accepted invalid prepend_scheme: 'blargg'")
@@ -282,7 +285,7 @@ class TestIsUrl(unittest.TestCase):
# prepend_scheme has the invalid value 'http', we don't care! # prepend_scheme has the invalid value 'http', we don't care!
x = IS_URL(mode='generic', allowed_schemes=['https'], x = IS_URL(mode='generic', allowed_schemes=['https'],
prepend_scheme='https') prepend_scheme='https')
self.assertEqual(x('google.ca'), ('google.ca', 'enter a valid URL')) self.assertEqual(x('google.ca'), ('google.ca', 'enter a valid URL'))
@@ -335,12 +338,12 @@ class TestIsGenericUrl(unittest.TestCase):
'ht,tp://www.benn.ca', 'ht,tp://www.benn.ca',
'ht:tp://www.benn.ca', 'ht:tp://www.benn.ca',
'htp://invalid_scheme.com', 'htp://invalid_scheme.com',
] ]
failures = [] failures = []
for url in urlsToCheckA + urlsToCheckB: for url in urlsToCheckA + urlsToCheckB:
if self.x(url)[1] == None: if self.x(url)[1] is None:
failures.append('Incorrectly accepted: ' + str(url)) failures.append('Incorrectly accepted: ' + str(url))
if len(failures) > 0: if len(failures) > 0:
@@ -377,14 +380,13 @@ class TestIsGenericUrl(unittest.TestCase):
'http://localhost:8080/', 'http://localhost:8080/',
'http://localhost:8080/hello', 'http://localhost:8080/hello',
'http://localhost:8080/hello/', 'http://localhost:8080/hello/',
'file:///C:/Documents%20and%20Settings/Jonathan/Desktop/view.py' 'file:///C:/Documents%20and%20Settings/Jonathan/Desktop/view.py',
, ]
]
failures = [] failures = []
for url in urlsToCheck: for url in urlsToCheck:
if self.x(url)[1] != None: if self.x(url)[1] is not None:
failures.append('Incorrectly rejected: ' + str(url)) failures.append('Incorrectly rejected: ' + str(url))
if len(failures) > 0: if len(failures) > 0:
@@ -405,7 +407,7 @@ class TestIsGenericUrl(unittest.TestCase):
# because a scheme is required # because a scheme is required
y = IS_GENERIC_URL(allowed_schemes=['http', 'blargg'], y = IS_GENERIC_URL(allowed_schemes=['http', 'blargg'],
prepend_scheme='http') prepend_scheme='http')
self.assertEqual(y('google.ca'), ('google.ca', 'enter a valid URL')) self.assertEqual(y('google.ca'), ('google.ca', 'enter a valid URL'))
@@ -459,12 +461,12 @@ class TestIsHttpUrl(unittest.TestCase):
'path/segment/without/starting/slash', 'path/segment/without/starting/slash',
'http://www.math.uio.no;param=3', 'http://www.math.uio.no;param=3',
'://ABC.com:/%7esmith/home.html', '://ABC.com:/%7esmith/home.html',
] ]
failures = [] failures = []
for url in urlsToCheck: for url in urlsToCheck:
if self.x(url)[1] == None: if self.x(url)[1] is None:
failures.append('Incorrectly accepted: ' + str(url)) failures.append('Incorrectly accepted: ' + str(url))
if len(failures) > 0: if len(failures) > 0:
@@ -523,8 +525,7 @@ class TestIsHttpUrl(unittest.TestCase):
'HTTPS://localhost.', 'HTTPS://localhost.',
'http://localhost#fragment', 'http://localhost#fragment',
'http://localhost/hello;param=value', 'http://localhost/hello;param=value',
'http://localhost/hello;param=value/hi;param2=value2;param3=value3' 'http://localhost/hello;param=value/hi;param2=value2;param3=value3',
,
'http://localhost/hello?query=True', 'http://localhost/hello?query=True',
'http://www.benn.ca/hello;param=value/hi;param2=value2;param3=value3/index.html?query=3', 'http://www.benn.ca/hello;param=value/hi;param2=value2;param3=value3/index.html?query=3',
'http://localhost/hello/?query=1500&five=6', 'http://localhost/hello/?query=1500&five=6',
@@ -534,12 +535,12 @@ class TestIsHttpUrl(unittest.TestCase):
'http://localhost:8080/hello%20world/', 'http://localhost:8080/hello%20world/',
'http://www.a.3.be-nn.5.ca', 'http://www.a.3.be-nn.5.ca',
'http://www.amazon.COM', 'http://www.amazon.COM',
] ]
failures = [] failures = []
for url in urlsToCheck: for url in urlsToCheck:
if self.x(url)[1] != None: if self.x(url)[1] is not None:
failures.append('Incorrectly rejected: ' + str(url)) failures.append('Incorrectly rejected: ' + str(url))
if len(failures) > 0: if len(failures) > 0:
@@ -557,17 +558,20 @@ class TestIsHttpUrl(unittest.TestCase):
self.assertEqual(self.x('https://google.ca'), self.assertEqual(self.x('https://google.ca'),
('https://google.ca', None)) ('https://google.ca', None))
y = IS_HTTP_URL(prepend_scheme='https', allowed_schemes=[None, 'https']) y = IS_HTTP_URL(
self.assertEqual(y('google.ca'), ('https://google.ca', None)) # prepends https if asked prepend_scheme='https', allowed_schemes=[None, 'https'])
self.assertEqual(y('google.ca'), (
'https://google.ca', None)) # prepends https if asked
z = IS_HTTP_URL(prepend_scheme=None) z = IS_HTTP_URL(prepend_scheme=None)
self.assertEqual(z('google.ca:8080'), ('google.ca:8080', None)) # prepending disabled self.assertEqual(z('google.ca:8080'), ('google.ca:8080',
None)) # prepending disabled
try: try:
IS_HTTP_URL(prepend_scheme='mailto') IS_HTTP_URL(prepend_scheme='mailto')
except Exception, e: except Exception, e:
if str(e)\ if str(e)\
!= "prepend_scheme='mailto' is not in allowed_schemes=[None, 'http', 'https']": != "prepend_scheme='mailto' is not in allowed_schemes=[None, 'http', 'https']":
self.fail('Wrong exception: ' + str(e)) self.fail('Wrong exception: ' + str(e))
else: else:
self.fail("Got invalid prepend_scheme: 'mailto'") self.fail("Got invalid prepend_scheme: 'mailto'")
@@ -579,67 +583,93 @@ class TestIsHttpUrl(unittest.TestCase):
self.assertEqual(a('google.ca:80'), ('google.ca:80', self.assertEqual(a('google.ca:80'), ('google.ca:80',
'enter a valid URL')) 'enter a valid URL'))
class TestUnicode(unittest.TestCase): class TestUnicode(unittest.TestCase):
x = IS_URL() x = IS_URL()
y = IS_URL(allowed_schemes=['https'], prepend_scheme='https') #excludes the option for abbreviated URLs with no scheme y = IS_URL(allowed_schemes=['https'], prepend_scheme='https')
z = IS_URL(prepend_scheme=None) # disables prepending the scheme in the return value #excludes the option for abbreviated URLs with no scheme
z = IS_URL(prepend_scheme=None)
# disables prepending the scheme in the return value
def testUnicodeToAsciiUrl(self): def testUnicodeToAsciiUrl(self):
self.assertEquals(unicode_to_ascii_authority(u'www.Alliancefran\xe7aise.nu'), 'www.xn--alliancefranaise-npb.nu') self.assertEquals(unicode_to_ascii_authority(u'www.Alliancefran\xe7aise.nu'), 'www.xn--alliancefranaise-npb.nu')
self.assertEquals(unicode_to_ascii_authority(u'www.benn.ca'), 'www.benn.ca') self.assertEquals(
self.assertRaises(UnicodeError, unicode_to_ascii_authority, u'\u4e2d'*1000) #label is too long unicode_to_ascii_authority(u'www.benn.ca'), 'www.benn.ca')
self.assertRaises(UnicodeError, unicode_to_ascii_authority,
u'\u4e2d' * 1000) # label is too long
def testValidUrls(self): def testValidUrls(self):
self.assertEquals(self.x(u'www.Alliancefrancaise.nu'), ('http://www.Alliancefrancaise.nu', None)) self.assertEquals(self.x(u'www.Alliancefrancaise.nu'), (
self.assertEquals(self.x(u'www.Alliancefran\xe7aise.nu'), ('http://www.xn--alliancefranaise-npb.nu', None)) 'http://www.Alliancefrancaise.nu', None))
self.assertEquals(self.x(u'www.Alliancefran\xe7aise.nu:8080'), ('http://www.xn--alliancefranaise-npb.nu:8080', None)) self.assertEquals(self.x(u'www.Alliancefran\xe7aise.nu'), (
self.assertEquals(self.x(u'http://www.Alliancefran\xe7aise.nu'), ('http://www.xn--alliancefranaise-npb.nu', None)) 'http://www.xn--alliancefranaise-npb.nu', None))
self.assertEquals(self.x(u'www.Alliancefran\xe7aise.nu:8080'), (
'http://www.xn--alliancefranaise-npb.nu:8080', None))
self.assertEquals(self.x(u'http://www.Alliancefran\xe7aise.nu'),
('http://www.xn--alliancefranaise-npb.nu', None))
self.assertEquals(self.x(u'http://www.Alliancefran\xe7aise.nu/parnaise/blue'), ('http://www.xn--alliancefranaise-npb.nu/parnaise/blue', None)) self.assertEquals(self.x(u'http://www.Alliancefran\xe7aise.nu/parnaise/blue'), ('http://www.xn--alliancefranaise-npb.nu/parnaise/blue', None))
self.assertEquals(self.x(u'http://www.Alliancefran\xe7aise.nu/parnaise/blue#fragment'), ('http://www.xn--alliancefranaise-npb.nu/parnaise/blue#fragment', None)) self.assertEquals(self.x(u'http://www.Alliancefran\xe7aise.nu/parnaise/blue#fragment'), ('http://www.xn--alliancefranaise-npb.nu/parnaise/blue#fragment', None))
self.assertEquals(self.x(u'http://www.Alliancefran\xe7aise.nu/parnaise/blue?query=value#fragment'), ('http://www.xn--alliancefranaise-npb.nu/parnaise/blue?query=value#fragment', None)) self.assertEquals(self.x(u'http://www.Alliancefran\xe7aise.nu/parnaise/blue?query=value#fragment'), ('http://www.xn--alliancefranaise-npb.nu/parnaise/blue?query=value#fragment', None))
self.assertEquals(self.x(u'http://www.Alliancefran\xe7aise.nu:8080/parnaise/blue?query=value#fragment'), ('http://www.xn--alliancefranaise-npb.nu:8080/parnaise/blue?query=value#fragment', None)) self.assertEquals(self.x(u'http://www.Alliancefran\xe7aise.nu:8080/parnaise/blue?query=value#fragment'), ('http://www.xn--alliancefranaise-npb.nu:8080/parnaise/blue?query=value#fragment', None))
self.assertEquals(self.x(u'www.Alliancefran\xe7aise.nu/parnaise/blue?query=value#fragment'), ('http://www.xn--alliancefranaise-npb.nu/parnaise/blue?query=value#fragment', None)) self.assertEquals(self.x(u'www.Alliancefran\xe7aise.nu/parnaise/blue?query=value#fragment'), ('http://www.xn--alliancefranaise-npb.nu/parnaise/blue?query=value#fragment', None))
self.assertEquals(self.x(u'http://\u4e2d\u4fd4.com'), ('http://xn--fiq13b.com', None)) self.assertEquals(self.x(
self.assertEquals(self.x(u'http://\u4e2d\u4fd4.com/\u4e86'), ('http://xn--fiq13b.com/%4e%86', None)) u'http://\u4e2d\u4fd4.com'), ('http://xn--fiq13b.com', None))
self.assertEquals(self.x(u'http://\u4e2d\u4fd4.com/\u4e86'),
('http://xn--fiq13b.com/%4e%86', None))
self.assertEquals(self.x(u'http://\u4e2d\u4fd4.com/\u4e86?query=\u4e86'), ('http://xn--fiq13b.com/%4e%86?query=%4e%86', None)) self.assertEquals(self.x(u'http://\u4e2d\u4fd4.com/\u4e86?query=\u4e86'), ('http://xn--fiq13b.com/%4e%86?query=%4e%86', None))
self.assertEquals(self.x(u'http://\u4e2d\u4fd4.com/\u4e86?query=\u4e86#fragment'), ('http://xn--fiq13b.com/%4e%86?query=%4e%86#fragment', None)) self.assertEquals(self.x(u'http://\u4e2d\u4fd4.com/\u4e86?query=\u4e86#fragment'), ('http://xn--fiq13b.com/%4e%86?query=%4e%86#fragment', None))
self.assertEquals(self.x(u'http://\u4e2d\u4fd4.com?query=\u4e86#fragment'), ('http://xn--fiq13b.com?query=%4e%86#fragment', None)) self.assertEquals(self.x(u'http://\u4e2d\u4fd4.com?query=\u4e86#fragment'), ('http://xn--fiq13b.com?query=%4e%86#fragment', None))
self.assertEquals(self.x(u'http://B\xfccher.ch'), ('http://xn--bcher-kva.ch', None)) self.assertEquals(
self.assertEquals(self.x(u'http://\xe4\xf6\xfc\xdf.com'), ('http://xn--ss-uia6e4a.com', None)) self.x(u'http://B\xfccher.ch'), ('http://xn--bcher-kva.ch', None))
self.assertEquals(self.x(u'http://visegr\xe1d.com'), ('http://xn--visegrd-mwa.com', None)) self.assertEquals(self.x(u'http://\xe4\xf6\xfc\xdf.com'), (
self.assertEquals(self.x(u'http://h\xe1zipatika.com'), ('http://xn--hzipatika-01a.com', None)) 'http://xn--ss-uia6e4a.com', None))
self.assertEquals(self.x(u'http://www.\xe7ukurova.com'), ('http://www.xn--ukurova-txa.com', None)) self.assertEquals(self.x(
u'http://visegr\xe1d.com'), ('http://xn--visegrd-mwa.com', None))
self.assertEquals(self.x(u'http://h\xe1zipatika.com'), (
'http://xn--hzipatika-01a.com', None))
self.assertEquals(self.x(u'http://www.\xe7ukurova.com'), (
'http://www.xn--ukurova-txa.com', None))
self.assertEquals(self.x(u'http://nixier\xf6hre.nixieclock-tube.com'), ('http://xn--nixierhre-57a.nixieclock-tube.com', None)) self.assertEquals(self.x(u'http://nixier\xf6hre.nixieclock-tube.com'), ('http://xn--nixierhre-57a.nixieclock-tube.com', None))
self.assertEquals(self.x(u'google.ca.'), ('http://google.ca.', None)) self.assertEquals(self.x(u'google.ca.'), ('http://google.ca.', None))
self.assertEquals(self.y(u'https://google.ca'), ('https://google.ca', None)) self.assertEquals(
self.assertEquals(self.y(u'https://\u4e2d\u4fd4.com'), ('https://xn--fiq13b.com', None)) self.y(u'https://google.ca'), ('https://google.ca', None))
self.assertEquals(self.y(
u'https://\u4e2d\u4fd4.com'), ('https://xn--fiq13b.com', None))
self.assertEquals(self.z(u'google.ca'), ('google.ca', None)) self.assertEquals(self.z(u'google.ca'), ('google.ca', None))
def testInvalidUrls(self): def testInvalidUrls(self):
self.assertEquals(self.x(u'://ABC.com'), (u'://ABC.com', 'enter a valid URL')) self.assertEquals(
self.assertEquals(self.x(u'http://\u4e2d\u4fd4.dne'), (u'http://\u4e2d\u4fd4.dne', 'enter a valid URL')) self.x(u'://ABC.com'), (u'://ABC.com', 'enter a valid URL'))
self.assertEquals(self.x(u'https://google.dne'), (u'https://google.dne', 'enter a valid URL')) self.assertEquals(self.x(u'http://\u4e2d\u4fd4.dne'), (
self.assertEquals(self.x(u'https://google..ca'), (u'https://google..ca', 'enter a valid URL')) u'http://\u4e2d\u4fd4.dne', 'enter a valid URL'))
self.assertEquals(self.x(u'google..ca'), (u'google..ca', 'enter a valid URL')) self.assertEquals(self.x(u'https://google.dne'), (
self.assertEquals(self.x(u'http://' + u'\u4e2d'*1000 + u'.com'), (u'http://' + u'\u4e2d'*1000 + u'.com', 'enter a valid URL')) u'https://google.dne', 'enter a valid URL'))
self.assertEquals(self.x(u'https://google..ca'), (
u'https://google..ca', 'enter a valid URL'))
self.assertEquals(
self.x(u'google..ca'), (u'google..ca', 'enter a valid URL'))
self.assertEquals(self.x(u'http://' + u'\u4e2d' * 1000 + u'.com'), (
u'http://' + u'\u4e2d' * 1000 + u'.com', 'enter a valid URL'))
self.assertEquals(self.x(u'http://google.com#fragment_\u4e86'), (u'http://google.com#fragment_\u4e86', 'enter a valid URL')) self.assertEquals(self.x(u'http://google.com#fragment_\u4e86'), (
self.assertEquals(self.x(u'http\u4e86://google.com'), (u'http\u4e86://google.com', 'enter a valid URL')) u'http://google.com#fragment_\u4e86', 'enter a valid URL'))
self.assertEquals(self.x(u'http\u4e86://google.com#fragment_\u4e86'), (u'http\u4e86://google.com#fragment_\u4e86', 'enter a valid URL')) self.assertEquals(self.x(u'http\u4e86://google.com'), (
u'http\u4e86://google.com', 'enter a valid URL'))
self.assertEquals(self.x(u'http\u4e86://google.com#fragment_\u4e86'), (
u'http\u4e86://google.com#fragment_\u4e86', 'enter a valid URL'))
self.assertEquals(self.y(u'http://\u4e2d\u4fd4.com/\u4e86'), (u'http://\u4e2d\u4fd4.com/\u4e86', 'enter a valid URL')) self.assertEquals(self.y(u'http://\u4e2d\u4fd4.com/\u4e86'), (
u'http://\u4e2d\u4fd4.com/\u4e86', 'enter a valid URL'))
#self.assertEquals(self.y(u'google.ca'), (u'google.ca', 'enter a valid URL')) #self.assertEquals(self.y(u'google.ca'), (u'google.ca', 'enter a valid URL'))
self.assertEquals(self.z(u'invalid.domain..com'), (u'invalid.domain..com', 'enter a valid URL')) self.assertEquals(self.z(u'invalid.domain..com'), (
self.assertEquals(self.z(u'invalid.\u4e2d\u4fd4.blargg'), (u'invalid.\u4e2d\u4fd4.blargg', 'enter a valid URL')) u'invalid.domain..com', 'enter a valid URL'))
self.assertEquals(self.z(u'invalid.\u4e2d\u4fd4.blargg'), (
u'invalid.\u4e2d\u4fd4.blargg', 'enter a valid URL'))
# ############################################################################## # ##############################################################################
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()
+3 -5
View File
@@ -49,12 +49,11 @@ try:
def test_reads_and_writes(self): def test_reads_and_writes(self):
readwriters = 10 readwriters = 10
pool = multiprocessing.Pool(processes = readwriters) pool = multiprocessing.Pool(processes=readwriters)
results = pool.map(read_write, [[self.filename, 10]] * readwriters) results = pool.map(read_write, [[self.filename, 10]] * readwriters)
for result in results: for result in results:
self.assertTrue(result) self.assertTrue(result)
class TestTranslations(unittest.TestCase): class TestTranslations(unittest.TestCase):
def setUp(self): def setUp(self):
@@ -62,11 +61,11 @@ try:
if os.path.isdir('gluon'): if os.path.isdir('gluon'):
self.request.folder = 'applications/welcome' self.request.folder = 'applications/welcome'
else: else:
self.request.folder = os.path.realpath('../../applications/welcome') self.request.folder = os.path.realpath(
'../../applications/welcome')
self.request.env = Storage() self.request.env = Storage()
self.request.env.http_accept_language = 'en' self.request.env.http_accept_language = 'en'
def tearDown(self): def tearDown(self):
pass pass
@@ -99,4 +98,3 @@ except ImportError:
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()
+1 -1
View File
@@ -13,10 +13,10 @@ else:
import unittest import unittest
from contrib.markmin.markmin2html import run_doctests from contrib.markmin.markmin2html import run_doctests
class TestMarkmin(unittest.TestCase): class TestMarkmin(unittest.TestCase):
def testMarkmin(self): def testMarkmin(self):
run_doctests() run_doctests()
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()
+868 -513
View File
File diff suppressed because it is too large. Load diff
+172 -91
View File
@@ -10,9 +10,9 @@ import tempfile
import logging import logging
if os.path.isdir('gluon'): if os.path.isdir('gluon'):
sys.path.append(os.path.realpath('gluon')) # running from web2py base sys.path.append(os.path.realpath('gluon')) # running from web2py base
else: else:
sys.path.append(os.path.realpath('../')) # running from gluon/tests/ sys.path.append(os.path.realpath('../')) # running from gluon/tests/
os.environ['web2py_path'] = os.path.realpath('../../') # for settings os.environ['web2py_path'] = os.path.realpath('../../') # for settings
from rewrite import load, filter_url, filter_err, get_effective_router, regex_filter_out, regex_select from rewrite import load, filter_url, filter_err, get_effective_router, regex_filter_out, regex_select
@@ -26,6 +26,7 @@ logger = None
oldcwd = None oldcwd = None
root = None root = None
def setUpModule(): def setUpModule():
def make_apptree(): def make_apptree():
"build a temporary applications tree" "build a temporary applications tree"
@@ -39,13 +40,16 @@ def setUpModule():
os.mkdir(abspath('applications', app, subdir)) os.mkdir(abspath('applications', app, subdir))
# applications/admin/controllers/*.py # applications/admin/controllers/*.py
for ctr in ('appadmin', 'default', 'gae', 'mercurial', 'shell', 'wizard'): for ctr in ('appadmin', 'default', 'gae', 'mercurial', 'shell', 'wizard'):
open(abspath('applications', 'admin', 'controllers', '%s.py' % ctr), 'w').close() open(abspath('applications', 'admin',
'controllers', '%s.py' % ctr), 'w').close()
# applications/examples/controllers/*.py # applications/examples/controllers/*.py
for ctr in ('ajax_examples', 'appadmin', 'default', 'global', 'spreadsheet'): for ctr in ('ajax_examples', 'appadmin', 'default', 'global', 'spreadsheet'):
open(abspath('applications', 'examples', 'controllers', '%s.py' % ctr), 'w').close() open(abspath('applications', 'examples',
'controllers', '%s.py' % ctr), 'w').close()
# applications/welcome/controllers/*.py # applications/welcome/controllers/*.py
for ctr in ('appadmin', 'default'): for ctr in ('appadmin', 'default'):
open(abspath('applications', 'welcome', 'controllers', '%s.py' % ctr), 'w').close() open(abspath('applications', 'welcome',
'controllers', '%s.py' % ctr), 'w').close()
# create an app-specific routes.py for examples app # create an app-specific routes.py for examples app
routes = open(abspath('applications', 'examples', 'routes.py'), 'w') routes = open(abspath('applications', 'examples', 'routes.py'), 'w')
routes.write("default_function='exdef'\n") routes.write("default_function='exdef'\n")
@@ -55,7 +59,8 @@ def setUpModule():
if oldcwd is None: # do this only once if oldcwd is None: # do this only once
oldcwd = os.getcwd() oldcwd = os.getcwd()
if not os.path.isdir('gluon'): if not os.path.isdir('gluon'):
os.chdir(os.path.realpath('../../')) # run from web2py base directory os.chdir(os.path.realpath(
'../../')) # run from web2py base directory
import main # for initialization after chdir import main # for initialization after chdir
global logger global logger
logger = logging.getLogger('web2py.rewrite') logger = logging.getLogger('web2py.rewrite')
@@ -64,6 +69,7 @@ def setUpModule():
root = global_settings.applications_parent root = global_settings.applications_parent
make_apptree() make_apptree()
def tearDownModule(): def tearDownModule():
global oldcwd global oldcwd
if oldcwd is not None: if oldcwd is not None:
@@ -78,16 +84,26 @@ class TestRoutes(unittest.TestCase):
""" Tests a null routes table """ """ Tests a null routes table """
load(data='') load(data='')
# incoming # incoming
self.assertEqual(filter_url('http://domain.com'), '/init/default/index') self.assertEqual(
self.assertEqual(filter_url('http://domain.com/'), '/init/default/index') filter_url('http://domain.com'), '/init/default/index')
self.assertEqual(filter_url('http://domain.com/abc'), '/abc/default/index') self.assertEqual(
self.assertEqual(filter_url('http://domain.com/abc/'), '/abc/default/index') filter_url('http://domain.com/'), '/init/default/index')
self.assertEqual(filter_url('http://domain.com/abc/def'), "/abc/def/index") self.assertEqual(
self.assertEqual(filter_url('http://domain.com/abc/def/'), "/abc/def/index") filter_url('http://domain.com/abc'), '/abc/default/index')
self.assertEqual(filter_url('http://domain.com/abc/def/ghi'), "/abc/def/ghi") self.assertEqual(
self.assertEqual(filter_url('http://domain.com/abc/def/ghi/'), "/abc/def/ghi") filter_url('http://domain.com/abc/'), '/abc/default/index')
self.assertEqual(filter_url('http://domain.com/abc/def/ghi/jkl'), "/abc/def/ghi ['jkl']") self.assertEqual(
self.assertEqual(filter_url('http://domain.com/abc/def/ghi/j%20kl'), "/abc/def/ghi ['j_kl']") filter_url('http://domain.com/abc/def'), "/abc/def/index")
self.assertEqual(
filter_url('http://domain.com/abc/def/'), "/abc/def/index")
self.assertEqual(
filter_url('http://domain.com/abc/def/ghi'), "/abc/def/ghi")
self.assertEqual(
filter_url('http://domain.com/abc/def/ghi/'), "/abc/def/ghi")
self.assertEqual(filter_url(
'http://domain.com/abc/def/ghi/jkl'), "/abc/def/ghi ['jkl']")
self.assertEqual(filter_url(
'http://domain.com/abc/def/ghi/j%20kl'), "/abc/def/ghi ['j_kl']")
self.assertEqual(filter_url('http://domain.com/welcome/static/path/to/static'), "%s/applications/welcome/static/path/to/static" % root) self.assertEqual(filter_url('http://domain.com/welcome/static/path/to/static'), "%s/applications/welcome/static/path/to/static" % root)
self.assertRaises(HTTP, filter_url, 'http://domain.com/welcome/static/bad/path/to/st~tic') self.assertRaises(HTTP, filter_url, 'http://domain.com/welcome/static/bad/path/to/st~tic')
try: try:
@@ -96,9 +112,11 @@ class TestRoutes(unittest.TestCase):
except AttributeError: except AttributeError:
pass pass
# outgoing # outgoing
self.assertEqual(filter_url('http://domain.com/init/default/index', out=True), '/init/default/index') self.assertEqual(filter_url('http://domain.com/init/default/index',
out=True), '/init/default/index')
self.assertEqual(filter_url('http://domain.com/init/default/index/arg1', out=True), '/init/default/index/arg1') self.assertEqual(filter_url('http://domain.com/init/default/index/arg1', out=True), '/init/default/index/arg1')
self.assertEqual(filter_url('http://domain.com/init/default/abc', out=True), '/init/default/abc') self.assertEqual(filter_url('http://domain.com/init/default/abc',
out=True), '/init/default/abc')
def test_routes_query(self): def test_routes_query(self):
""" Test query appending """ """ Test query appending """
@@ -125,8 +143,10 @@ routes_app = [
] ]
''' '''
load(data=data) load(data=data)
self.assertEqual(filter_url('http://domain.com/welcome'), '/welcome/default/index') self.assertEqual(
self.assertEqual(filter_url('http://domain.com/examples'), '/examples/default/exdef') filter_url('http://domain.com/welcome'), '/welcome/default/index')
self.assertEqual(filter_url(
'http://domain.com/examples'), '/examples/default/exdef')
def test_routes_defapp(self): def test_routes_defapp(self):
""" Test the default-application function """ """ Test the default-application function """
@@ -135,12 +155,17 @@ default_application = 'defapp'
''' '''
load(data=data) load(data=data)
# incoming # incoming
self.assertEqual(filter_url('http://domain.com'), '/defapp/default/index') self.assertEqual(
self.assertEqual(filter_url('http://domain.com/'), '/defapp/default/index') filter_url('http://domain.com'), '/defapp/default/index')
self.assertEqual(filter_url('http://domain.com/welcome'), '/welcome/default/index') self.assertEqual(
self.assertEqual(filter_url('http://domain.com/app'), '/app/default/index') filter_url('http://domain.com/'), '/defapp/default/index')
self.assertEqual(
filter_url('http://domain.com/welcome'), '/welcome/default/index')
self.assertEqual(
filter_url('http://domain.com/app'), '/app/default/index')
self.assertEqual(filter_url('http://domain.com/welcome/default/index/abc'), "/welcome/default/index ['abc']") self.assertEqual(filter_url('http://domain.com/welcome/default/index/abc'), "/welcome/default/index ['abc']")
self.assertEqual(filter_url('http://domain.com/welcome/static/abc'), '%s/applications/welcome/static/abc' % root) self.assertEqual(filter_url('http://domain.com/welcome/static/abc'),
'%s/applications/welcome/static/abc' % root)
self.assertEqual(filter_url('http://domain.com/defapp/static/path/to/static'), "%s/applications/defapp/static/path/to/static" % root) self.assertEqual(filter_url('http://domain.com/defapp/static/path/to/static'), "%s/applications/defapp/static/path/to/static" % root)
def test_routes_raise(self): def test_routes_raise(self):
@@ -149,15 +174,21 @@ default_application = 'defapp'
''' '''
# test non-exception variants # test non-exception variants
load(data='') load(data='')
self.assertEqual(filter_url('http://domain.com/init'), "/init/default/index") self.assertEqual(
self.assertEqual(filter_url('http://domain.com/init/default'), "/init/default/index") filter_url('http://domain.com/init'), "/init/default/index")
self.assertEqual(filter_url('http://domain.com/init/default/fcn.ext'), "/init/default/fcn.ext") self.assertEqual(filter_url(
self.assertEqual(filter_url('http://domain.com/init/default/fcn/arg'), "/init/default/fcn ['arg']") 'http://domain.com/init/default'), "/init/default/index")
self.assertEqual(filter_url('http://domain.com/init/default/fcn.ext'),
"/init/default/fcn.ext")
self.assertEqual(filter_url('http://domain.com/init/default/fcn/arg'),
"/init/default/fcn ['arg']")
# now raise-HTTP variants # now raise-HTTP variants
self.assertRaises(HTTP, filter_url, 'http://domain.com/bad!ctl') self.assertRaises(HTTP, filter_url, 'http://domain.com/bad!ctl')
self.assertRaises(HTTP, filter_url, 'http://domain.com/ctl/bad!fcn') self.assertRaises(HTTP, filter_url, 'http://domain.com/ctl/bad!fcn')
self.assertRaises(HTTP, filter_url, 'http://domain.com/ctl/fcn.bad!ext') self.assertRaises(
self.assertRaises(HTTP, filter_url, 'http://domain.com/ctl/fcn/bad!arg') HTTP, filter_url, 'http://domain.com/ctl/fcn.bad!ext')
self.assertRaises(
HTTP, filter_url, 'http://domain.com/ctl/fcn/bad!arg')
try: try:
# 2.7+ only # 2.7+ only
self.assertRaisesRegexp(HTTP, '400 BAD REQUEST \[invalid path\]', filter_url, 'http://domain.com/init/bad!ctl') self.assertRaisesRegexp(HTTP, '400 BAD REQUEST \[invalid path\]', filter_url, 'http://domain.com/init/bad!ctl')
@@ -167,7 +198,8 @@ default_application = 'defapp'
except AttributeError: except AttributeError:
pass pass
self.assertEqual(filter_url('http://domain.com/welcome/default/fcn_1'), "/welcome/default/fcn_1") self.assertEqual(filter_url('http://domain.com/welcome/default/fcn_1'),
"/welcome/default/fcn_1")
#self.assertRaises(HTTP, filter_url, 'http://domain.com/welcome/default/fcn-1') #self.assertRaises(HTTP, filter_url, 'http://domain.com/welcome/default/fcn-1')
#try: #try:
# # 2.7+ only # # 2.7+ only
@@ -194,17 +226,26 @@ default_application = 'defapp'
('/favicon.ico', '/welcome/static/favicon.ico'), ('/favicon.ico', '/welcome/static/favicon.ico'),
('/admin$anything', '/admin$anything'), ('/admin$anything', '/admin$anything'),
('.*:https?://(.*\\.)?domain1.com:$method /', '/app1/default'), ('.*:https?://(.*\\.)?domain1.com:$method /', '/app1/default'),
('.*:https?://(.*\\.)?domain1.com:$method /static/$anything', '/app1/static/$anything'), ('.*:https?://(.*\\.)?domain1.com:$method /static/$anything',
('.*:https?://(.*\\.)?domain1.com:$method /appadmin/$anything', '/app1/appadmin/$anything'), '/app1/static/$anything'),
('.*:https?://(.*\\.)?domain1.com:$method /$anything', '/app1/default/$anything'), ('.*:https?://(.*\\.)?domain1.com:$method /appadmin/$anything',
'/app1/appadmin/$anything'),
('.*:https?://(.*\\.)?domain1.com:$method /$anything',
'/app1/default/$anything'),
('.*:https?://(.*\\.)?domain2.com:$method /', '/app2/default'), ('.*:https?://(.*\\.)?domain2.com:$method /', '/app2/default'),
('.*:https?://(.*\\.)?domain2.com:$method /static/$anything', '/app2/static/$anything'), ('.*:https?://(.*\\.)?domain2.com:$method /static/$anything',
('.*:https?://(.*\\.)?domain2.com:$method /appadmin/$anything', '/app2/appadmin/$anything'), '/app2/static/$anything'),
('.*:https?://(.*\\.)?domain2.com:$method /$anything', '/app2/default/$anything'), ('.*:https?://(.*\\.)?domain2.com:$method /appadmin/$anything',
'/app2/appadmin/$anything'),
('.*:https?://(.*\\.)?domain2.com:$method /$anything',
'/app2/default/$anything'),
('.*:https?://(.*\\.)?domain3.com:$method /', '/app3/defcon3'), ('.*:https?://(.*\\.)?domain3.com:$method /', '/app3/defcon3'),
('.*:https?://(.*\\.)?domain3.com:$method /static/$anything', '/app3/static/$anything'), ('.*:https?://(.*\\.)?domain3.com:$method /static/$anything',
('.*:https?://(.*\\.)?domain3.com:$method /appadmin/$anything', '/app3/appadmin/$anything'), '/app3/static/$anything'),
('.*:https?://(.*\\.)?domain3.com:$method /$anything', '/app3/defcon3/$anything'), ('.*:https?://(.*\\.)?domain3.com:$method /appadmin/$anything',
'/app3/appadmin/$anything'),
('.*:https?://(.*\\.)?domain3.com:$method /$anything',
'/app3/defcon3/$anything'),
('/', '/welcome/default'), ('/', '/welcome/default'),
('/welcome/default/$anything', '/welcome/default/$anything'), ('/welcome/default/$anything', '/welcome/default/$anything'),
('/welcome/$anything', '/welcome/default/$anything'), ('/welcome/$anything', '/welcome/default/$anything'),
@@ -228,47 +269,69 @@ routes_out = [
] ]
''' '''
load(data=data) load(data=data)
self.assertEqual(filter_url('http://domain.com/welcome/default/f/arg1'), self.assertEqual(
filter_url('http://domain.com/welcome/default/f/arg1'),
"/welcome/default/f ['arg1']") "/welcome/default/f ['arg1']")
self.assertEqual(filter_url('http://domain.com/welcome/default/f/arg1/'), self.assertEqual(
filter_url('http://domain.com/welcome/default/f/arg1/'),
"/welcome/default/f ['arg1']") "/welcome/default/f ['arg1']")
self.assertEqual(filter_url('http://domain.com/welcome/default/f/arg1//'), self.assertEqual(
filter_url('http://domain.com/welcome/default/f/arg1//'),
"/welcome/default/f ['arg1', '']") "/welcome/default/f ['arg1', '']")
self.assertEqual(filter_url('http://domain.com/welcome/default/f//arg1'), self.assertEqual(
filter_url('http://domain.com/welcome/default/f//arg1'),
"/welcome/default/f ['', 'arg1']") "/welcome/default/f ['', 'arg1']")
self.assertEqual(filter_url('http://domain.com/welcome/default/f/arg1/arg2'), self.assertEqual(
filter_url('http://domain.com/welcome/default/f/arg1/arg2'),
"/welcome/default/f ['arg1', 'arg2']") "/welcome/default/f ['arg1', 'arg2']")
self.assertEqual(filter_url('http://domain.com/welcome/default/f/arg1//arg2'), self.assertEqual(
filter_url('http://domain.com/welcome/default/f/arg1//arg2'),
"/welcome/default/f ['arg1', '', 'arg2']") "/welcome/default/f ['arg1', '', 'arg2']")
self.assertEqual(filter_url('http://domain.com/welcome/default/f/arg1//arg3/'), self.assertEqual(
filter_url('http://domain.com/welcome/default/f/arg1//arg3/'),
"/welcome/default/f ['arg1', '', 'arg3']") "/welcome/default/f ['arg1', '', 'arg3']")
self.assertEqual(filter_url('http://domain.com/welcome/default/f/arg1//arg3//'), self.assertEqual(
filter_url('http://domain.com/welcome/default/f/arg1//arg3//'),
"/welcome/default/f ['arg1', '', 'arg3', '']") "/welcome/default/f ['arg1', '', 'arg3', '']")
self.assertEqual(filter_url('http://domain.com/welcome/default/f', out=True), "/f") self.assertEqual(
filter_url('http://domain.com/welcome/default/f', out=True), "/f")
self.assertEqual(regex_filter_out('/welcome/default/f'), "/f") self.assertEqual(regex_filter_out('/welcome/default/f'), "/f")
self.assertEqual(str(URL(a='welcome', c='default', f='f', args=None)), "/f") self.assertEqual(
self.assertEqual(str(URL(a='welcome', c='default', f='f', args=['arg1'])), "/f/arg1") str(URL(a='welcome', c='default', f='f', args=None)), "/f")
self.assertEqual(str(URL(a='welcome', c='default', f='f', args=['arg1', ''])), "/f/arg1//") self.assertEqual(str(
self.assertEqual(str(URL(a='welcome', c='default', f='f', args=['arg1', '', 'arg3'])), "/f/arg1//arg3") URL(a='welcome', c='default', f='f', args=['arg1'])), "/f/arg1")
self.assertEqual(str(URL(a='welcome', c='default', f='f', args=['ar g'])), "/f/ar%20g") self.assertEqual(str(URL(
self.assertEqual(str(URL(a='welcome', c='default', f='f', args=['årg'])), "/f/%C3%A5rg") a='welcome', c='default', f='f', args=['arg1', ''])), "/f/arg1//")
self.assertEqual(str(URL(a='welcome', c='default', f='fünc')), "/f\xc3\xbcnc") self.assertEqual(str(URL(a='welcome', c='default', f='f',
args=['arg1', '', 'arg3'])), "/f/arg1//arg3")
self.assertEqual(str(
URL(a='welcome', c='default', f='f', args=['ar g'])), "/f/ar%20g")
self.assertEqual(str(URL(
a='welcome', c='default', f='f', args=['årg'])), "/f/%C3%A5rg")
self.assertEqual(
str(URL(a='welcome', c='default', f='fünc')), "/f\xc3\xbcnc")
def test_routes_anchor(self): def test_routes_anchor(self):
''' '''
Test URL with anchor Test URL with anchor
''' '''
self.assertEqual(str(URL(a='a', c='c', f='f', anchor='anchor')), "/a/c/f#anchor") self.assertEqual(
str(URL(a='a', c='c', f='f', anchor='anchor')), "/a/c/f#anchor")
load(data='') load(data='')
self.assertEqual(str(URL(a='a', c='c', f='f', anchor='anchor')), "/a/c/f#anchor") self.assertEqual(
str(URL(a='a', c='c', f='f', anchor='anchor')), "/a/c/f#anchor")
args = ['a1', 'a2'] args = ['a1', 'a2']
self.assertEqual(str(URL(a='a', c='c', f='f', args=args, anchor='anchor')), self.assertEqual(
str(URL(a='a', c='c', f='f', args=args, anchor='anchor')),
"/a/c/f/a1/a2#anchor") "/a/c/f/a1/a2#anchor")
vars = dict(v1=1, v2=2) vars = dict(v1=1, v2=2)
self.assertEqual(str(URL(a='a', c='c', f='f', vars=vars, anchor='anchor')), self.assertEqual(
str(URL(a='a', c='c', f='f', vars=vars, anchor='anchor')),
"/a/c/f?v1=1&v2=2#anchor") "/a/c/f?v1=1&v2=2#anchor")
self.assertEqual(str(URL(a='a', c='c', f='f', args=args, vars=vars, anchor='anchor')), self.assertEqual(
str(URL(
a='a', c='c', f='f', args=args, vars=vars, anchor='anchor')),
"/a/c/f/a1/a2?v1=1&v2=2#anchor") "/a/c/f/a1/a2?v1=1&v2=2#anchor")
data = r'''routes_out = [ data = r'''routes_out = [
@@ -276,8 +339,9 @@ routes_out = [
]''' ]'''
load(data=data) load(data=data)
self.assertEqual(str(URL(a='init', c='default', f='index')), self.assertEqual(str(URL(a='init', c='default', f='index')),
"/") "/")
self.assertEqual(str(URL(a='init', c='default', f='index', anchor='anchor')), self.assertEqual(
str(URL(a='init', c='default', f='index', anchor='anchor')),
"/init/default/index#anchor") "/init/default/index#anchor")
data = r'''routes_out = [ data = r'''routes_out = [
@@ -285,8 +349,9 @@ routes_out = [
]''' ]'''
load(data=data) load(data=data)
self.assertEqual(str(URL(a='init', c='default', f='index')), self.assertEqual(str(URL(a='init', c='default', f='index')),
"/") "/")
self.assertEqual(str(URL(a='init', c='default', f='index', anchor='anchor')), self.assertEqual(
str(URL(a='init', c='default', f='index', anchor='anchor')),
"/#anchor") "/#anchor")
data = r'''routes_out = [ data = r'''routes_out = [
@@ -294,13 +359,17 @@ routes_out = [
]''' ]'''
load(data=data) load(data=data)
self.assertEqual(str(URL(a='init', c='default', f='index')), self.assertEqual(str(URL(a='init', c='default', f='index')),
"/") "/")
self.assertEqual(str(URL(a='init', c='default', f='index', anchor='anchor')), self.assertEqual(
str(URL(a='init', c='default', f='index', anchor='anchor')),
"/#anchor") "/#anchor")
query = dict(var='abc') query = dict(var='abc')
self.assertEqual(str(URL(a='init', c='default', f='index', vars=query)), self.assertEqual(
str(URL(a='init', c='default', f='index', vars=query)),
"/?var=abc") "/?var=abc")
self.assertEqual(str(URL(a='init', c='default', f='index', vars=query, anchor='anchor')), self.assertEqual(
str(URL(a='init', c='default', f='index',
vars=query, anchor='anchor')),
"/?var=abc#anchor") "/?var=abc#anchor")
def test_routes_absolute(self): def test_routes_absolute(self):
@@ -311,37 +380,46 @@ routes_out = [
r = Storage() r = Storage()
r.env = Storage() r.env = Storage()
r.env.http_host = 'domain.com' r.env.http_host = 'domain.com'
r.env.wsgi_url_scheme = 'httpx' # distinguish incoming scheme r.env.wsgi_url_scheme = 'httpx' # distinguish incoming scheme
self.assertEqual(str(URL(r=r, a='a', c='c', f='f')), "/a/c/f") self.assertEqual(str(URL(r=r, a='a', c='c', f='f')), "/a/c/f")
self.assertEqual(str(URL(r=r, a='a', c='c', f='f', host=True)), self.assertEqual(str(URL(r=r, a='a', c='c', f='f', host=True)),
"httpx://domain.com/a/c/f") "httpx://domain.com/a/c/f")
self.assertEqual(str(URL(r=r, a='a', c='c', f='f', host='host.com')), self.assertEqual(str(URL(r=r, a='a', c='c', f='f', host='host.com')),
"httpx://host.com/a/c/f") "httpx://host.com/a/c/f")
self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=True)), self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=True)),
"httpx://domain.com/a/c/f") "httpx://domain.com/a/c/f")
self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=False)), self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=False)),
"/a/c/f") "/a/c/f")
self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme='https')), self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme='https')),
"https://domain.com/a/c/f") "https://domain.com/a/c/f")
self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme='wss')), self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme='wss')),
"wss://domain.com/a/c/f") "wss://domain.com/a/c/f")
self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=True, host=True)), self.assertEqual(
str(URL(r=r, a='a', c='c', f='f', scheme=True, host=True)),
"httpx://domain.com/a/c/f") "httpx://domain.com/a/c/f")
self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme='https', host=True)), self.assertEqual(
str(URL(r=r, a='a', c='c', f='f', scheme='https', host=True)),
"https://domain.com/a/c/f") "https://domain.com/a/c/f")
self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=False, host=True)), self.assertEqual(
str(URL(r=r, a='a', c='c', f='f', scheme=False, host=True)),
"httpx://domain.com/a/c/f") "httpx://domain.com/a/c/f")
self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=True, host='host.com')), self.assertEqual(
str(URL(r=r, a='a', c='c', f='f', scheme=True, host='host.com')),
"httpx://host.com/a/c/f") "httpx://host.com/a/c/f")
self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=False, host='host.com')), self.assertEqual(
str(URL(r=r, a='a', c='c', f='f', scheme=False, host='host.com')),
"httpx://host.com/a/c/f") "httpx://host.com/a/c/f")
self.assertEqual(str(URL(r=r, a='a', c='c', f='f', port=1234)), self.assertEqual(str(URL(r=r, a='a', c='c', f='f', port=1234)),
"httpx://domain.com:1234/a/c/f")
self.assertEqual(
str(URL(r=r, a='a', c='c', f='f', scheme=True, port=1234)),
"httpx://domain.com:1234/a/c/f") "httpx://domain.com:1234/a/c/f")
self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme=True, port=1234)), self.assertEqual(
"httpx://domain.com:1234/a/c/f") str(URL(r=r, a='a', c='c', f='f', host='host.com', port=1234)),
self.assertEqual(str(URL(r=r, a='a', c='c', f='f', host='host.com', port=1234)),
"httpx://host.com:1234/a/c/f") "httpx://host.com:1234/a/c/f")
self.assertEqual(str(URL(r=r, a='a', c='c', f='f', scheme='wss', host='host.com', port=1234)), self.assertEqual(
str(URL(r=r, a='a', c='c', f='f', scheme='wss',
host='host.com', port=1234)),
"wss://host.com:1234/a/c/f") "wss://host.com:1234/a/c/f")
def test_request_uri(self): def test_request_uri(self):
@@ -354,13 +432,17 @@ routes_out = [
] ]
''' '''
load(data=data) load(data=data)
self.assertEqual(filter_url('http://domain.com/abc', env=True).request_uri, self.assertEqual(
filter_url('http://domain.com/abc', env=True).request_uri,
'/init/default/abc') '/init/default/abc')
self.assertEqual(filter_url('http://domain.com/abc?def', env=True).request_uri, self.assertEqual(
filter_url('http://domain.com/abc?def', env=True).request_uri,
'/init/default/abc?def') '/init/default/abc?def')
self.assertEqual(filter_url('http://domain.com/index/abc', env=True).request_uri, self.assertEqual(
filter_url('http://domain.com/index/abc', env=True).request_uri,
"/init/default/index/abc") "/init/default/index/abc")
self.assertEqual(filter_url('http://domain.com/index/a%20bc', env=True).request_uri, self.assertEqual(
filter_url('http://domain.com/index/a%20bc', env=True).request_uri,
"/init/default/index/a bc") "/init/default/index/a bc")
@@ -368,4 +450,3 @@ if __name__ == '__main__':
setUpModule() # pre-2.7 setUpModule() # pre-2.7
unittest.main() unittest.main()
tearDownModule() tearDownModule()
-3
View File
@@ -39,7 +39,6 @@ class TestStorage(unittest.TestCase):
s.d = list() s.d = list()
self.assertTrue(s.d is s['d']) self.assertTrue(s.d is s['d'])
def test_store_none(self): def test_store_none(self):
""" Test Storage store-None handling """ Test Storage store-None handling
s.key = None deletes an item s.key = None deletes an item
@@ -59,7 +58,6 @@ class TestStorage(unittest.TestCase):
self.assertTrue('a' in s) self.assertTrue('a' in s)
self.assertTrue(s.a is None) self.assertTrue(s.a is None)
def test_item(self): def test_item(self):
""" Tests Storage item handling """ """ Tests Storage item handling """
@@ -76,4 +74,3 @@ class TestStorage(unittest.TestCase):
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()
+20 -12
View File
@@ -14,15 +14,17 @@ else:
import unittest import unittest
from template import render from template import render
class TestVirtualFields(unittest.TestCase): class TestVirtualFields(unittest.TestCase):
def testRun(self): def testRun(self):
self.assertEqual(render(content='{{for i in range(n):}}{{=i}}{{pass}}', self.assertEqual(render(content='{{for i in range(n):}}{{=i}}{{pass}}',
context=dict(n=3)), '012') context=dict(n=3)), '012')
self.assertEqual(render(content='{{if n>2:}}ok{{pass}}', self.assertEqual(render(content='{{if n>2:}}ok{{pass}}',
context=dict(n=3)), 'ok') context=dict(n=3)), 'ok')
self.assertEqual(render(content='{{try:}}{{n/0}}{{except:}}fail{{pass}}', self.assertEqual(
context=dict(n=3)), 'fail') render(content='{{try:}}{{n/0}}{{except:}}fail{{pass}}',
context=dict(n=3)), 'fail')
self.assertEqual(render(content='{{="<&>"}}'), '&lt;&amp;&gt;') self.assertEqual(render(content='{{="<&>"}}'), '&lt;&amp;&gt;')
self.assertEqual(render(content='"abc"'), '"abc"') self.assertEqual(render(content='"abc"'), '"abc"')
self.assertEqual(render(content='"a\'bc"'), '"a\'bc"') self.assertEqual(render(content='"a\'bc"'), '"a\'bc"')
@@ -38,13 +40,18 @@ class TestVirtualFields(unittest.TestCase):
self.assertEqual(render(content='{{ ="abc" }}'), 'abc') self.assertEqual(render(content='{{ ="abc" }}'), 'abc')
self.assertEqual(render(content='{{pass\n="abc" }}'), 'abc') self.assertEqual(render(content='{{pass\n="abc" }}'), 'abc')
# = recognized only at the beginning of a physical line # = recognized only at the beginning of a physical line
self.assertEqual(render(content='{{xyz = "xyz"\n="abc"\n="def"\n=xyz }}'), 'abcdefxyz') self.assertEqual(render(
content='{{xyz = "xyz"\n="abc"\n="def"\n=xyz }}'), 'abcdefxyz')
# = in python blocks # = in python blocks
self.assertEqual(render(content='{{if True:\n="abc"\npass }}'), 'abc') self.assertEqual(render(content='{{if True:\n="abc"\npass }}'), 'abc')
self.assertEqual(render(content='{{if True:\n="abc"\npass\n="def" }}'), 'abcdef') self.assertEqual(
self.assertEqual(render(content='{{if False:\n="abc"\npass\n="def" }}'), 'def') render(content='{{if True:\n="abc"\npass\n="def" }}'), 'abcdef')
self.assertEqual(render(content='{{if True:\n="abc"\nelse:\n="def"\npass }}'), 'abc') self.assertEqual(
self.assertEqual(render(content='{{if False:\n="abc"\nelse:\n="def"\npass }}'), 'def') render(content='{{if False:\n="abc"\npass\n="def" }}'), 'def')
self.assertEqual(render(
content='{{if True:\n="abc"\nelse:\n="def"\npass }}'), 'abc')
self.assertEqual(render(
content='{{if False:\n="abc"\nelse:\n="def"\npass }}'), 'def')
# codeblock-leading = handles internal newlines, escaped or not # codeblock-leading = handles internal newlines, escaped or not
self.assertEqual(render(content='{{=list((1,2,3))}}'), '[1, 2, 3]') self.assertEqual(render(content='{{=list((1,2,3))}}'), '[1, 2, 3]')
self.assertEqual(render(content='{{=list((1,2,\\\n3))}}'), '[1, 2, 3]') self.assertEqual(render(content='{{=list((1,2,\\\n3))}}'), '[1, 2, 3]')
@@ -52,10 +59,11 @@ class TestVirtualFields(unittest.TestCase):
# ...but that means no more = operators in the codeblock # ...but that means no more = operators in the codeblock
self.assertRaises(SyntaxError, render, content='{{="abc"\n="def" }}') self.assertRaises(SyntaxError, render, content='{{="abc"\n="def" }}')
# = embedded in codeblock won't handle newlines in its argument # = embedded in codeblock won't handle newlines in its argument
self.assertEqual(render(content='{{pass\n=list((1,2,\\\n3))}}'), '[1, 2, 3]') self.assertEqual(
self.assertRaises(SyntaxError, render, content='{{pass\n=list((1,2,\n3))}}') render(content='{{pass\n=list((1,2,\\\n3))}}'), '[1, 2, 3]')
self.assertRaises(
SyntaxError, render, content='{{pass\n=list((1,2,\n3))}}')
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()
-1
View File
@@ -25,4 +25,3 @@ class TestUtils(unittest.TestCase):
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()
+13 -12
View File
@@ -13,6 +13,7 @@ else:
import unittest import unittest
from contrib.webclient import WebClient from contrib.webclient import WebClient
class TestWeb(unittest.TestCase): class TestWeb(unittest.TestCase):
def testWebClient(self): def testWebClient(self):
client = WebClient('http://127.0.0.1:8000/welcome/default/') client = WebClient('http://127.0.0.1:8000/welcome/default/')
@@ -20,13 +21,13 @@ class TestWeb(unittest.TestCase):
client.get('index') client.get('index')
# register # register
data = dict(first_name = 'Homer', data = dict(first_name='Homer',
last_name = 'Simpson', last_name='Simpson',
email = 'homer@web2py.com', email='homer@web2py.com',
password = 'test', password='test',
password_two = 'test', password_two='test',
_formname = 'register') _formname='register')
client.post('user/register',data = data) client.post('user/register', data=data)
# logout # logout
client.get('user/logout') client.get('user/logout')
@@ -34,21 +35,22 @@ class TestWeb(unittest.TestCase):
# login again # login again
data = dict(email='homer@web2py.com', data = dict(email='homer@web2py.com',
password='test', password='test',
_formname = 'login') _formname='login')
client.post('user/login',data = data) client.post('user/login', data=data)
# check registration and login were successful # check registration and login were successful
client.get('index') client.get('index')
self.assertTrue('Welcome Homer' in client.text) self.assertTrue('Welcome Homer' in client.text)
client = WebClient('http://127.0.0.1:8000/admin/default/') client = WebClient('http://127.0.0.1:8000/admin/default/')
client.post('index',data=dict(password='hello')) client.post('index', data=dict(password='hello'))
client.get('site') client.get('site')
client.get('design/welcome') client.get('design/welcome')
class TestStaticCacheControl(unittest.TestCase): class TestStaticCacheControl(unittest.TestCase):
def testWebClient(self): def testWebClient(self):
s=WebClient('http://127.0.0.1:8000/welcome/') s = WebClient('http://127.0.0.1:8000/welcome/')
s.get('static/js/web2py.js') s.get('static/js/web2py.js')
assert('expires' not in s.headers) assert('expires' not in s.headers)
assert(not s.headers['cache-control'].startswith('max-age')) assert(not s.headers['cache-control'].startswith('max-age'))
@@ -60,4 +62,3 @@ class TestStaticCacheControl(unittest.TestCase):
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()
Loaded 100 of 117 files, more files were not shown because too many files have changed in this diff. Show more