From b47a94852a0f1608af1c5448eaeb59a27d7d03b0 Mon Sep 17 00:00:00 2001 From: Ruud Date: Mon, 20 Jan 2014 16:55:58 +0100 Subject: [PATCH] Update library: tornado --- libs/tornado/__init__.py | 4 +- libs/tornado/auth.py | 27 +++----- libs/tornado/escape.py | 11 ++- libs/tornado/gen.py | 1 - libs/tornado/ioloop.py | 26 ++++--- libs/tornado/log.py | 121 ++++++++++++++++++--------------- libs/tornado/platform/asyncio.py | 8 ++- libs/tornado/platform/caresresolver.py | 1 + libs/tornado/platform/twisted.py | 1 + libs/tornado/web.py | 9 +++ 10 files changed, 126 insertions(+), 83 deletions(-) diff --git a/libs/tornado/__init__.py b/libs/tornado/__init__.py index bec636f..c41ec97 100755 --- a/libs/tornado/__init__.py +++ b/libs/tornado/__init__.py @@ -25,5 +25,5 @@ from __future__ import absolute_import, division, print_function, with_statement # is zero for an official release, positive for a development branch, # or negative for a release candidate or beta (after the base version # number has been incremented) -version = "3.2b1" -version_info = (3, 2, 0, -98) +version = "3.2" +version_info = (3, 2, 0, 0) diff --git a/libs/tornado/auth.py b/libs/tornado/auth.py index f2080f1..9baac9b 100755 --- a/libs/tornado/auth.py +++ b/libs/tornado/auth.py @@ -36,7 +36,6 @@ Example usage for Google OpenID:: class GoogleLoginHandler(tornado.web.RequestHandler, tornado.auth.GoogleMixin): - @tornado.web.asynchronous @tornado.gen.coroutine def get(self): if self.get_argument("openid.mode", None): @@ -607,7 +606,6 @@ class TwitterMixin(OAuthMixin): class TwitterLoginHandler(tornado.web.RequestHandler, tornado.auth.TwitterMixin): - @tornado.web.asynchronous @tornado.gen.coroutine def get(self): if self.get_argument("oauth_token", None): @@ -669,7 +667,6 @@ class TwitterMixin(OAuthMixin): class MainHandler(tornado.web.RequestHandler, tornado.auth.TwitterMixin): @tornado.web.authenticated - @tornado.web.asynchronous @tornado.gen.coroutine def get(self): new_entry = yield self.twitter_request( @@ -748,7 +745,6 @@ class FriendFeedMixin(OAuthMixin): class FriendFeedLoginHandler(tornado.web.RequestHandler, tornado.auth.FriendFeedMixin): - @tornado.web.asynchronous @tornado.gen.coroutine def get(self): if self.get_argument("oauth_token", None): @@ -793,7 +789,6 @@ class FriendFeedMixin(OAuthMixin): class MainHandler(tornado.web.RequestHandler, tornado.auth.FriendFeedMixin): @tornado.web.authenticated - @tornado.web.asynchronous @tornado.gen.coroutine def get(self): new_entry = yield self.friendfeed_request( @@ -877,7 +872,6 @@ class GoogleMixin(OpenIdMixin, OAuthMixin): class GoogleLoginHandler(tornado.web.RequestHandler, tornado.auth.GoogleMixin): - @tornado.web.asynchronous @tornado.gen.coroutine def get(self): if self.get_argument("openid.mode", None): @@ -949,7 +943,10 @@ class GoogleMixin(OpenIdMixin, OAuthMixin): class GoogleOAuth2Mixin(OAuth2Mixin): - """Google authentication using OAuth2.""" + """Google authentication using OAuth2. + + .. versionadded:: 3.2 + """ _OAUTH_AUTHORIZE_URL = "https://accounts.google.com/o/oauth2/auth" _OAUTH_ACCESS_TOKEN_URL = "https://accounts.google.com/o/oauth2/token" _OAUTH_NO_CALLBACKS = False @@ -961,22 +958,22 @@ class GoogleOAuth2Mixin(OAuth2Mixin): Example usage:: - class GoogleOAuth2LoginHandler(LoginHandler, tornado.auth.GoogleOAuth2Mixin): - @tornado.web.asynchronous + class GoogleOAuth2LoginHandler(LoginHandler, + tornado.auth.GoogleOAuth2Mixin): @tornado.gen.coroutine def get(self): - if self.get_argument("code", False): + if self.get_argument('code', False): user = yield self.get_authenticated_user( redirect_uri='http://your.site.com/auth/google', - code=self.get_argument("code")) + code=self.get_argument('code')) # Save the user with e.g. set_secure_cookie else: yield self.authorize_redirect( redirect_uri='http://your.site.com/auth/google', - client_id=self.settings["google_consumer_key"], - scope=['openid', 'email'], + client_id=self.settings['google_oauth']['key'], + scope=['profile', 'email'], response_type='code', - extra_params={"approval_prompt": "auto"}) + extra_params={'approval_prompt': 'auto'}) """ http = self.get_auth_http_client() body = urllib_parse.urlencode({ @@ -1234,7 +1231,6 @@ class FacebookGraphMixin(OAuth2Mixin): Example usage:: class FacebookGraphLoginHandler(LoginHandler, tornado.auth.FacebookGraphMixin): - @tornado.web.asynchronous @tornado.gen.coroutine def get(self): if self.get_argument("code", False): @@ -1321,7 +1317,6 @@ class FacebookGraphMixin(OAuth2Mixin): class MainHandler(tornado.web.RequestHandler, tornado.auth.FacebookGraphMixin): @tornado.web.authenticated - @tornado.web.asynchronous @tornado.gen.coroutine def get(self): new_entry = yield self.facebook_request( diff --git a/libs/tornado/escape.py b/libs/tornado/escape.py index 302e556..95c0f24 100755 --- a/libs/tornado/escape.py +++ b/libs/tornado/escape.py @@ -55,7 +55,16 @@ _XHTML_ESCAPE_DICT = {'&': '&', '<': '<', '>': '>', '"': '"', def xhtml_escape(value): - """Escapes a string so it is valid within HTML or XML.""" + """Escapes a string so it is valid within HTML or XML. + + Escapes the characters ``<``, ``>``, ``"``, ``'``, and ``&``. + When used in attribute values the escaped strings must be enclosed + in quotes. + + .. versionchanged:: 3.2 + + Added the single quote to the list of escaped characters. + """ return _XHTML_ESCAPE_RE.sub(lambda match: _XHTML_ESCAPE_DICT[match.group(0)], to_basestring(value)) diff --git a/libs/tornado/gen.py b/libs/tornado/gen.py index 21f692a..aa931b4 100755 --- a/libs/tornado/gen.py +++ b/libs/tornado/gen.py @@ -59,7 +59,6 @@ For more complicated interfaces, `Task` can be split into two parts: `Callback` and `Wait`:: class GenAsyncHandler2(RequestHandler): - @asynchronous @gen.coroutine def get(self): http_client = AsyncHTTPClient() diff --git a/libs/tornado/ioloop.py b/libs/tornado/ioloop.py index 0477ade..e7b84dd 100755 --- a/libs/tornado/ioloop.py +++ b/libs/tornado/ioloop.py @@ -301,6 +301,22 @@ class IOLoop(Configurable): """ raise NotImplementedError() + def _setup_logging(self): + """The IOLoop catches and logs exceptions, so it's + important that log output be visible. However, python's + default behavior for non-root loggers (prior to python + 3.2) is to print an unhelpful "no handlers could be + found" message rather than the actual log entry, so we + must explicitly configure logging if we've made it this + far without anything. + + This method should be called from start() in subclasses. + """ + if not any([logging.getLogger().handlers, + logging.getLogger('tornado').handlers, + logging.getLogger('tornado.application').handlers]): + logging.basicConfig() + def stop(self): """Stop the I/O loop. @@ -550,15 +566,7 @@ class PollIOLoop(IOLoop): action if action is not None else signal.SIG_DFL) def start(self): - if not logging.getLogger().handlers: - # The IOLoop catches and logs exceptions, so it's - # important that log output be visible. However, python's - # default behavior for non-root loggers (prior to python - # 3.2) is to print an unhelpful "no handlers could be - # found" message rather than the actual log entry, so we - # must explicitly configure logging if we've made it this - # far without anything. - logging.basicConfig() + self._setup_logging() if self._stopped: self._stopped = False return diff --git a/libs/tornado/log.py b/libs/tornado/log.py index bc6898c..36c3dd4 100755 --- a/libs/tornado/log.py +++ b/libs/tornado/log.py @@ -60,6 +60,13 @@ def _stderr_supports_color(): return color +def _safe_unicode(s): + try: + return _unicode(s) + except UnicodeDecodeError: + return repr(s) + + class LogFormatter(logging.Formatter): """Log formatter used in Tornado. @@ -73,23 +80,37 @@ class LogFormatter(logging.Formatter): `tornado.options.parse_command_line` (unless ``--logging=none`` is used). """ - DEFAULT_PREFIX_FORMAT = '[%(levelname)1.1s %(asctime)s %(module)s:%(lineno)d]' + DEFAULT_FORMAT = '%(color)s[%(levelname)1.1s %(asctime)s %(module)s:%(lineno)d]%(end_color)s %(message)s' DEFAULT_DATE_FORMAT = '%y%m%d %H:%M:%S' - - def __init__(self, color=True, prefix_fmt=None, datefmt=None): + DEFAULT_COLORS = { + logging.DEBUG: 4, # Blue + logging.INFO: 2, # Green + logging.WARNING: 3, # Yellow + logging.ERROR: 1, # Red + } + + def __init__(self, color=True, fmt=DEFAULT_FORMAT, + datefmt=DEFAULT_DATE_FORMAT, colors=DEFAULT_COLORS): r""" - :arg bool color: Enables color support - :arg string prefix_fmt: Log message prefix format. - Prefix is a part of the log message, directly preceding the actual - message text. + :arg bool color: Enables color support. + :arg string fmt: Log message format. + It will be applied to the attributes dict of log records. The + text between ``%(color)s`` and ``%(end_color)s`` will be colored + depending on the level if color support is on. + :arg dict colors: color mappings from logging level to terminal color + code :arg string datefmt: Datetime format. Used for formatting ``(asctime)`` placeholder in ``prefix_fmt``. + + .. versionchanged:: 3.2 + + Added ``fmt`` and ``datefmt`` arguments. """ - self.__prefix_fmt = prefix_fmt if prefix_fmt is not None else self.DEFAULT_PREFIX_FORMAT - datefmt = datefmt if datefmt is not None else self.DEFAULT_DATE_FORMAT logging.Formatter.__init__(self, datefmt=datefmt) - self._color = color and _stderr_supports_color() - if self._color: + self._fmt = fmt + + self._colors = {} + if color and _stderr_supports_color(): # The curses module has some str/bytes confusion in # python3. Until version 3.2.3, most methods return # bytes, but only accept strings. In addition, we want to @@ -101,62 +122,56 @@ class LogFormatter(logging.Formatter): curses.tigetstr("setf") or "") if (3, 0) < sys.version_info < (3, 2, 3): fg_color = unicode_type(fg_color, "ascii") - self._colors = { - logging.DEBUG: unicode_type(curses.tparm(fg_color, 4), # Blue - "ascii"), - logging.INFO: unicode_type(curses.tparm(fg_color, 2), # Green - "ascii"), - logging.WARNING: unicode_type(curses.tparm(fg_color, 3), # Yellow - "ascii"), - logging.ERROR: unicode_type(curses.tparm(fg_color, 1), # Red - "ascii"), - } + + for levelno, code in colors.items(): + self._colors[levelno] = unicode_type(curses.tparm(fg_color, code), "ascii") self._normal = unicode_type(curses.tigetstr("sgr0"), "ascii") + else: + self._normal = '' def format(self, record): try: - record.message = record.getMessage() + message = record.getMessage() + assert isinstance(message, basestring_type) # guaranteed by logging + # Encoding notes: The logging module prefers to work with character + # strings, but only enforces that log messages are instances of + # basestring. In python 2, non-ascii bytestrings will make + # their way through the logging framework until they blow up with + # an unhelpful decoding error (with this formatter it happens + # when we attach the prefix, but there are other opportunities for + # exceptions further along in the framework). + # + # If a byte string makes it this far, convert it to unicode to + # ensure it will make it out to the logs. Use repr() as a fallback + # to ensure that all byte strings can be converted successfully, + # but don't do it by default so we don't add extra quotes to ascii + # bytestrings. This is a bit of a hacky place to do this, but + # it's worth it since the encoding errors that would otherwise + # result are so useless (and tornado is fond of using utf8-encoded + # byte strings whereever possible). + record.message = _safe_unicode(message) except Exception as e: record.message = "Bad message (%r): %r" % (e, record.__dict__) - assert isinstance(record.message, basestring_type) # guaranteed by logging + record.asctime = self.formatTime(record, self.datefmt) - prefix = self.__prefix_fmt % record.__dict__ - if self._color: - prefix = (self._colors.get(record.levelno, self._normal) + - prefix + self._normal) - - # Encoding notes: The logging module prefers to work with character - # strings, but only enforces that log messages are instances of - # basestring. In python 2, non-ascii bytestrings will make - # their way through the logging framework until they blow up with - # an unhelpful decoding error (with this formatter it happens - # when we attach the prefix, but there are other opportunities for - # exceptions further along in the framework). - # - # If a byte string makes it this far, convert it to unicode to - # ensure it will make it out to the logs. Use repr() as a fallback - # to ensure that all byte strings can be converted successfully, - # but don't do it by default so we don't add extra quotes to ascii - # bytestrings. This is a bit of a hacky place to do this, but - # it's worth it since the encoding errors that would otherwise - # result are so useless (and tornado is fond of using utf8-encoded - # byte strings whereever possible). - def safe_unicode(s): - try: - return _unicode(s) - except UnicodeDecodeError: - return repr(s) - - formatted = prefix + " " + safe_unicode(record.message) + + if record.levelno in self._colors: + record.color = self._colors[record.levelno] + record.end_color = self._normal + else: + record.color = record.end_color = '' + + formatted = self._fmt % record.__dict__ + if record.exc_info: if not record.exc_text: record.exc_text = self.formatException(record.exc_info) if record.exc_text: - # exc_text contains multiple lines. We need to safe_unicode + # exc_text contains multiple lines. We need to _safe_unicode # each line separately so that non-utf8 bytes don't cause # all the newlines to turn into '\n'. lines = [formatted.rstrip()] - lines.extend(safe_unicode(ln) for ln in record.exc_text.split('\n')) + lines.extend(_safe_unicode(ln) for ln in record.exc_text.split('\n')) formatted = '\n'.join(lines) return formatted.replace("\n", "\n ") diff --git a/libs/tornado/platform/asyncio.py b/libs/tornado/platform/asyncio.py index 09b2bf3..162b367 100644 --- a/libs/tornado/platform/asyncio.py +++ b/libs/tornado/platform/asyncio.py @@ -8,6 +8,8 @@ python3.4 -m tornado.test.runtests --ioloop=tornado.platform.asyncio.AsyncIOMain (the tests log a few warnings with AsyncIOMainLoop because they leave some unfinished callbacks on the event loop that fail when it resumes) """ + +from __future__ import absolute_import, division, print_function, with_statement import asyncio import datetime import functools @@ -34,7 +36,10 @@ class BaseAsyncIOLoop(IOLoop): for fd in list(self.handlers): self.remove_handler(fd) if all_fds: - os.close(fd) + try: + os.close(fd) + except OSError: + pass if self.close_loop: self.asyncio_loop.close() @@ -86,6 +91,7 @@ class BaseAsyncIOLoop(IOLoop): self.handlers[fd](fd, events) def start(self): + self._setup_logging() self.asyncio_loop.run_forever() def stop(self): diff --git a/libs/tornado/platform/caresresolver.py b/libs/tornado/platform/caresresolver.py index 7c16705..c4648c2 100755 --- a/libs/tornado/platform/caresresolver.py +++ b/libs/tornado/platform/caresresolver.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import, division, print_function, with_statement import pycares import socket diff --git a/libs/tornado/platform/twisted.py b/libs/tornado/platform/twisted.py index 86ef71b..0c8a310 100755 --- a/libs/tornado/platform/twisted.py +++ b/libs/tornado/platform/twisted.py @@ -456,6 +456,7 @@ class TwistedIOLoop(tornado.ioloop.IOLoop): del self.fds[fd] def start(self): + self._setup_logging() self.reactor.run() def stop(self): diff --git a/libs/tornado/web.py b/libs/tornado/web.py index 65a76cd..b22b11f 100755 --- a/libs/tornado/web.py +++ b/libs/tornado/web.py @@ -516,6 +516,10 @@ class RequestHandler(object): See `clear_cookie` for more information on the path and domain parameters. + + .. versionchanged:: 3.2 + + Added the ``path`` and ``domain`` parameters. """ for name in self.request.cookies: self.clear_cookie(name, path=path, domain=domain) @@ -1850,6 +1854,11 @@ class StaticFileHandler(RequestHandler): class method. Instance methods may use the attributes ``self.path`` ``self.absolute_path``, and ``self.modified``. + Subclasses should only override methods discussed in this section; + overriding other methods is error-prone. Overriding + ``StaticFileHandler.get`` is particularly problematic due to the + tight coupling with ``compute_etag`` and other methods. + To change the way static urls are generated (e.g. to match the behavior of another server or CDN), override `make_static_url`, `parse_url_path`, `get_cache_time`, and/or `get_version`.