From: Ben Darnell Date: Sun, 14 Apr 2013 20:03:08 +0000 (-0400) Subject: Merge remote-tracking branch 'kachayev/import-object' X-Git-Tag: v3.1.0~111 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=55d33945abc89a3ad7783e6c2ccc6ef44909008c;p=thirdparty%2Ftornado.git Merge remote-tracking branch 'kachayev/import-object' --- 55d33945abc89a3ad7783e6c2ccc6ef44909008c diff --cc tornado/util.py index 939ee6ab1,584f91791..3e6574315 --- a/tornado/util.py +++ b/tornado/util.py @@@ -1,25 -1,9 +1,27 @@@ -"""Miscellaneous utility functions.""" +"""Miscellaneous utility functions and classes. + +This module is used internally by Tornado. It is not necessarily expected +that the functions and classes defined here will be useful to other +applications, but they are documented here in case they are. + +The one public-facing part of this module is the `Configurable` class +and its `~Configurable.configure` method, which becomes a part of the +interface of its subclasses, including `.AsyncHTTPClient`, `.IOLoop`, +and `.Resolver`. +""" + +from __future__ import absolute_import, division, print_function, with_statement + +import inspect +import sys +import zlib + + import sys + class ObjectDict(dict): - """Makes a dictionary behave like an object.""" + """Makes a dictionary behave like an object, with attribute-style access. + """ def __getattr__(self, name): try: return self[name] @@@ -70,186 -25,42 +73,203 @@@ def import_object(name) True >>> import_object('tornado.escape.utf8') is tornado.escape.utf8 True + >>> import_object('tornado') is tornado + True + >>> import_object('missing_module') + Traceback (most recent call last): + ... + ImportError: No module named missing_module + >>> import_object('tornado.missing_module') + Traceback (most recent call last): + ... + ImportError: No module named missing_module """ + if name.count('.') == 0: + return __import__(name, None, None) + parts = name.split('.') obj = __import__('.'.join(parts[:-1]), None, None, [parts[-1]], 0) - return getattr(obj, parts[-1]) + try: + return getattr(obj, parts[-1]) + except AttributeError: + exc_info = sys.exc_info() + raise ImportError, "No module named %s" % parts[-1], exc_info[2] -# Fake byte literal support: In python 2.6+, you can say b"foo" to get -# a byte literal (str in 2.x, bytes in 3.x). There's no way to do this -# in a way that supports 2.5, though, so we need a function wrapper -# to convert our string literals. b() should only be applied to literal -# latin1 strings. Once we drop support for 2.5, we can remove this function -# and just use byte literals. -if str is unicode: - def b(s): - return s.encode('latin1') +# Fake unicode literal support: Python 3.2 doesn't have the u'' marker for +# literal strings, and alternative solutions like "from __future__ import +# unicode_literals" have other problems (see PEP 414). u() can be applied +# to ascii strings that include \u escapes (but they must not contain +# literal non-ascii characters). +if type('') is not type(b''): + def u(s): + return s bytes_type = bytes + unicode_type = str + basestring_type = str else: - def b(s): - return s + def u(s): + return s.decode('unicode_escape') bytes_type = str + unicode_type = unicode + basestring_type = basestring + + +if sys.version_info > (3,): + exec(""" +def raise_exc_info(exc_info): + raise exc_info[1].with_traceback(exc_info[2]) + +def exec_in(code, glob, loc=None): + if isinstance(code, str): + code = compile(code, '', 'exec', dont_inherit=True) + exec(code, glob, loc) +""") +else: + exec(""" +def raise_exc_info(exc_info): + raise exc_info[0], exc_info[1], exc_info[2] + +def exec_in(code, glob, loc=None): + if isinstance(code, basestring): + # exec(string) inherits the caller's future imports; compile + # the string first to prevent that. + code = compile(code, '', 'exec', dont_inherit=True) + exec code in glob, loc +""") + + +class Configurable(object): + """Base class for configurable interfaces. + + A configurable interface is an (abstract) class whose constructor + acts as a factory function for one of its implementation subclasses. + The implementation subclass as well as optional keyword arguments to + its initializer can be set globally at runtime with `configure`. + + By using the constructor as the factory method, the interface + looks like a normal class, `isinstance` works as usual, etc. This + pattern is most useful when the choice of implementation is likely + to be a global decision (e.g. when `~select.epoll` is available, + always use it instead of `~select.select`), or when a + previously-monolithic class has been split into specialized + subclasses. + + Configurable subclasses must define the class methods + `configurable_base` and `configurable_default`, and use the instance + method `initialize` instead of ``__init__``. + """ + __impl_class = None + __impl_kwargs = None + + def __new__(cls, **kwargs): + base = cls.configurable_base() + args = {} + if cls is base: + impl = cls.configured_class() + if base.__impl_kwargs: + args.update(base.__impl_kwargs) + else: + impl = cls + args.update(kwargs) + instance = super(Configurable, cls).__new__(impl) + # initialize vs __init__ chosen for compatiblity with AsyncHTTPClient + # singleton magic. If we get rid of that we can switch to __init__ + # here too. + instance.initialize(**args) + return instance + + @classmethod + def configurable_base(cls): + """Returns the base class of a configurable hierarchy. + + This will normally return the class in which it is defined. + (which is *not* necessarily the same as the cls classmethod parameter). + """ + raise NotImplementedError() + + @classmethod + def configurable_default(cls): + """Returns the implementation class to be used if none is configured.""" + raise NotImplementedError() + + def initialize(self): + """Initialize a `Configurable` subclass instance. + + Configurable classes should use `initialize` instead of ``__init__``. + """ + + @classmethod + def configure(cls, impl, **kwargs): + """Sets the class to use when the base class is instantiated. + + Keyword arguments will be saved and added to the arguments passed + to the constructor. This can be used to set global defaults for + some parameters. + """ + base = cls.configurable_base() + if isinstance(impl, (unicode_type, bytes_type)): + impl = import_object(impl) + if impl is not None and not issubclass(impl, cls): + raise ValueError("Invalid subclass of %s" % cls) + base.__impl_class = impl + base.__impl_kwargs = kwargs + + @classmethod + def configured_class(cls): + """Returns the currently configured class.""" + base = cls.configurable_base() + if cls.__impl_class is None: + base.__impl_class = cls.configurable_default() + return base.__impl_class + + @classmethod + def _save_configuration(cls): + base = cls.configurable_base() + return (base.__impl_class, base.__impl_kwargs) + + @classmethod + def _restore_configuration(cls, saved): + base = cls.configurable_base() + base.__impl_class = saved[0] + base.__impl_kwargs = saved[1] + + +class ArgReplacer(object): + """Replaces one value in an ``args, kwargs`` pair. + + Inspects the function signature to find an argument by name + whether it is passed by position or keyword. For use in decorators + and similar wrappers. + """ + def __init__(self, func, name): + self.name = name + try: + self.arg_pos = inspect.getargspec(func).args.index(self.name) + except ValueError: + # Not a positional parameter + self.arg_pos = None + + def replace(self, new_value, args, kwargs): + """Replace the named argument in ``args, kwargs`` with ``new_value``. + + Returns ``(old_value, args, kwargs)``. The returned ``args`` and + ``kwargs`` objects may not be the same as the input objects, or + the input objects may be mutated. + + If the named argument was not found, ``new_value`` will be added + to ``kwargs`` and None will be returned as ``old_value``. + """ + if self.arg_pos is not None and len(args) > self.arg_pos: + # The arg to replace is passed positionally + old_value = args[self.arg_pos] + args = list(args) # *args is normally a tuple + args[self.arg_pos] = new_value + else: + # The arg to replace is either omitted or passed by keyword. + old_value = kwargs.get(self.name) + kwargs[self.name] = new_value + return old_value, args, kwargs + def doctests(): import doctest