This repository has been archived by the owner on Oct 12, 2017. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 15
/
Publisher
180 lines (140 loc) · 7.39 KB
/
Publisher
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
This is a general publishing tool for cherrypy3. The intent is to allow for multiple response formats based on a getvar (or in abscense of, a default that is overridable using cherrypy config options). It was tested to work with python3 some time ago, but this recent revision hasn't been tested against python3. Dependencies include: lxml, json, yaml. These can be edited in source where not needed.
It is also designed to be extensible with more rendering options easily by declaring a new Publisher method named render_[renderer]. To request use of it, you can pass the getvar format=[renderer] or set a cherrypy config option, publisher.format = [renderer], or of course add it to the decorator options @publisher(format='json')
This code also features a dict2xml converter, and conversely, requires that all controller methods that use publisher return dict objects. This allows straight dicts to be returned, or a generator that yields [(key, value), (key, value)] and will automatically convert to a workable xml document. This makes publisher incompatible with cherrypy streaming responses (i believe). Take the following example:
{{{
...
@publisher(template='index.xsl')
def index(self):
return dict(content='Hello World!')
}}}
This will yield an xml document like so that will be passed to your stylesheet:
{{{
<document>
<content>Hello World!</content>
</document>
}}}
if format='xml', you can retrieve the straight xml document (useful for debugging xslt). Generally, you will want to set the publisher.template_dir in your app.conf file. In some cases, such as working with multiple apps being mashed up into one new app, it might be useful to declare template_dir in the decorator, or w/e your preference is.
Publisher also supports adding your own custom xslt extensions written with lxml and python. Once you have written and created your lxml extension instance, simply set it as follows
Publisher.lxmlext = extensions
Now your extensions will be available for declaration in your stylesheets. Also, Publisher supports extending default returns and these extensions should be localized to each app configuration, in the case that publisher is being shared across multiple instances of cherrypy apps. This can be useful for things such as site menus that require a server side component to generate or page titles where multiple apps are sharing similar stylesheets. Here's an example:
{{{
class Title(object):
def __call__(self):
return {'name': 'JS', 'desc': 'Easy Prototypal Inheritance with Javascript'}
title = Title()
class Root(object):
_cp_config = {'publisher.extend_xml': {'title': title}}
}}}
And now your stylesheets can grab the value-of select="//title/name", or "//title/desc".
Ideally, this tool should be added to your own toolbox or cherrypy's default. In which case, all examples above (especially in conf declarations) "publisher" should be prepended with the name of your toolbox.
The code is as follows:
{{{
# -*- coding: utf-8 -*-
import cherrypy
from cherrypy._cptools import HandlerWrapperTool
from lxml.etree import Element, tostring, _Element, parse, XSLT
from copy import deepcopy
import json
import yaml
import os
import sys
from types import GeneratorType
try:
from pymongo.bson import ObjectId
except:
class ObjectId(object): pass
if sys.version[0] == '2':
dtypes = (unicode, str, int, float, ObjectId)
else:
dtypes = (str, int, float, ObjectId)
def str_format(data):
if sys.version[0] == '2':
return unicode(data)
else:
if isinstance(data, bytes):
return data.decode()
return str(data)
def dict2etree(response_dict):
document = Element('document')
def _dict2etree(data, et=None):
'''Iterates over a dict and produces a basic xml file'''
if isinstance(data, dtypes):
et.text = str_format(data)
elif isinstance(data, list):
for i,x in enumerate(data):
tag = deepcopy(et)
et.getparent().extend([tag])
_dict2etree(x, tag)
et.getparent().remove(et)
elif isinstance(data, dict):
for k, v in data.items():
tag = Element(k)
if et is None:
document.append(tag)
else:
et.append(tag)
_dict2etree(v, tag)
_dict2etree(response_dict)
return document
class Publisher(HandlerWrapperTool):
lxmlext = None
def __init__(self):
self.extend = {}
HandlerWrapperTool.__init__(self, self.publish)
def _setup(self, *args, **kwargs):
if cherrypy.request.config.get('tools.staticdir.on', False) or \
cherrypy.request.config.get('tools.staticfile.on', False):
return
HandlerWrapperTool._setup(self, *args, **kwargs)
def callable(self, format='xslt', template_dir='views/site', template='index.xsl', extend_xml={}):
cherrypy.request.format = format
HandlerWrapperTool.callable(self)
def params(self):
if 'format' in cherrypy.request.params:
format = cherrypy.request.params.pop('format')
cherrypy.request.format = format
def publish(self, next_handler, *args, **kwargs):
self.params()
response_dict = next_handler(*args, **kwargs)
if isinstance(response_dict, GeneratorType):
response_dict = dict(response_dict)
conf = self._merged_args()
response = getattr(self, 'render_%s' % cherrypy.request.format)(response_dict, conf)
return response
def extend_xml(self, e):
r = {}
if isinstance(e, dict):
for k, v in e.items():
if hasattr(v, '__call__'):
r[k] = v()
if isinstance(v, dict):
r[k] = v
return r
def render_raw(self, response_dict, conf):
return response_dict
def render_json(self, response_dict, conf):
cherrypy.response.headers['Content-Type'] = 'text/json'
return json.dumps(response_dict)
def render_yaml(self, response_dict, conf):
cherrypy.response.headers['Content-Type'] = 'text/yaml'
return yaml.dump(response_dict)
def render_xml(self, response_dict, conf):
cherrypy.response.headers['Content-Type'] = 'text/xml'
x = conf.get('extend_xml', False)
if x:
response_dict.update(self.extend_xml(x))
document = dict2etree(response_dict)
return tostring(document, pretty_print=True, xml_declaration=True, encoding='UTF-8')
def render_xslt(self, response_dict, conf):
cherrypy.response.headers['Content-Type'] = 'text/html'
x = conf.get('extend_xml', False)
if x:
response_dict.update(self.extend_xml(x))
document = dict2etree(response_dict)
template = os.path.join(conf['template_dir'], conf.get('template', 'index.xsl'))
cherrypy.response.template = template # for use outside of publisher, such as xslt extensions
l = parse(open(template))
xslt = XSLT(l, extensions=self.lxmlext)
response = xslt(document)
return tostring(response, pretty_print=True, method='html', xml_declaration=False, encoding='UTF-8')
}}}