Source code for soco.utils

# Disable while we have Python 2.x compatability
# pylint: disable=useless-object-inheritance,import-outside-toplevel

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


import functools
import re
import warnings
from urllib.parse import quote as 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, bytes): 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, str): 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 http://stackoverflow.com/a/1176023 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) print(prettify(string))
[docs]class deprecated: """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. alternative_not_referable (bool): (optional) Indicate that ``alternative`` cannot be used as a sphinx reference 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, alternative_not_referable=False, ): self.since_version = since self.alternative = alternative self.will_be_removed_in = will_be_removed_in self.alternative_not_referable = alternative_not_referable 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: if self.alternative_not_referable: docs += "\n Use ``{}`` instead.".format(self.alternative) else: 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")
[docs]def first_cap(string): """Return upper cased first character""" return string[0].upper() + string[1:]