Code

xmlrpc handling of unicode characters, see
[roundup.git] / roundup / xmlrpc.py
1 #
2 # Copyright (C) 2007 Stefan Seefeld
3 # All rights reserved.
4 # For license terms see the file COPYING.txt.
5 #
7 from roundup import hyperdb
8 from roundup.cgi.exceptions import *
9 from roundup.exceptions import UsageError
10 from roundup.date import Date, Range, Interval
11 from roundup import actions
12 from SimpleXMLRPCServer import *
14 def translate(value):
15     """Translate value to becomes valid for XMLRPC transmission."""
17     if isinstance(value, (Date, Range, Interval)):
18         return repr(value)
19     elif type(value) is list:
20         return [translate(v) for v in value]
21     elif type(value) is tuple:
22         return tuple([translate(v) for v in value])
23     elif type(value) is dict:
24         return dict([[translate(k), translate(value[k])] for k in value])
25     else:
26         return value
29 def props_from_args(db, cl, args, itemid=None):
30     """Construct a list of properties from the given arguments,
31     and return them after validation."""
33     props = {}
34     for arg in args:
35         if arg.find('=') == -1:
36             raise UsageError, 'argument "%s" not propname=value'%arg
37         try :
38             key, value = arg.split('=', 1)
39         except ValueError :
40             raise UsageError, 'argument "%s" not propname=value'%arg
41         if isinstance (key, unicode) :
42             try :
43                 key = key.encode ('ascii')
44             except UnicodeEncodeError:
45                 raise UsageError, 'argument %r is no valid ascii keyword'%key
46         if isinstance (value, unicode) :
47             value = value.encode ('utf-8')
48         if value:
49             try:
50                 props[key] = hyperdb.rawToHyperdb(db, cl, itemid,
51                                                   key, value)
52             except hyperdb.HyperdbValueError, message:
53                 raise UsageError, message
54         else:
55             props[key] = None
57     return props
59 class RoundupInstance:
60     """The RoundupInstance provides the interface accessible through
61     the Python XMLRPC mapping."""
63     def __init__(self, db, actions, translator):
65         self.db = db
66         self.actions = actions
67         self.translator = translator
69     def schema(self):
70         s = {}
71         for c in self.db.classes:
72             cls = self.db.classes[c]
73             props = [(n,repr(v)) for n,v in cls.properties.items()]
74             s[c] = props
75         return s
77     def list(self, classname, propname=None):
78         cl = self.db.getclass(classname)
79         if not propname:
80             propname = cl.labelprop()
81         result = [cl.get(itemid, propname)
82                   for itemid in cl.list()
83                   if self.db.security.hasPermission('View', self.db.getuid(),
84                                                     classname, propname, itemid)
85                   ]
86         return result
88     def filter(self, classname, search_matches, filterspec,
89                sort=[], group=[]):
90         cl = self.db.getclass(classname)
91         result = cl.filter(search_matches, filterspec, sort=sort, group=group)
92         return result
94     def display(self, designator, *properties):
95         classname, itemid = hyperdb.splitDesignator(designator)
96         cl = self.db.getclass(classname)
97         props = properties and list(properties) or cl.properties.keys()
98         props.sort()
99         for p in props:
100             if not self.db.security.hasPermission('View', self.db.getuid(),
101                                                   classname, p, itemid):
102                 raise Unauthorised('Permission to view %s of %s denied'%
103                                    (p, designator))
104             result = [(prop, cl.get(itemid, prop)) for prop in props]
105         return dict(result)
107     def create(self, classname, *args):
108         
109         if not self.db.security.hasPermission('Create', self.db.getuid(), classname):
110             raise Unauthorised('Permission to create %s denied'%classname)
112         cl = self.db.getclass(classname)
114         # convert types
115         props = props_from_args(self.db, cl, args)
117         # check for the key property
118         key = cl.getkey()
119         if key and not props.has_key(key):
120             raise UsageError, 'you must provide the "%s" property.'%key
122         for key in props:
123             if not self.db.security.hasPermission('Create', self.db.getuid(),
124                 classname, property=key):
125                 raise Unauthorised('Permission to create %s.%s denied'%(classname, key))
127         # do the actual create
128         try:
129             result = cl.create(**props)
130             self.db.commit()
131         except (TypeError, IndexError, ValueError), message:
132             raise UsageError, message
133         return result
135     def set(self, designator, *args):
137         classname, itemid = hyperdb.splitDesignator(designator)
138         cl = self.db.getclass(classname)
139         props = props_from_args(self.db, cl, args, itemid) # convert types
140         for p in props.iterkeys():
141             if not self.db.security.hasPermission('Edit', self.db.getuid(),
142                                                   classname, p, itemid):
143                 raise Unauthorised('Permission to edit %s of %s denied'%
144                                    (p, designator))
145         try:
146             result = cl.set(itemid, **props)
147             self.db.commit()
148         except (TypeError, IndexError, ValueError), message:
149             raise UsageError, message
150         return result
153     builtin_actions = {'retire': actions.Retire}
155     def action(self, name, *args):
156         """Execute a named action."""
157         
158         if name in self.actions:
159             action_type = self.actions[name]
160         elif name in self.builtin_actions:
161             action_type = self.builtin_actions[name]
162         else:
163             raise Exception('action "%s" is not supported %s' % (name, ','.join(self.actions.keys())))
164         action = action_type(self.db, self.translator)
165         return action.execute(*args)
168 class RoundupDispatcher(SimpleXMLRPCDispatcher):
169     """RoundupDispatcher bridges from cgi.client to RoundupInstance.
170     It expects user authentication to be done."""
172     def __init__(self, db, actions, translator,
173                  allow_none=False, encoding=None):
175         try:
176             # python2.5 and beyond
177             SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding)
178         except TypeError:
179             # python2.4
180             SimpleXMLRPCDispatcher.__init__(self)
181         self.register_instance(RoundupInstance(db, actions, translator))
182                  
184     def dispatch(self, input):
185         return self._marshaled_dispatch(input)
187     def _dispatch(self, method, params):
189         retn = SimpleXMLRPCDispatcher._dispatch(self, method, params)
190         retn = translate(retn)
191         return retn
192