4
from simplegeneric import generic
6
from wsme.exc import ClientSideError
7
from wsme.protocol import CallContext, Protocol, expose
8
from wsme.utils import parse_isodate, parse_isodatetime, parse_isotime
9
from wsme.rest.args import from_params
10
from wsme.types import iscomplex, isusertype, list_attributes, Unset
14
import simplejson as json
21
class APIDefinitionGenerator(object):
25
if (!%(rootns)s.wsroot) {
26
%(rootns)s.wsroot = "%(webpath)s.
31
Ext.syncRequire(['Ext.direct.*'], function() {
38
%(fullns)s.Descriptor = {
39
"url": %(rootns)s.wsroot + "extdirect/router/%(ns)s",
40
"namespace": "%(fullns)s",
42
"actions": %(actions)s
47
Ext.direct.Manager.addProvider(%(fullns)s.Descriptor);
53
def render(self, rootns, webpath, namespaces, fullns):
55
for ns in sorted(namespaces):
56
descriptors += self.descriptor_tpl % {
60
'actions': '\n'.join((
63
in json.dumps(namespaces[ns], indent=4).split('\n')
68
for ns in sorted(namespaces):
69
providers += self.provider_tpl % {
76
'descriptors': descriptors,
77
'providers': providers,
83
def fromjson(datatype, value):
84
if iscomplex(datatype):
86
for attrdef in list_attributes(datatype):
87
if attrdef.name in value:
88
setattr(newvalue, attrdef.key,
89
fromjson(attrdef.datatype, value[attrdef.name]))
91
elif isusertype(datatype):
92
value = datatype.frombasetype(fromjson(datatype.basetype, value))
97
def tojson(datatype, value):
100
if iscomplex(datatype):
102
for attrdef in list_attributes(datatype):
103
attrvalue = getattr(value, attrdef.key)
104
if attrvalue is not Unset:
105
d[attrdef.name] = tojson(attrdef.datatype, attrvalue)
107
elif isusertype(datatype):
108
value = tojson(datatype.basetype, datatype.tobasetype(value))
112
@fromjson.when_type(wsme.types.ArrayType)
113
def array_fromjson(datatype, value):
114
return [fromjson(datatype.item_type, item) for item in value]
117
@tojson.when_type(wsme.types.ArrayType)
118
def array_tojson(datatype, value):
121
return [tojson(datatype.item_type, item) for item in value]
124
@fromjson.when_type(wsme.types.DictType)
125
def dict_fromjson(datatype, value):
129
(fromjson(datatype.key_type, key),
130
fromjson(datatype.value_type, value))
131
for key, value in value.items()
135
@tojson.when_type(wsme.types.DictType)
136
def dict_tojson(datatype, value):
140
(tojson(datatype.key_type, key),
141
tojson(datatype.value_type, value))
142
for key, value in value.items()
146
@tojson.when_object(wsme.types.bytes)
147
def bytes_tojson(datatype, value):
150
return value.decode('ascii')
154
@fromjson.when_object(wsme.types.bytes)
155
def bytes_fromjson(datatype, value):
156
if value is not None:
157
value = value.encode('ascii')
163
@fromjson.when_object(wsme.types.text)
164
def text_fromjson(datatype, value):
165
if isinstance(value, wsme.types.bytes):
166
return value.decode('utf-8')
172
@fromjson.when_object(datetime.time)
173
def time_fromjson(datatype, value):
174
if value is None or value == '':
176
return parse_isotime(value)
179
@tojson.when_object(datetime.time)
180
def time_tojson(datatype, value):
183
return value.isoformat()
188
@fromjson.when_object(datetime.date)
189
def date_fromjson(datatype, value):
190
if value is None or value == '':
192
return parse_isodate(value)
195
@tojson.when_object(datetime.date)
196
def date_tojson(datatype, value):
199
return value.isoformat()
204
@fromjson.when_object(datetime.datetime)
205
def datetime_fromjson(datatype, value):
206
if value is None or value == '':
208
return parse_isodatetime(value)
211
@tojson.when_object(datetime.datetime)
212
def datetime_tojson(datatype, value):
215
return value.isoformat()
220
@fromjson.when_object(decimal.Decimal)
221
def decimal_fromjson(datatype, value):
224
return decimal.Decimal(value)
227
@tojson.when_object(decimal.Decimal)
228
def decimal_tojson(datatype, value):
234
class ExtCallContext(CallContext):
235
def __init__(self, request, namespace, calldata):
236
super(ExtCallContext, self).__init__(request)
237
self.namespace = namespace
239
self.tid = calldata['tid']
240
self.action = calldata['action']
241
self.method = calldata['method']
242
self.params = calldata['data']
245
class FormExtCallContext(CallContext):
246
def __init__(self, request, namespace):
247
super(FormExtCallContext, self).__init__(request)
248
self.namespace = namespace
250
self.tid = request.params['extTID']
251
self.action = request.params['extAction']
252
self.method = request.params['extMethod']
256
class ExtDirectProtocol(Protocol):
260
For more detail on the protocol, see
261
http://www.sencha.com/products/extjs/extdirect.
263
.. autoattribute:: name
264
.. autoattribute:: content_types
267
displayname = 'ExtDirect'
268
content_types = ['application/json', 'text/javascript']
270
def __init__(self, namespace='', params_notation='named',
272
self.namespace = namespace
273
self.appns, self.apins = namespace.rsplit('.', 2) \
274
if '.' in namespace else (namespace, '')
275
self.default_params_notation = params_notation
276
self.appnsfolder = nsfolder
281
alias = '/%s/%s.js' % (
283
self.apins.replace('.', '/'))
286
def accept(self, req):
288
assert path.startswith(self.root._webpath)
289
path = path[len(self.root._webpath):]
291
return path == self.api_alias or \
292
path == "/extdirect/api" or \
293
path.startswith("/extdirect/router")
295
def iter_calls(self, req):
298
assert path.startswith(self.root._webpath)
299
path = path[len(self.root._webpath):].strip()
301
assert path.startswith('/extdirect/router'), path
302
path = path[17:].strip('/')
305
namespace = path.split('.')
309
if 'extType' in req.params:
310
req.wsme_extdirect_batchcall = False
311
yield FormExtCallContext(req, namespace)
313
data = json.loads(req.body.decode('utf8'))
314
req.wsme_extdirect_batchcall = isinstance(data, list)
315
if not req.wsme_extdirect_batchcall:
317
req.callcount = len(data)
320
yield ExtCallContext(req, namespace, call)
322
def extract_path(self, context):
323
path = list(context.namespace)
326
path.append(context.action)
328
path.append(context.method)
332
def read_std_arguments(self, context):
333
funcdef = context.funcdef
334
notation = funcdef.extra_options.get(
335
'extdirect_params_notation', self.default_params_notation)
336
args = context.params
337
if notation == 'positional':
338
kw = dict((argdef.name, fromjson(argdef.datatype, arg))
339
for argdef, arg in zip(funcdef.arguments, args))
340
elif notation == 'named':
344
raise ClientSideError(
345
"Named arguments: takes a single object argument")
348
(argdef.name, fromjson(argdef.datatype, args[argdef.name]))
349
for argdef in funcdef.arguments if argdef.name in args
352
raise ValueError("Invalid notation: %s" % notation)
355
def read_form_arguments(self, context):
357
for argdef in context.funcdef.arguments:
358
value = from_params(argdef.datatype,
359
context.request.params, argdef.name, set())
360
if value is not Unset:
361
kw[argdef.name] = value
364
def read_arguments(self, context):
365
if isinstance(context, ExtCallContext):
366
kwargs = self.read_std_arguments(context)
367
elif isinstance(context, FormExtCallContext):
368
kwargs = self.read_form_arguments(context)
369
wsme.runtime.check_arguments(context.funcdef, (), kwargs)
372
def encode_result(self, context, result):
376
'action': context.action,
377
'method': context.method,
378
'result': tojson(context.funcdef.return_type, result)
381
def encode_error(self, context, infos):
385
'action': context.action,
386
'method': context.method,
387
'message': '%(faultcode)s: %(faultstring)s' % infos,
388
'where': infos['debuginfo']})
390
def prepare_response_body(self, request, results):
391
r = ",\n".join(results)
392
if request.wsme_extdirect_batchcall:
393
return "[\n%s\n]" % r
397
def get_response_status(self, request):
400
def get_response_contenttype(self, request):
401
return "text/javascript"
403
def fullns(self, ns):
404
return ns and '%s.%s' % (self.namespace, ns) or self.namespace
406
@expose('/extdirect/api', "text/javascript")
407
@expose('${api_alias}', "text/javascript")
410
for path, funcdef in self.root.getapi():
412
namespace = '.'.join(path[:-2])
417
if namespace not in namespaces:
418
namespaces[namespace] = {}
419
if action not in namespaces[namespace]:
420
namespaces[namespace][action] = []
421
notation = funcdef.extra_options.get('extdirect_params_notation',
422
self.default_params_notation)
424
'name': funcdef.name}
426
if funcdef.extra_options.get('extdirect_formhandler', False):
427
method['formHandler'] = True
428
method['len'] = 1 if notation == 'named' \
429
else len(funcdef.arguments)
430
namespaces[namespace][action].append(method)
431
webpath = self.root._webpath
432
if webpath and not webpath.endswith('/'):
434
return APIDefinitionGenerator().render(
435
namespaces=namespaces,
437
rootns=self.namespace,
441
def encode_sample_value(self, datatype, value, format=False):
442
r = tojson(datatype, value)
443
content = json.dumps(r, ensure_ascii=False,
444
indent=4 if format else 0,
446
return ('javascript', content)