Mercurial > p > roundup > code
view roundup/xmlrpc.py @ 8423:94eed885e958
feat: add support for using dictConfig to configure logging.
Basic logging config (one level and one output file non-rotating) was
always possible from config.ini. However the LOGGING_CONFIG setting
could be used to load an ini fileConfig style file to set various
channels (e.g. roundup.hyperdb) (also called qualname or tags) with
their own logging level, destination (rotating file, socket,
/dev/null) and log format.
This is now a deprecated method in newer logging modules. The
dictConfig format is preferred and allows disabiling other loggers as
well as invoking new loggers in local code. This commit adds support
for it reading the dict from a .json file. It also implements a
comment convention so you can document the dictConfig.
configuration.py:
new code
test_config.py:
test added for the new code.
admin_guide.txt, upgrading.txt CHANGES.txt:
docs added upgrading references the section in admin_guid.
| author | John Rouillard <rouilj@ieee.org> |
|---|---|
| date | Tue, 19 Aug 2025 22:32:46 -0400 |
| parents | f7bd22bdef9d |
| children |
line wrap: on
line source
# # Copyright (C) 2007 Stefan Seefeld # All rights reserved. # For license terms see the file COPYING.txt. # import logging from roundup import hyperdb from roundup.exceptions import Unauthorised, UsageError from roundup.date import Date, Range, Interval from roundup import actions from roundup.anypy.strings import us2s from traceback import format_exc from roundup.anypy import xmlrpc_ SimpleXMLRPCDispatcher = xmlrpc_.server.SimpleXMLRPCDispatcher Binary = xmlrpc_.client.Binary def translate(value): """Translate value to becomes valid for XMLRPC transmission.""" if isinstance(value, (Date, Range, Interval)): return repr(value) elif type(value) is list: return [translate(v) for v in value] elif type(value) is tuple: return tuple([translate(v) for v in value]) elif type(value) is dict: return dict([[translate(k), translate(value[k])] for k in value]) else: return value def props_from_args(db, cl, args, itemid=None): """Construct a list of properties from the given arguments, and return them after validation.""" props = {} for arg in args: if isinstance(arg, Binary): arg = arg.data try: key, value = arg.split('=', 1) except ValueError: raise UsageError('argument "%s" not propname=value' % arg) key = us2s(key) value = us2s(value) if value: try: props[key] = hyperdb.rawToHyperdb(db, cl, itemid, key, value) except hyperdb.HyperdbValueError as message: raise UsageError(message) else: # If we're syncing a file the contents may not be None if key == 'content': props[key] = '' else: props[key] = None return props class RoundupInstance: """The RoundupInstance provides the interface accessible through the Python XMLRPC mapping.""" def __init__(self, db, actions, translator): self.db = db self.actions = actions self.translator = translator def schema(self): s = {} for c in self.db.classes: cls = self.db.classes[c] props = [(n, repr(v)) for n, v in sorted(cls.properties.items())] s[c] = props return s def list(self, classname, propname=None): cl = self.db.getclass(classname) if not propname: propname = cl.labelprop() result = [cl.get(itemid, propname) for itemid in cl.list() if self.db.security.hasPermission('View', self.db.getuid(), classname, propname, itemid) ] return result def filter(self, classname, search_matches, filterspec, sort=[], group=[]): cl = self.db.getclass(classname) return cl.filter_with_permissions( search_matches, filterspec, sort=sort, group=group ) def lookup(self, classname, key): cl = self.db.getclass(classname) uid = self.db.getuid() prop = cl.getkey() search = self.db.security.hasSearchPermission access = self.db.security.hasPermission if (not search(uid, classname, prop) and not access('View', uid, classname, prop)): raise Unauthorised('Permission to lookup %s denied' % classname) return cl.lookup(key) def display(self, designator, *properties): classname, itemid = hyperdb.splitDesignator(designator) cl = self.db.getclass(classname) props = properties and list(properties) or list(cl.properties.keys()) props.sort() for p in props: if not self.db.security.hasPermission('View', self.db.getuid(), classname, p, itemid): raise Unauthorised('Permission to view %s of %s denied' % (p, designator)) result = [(prop, cl.get(itemid, prop)) for prop in props] return dict(result) def create(self, classname, *args): if not self.db.security.hasPermission('Create', self.db.getuid(), classname): raise Unauthorised('Permission to create %s denied' % classname) cl = self.db.getclass(classname) # convert types props = props_from_args(self.db, cl, args) # check for the key property key = cl.getkey() if key and key not in props: raise UsageError('you must provide the "%s" property.' % key) for key in props: if not self.db.security.hasPermission('Create', self.db.getuid(), classname, property=key): raise Unauthorised('Permission to create %s.%s denied' % (classname, key)) # do the actual create try: result = cl.create(**props) self.db.commit() except (TypeError, IndexError, ValueError) as message: # The exception we get may be a real error, log the traceback # if we're debugging logger = logging.getLogger('roundup.xmlrpc') for l in format_exc().split('\n'): logger.debug(l) raise UsageError(message) return result def set(self, designator, *args): classname, itemid = hyperdb.splitDesignator(designator) cl = self.db.getclass(classname) props = props_from_args(self.db, cl, args, itemid) # convert types for p in props.keys(): if not self.db.security.hasPermission('Edit', self.db.getuid(), classname, p, itemid): raise Unauthorised('Permission to edit %s of %s denied' % (p, designator)) try: result = cl.set(itemid, **props) self.db.commit() except (TypeError, IndexError, ValueError) as message: # The exception we get may be a real error, log the # traceback if we're debugging logger = logging.getLogger('roundup.xmlrpc') for l in format_exc().split('\n'): logger.debug(l) raise UsageError(message) return result builtin_actions = dict(retire=actions.Retire, restore=actions.Restore) def action(self, name, *args): """Execute a named action.""" if name in self.actions: action_type = self.actions[name] elif name in self.builtin_actions: action_type = self.builtin_actions[name] else: raise Exception('action "%s" is not supported %s' % (name, ','.join(self.actions.keys()))) action = action_type(self.db, self.translator) return action.execute(*args) class RoundupDispatcher(SimpleXMLRPCDispatcher): """RoundupDispatcher bridges from cgi.client to RoundupInstance. It expects user authentication to be done.""" def __init__(self, db, actions, translator, allow_none=False, encoding=None): SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding) self.register_instance(RoundupInstance(db, actions, translator)) self.register_multicall_functions() def dispatch(self, input): return self._marshaled_dispatch(input) def _dispatch(self, method, params): retn = SimpleXMLRPCDispatcher._dispatch(self, method, params) retn = translate(retn) return retn
