diff options
Diffstat (limited to 'openstack')
| -rw-r--r-- | openstack/common/config.py | 337 | ||||
| -rw-r--r-- | openstack/common/extensions.py | 539 |
2 files changed, 0 insertions, 876 deletions
diff --git a/openstack/common/config.py b/openstack/common/config.py deleted file mode 100644 index 12645f3..0000000 --- a/openstack/common/config.py +++ /dev/null @@ -1,337 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2011 OpenStack LLC. -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -""" -Routines for configuring Openstack Projects -""" - -import logging -import logging.config -import logging.handlers -import optparse -import os -import sys - -from paste import deploy - -DEFAULT_LOG_FORMAT = "%(asctime)s %(levelname)8s [%(name)s] %(message)s" -DEFAULT_LOG_DATE_FORMAT = "%Y-%m-%d %H:%M:%S" - - -def parse_options(parser, cli_args=None): - """ - Returns the parsed CLI options, command to run and its arguments, merged - with any same-named options found in a configuration file. - - The function returns a tuple of (options, args), where options is a - mapping of option key/str(value) pairs, and args is the set of arguments - (not options) supplied on the command-line. - - The reason that the option values are returned as strings only is that - ConfigParser and paste.deploy only accept string values... - - :param parser: The option parser - :param cli_args: (Optional) Set of arguments to process. If not present, - sys.argv[1:] is used. - :retval tuple of (options, args) - """ - - (options, args) = parser.parse_args(cli_args) - - return (vars(options), args) - - -def add_common_options(parser): - """ - Given a supplied optparse.OptionParser, adds an OptionGroup that - represents all common configuration options. - - :param parser: optparse.OptionParser - """ - help_text = ("The following configuration options are common to " - "this app's programs.") - - group = optparse.OptionGroup(parser, "Common Options", help_text) - group.add_option('-v', '--verbose', default=False, dest="verbose", - action="store_true", - help="Print more verbose output") - group.add_option('-d', '--debug', default=False, dest="debug", - action="store_true", - help="Print debugging output") - group.add_option('--config-file', default=None, metavar="PATH", - help="Path to the config file to use. When not specified " - "(the default), we generally look at the first " - "argument specified to be a config file, and if " - "that is also missing, we search standard " - "directories for a config file.") - parser.add_option_group(group) - - -def add_log_options(parser): - """ - Given a supplied optparse.OptionParser, adds an OptionGroup that - represents all the configuration options around logging. - - :param parser: optparse.OptionParser - """ - help_text = ("The following configuration options are specific to " - "logging functionality for this program.") - - group = optparse.OptionGroup(parser, "Logging Options", help_text) - group.add_option('--log-config', default=None, metavar="PATH", - help="If this option is specified, the logging " - "configuration file specified is used and overrides " - "any other logging options specified. Please see " - "the Python logging module documentation for " - "details on logging configuration files.") - group.add_option('--log-date-format', metavar="FORMAT", - default=DEFAULT_LOG_DATE_FORMAT, - help="Format string for %(asctime)s in log records. " - "Default: %default") - group.add_option('--log-file', default=None, metavar="PATH", - help="(Optional) Name of log file to output to. " - "If not set, logging will go to stdout.") - group.add_option("--log-dir", default=None, - help="(Optional) The directory to keep log files in " - "(will be prepended to --logfile)") - group.add_option('--use-syslog', default=False, dest="use_syslog", - action="store_true", - help="Use syslog for logging.") - parser.add_option_group(group) - - -def setup_logging(options, conf): - """ - Sets up the logging options for a log with supplied name - - :param options: Mapping of typed option key/values - :param conf: Mapping of untyped key/values from config file - """ - - if options.get('log_config', None): - # Use a logging configuration file for all settings... - if os.path.exists(options['log_config']): - logging.config.fileConfig(options['log_config']) - return - else: - raise RuntimeError("Unable to locate specified logging " - "config file: %s" % options['log_config']) - - # If either the CLI option or the conf value - # is True, we set to True - debug = (options.get('debug') or - get_option(conf, 'debug', type='bool', default=False)) - verbose = (options.get('verbose') or - get_option(conf, 'verbose', type='bool', default=False)) - root_logger = logging.root - if debug: - root_logger.setLevel(logging.DEBUG) - elif verbose: - root_logger.setLevel(logging.INFO) - else: - root_logger.setLevel(logging.WARNING) - - # Set log configuration from options... - # Note that we use a hard-coded log format in the options - # because of Paste.Deploy bug #379 - # http://trac.pythonpaste.org/pythonpaste/ticket/379 - log_format = options.get('log_format', DEFAULT_LOG_FORMAT) - log_date_format = options.get('log_date_format', DEFAULT_LOG_DATE_FORMAT) - formatter = logging.Formatter(log_format, log_date_format) - - logfile = options.get('log_file') - if not logfile: - logfile = conf.get('log_file') - - use_syslog = (options.get('use_syslog') or - get_option(conf, 'use_syslog', type='bool', default=False)) - - if use_syslog: - handler = logging.handlers.SysLogHandler(address='/dev/log') - elif logfile: - logdir = options.get('log_dir') - if not logdir: - logdir = conf.get('log_dir') - if logdir: - logfile = os.path.join(logdir, logfile) - handler = logging.FileHandler(logfile) - else: - handler = logging.StreamHandler(sys.stdout) - - handler.setFormatter(formatter) - root_logger.addHandler(handler) - - -def fix_path(path): - """ - Return the full absolute path - """ - return os.path.abspath(os.path.expanduser(path)) - - -def find_config_file(app_name, options, args, config_dir=None): - """ - Return the first config file found for an application. - - We search for the paste config file in the following order: - * If --config-file option is used, use that - * If args[0] is a file, use that - * Search for $app.conf in standard directories: - * . - * ~.config_dir/ - * ~ - * /etc/config_dir - * /etc - - :retval Full path to config file, or None if no config file found - """ - config_dir = config_dir or app_name - - if options.get('config_file'): - if os.path.exists(options['config_file']): - return fix_path(options['config_file']) - elif args: - if os.path.exists(args[0]): - return fix_path(args[0]) - - # Handle standard directory search for $app_name.conf - config_file_dirs = [fix_path(os.getcwd()), - fix_path(os.path.join('~', '.' + config_dir)), - fix_path('~'), - os.path.join('/etc', config_dir), - '/etc'] - - for cfg_dir in config_file_dirs: - cfg_file = os.path.join(cfg_dir, '%s.conf' % app_name) - if os.path.exists(cfg_file): - return cfg_file - - -def load_paste_config(app_name, options, args, config_dir=None): - """ - Looks for a config file to use for an app and returns the - config file path and a configuration mapping from a paste config file. - - We search for the paste config file in the following order: - * If --config-file option is used, use that - * If args[0] is a file, use that - * Search for $app_name.conf in standard directories: - * . - * ~.config_dir/ - * ~ - * /etc/config_dir - * /etc - - :param app_name: Name of the application to load config for, or None. - None signifies to only load the [DEFAULT] section of - the config file. - :param options: Set of typed options returned from parse_options() - :param args: Command line arguments from argv[1:] - :retval Tuple of (conf_file, conf) - - :raises RuntimeError when config file cannot be located or there was a - problem loading the configuration file. - """ - conf_file = find_config_file(app_name, options, args, config_dir) - if not conf_file: - raise RuntimeError("Unable to locate any configuration file. " - "Cannot load application %s" % app_name) - try: - conf = deploy.appconfig("config:%s" % conf_file, name=app_name) - return conf_file, conf - except Exception, e: - raise RuntimeError("Error trying to load config %s: %s" - % (conf_file, e)) - - -def load_paste_app(app_name, options, args, config_dir=None): - """ - Builds and returns a WSGI app from a paste config file. - - We search for the paste config file in the following order: - * If --config-file option is used, use that - * If args[0] is a file, use that - * Search for $app_name.conf in standard directories: - * . - * ~.config_dir/ - * ~ - * /etc/config_dir - * /etc - - :param app_name: Name of the application to load - :param options: Set of typed options returned from parse_options() - :param args: Command line arguments from argv[1:] - - :raises RuntimeError when config file cannot be located or application - cannot be loaded from config file - """ - conf_file, conf = load_paste_config(app_name, options, - args, config_dir) - - try: - # Setup logging early, supplying both the CLI options and the - # configuration mapping from the config file - setup_logging(options, conf) - - # We only update the conf dict for the verbose and debug - # flags. Everything else must be set up in the conf file... - debug = (options.get('debug') or - get_option(conf, 'debug', type='bool', default=False)) - verbose = (options.get('verbose') or - get_option(conf, 'verbose', type='bool', default=False)) - conf['debug'] = debug - conf['verbose'] = verbose - - # Log the options used when starting if we're in debug mode... - if debug: - logger = logging.getLogger(app_name) - logger.debug("*" * 80) - logger.debug("Configuration options gathered from config file:") - logger.debug(conf_file) - logger.debug("================================================") - items = dict([(k, v) for k, v in conf.items() - if k not in ('__file__', 'here')]) - for key, value in sorted(items.items()): - logger.debug("%(key)-30s %(value)s" % locals()) - logger.debug("*" * 80) - app = deploy.loadapp("config:%s" % conf_file, name=app_name) - except (LookupError, ImportError), e: - raise RuntimeError("Unable to load %(app_name)s from " - "configuration file %(conf_file)s." - "\nGot: %(e)r" % locals()) - return conf, app - - -def get_option(options, option, **kwargs): - if option in options: - value = options[option] - type_ = kwargs.get('type', 'str') - if type_ == 'bool': - if hasattr(value, 'lower'): - return value.lower() == 'true' - else: - return value - elif type_ == 'int': - return int(value) - elif type_ == 'float': - return float(value) - else: - return value - elif 'default' in kwargs: - return kwargs['default'] - else: - raise KeyError("option '%s' not found" % option) diff --git a/openstack/common/extensions.py b/openstack/common/extensions.py deleted file mode 100644 index 906906d..0000000 --- a/openstack/common/extensions.py +++ /dev/null @@ -1,539 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2011 OpenStack LLC. -# Copyright 2011 Justin Santa Barbara -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import imp -import logging -from lxml import etree -import os -import routes -import webob.dec -import webob.exc - -from openstack.common import exception -from openstack.common.gettextutils import _ -from openstack.common import wsgi - - -LOG = logging.getLogger('extensions') -DEFAULT_XMLNS = "http://docs.openstack.org/" -XMLNS_ATOM = "http://www.w3.org/2005/Atom" - - -class ExtensionDescriptor(object): - """Base class that defines the contract for extensions. - - Note that you don't have to derive from this class to have a valid - extension; it is purely a convenience. - - """ - - def get_name(self): - """The name of the extension. - - e.g. 'Fox In Socks' - - """ - raise NotImplementedError() - - def get_alias(self): - """The alias for the extension. - - e.g. 'FOXNSOX' - - """ - raise NotImplementedError() - - def get_description(self): - """Friendly description for the extension. - - e.g. 'The Fox In Socks Extension' - - """ - raise NotImplementedError() - - def get_namespace(self): - """The XML namespace for the extension. - - e.g. 'http://www.fox.in.socks/api/ext/pie/v1.0' - - """ - raise NotImplementedError() - - def get_updated(self): - """The timestamp when the extension was last updated. - - e.g. '2011-01-22T13:25:27-06:00' - - """ - # NOTE(justinsb): Not sure of the purpose of this is, vs the XML NS - raise NotImplementedError() - - def get_resources(self): - """List of extensions.ResourceExtension extension objects. - - Resources define new nouns, and are accessible through URLs. - - """ - resources = [] - return resources - - def get_actions(self): - """List of extensions.ActionExtension extension objects. - - Actions are verbs callable from the API. - - """ - actions = [] - return actions - - def get_request_extensions(self): - """List of extensions.RequestException extension objects. - - Request extensions are used to handle custom request data. - - """ - request_exts = [] - return request_exts - - -class ActionExtensionController(object): - def __init__(self, application): - self.application = application - self.action_handlers = {} - - def add_action(self, action_name, handler): - self.action_handlers[action_name] = handler - - def action(self, req, id, body): - for action_name, handler in self.action_handlers.iteritems(): - if action_name in body: - return handler(body, req, id) - # no action handler found (bump to downstream application) - res = self.application - return res - - -class ActionExtensionResource(wsgi.Resource): - - def __init__(self, application): - controller = ActionExtensionController(application) - wsgi.Resource.__init__(self, controller) - - def add_action(self, action_name, handler): - self.controller.add_action(action_name, handler) - - -class RequestExtensionController(object): - - def __init__(self, application): - self.application = application - self.handlers = [] - - def add_handler(self, handler): - self.handlers.append(handler) - - def process(self, req, *args, **kwargs): - res = req.get_response(self.application) - # currently request handlers are un-ordered - for handler in self.handlers: - res = handler(req, res) - return res - - -class RequestExtensionResource(wsgi.Resource): - - def __init__(self, application): - controller = RequestExtensionController(application) - wsgi.Resource.__init__(self, controller) - - def add_handler(self, handler): - self.controller.add_handler(handler) - - -class ExtensionsResource(wsgi.Resource): - - def __init__(self, extension_manager): - self.extension_manager = extension_manager - body_serializers = {'application/xml': ExtensionsXMLSerializer()} - serializer = wsgi.ResponseSerializer(body_serializers=body_serializers) - super(ExtensionsResource, self).__init__(self, None, serializer) - - def _translate(self, ext): - ext_data = {} - ext_data['name'] = ext.get_name() - ext_data['alias'] = ext.get_alias() - ext_data['description'] = ext.get_description() - ext_data['namespace'] = ext.get_namespace() - ext_data['updated'] = ext.get_updated() - ext_data['links'] = [] # TODO(dprince): implement extension links - return ext_data - - def index(self, req): - extensions = [] - for _alias, ext in self.extension_manager.extensions.iteritems(): - extensions.append(self._translate(ext)) - return dict(extensions=extensions) - - def show(self, req, id): - # NOTE(dprince): the extensions alias is used as the 'id' for show - ext = self.extension_manager.extensions.get(id, None) - if not ext: - raise webob.exc.HTTPNotFound( - _("Extension with alias %s does not exist") % id) - - return dict(extension=self._translate(ext)) - - def delete(self, req, id): - raise webob.exc.HTTPNotFound() - - def create(self, req): - raise webob.exc.HTTPNotFound() - - -class ExtensionMiddleware(wsgi.Middleware): - """Extensions middleware for WSGI.""" - - @classmethod - def factory(cls, global_config, **local_config): - """Paste factory.""" - def _factory(app): - return cls(app, global_config, **local_config) - return _factory - - def _action_ext_resources(self, application, ext_mgr, mapper): - """Return a dict of ActionExtensionResource-s by collection.""" - action_resources = {} - for action in ext_mgr.get_actions(): - if not action.collection in action_resources.keys(): - resource = ActionExtensionResource(application) - mapper.connect("/%s/:(id)/action.:(format)" % - action.collection, - action='action', - controller=resource, - conditions=dict(method=['POST'])) - mapper.connect("/%s/:(id)/action" % - action.collection, - action='action', - controller=resource, - conditions=dict(method=['POST'])) - action_resources[action.collection] = resource - - return action_resources - - def _request_ext_resources(self, application, ext_mgr, mapper): - """Returns a dict of RequestExtensionResource-s by collection.""" - request_ext_resources = {} - for req_ext in ext_mgr.get_request_extensions(): - if not req_ext.key in request_ext_resources.keys(): - resource = RequestExtensionResource(application) - mapper.connect(req_ext.url_route + '.:(format)', - action='process', - controller=resource, - conditions=req_ext.conditions) - - mapper.connect(req_ext.url_route, - action='process', - controller=resource, - conditions=req_ext.conditions) - request_ext_resources[req_ext.key] = resource - - return request_ext_resources - - def __init__(self, application, config, ext_mgr=None): - ext_mgr = ext_mgr or ExtensionManager(config['api_extensions_path']) - mapper = routes.Mapper() - - # extended resources - for resource_ext in ext_mgr.get_resources(): - LOG.debug(_('Extended resource: %s'), resource_ext.collection) - controller_resource = wsgi.Resource(resource_ext.controller, - resource_ext.deserializer, - resource_ext.serializer) - self._map_custom_collection_actions(resource_ext, mapper, - controller_resource) - kargs = dict(controller=controller_resource, - collection=resource_ext.collection_actions, - member=resource_ext.member_actions) - if resource_ext.parent: - kargs['parent_resource'] = resource_ext.parent - mapper.resource(resource_ext.collection, - resource_ext.collection, **kargs) - - # extended actions - action_resources = self._action_ext_resources(application, ext_mgr, - mapper) - for action in ext_mgr.get_actions(): - LOG.debug(_('Extended action: %s'), action.action_name) - resource = action_resources[action.collection] - resource.add_action(action.action_name, action.handler) - - # extended requests - req_controllers = self._request_ext_resources(application, ext_mgr, - mapper) - for request_ext in ext_mgr.get_request_extensions(): - LOG.debug(_('Extended request: %s'), request_ext.key) - controller = req_controllers[request_ext.key] - controller.add_handler(request_ext.handler) - - self._router = routes.middleware.RoutesMiddleware(self._dispatch, - mapper) - - super(ExtensionMiddleware, self).__init__(application) - - def _map_custom_collection_actions(self, resource_ext, mapper, - controller_resource): - for action, method in resource_ext.collection_actions.iteritems(): - parent = resource_ext.parent - conditions = dict(method=[method]) - path = "/%s/%s" % (resource_ext.collection, action) - - path_prefix = "" - if parent: - path_prefix = "/%s/{%s_id}" % (parent["collection_name"], - parent["member_name"]) - - with mapper.submapper(controller=controller_resource, - action=action, - path_prefix=path_prefix, - conditions=conditions) as submap: - submap.connect(path) - submap.connect("%s.:(format)" % path) - - @webob.dec.wsgify(RequestClass=wsgi.Request) - def __call__(self, req): - """Route the incoming request with router.""" - req.environ['extended.app'] = self.application - return self._router - - @staticmethod - @webob.dec.wsgify(RequestClass=wsgi.Request) - def _dispatch(req): - """Dispatch the request. - - Returns the routed WSGI app's response or defers to the extended - application. - - """ - match = req.environ['wsgiorg.routing_args'][1] - if not match: - return req.environ['extended.app'] - app = match['controller'] - return app - - -class ExtensionManager(object): - """Load extensions from the configured extension path. - - See nova/tests/api/openstack/extensions/foxinsocks/extension.py for an - example extension implementation. - - """ - - def __init__(self, path): - LOG.debug(_('Initializing extension manager.')) - - self.path = path - self.extensions = {} - self._load_all_extensions() - - def get_resources(self): - """Returns a list of ResourceExtension objects.""" - resources = [] - extension_resource = ExtensionsResource(self) - res_ext = ResourceExtension('extensions', - extension_resource, - serializer=extension_resource.serializer) - resources.append(res_ext) - for alias, ext in self.extensions.iteritems(): - try: - resources.extend(ext.get_resources()) - except AttributeError: - # NOTE(dprince): Extension aren't required to have resource - # extensions - pass - return resources - - def get_actions(self): - """Returns a list of ActionExtension objects.""" - actions = [] - for alias, ext in self.extensions.iteritems(): - try: - actions.extend(ext.get_actions()) - except AttributeError: - # NOTE(dprince): Extension aren't required to have action - # extensions - pass - return actions - - def get_request_extensions(self): - """Returns a list of RequestExtension objects.""" - request_exts = [] - for alias, ext in self.extensions.iteritems(): - try: - request_exts.extend(ext.get_request_extensions()) - except AttributeError: - # NOTE(dprince): Extension aren't required to have request - # extensions - pass - return request_exts - - def _check_extension(self, extension): - """Checks for required methods in extension objects.""" - try: - LOG.debug(_('Ext name: %s'), extension.get_name()) - LOG.debug(_('Ext alias: %s'), extension.get_alias()) - LOG.debug(_('Ext description: %s'), extension.get_description()) - LOG.debug(_('Ext namespace: %s'), extension.get_namespace()) - LOG.debug(_('Ext updated: %s'), extension.get_updated()) - except AttributeError as ex: - LOG.exception(_("Exception loading extension: %s"), unicode(ex)) - return False - return True - - def _load_all_extensions(self): - """Load extensions from the configured path. - - Load extensions from the configured path. The extension name is - constructed from the module_name. If your extension module was named - widgets.py the extension class within that module should be - 'Widgets'. - - In addition, extensions are loaded from the 'contrib' directory. - - See nova/tests/api/openstack/extensions/foxinsocks.py for an example - extension implementation. - - """ - if os.path.exists(self.path): - self._load_all_extensions_from_path(self.path) - - contrib_path = os.path.join(os.path.dirname(__file__), "contrib") - if os.path.exists(contrib_path): - self._load_all_extensions_from_path(contrib_path) - - def _load_all_extensions_from_path(self, path): - for f in os.listdir(path): - LOG.debug(_('Loading extension file: %s'), f) - mod_name, file_ext = os.path.splitext(os.path.split(f)[-1]) - ext_path = os.path.join(path, f) - if file_ext.lower() == '.py' and not mod_name.startswith('_'): - mod = imp.load_source(mod_name, ext_path) - ext_name = mod_name[0].upper() + mod_name[1:] - new_ext_class = getattr(mod, ext_name, None) - if not new_ext_class: - LOG.warn(_('Did not find expected name ' - '"%(ext_name)s" in %(file)s'), - {'ext_name': ext_name, - 'file': ext_path}) - continue - new_ext = new_ext_class() - self.add_extension(new_ext) - - def add_extension(self, ext): - # Do nothing if the extension doesn't check out - if not self._check_extension(ext): - return - - alias = ext.get_alias() - LOG.debug(_('Loaded extension: %s'), alias) - - if alias in self.extensions: - raise exception.Error("Found duplicate extension: %s" % alias) - self.extensions[alias] = ext - - -class RequestExtension(object): - """Extend requests and responses of core nova OpenStack API resources. - - Provide a way to add data to responses and handle custom request data - that is sent to core nova OpenStack API controllers. - - """ - def __init__(self, method, url_route, handler): - self.url_route = url_route - self.handler = handler - self.conditions = dict(method=[method]) - self.key = "%s-%s" % (method, url_route) - - -class ActionExtension(object): - """Add custom actions to core nova OpenStack API resources.""" - - def __init__(self, collection, action_name, handler): - self.collection = collection - self.action_name = action_name - self.handler = handler - - -class ResourceExtension(object): - """Add top level resources to the OpenStack API in nova.""" - - def __init__(self, collection, controller, parent=None, - collection_actions=None, member_actions=None, - deserializer=None, serializer=None): - if not collection_actions: - collection_actions = {} - if not member_actions: - member_actions = {} - self.collection = collection - self.controller = controller - self.parent = parent - self.collection_actions = collection_actions - self.member_actions = member_actions - self.deserializer = deserializer - self.serializer = serializer - - -class ExtensionsXMLSerializer(wsgi.XMLDictSerializer): - - def __init__(self): - self.nsmap = {None: DEFAULT_XMLNS, 'atom': XMLNS_ATOM} - - def show(self, ext_dict): - ext = etree.Element('extension', nsmap=self.nsmap) - self._populate_ext(ext, ext_dict['extension']) - return self._to_xml(ext) - - def index(self, exts_dict): - exts = etree.Element('extensions', nsmap=self.nsmap) - for ext_dict in exts_dict['extensions']: - ext = etree.SubElement(exts, 'extension') - self._populate_ext(ext, ext_dict) - return self._to_xml(exts) - - def _populate_ext(self, ext_elem, ext_dict): - """Populate an extension xml element from a dict.""" - - ext_elem.set('name', ext_dict['name']) - ext_elem.set('namespace', ext_dict['namespace']) - ext_elem.set('alias', ext_dict['alias']) - ext_elem.set('updated', ext_dict['updated']) - desc = etree.Element('description') - desc.text = ext_dict['description'] - ext_elem.append(desc) - for link in ext_dict.get('links', []): - elem = etree.SubElement(ext_elem, '{%s}link' % XMLNS_ATOM) - elem.set('rel', link['rel']) - elem.set('href', link['href']) - elem.set('type', link['type']) - return ext_elem - - def _to_xml(self, root): - """Convert the xml object to an xml string.""" - - return etree.tostring(root, encoding='UTF-8') |
