Initial commit.
This commit is contained in:
commit
5c32201386
|
@ -0,0 +1,10 @@
|
|||
Metadata-Version: 1.0
|
||||
Name: pecan
|
||||
Version: 0.1dev
|
||||
Summary: A WSGI object-dispatching web framework, in the spirit of TurboGears, only much much smaller, with many fewer dependancies.
|
||||
Home-page: http://code.google.com/p/pecan
|
||||
Author: Jonathan LaCour
|
||||
Author-email: jonathan@cleverdevil.org
|
||||
License: BSD
|
||||
Description: UNKNOWN
|
||||
Platform: UNKNOWN
|
|
@ -0,0 +1,14 @@
|
|||
setup.cfg
|
||||
setup.py
|
||||
pecan/__init__.py
|
||||
pecan/decorators.py
|
||||
pecan/jsonify.py
|
||||
pecan/pecan.py
|
||||
pecan/templating.py
|
||||
pecan.egg-info/PKG-INFO
|
||||
pecan.egg-info/SOURCES.txt
|
||||
pecan.egg-info/dependency_links.txt
|
||||
pecan.egg-info/entry_points.txt
|
||||
pecan.egg-info/requires.txt
|
||||
pecan.egg-info/top_level.txt
|
||||
pecan.egg-info/zip-safe
|
|
@ -0,0 +1 @@
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
|
||||
# -*- Entry points: -*-
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
WebOb >= 0.9.8
|
||||
simplejson >= 2.0.9
|
||||
simplegeneric >= 0.7
|
||||
Genshi >= 0.6
|
||||
Kajiki >= 0.2.2
|
||||
Mako >= 0.3
|
|
@ -0,0 +1 @@
|
|||
pecan
|
|
@ -0,0 +1 @@
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
from pecan import Pecan, request, override_template
|
||||
from decorators import expose
|
Binary file not shown.
|
@ -0,0 +1,9 @@
|
|||
def expose(template=None, content_type='text/html'):
|
||||
if template == 'json': content_type = 'application/json'
|
||||
def decorate(f):
|
||||
f.exposed = True
|
||||
if not hasattr(f, 'pecan'): f.pecan = {}
|
||||
f.pecan.setdefault('template', []).append(template)
|
||||
f.pecan.setdefault('content_types', {})[content_type] = template
|
||||
return f
|
||||
return decorate
|
Binary file not shown.
|
@ -0,0 +1,65 @@
|
|||
from simplejson import JSONEncoder, dumps
|
||||
from datetime import datetime, date
|
||||
from decimal import Decimal
|
||||
from webob.multidict import MultiDict
|
||||
from simplegeneric import generic
|
||||
|
||||
|
||||
#
|
||||
# exceptions
|
||||
#
|
||||
|
||||
class JsonEncodeError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
#
|
||||
# encoders
|
||||
#
|
||||
|
||||
class BaseEncoder(JSONEncoder):
|
||||
def is_saobject(self, obj):
|
||||
return hasattr(obj, '_sa_class_manager')
|
||||
|
||||
def jsonify(self, obj):
|
||||
return dumps(self.encode(obj))
|
||||
|
||||
def encode(self, obj):
|
||||
if hasattr(obj, '__json__') and callable(obj.__json__):
|
||||
return obj.__json__()
|
||||
elif isinstance(obj, (date, datetime)):
|
||||
return str(obj)
|
||||
elif isinstance(obj, Decimal):
|
||||
return float(obj)
|
||||
elif self.is_saobject(obj):
|
||||
props = {}
|
||||
for key in obj.__dict__:
|
||||
if not key.startswith('_sa_'):
|
||||
props[key] = getattr(obj, key)
|
||||
return props
|
||||
elif isinstance(obj, MultiDict):
|
||||
return obj.mixed()
|
||||
else:
|
||||
try:
|
||||
from sqlalchemy.engine.base import ResultProxy, RowProxy
|
||||
if isinstance(obj, ResultProxy):
|
||||
return dict(rows=list(obj), count=obj.rowcount)
|
||||
elif isinstance(obj, RowProxy):
|
||||
return dict(rows=dict(obj), count=1)
|
||||
except:
|
||||
pass
|
||||
return obj
|
||||
|
||||
|
||||
#
|
||||
# generic function support
|
||||
#
|
||||
|
||||
encoder = BaseEncoder()
|
||||
|
||||
@generic
|
||||
def jsonify(obj):
|
||||
return encoder.encode(obj)
|
||||
|
||||
def encode(obj):
|
||||
return dumps(jsonify(obj))
|
Binary file not shown.
|
@ -0,0 +1,137 @@
|
|||
from templating import renderers
|
||||
from webob import Request, Response, exc
|
||||
from threading import local
|
||||
|
||||
import string
|
||||
|
||||
|
||||
state = local()
|
||||
|
||||
|
||||
class RequestWrapper(object):
|
||||
def __getattr__(self, attr):
|
||||
return getattr(state.request, attr)
|
||||
def __setattr__(self, attr, value):
|
||||
return setattr(state.request, attr, value)
|
||||
|
||||
|
||||
request = RequestWrapper()
|
||||
|
||||
|
||||
def override_template(template):
|
||||
request.override_template = template
|
||||
|
||||
|
||||
class Pecan(object):
|
||||
def __init__(self, root, renderers=renderers, default_renderer='genshi'):
|
||||
self.root = root
|
||||
self.renderers = renderers
|
||||
self.default_renderer = default_renderer
|
||||
self.translate = string.maketrans(
|
||||
string.punctuation,
|
||||
'_' * len(string.punctuation)
|
||||
)
|
||||
|
||||
def get_content_type(self, format):
|
||||
return {
|
||||
'html' : 'text/html',
|
||||
'xhtml' : 'text/html',
|
||||
'json' : 'application/json'
|
||||
}.get(format, 'text/html')
|
||||
|
||||
def route(self, node, path):
|
||||
|
||||
curpath = ""
|
||||
nodeconf = {}
|
||||
object_trail = [['root', self.root, nodeconf, curpath]]
|
||||
|
||||
names = [x for x in path.strip('/').split('/') if x] + ['index']
|
||||
iternames = names[:]
|
||||
while iternames:
|
||||
name = iternames[0]
|
||||
objname = name.translate(self.translate)
|
||||
nodeconf = {}
|
||||
subnode = getattr(node, objname, None)
|
||||
name = iternames.pop(0)
|
||||
node = subnode
|
||||
curpath = "/".join((curpath, name))
|
||||
object_trail.append([name, node, nodeconf, curpath])
|
||||
|
||||
# try successive objects (reverse order)
|
||||
num_candidates = len(object_trail) - 1
|
||||
for i in range(num_candidates, -1, -1):
|
||||
name, candidate, nodeconf, curpath = object_trail[i]
|
||||
if candidate is None:
|
||||
continue
|
||||
|
||||
# try a "_route" method on the current leaf.
|
||||
if hasattr(candidate, "_route"):
|
||||
processed_path = object_trail[i][-1]
|
||||
unprocessed_path = object_trail[-1][-1].replace(processed_path, '')
|
||||
return candidate._route(unprocessed_path)
|
||||
|
||||
# try a "_lookup" method on the current leaf.
|
||||
if hasattr(candidate, "_lookup"):
|
||||
lookup = candidate._lookup(object_trail[i+1][0])
|
||||
processed_path = object_trail[i+1][-1]
|
||||
unprocessed_path = object_trail[-2][-1].replace(processed_path, '')
|
||||
return self.route(lookup, unprocessed_path)
|
||||
|
||||
# try a "_default" method on the current leaf.
|
||||
if hasattr(candidate, "_default"):
|
||||
defhandler = candidate._default
|
||||
if getattr(defhandler, 'exposed', False):
|
||||
object_trail.insert(i+1, ["_default", defhandler, {}, curpath])
|
||||
return defhandler
|
||||
|
||||
# try the current leaf.
|
||||
if getattr(candidate, 'exposed', False):
|
||||
return candidate
|
||||
|
||||
# we didn't find anything
|
||||
return None
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
# create the request object
|
||||
state.request = Request(environ)
|
||||
|
||||
# lookup the controller
|
||||
path = state.request.path
|
||||
content_type = 'text/html'
|
||||
if '.' in path.split('/')[-1]:
|
||||
path, format = path.split('.')
|
||||
content_type = self.get_content_type(format)
|
||||
controller = self.route(self.root, path)
|
||||
|
||||
# if we didn't find a controller, issue a 404
|
||||
if controller is None:
|
||||
response = Response()
|
||||
response.status = 404
|
||||
return response(environ, start_response)
|
||||
|
||||
# get the result from the controller
|
||||
result = controller(**dict(state.request.str_params))
|
||||
|
||||
# pull the template out based upon content type
|
||||
template = controller.pecan.get('content_types', {}).get(content_type)
|
||||
|
||||
# handle template overrides
|
||||
template = getattr(request, 'override_template', template)
|
||||
|
||||
if template:
|
||||
renderer = self.renderers[self.default_renderer]
|
||||
if template == 'json':
|
||||
renderer = self.renderers['json']
|
||||
elif len(self.renderers) > 1 and ':' in template:
|
||||
renderer = self.renderers.get(template.split(':')[0], self.renderers.values()[0])
|
||||
template = template.split(':')[1]
|
||||
result = renderer.render(template, result)
|
||||
content_type = renderer.content_type
|
||||
|
||||
response = Response(result)
|
||||
if content_type:
|
||||
response.content_type = content_type
|
||||
|
||||
del state.request
|
||||
|
||||
return response(environ, start_response)
|
Binary file not shown.
|
@ -0,0 +1,82 @@
|
|||
renderers = {}
|
||||
|
||||
|
||||
#
|
||||
# JSON rendering engine
|
||||
#
|
||||
|
||||
class JsonRenderer(object):
|
||||
content_type = 'application/json'
|
||||
|
||||
def render(self, template_path, namespace):
|
||||
from jsonify import encode
|
||||
return encode(namespace)
|
||||
|
||||
renderers['json'] = JsonRenderer()
|
||||
|
||||
|
||||
#
|
||||
# Genshi rendering engine
|
||||
#
|
||||
|
||||
try:
|
||||
from genshi.template import TemplateLoader
|
||||
|
||||
class GenshiRenderer(object):
|
||||
content_type = 'text/html'
|
||||
|
||||
def __init__(self):
|
||||
self.loader = TemplateLoader(['templates'], auto_reload=True)
|
||||
|
||||
def render(self, template_path, namespace):
|
||||
tmpl = self.loader.load(template_path)
|
||||
stream = tmpl.generate(**namespace)
|
||||
return stream.render('html')
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
renderers['genshi'] = GenshiRenderer()
|
||||
|
||||
|
||||
#
|
||||
# Mako rendering engine
|
||||
#
|
||||
|
||||
try:
|
||||
from mako.lookup import TemplateLookup
|
||||
class MakoRenderer(object):
|
||||
content_type = 'text/html'
|
||||
|
||||
def __init__(self):
|
||||
self.loader = TemplateLookup(directories=['templates'])
|
||||
|
||||
def render(self, template_path, namespace):
|
||||
tmpl = self.loader.get_template(template_path)
|
||||
return tmpl.render(**namespace)
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
renderers['mako'] = MakoRenderer()
|
||||
|
||||
|
||||
#
|
||||
# Kajiki rendering engine
|
||||
#
|
||||
|
||||
try:
|
||||
from kajiki.loader import FileLoader
|
||||
|
||||
class KajikiRenderer(object):
|
||||
content_type = 'text/html'
|
||||
|
||||
def __init__(self):
|
||||
self.loader = FileLoader('templates', reload=True)
|
||||
|
||||
def render(self, template_path, namespace):
|
||||
Template = self.loader.import_(template_path)
|
||||
stream = Template(namespace)
|
||||
return stream.render()
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
renderers['kajiki'] = KajikiRenderer()
|
Binary file not shown.
|
@ -0,0 +1,31 @@
|
|||
from setuptools import setup, find_packages
|
||||
import sys, os
|
||||
|
||||
version = '0.1'
|
||||
|
||||
setup(
|
||||
name = 'pecan',
|
||||
version = version,
|
||||
description = "A WSGI object-dispatching web framework, in the spirit of TurboGears, only much much smaller, with many fewer dependancies.",
|
||||
long_description = None,
|
||||
classifiers = [],
|
||||
keywords = '',
|
||||
author = 'Jonathan LaCour',
|
||||
author_email = 'jonathan@cleverdevil.org',
|
||||
url = 'http://code.google.com/p/pecan',
|
||||
license = 'BSD',
|
||||
packages = find_packages(exclude=['ez_setup', 'examples', 'tests']),
|
||||
include_package_data = True,
|
||||
zip_safe = True,
|
||||
install_requires=[
|
||||
"WebOb >= 0.9.8",
|
||||
"simplejson >= 2.0.9",
|
||||
"simplegeneric >= 0.7",
|
||||
"Genshi >= 0.6",
|
||||
"Kajiki >= 0.2.2",
|
||||
"Mako >= 0.3"
|
||||
],
|
||||
entry_points = """
|
||||
# -*- Entry points: -*-
|
||||
""",
|
||||
)
|
Loading…
Reference in New Issue