]> git.ipfire.org Git - thirdparty/tornado.git/commitdiff
Add type annotations to util.py
authorBen Darnell <ben@bendarnell.com>
Sun, 24 Apr 2016 16:43:14 +0000 (12:43 -0400)
committerBen Darnell <ben@bendarnell.com>
Sun, 24 Apr 2016 17:55:05 +0000 (13:55 -0400)
Mypy now reports "15% imprecise", down from 25%.

tornado/util.py

index d49a84f42b6e2ef5b1cfc3ba059ecb43a359a6c2..53584f98b4282900ab8363d7cb033f5f84bed3f3 100644 (file)
@@ -43,16 +43,41 @@ else:
     basestring_type = basestring  # noqa
 
 
-class ObjectDict(dict):
+try:
+    import typing  # noqa
+    from typing import cast
+
+    _ObjectDictBase = typing.Dict[str, typing.Any]
+except ImportError:
+    _ObjectDictBase = dict
+
+    def cast(typ, x):
+        return x
+else:
+    # More imports that are only needed in type comments.
+    import datetime  # noqa
+    import types  # noqa
+    from typing import Any, AnyStr, Union, Optional, Dict, Mapping  # noqa
+    from typing import Tuple, Match, Callable  # noqa
+
+    if PY3:
+        _BaseString = str
+    else:
+        _BaseString = Union[bytes, unicode_type]
+
+
+class ObjectDict(_ObjectDictBase):
     """Makes a dictionary behave like an object, with attribute-style access.
     """
     def __getattr__(self, name):
+        # type: (str) -> Any
         try:
             return self[name]
         except KeyError:
             raise AttributeError(name)
 
     def __setattr__(self, name, value):
+        # type: (str, Any) -> None
         self[name] = value
 
 
@@ -69,6 +94,7 @@ class GzipDecompressor(object):
         self.decompressobj = zlib.decompressobj(16 + zlib.MAX_WBITS)
 
     def decompress(self, value, max_length=None):
+        # type: (bytes, Optional[int]) -> bytes
         """Decompress a chunk, returning newly-available data.
 
         Some data may be buffered for later processing; `flush` must
@@ -83,11 +109,13 @@ class GzipDecompressor(object):
 
     @property
     def unconsumed_tail(self):
+        # type: () -> bytes
         """Returns the unconsumed portion left over
         """
         return self.decompressobj.unconsumed_tail
 
     def flush(self):
+        # type: () -> bytes
         """Return any remaining buffered data not yet returned by decompress.
 
         Also checks for errors such as truncated input.
@@ -97,6 +125,7 @@ class GzipDecompressor(object):
 
 
 def import_object(name):
+    # type: (_BaseString) -> Any
     """Imports an object by name.
 
     import_object('x') is equivalent to 'import x'.
@@ -114,8 +143,8 @@ def import_object(name):
         ...
     ImportError: No module named missing_module
     """
-    if isinstance(name, unicode_type) and str is not unicode_type:
-        # On python 2 a byte string is required.
+    if not isinstance(name, str):
+        # on python 2 a byte string is required.
         name = name.encode('utf-8')
     if name.count('.') == 0:
         return __import__(name, None, None)
@@ -130,10 +159,12 @@ def import_object(name):
 
 # Stubs to make mypy happy (and later for actual type-checking).
 def raise_exc_info(exc_info):
+    # type: (Tuple[type, BaseException, types.TracebackType]) -> None
     pass
 
 
 def exec_in(code, glob, loc=None):
+    # type: (Any, Dict[str, Any], Optional[Mapping[str, Any]]) -> Any
     pass
 
 
@@ -162,6 +193,7 @@ def exec_in(code, glob, loc=None):
 
 
 def errno_from_exception(e):
+    # type: (BaseException) -> Optional[int]
     """Provides the errno from an Exception object.
 
     There are cases that the errno attribute was not set so we pull
@@ -172,7 +204,7 @@ def errno_from_exception(e):
     """
 
     if hasattr(e, 'errno'):
-        return e.errno
+        return e.errno  # type: ignore
     elif e.args:
         return e.args[0]
     else:
@@ -184,6 +216,7 @@ _alphanum = frozenset(
 
 
 def _re_unescape_replacement(match):
+    # type: (Match[str]) -> str
     group = match.group(1)
     if group[0] in _alphanum:
         raise ValueError("cannot unescape '\\\\%s'" % group[0])
@@ -193,6 +226,7 @@ _re_unescape_pattern = re.compile(r'\\(.)', re.DOTALL)
 
 
 def re_unescape(s):
+    # type: (str) -> str
     """Unescape a string escaped by `re.escape`.
 
     May raise ``ValueError`` for regular expressions which could not
@@ -225,7 +259,7 @@ class Configurable(object):
     method `initialize` instead of ``__init__``.
     """
     __impl_class = None  # type: type
-    __impl_kwargs = None  # type: dict
+    __impl_kwargs = None  # type: Dict[str, Any]
 
     def __new__(cls, *args, **kwargs):
         base = cls.configurable_base()
@@ -246,6 +280,9 @@ class Configurable(object):
 
     @classmethod
     def configurable_base(cls):
+        # type: () -> Any
+        # TODO: This class needs https://github.com/python/typing/issues/107
+        # to be fully typeable.
         """Returns the base class of a configurable hierarchy.
 
         This will normally return the class in which it is defined.
@@ -255,10 +292,12 @@ class Configurable(object):
 
     @classmethod
     def configurable_default(cls):
+        # type: () -> type
         """Returns the implementation class to be used if none is configured."""
         raise NotImplementedError()
 
     def initialize(self):
+        # type: () -> None
         """Initialize a `Configurable` subclass instance.
 
         Configurable classes should use `initialize` instead of ``__init__``.
@@ -269,6 +308,7 @@ class Configurable(object):
 
     @classmethod
     def configure(cls, impl, **kwargs):
+        # type: (Any, **Any) -> None
         """Sets the class to use when the base class is instantiated.
 
         Keyword arguments will be saved and added to the arguments passed
@@ -276,7 +316,7 @@ class Configurable(object):
         some parameters.
         """
         base = cls.configurable_base()
-        if isinstance(impl, (unicode_type, bytes)):
+        if isinstance(impl, (str, unicode_type)):
             impl = import_object(impl)
         if impl is not None and not issubclass(impl, cls):
             raise ValueError("Invalid subclass of %s" % cls)
@@ -285,6 +325,7 @@ class Configurable(object):
 
     @classmethod
     def configured_class(cls):
+        # type: () -> type
         """Returns the currently configured class."""
         base = cls.configurable_base()
         if cls.__impl_class is None:
@@ -293,11 +334,13 @@ class Configurable(object):
 
     @classmethod
     def _save_configuration(cls):
+        # type: () -> Tuple[type, Dict[str, Any]]
         base = cls.configurable_base()
         return (base.__impl_class, base.__impl_kwargs)
 
     @classmethod
     def _restore_configuration(cls, saved):
+        # type: (Tuple[type, Dict[str, Any]]) -> None
         base = cls.configurable_base()
         base.__impl_class = saved[0]
         base.__impl_kwargs = saved[1]
@@ -311,6 +354,7 @@ class ArgReplacer(object):
     and similar wrappers.
     """
     def __init__(self, func, name):
+        # type: (Callable, str) -> None
         self.name = name
         try:
             self.arg_pos = self._getargnames(func).index(name)
@@ -319,6 +363,7 @@ class ArgReplacer(object):
             self.arg_pos = None
 
     def _getargnames(self, func):
+        # type: (Callable) -> List[str]
         try:
             return getargspec(func).args
         except TypeError:
@@ -329,11 +374,12 @@ class ArgReplacer(object):
                 # getargspec that we need here. Note that for static
                 # functions the @cython.binding(True) decorator must
                 # be used (for methods it works out of the box).
-                code = func.func_code
+                code = func.func_code  # type: ignore
                 return code.co_varnames[:code.co_argcount]
             raise
 
     def get_old_value(self, args, kwargs, default=None):
+        # type: (List[Any], Dict[str, Any], Any) -> Any
         """Returns the old value of the named argument without replacing it.
 
         Returns ``default`` if the argument is not present.
@@ -344,6 +390,7 @@ class ArgReplacer(object):
             return kwargs.get(self.name, default)
 
     def replace(self, new_value, args, kwargs):
+        # type: (Any, List[Any], Dict[str, Any]) -> Tuple[Any, List[Any], Dict[str, Any]]
         """Replace the named argument in ``args, kwargs`` with ``new_value``.
 
         Returns ``(old_value, args, kwargs)``.  The returned ``args`` and
@@ -366,11 +413,13 @@ class ArgReplacer(object):
 
 
 def timedelta_to_seconds(td):
+    # type: (datetime.timedelta) -> float
     """Equivalent to td.total_seconds() (introduced in python 2.7)."""
     return (td.microseconds + (td.seconds + td.days * 24 * 3600) * 10 ** 6) / float(10 ** 6)
 
 
 def _websocket_mask_python(mask, data):
+    # type: (bytes, bytes) -> bytes
     """Websocket masking function.
 
     `mask` is a `bytes` object of length 4; `data` is a `bytes` object of any length.
@@ -379,17 +428,17 @@ def _websocket_mask_python(mask, data):
 
     This pure-python implementation may be replaced by an optimized version when available.
     """
-    mask = array.array("B", mask)
-    unmasked = array.array("B", data)
+    mask_arr = array.array("B", mask)
+    unmasked_arr = array.array("B", data)
     for i in xrange(len(data)):
-        unmasked[i] = unmasked[i] ^ mask[i % 4]
-    if hasattr(unmasked, 'tobytes'):
+        unmasked_arr[i] = unmasked_arr[i] ^ mask_arr[i % 4]
+    if PY3:
         # tostring was deprecated in py32.  It hasn't been removed,
         # but since we turn on deprecation warnings in our tests
         # we need to use the right one.
-        return unmasked.tobytes()
+        return unmasked_arr.tobytes()
     else:
-        return unmasked.tostring()
+        return unmasked_arr.tostring()
 
 if (os.environ.get('TORNADO_NO_EXTENSION') or
         os.environ.get('TORNADO_EXTENSION') == '0'):