-"""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]
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, '<string>', '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, '<string>', '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