Source code for soco.utils

# -*- coding: utf-8 -*-

"""This class contains utility functions used internally by SoCo."""

from __future__ import (
    absolute_import, print_function, unicode_literals

import functools
import re
import warnings

from .compat import (
    StringType, UnicodeType, quote_url
from .xml import XML

[docs]def really_unicode(in_string): """Make a string unicode. Really. Ensure ``in_string`` is returned as unicode through a series of progressively relaxed decodings. Args: in_string (str): The string to convert. Returns: str: Unicode. Raises: ValueError """ if isinstance(in_string, StringType): for args in (('utf-8',), ('latin-1',), ('ascii', 'replace')): try: # pylint: disable=star-args in_string = in_string.decode(*args) break except UnicodeDecodeError: continue if not isinstance(in_string, UnicodeType): raise ValueError('%s is not a string at all.' % in_string)
return in_string
[docs]def really_utf8(in_string): """Encode a string with utf-8. Really. First decode ``in_string`` via `really_unicode` to ensure it can successfully be encoded as utf-8. This is required since just calling encode on a string will often cause Python 2 to perform a coerced strict auto-decode as ascii first and will result in a `UnicodeDecodeError` being raised. After `really_unicode` returns a safe unicode string, encode as utf-8 and return the utf-8 encoded string. Args: in_string (str): The string to convert. Returns: str: utf-encoded data. """
return really_unicode(in_string).encode('utf-8') FIRST_CAP_RE = re.compile('(.)([A-Z][a-z]+)') ALL_CAP_RE = re.compile('([a-z0-9])([A-Z])')
[docs]def camel_to_underscore(string): """Convert camelcase to lowercase and underscore. Recipe from Args: string (str): The string to convert. Returns: str: The converted string. """ string = FIRST_CAP_RE.sub(r'\1_\2', string)
return ALL_CAP_RE.sub(r'\1_\2', string).lower()
[docs]def prettify(unicode_text): """Return a pretty-printed version of a unicode XML string. Useful for debugging. Args: unicode_text (str): A text representation of XML (unicode, *not* utf-8). Returns: str: A pretty-printed version of the input. """ import xml.dom.minidom reparsed = xml.dom.minidom.parseString(unicode_text.encode('utf-8'))
return reparsed.toprettyxml(indent=" ", newl="\n")
[docs]def show_xml(xml): """Pretty print an :class:`~xml.etree.ElementTree.ElementTree` XML object. Args: xml (:class:`~xml.etree.ElementTree.ElementTree`): The :class:`~xml.etree.ElementTree.ElementTree` to pretty print Note: This is used a convenience function used during development. It is not used anywhere in the main code base. """ string = XML.tostring(xml)
[docs]class deprecated(object): """A decorator for marking deprecated objects. Used internally by SoCo to cause a warning to be issued when the object is used, and marks the object as deprecated in the Sphinx documentation. Args: since (str): The version in which the object is deprecated. alternative (str, optional): The name of an alternative object to use will_be_removed_in (str, optional): The version in which the object is likely to be removed. Example: .. code-block:: python @deprecated(since="0.7", alternative="new_function") def old_function(args): pass """ # pylint really doesn't like decorators! # pylint: disable=invalid-name, too-few-public-methods # pylint: disable=missing-docstring def __init__(self, since, alternative=None, will_be_removed_in=None): self.since_version = since self.alternative = alternative self.will_be_removed_in = will_be_removed_in def __call__(self, deprecated_fn): @functools.wraps(deprecated_fn) def decorated(*args, **kwargs): message = "Call to deprecated function {}.".format( deprecated_fn.__name__) if self.will_be_removed_in is not None: message += " Will be removed in version {}.".format( self.will_be_removed_in) if self.alternative is not None: message += " Use {} instead.".format(self.alternative) warnings.warn(message, stacklevel=2) return deprecated_fn(*args, **kwargs) docs = "\n\n .. deprecated:: {}\n".format(self.since_version) if self.will_be_removed_in is not None: docs += "\n Will be removed in version {}.".format( self.will_be_removed_in) if self.alternative is not None: docs += "\n Use `{}` instead.".format(self.alternative) if decorated.__doc__ is None: decorated.__doc__ = '' decorated.__doc__ += docs
return decorated
[docs]def url_escape_path(path): """Escape a string value for a URL request path. Args: str: The path to escape Returns: str: The escaped path >>> url_escape_path("Foo, bar & baz / the hackers") u'Foo%2C%20bar%20%26%20baz%20%2F%20the%20hackers' """ # Using 'safe' arg does not seem to work for python 2.6
return quote_url(path.encode('utf-8')).replace('/', '%2F')