from paste.util import import_string
import routes
import pkg_resources
import sys
import inspect
import re
from wareweb import cgifields
import paste.request
from formencode import variabledecode
from paste.deploy.config import CONFIG, ConfigMiddleware
from paste import httpexceptions
from paste.exceptions import errormiddleware
from paste.evalexception.middleware import EvalException
class Project(object):
# These modules get imported early on, and so they can
# do side-effect operations.
#import_modules = ['connect']
import_modules = []
_template_factories = {}
_controllers = {}
config = CONFIG
def __init__(self, package_name,
default_template_type=None,
default_template_format='html',
controller_prefix=None):
self.package_name = package_name
self.urlmap = routes.Mapper()
#self.middleware_factories = [errormiddleware.ErrorMiddleware]
self.middleware_factories = [EvalException]
self.default_template_type = default_template_type
self.default_template_format = default_template_format
if controller_prefix is None:
controller_prefix = package_name + '.'
self.controller_prefix = controller_prefix
# Somehow this should handle changing SCRIPT_NAME and whatnot
self.head_html = []
def template_factory(self, type=None):
if type is None:
type = self.default_template_type
if type not in self._template_factories:
for entry in pkg_resources.iter_entry_points(
'python.templating.engines', name=type):
self._template_factories[type] = entry.load()
break
else:
raise LookupError(
"The template factory %r could not be found"
% type)
return self._template_factories[type]()
def connect(self, *args, **kw):
self.urlmap.connect(*args, **kw)
self.urlmap.create_regs(['[a-zA-Z][a-zA-Z0-9_]*'])
def add_middleware(self, middleware_factory):
self.middleware_factories.append(middleware_factory)
def init(self):
for module_name in self.import_modules:
module_name = self.package_name + '.' + module_name
import_string.simple_import(module_name)
def controller(self, **kw):
def decorate(func):
func.wsgi_application = WSGIController(self, func, **kw)
return func
return decorate
def redirect(self, url):
raise NotImplementedError
def wsgi_entry_point(self, environ, start_response):
path_info = environ.get('PATH_INFO')
reqconfig = routes.request_config()
reqconfig.environ = environ
reqconfig.mapper = self.urlmap
reqconfig.redirect = self.redirect
result = self.urlmap.routematch(path_info)
if result is None:
# @@: Put this in the not found error
msg = 'Did not match: %r\n' % path_info
for route in self.urlmap.matchlist:
msg += 'Regex: %s' % route.regexp
if re.search(route.regexp, path_info):
msg += " regex matches (but fails restriction)"
msg += '\n'
exc = httpexceptions.HTTPNotFound(comment=msg)
return exc.wsgi_application(environ, start_response)
vars, routeobj = result
if vars.get('path_info'):
match = routeobj.regmatch.match(path_info)
pos = match.start('path_info')
new_script_name = path_info[:pos]
new_path_info = path_info[pos:]
if not new_path_info.startswith('/'):
new_path_info = '/'+new_path_info
new_script_name = new_script_name.rstrip('/')
else:
new_script_name = path_info
new_path_info = ''
if new_path_info and not new_path_info.startswith('/'):
assert new_script_name.endswith('/')
new_script_name = new_script_name[:-1]
new_path_info = '/' + new_path_info
environ['SCRIPT_NAME'] += new_script_name
environ['PATH_INFO'] = new_path_info
environ['poorman.urlvars'] = vars
environ['poorman.routeobj'] = routeobj
wsgi_object = self.get_object(vars, routeobj)
wsgi_app = self.get_wsgi_app(vars, wsgi_object)
return wsgi_app(environ, start_response)
def get_object(self, vars, routeobj):
"""
Given the matching variables, find the object that it points
to. Typically this means finding the ``controller.action``
module/object/method.
"""
controller = vars['controller']
if controller.startswith('egg:'):
return self.get_egg_object(vars, routeobj)
controller = self.controller_prefix + controller
if vars.get('action'):
if ':' not in controller:
controller = controller + ':' + vars['action']
else:
controller = controller + '.' + vars['action']
if ':' in controller:
controller_mod, controller_name = controller.split(':', 1)
else:
controller_mod = controller
controller_name = 'index'
if controller_mod not in sys.modules:
import_string.simple_import(controller_mod)
controller_mod = sys.modules[controller_mod]
obj = controller_mod
for part in controller_name.split('.'):
obj = getattr(obj, part)
return obj
def get_egg_object(self, vars, routeobj):
vars = vars.copy()
uri = vars.pop('controller')
if vars.get('action') == 'index':
# This gets set by default, but probably doesn't apply
# if it is still the default.
# @@: Maybe I should check if it was explicitly set.
del vars['action']
if 'path_info' in vars:
del vars['path_info']
assert uri.startswith('egg:')
dist = uri[4:].strip()
if '#' in dist:
dist, ep_name = dist.split('#', 1)
else:
ep_name = 'main'
ep = pkg_resources.load_entry_point(
dist, 'paste.app_factory', ep_name)
app = ep(self.config.copy(), **vars)
return app
def get_wsgi_app(self, vars, obj):
"""
Convert an object into a WSGI application. Right now it only
looks for a .wsgi_application value on the object.
"""
if hasattr(obj, 'wsgi_application'):
return obj.wsgi_application
else:
return obj
def paste_deploy_app(self, global_conf, **local_conf):
"""
This is the method that implements [paste.app_factory]
"""
conf = global_conf.copy()
conf.update(local_conf)
app = self.wsgi_entry_point
app = ConfigMiddleware(app, conf)
for middleware in self.middleware_factories:
# They'll just have to pick it up from defaults...
app = middleware(app, conf)
return app
class WSGIController(object):
def __init__(self, project, func, template_name=None,
template_type=None, fragment=False,
template_format=None, variabledecode=False):
self.project = project
self.func = func
self.template_name = template_name
self.template_type = template_type
self.fragment = fragment
self.template_format = (
template_format or project.default_template_format)
(self.func_args, self.func_varargs, self.func_varkw,
self.func_defaults) = inspect.getargspec(self.func)
self.variabledecode = variabledecode
def __call__(self, environ, start_response):
req = Request(environ, self)
req.head_html.extend(self.project.head_html)
req.set_header('Content-Type', 'text/html; charset=utf8')
call_vars = dict(req.fields)
call_vars.update(environ.get('poorman.urlvars', {}))
if self.variabledecode:
call_vars = variabledecode.variable_decode(call_vars)
if not self.func_varkw:
good = {}
for name in call_vars:
if name in self.func_args:
good[name] = call_vars[name]
call_vars = good
res = self.func(req, **call_vars)
start_response(req.status, req.response_headers())
if isinstance(res, basestring):
res = [res]
# @@: Should encode response here if necessary
return res
def fill_template(self, template_name, info):
factory = self.project.template_factory(type=self.template_type)
# @@: What's the point of this?
#template = factory.load_template(template_name)
result = factory.render(info, format=self.template_format,
fragment=self.fragment, template=template_name)
return result
class Request(object):
def __init__(self, environ, controller):
self.environ = environ
self.controller = controller
self.fields = cgifields.Fields(
paste.request.parse_formvars(environ))
self.headers_out = {}
self.cookies = {}
if 'HTTP_COOKIE' in environ:
cookies = paste.request.get_cookies(environ)
for key in cookies.keys():
self.cookies[key] = cookies[key].value
self._cookies_out = {}
self.status = '200 OK'
self.head_html = []
def add_javascript(self, href=None, content=None):
if href:
self.head_html.append(
''
% href)
if content:
self.head_html.append(
''
% content)
def add_css(self, href=None, content=None):
if href:
self.head_html.append(
''
% href)
if content:
self.head_html.append(
'' % content)
def response_headers(self):
headers = []
for name, value in self.headers_out.items():
if isinstance(value, list):
for v in value:
headers.append((name, v))
else:
headers.append((name, value))
for cookie in self._cookies_out.values():
headers.append(('Set-Cookie', cookie.header()))
return headers
def session__get(self):
if 'paste.session.factory' in self.environ:
sess = self.environ['paste.session.factory']()
elif 'paste.flup_session_service' in self.environ:
sess = self.environ['paste.flup_session_service'].session
self.__dict__['session'] = sess
return sess
session = property(session__get)
def set_cookie(self, cookie_name, value, path='/',
expires='ONCLOSE', secure=False):
c = cookiewriter.Cookie(cookie_name, value, path=path,
expires=expires, secure=secure)
self._cookies_out[cookie_name] = c
def set_header(self, header_name, header_value):
header_name = header_name.lower()
if header_name == 'status':
self.status = header_value
return
self.headers_out[header_name] = header_value
def add_header(self, header_name, header_value):
header_name = header_name.lower()
if header_name == 'status':
self.status = header_value
return
if self.headers_out.has_key(header_name):
if not isinstance(self.headers_out[header_name], list):
self.headers_out[header_name] = [self.headers_out[header_name],
header_value]
else:
self.headers_out[header_name].append(header_value)
else:
self.headers_out[header_name] = header_value
def render(self, template_name=None, stacklevel=1, **vars):
if not vars:
# @@: Should this just update vars?
frame = sys._getframe(stacklevel)
vars = frame.f_locals
if template_name is None:
# @@: This should pick up the package name too:
frame = sys._getframe(stacklevel)
mod_name = frame.f_globals['__name__']
base_package = self.controller.project.package_name
assert mod_name.startswith(base_package+'.'), (
"Unexpected module %r (expected %s.*)"
% (mod_name, base_package))
mod_name = mod_name[len(base_package)+1:]
mod_name = base_package + '.' + '/templates' + '/' + mod_name
func_name = frame.f_code.co_name
if func_name != 'index':
template_name = mod_name + '-' + func_name
else:
template_name = mod_name
print template_name
vars.setdefault('project', self.controller.project)
vars.setdefault('head_html', '\n'.join(self.head_html))
return self.controller.fill_template(template_name, vars)