Skip to content

Commit

Permalink
Merge branch 'master' of git://github.com/facebook/tornado
Browse files Browse the repository at this point in the history
  • Loading branch information
lepture committed Mar 12, 2012
2 parents a75c463 + bcbef8c commit ac40ded
Show file tree
Hide file tree
Showing 8 changed files with 109 additions and 24 deletions.
15 changes: 15 additions & 0 deletions .travis.yml
@@ -0,0 +1,15 @@
# http://travis-ci.org/#!/facebook/tornado
language: python
python:
- 2.6
- 2.7
- 3.2
# TODO: install pycurl, twisted, etc (depends on python version)
install:
- python setup.py install
script:
# Must cd somewhere else so python3 doesn't get confused and run
# the python2 code from the current directory instead of the installed
# 2to3 version.
- cd maint
- python -m tornado.test.runtests
59 changes: 44 additions & 15 deletions tornado/autoreload.py
Expand Up @@ -28,6 +28,39 @@

from __future__ import absolute_import, division, with_statement

import os
import sys

# sys.path handling
# -----------------
#
# If a module is run with "python -m", the current directory (i.e. "")
# is automatically prepended to sys.path, but not if it is run as
# "path/to/file.py". The processing for "-m" rewrites the former to
# the latter, so subsequent executions won't have the same path as the
# original.
#
# Conversely, when run as path/to/file.py, the directory containing
# file.py gets added to the path, which can cause confusion as imports
# may become relative in spite of the future import.
#
# We address the former problem by setting the $PYTHONPATH environment
# variable before re-execution so the new process will see the correct
# path. We attempt to address the latter problem when tornado.autoreload
# is run as __main__, although we can't fix the general case because
# we cannot reliably reconstruct the original command line
# (http://bugs.python.org/issue14208).

if __name__ == "__main__":
# This sys.path manipulation must come before our imports (as much
# as possible - if we introduced a tornado.sys or tornado.os
# module we'd be in trouble), or else our imports would become
# relative again despite the future import.
#
# There is a separate __main__ block at the end of the file to call main().
if sys.path[0] == os.path.dirname(__file__):
del sys.path[0]

import functools
import logging
import os
Expand Down Expand Up @@ -153,6 +186,15 @@ def _reload():
# ioloop.set_blocking_log_threshold so it doesn't fire
# after the exec.
signal.setitimer(signal.ITIMER_REAL, 0, 0)
# sys.path fixes: see comments at top of file. If sys.path[0] is an empty
# string, we were (probably) invoked with -m and the effective path
# is about to change on re-exec. Add the current directory to $PYTHONPATH
# to ensure that the new process sees the same path we did.
path_prefix = '.' + os.pathsep
if (sys.path[0] == '' and
not os.environ.get("PYTHONPATH", "").startswith(path_prefix)):
os.environ["PYTHONPATH"] = (path_prefix +
os.environ.get("PYTHONPATH", ""))
if sys.platform == 'win32':
# os.execv is broken on Windows and can't properly parse command line
# arguments and executable name if they contain whitespaces. subprocess
Expand Down Expand Up @@ -244,19 +286,6 @@ def main():


if __name__ == "__main__":
# If this module is run with "python -m tornado.autoreload", the current
# directory is automatically prepended to sys.path, but not if it is
# run as "path/to/tornado/autoreload.py". The processing for "-m" rewrites
# the former to the latter, so subsequent executions won't have the same
# path as the original. Modify os.environ here to ensure that the
# re-executed process will have the same path.
# Conversely, when run as path/to/tornado/autoreload.py, the directory
# containing autoreload.py gets added to the path, but we don't want
# tornado modules importable at top level, so remove it.
path_prefix = '.' + os.pathsep
if (sys.path[0] == '' and
not os.environ.get("PYTHONPATH", "").startswith(path_prefix)):
os.environ["PYTHONPATH"] = path_prefix + os.environ.get("PYTHONPATH", "")
elif sys.path[0] == os.path.dirname(__file__):
del sys.path[0]
# See also the other __main__ block at the top of the file, which modifies
# sys.path before our imports
main()
11 changes: 7 additions & 4 deletions tornado/httpclient.py
Expand Up @@ -330,12 +330,15 @@ class HTTPResponse(object):
plus 'queue', which is the delay (if any) introduced by waiting for
a slot under AsyncHTTPClient's max_clients setting.
"""
def __init__(self, request, code, headers={}, buffer=None,
def __init__(self, request, code, headers=None, buffer=None,
effective_url=None, error=None, request_time=None,
time_info={}):
time_info=None):
self.request = request
self.code = code
self.headers = headers
if headers is not None:
self.headers = headers
else:
self.headers = httputil.HTTPHeaders()
self.buffer = buffer
self._body = None
if effective_url is None:
Expand All @@ -350,7 +353,7 @@ def __init__(self, request, code, headers={}, buffer=None,
else:
self.error = error
self.request_time = request_time
self.time_info = time_info
self.time_info = time_info or {}

def _get_body(self):
if self.buffer is None:
Expand Down
9 changes: 8 additions & 1 deletion tornado/httputil.py
Expand Up @@ -58,7 +58,14 @@ def __init__(self, *args, **kwargs):
dict.__init__(self)
self._as_list = {}
self._last_key = None
self.update(*args, **kwargs)
if (len(args) == 1 and len(kwargs) == 0 and
isinstance(args[0], HTTPHeaders)):
# Copy constructor
for k,v in args[0].get_all():
self.add(k,v)
else:
# Dict-style initialization
self.update(*args, **kwargs)

# new public methods

Expand Down
6 changes: 4 additions & 2 deletions tornado/simple_httpclient.py
Expand Up @@ -94,8 +94,10 @@ def initialize(self, io_loop=None, max_clients=10,
def fetch(self, request, callback, **kwargs):
if not isinstance(request, HTTPRequest):
request = HTTPRequest(url=request, **kwargs)
if not isinstance(request.headers, HTTPHeaders):
request.headers = HTTPHeaders(request.headers)
# We're going to modify this (to add Host, Accept-Encoding, etc),
# so make sure we don't modify the caller's object. This is also
# where normal dicts get converted to HTTPHeaders objects.
request.headers = HTTPHeaders(request.headers)
callback = stack_context.wrap(callback)
self.queue.append((request, callback))
self._process_queue()
Expand Down
8 changes: 8 additions & 0 deletions tornado/test/simple_httpclient_test.py
Expand Up @@ -6,6 +6,7 @@
import re
import socket

from tornado.httputil import HTTPHeaders
from tornado.ioloop import IOLoop
from tornado.simple_httpclient import SimpleAsyncHTTPClient, _DEFAULT_CA_CERTS
from tornado.test.httpclient_test import HTTPClientCommonTestCase, ChunkHandler, CountdownHandler, HelloWorldHandler
Expand Down Expand Up @@ -179,6 +180,13 @@ def test_max_redirects(self):
self.assertTrue(response.effective_url.endswith("/countdown/2"))
self.assertTrue(response.headers["Location"].endswith("/countdown/1"))

def test_header_reuse(self):
# Apps may reuse a headers object if they are only passing in constant
# headers like user-agent. The header object should not be modified.
headers = HTTPHeaders({'User-Agent': 'Foo'})
self.fetch("/hello", headers=headers)
self.assertEqual(list(headers.get_all()), [('User-Agent', 'Foo')])

def test_303_redirect(self):
response = self.fetch("/303_post", method="POST", body="blah")
self.assertEqual(200, response.code)
Expand Down
21 changes: 21 additions & 0 deletions tornado/test/twisted_test.py
Expand Up @@ -20,6 +20,7 @@
from __future__ import absolute_import, division, with_statement

import os
import signal
import thread
import threading
import unittest
Expand Down Expand Up @@ -51,14 +52,26 @@ def implements(f):
from tornado.util import import_object
from tornado.web import RequestHandler, Application

def save_signal_handlers():
saved = {}
for sig in [signal.SIGINT, signal.SIGTERM, signal.SIGCHLD]:
saved[sig] = signal.getsignal(sig)
assert "twisted" not in repr(saved), repr(saved)
return saved

def restore_signal_handlers(saved):
for sig, handler in saved.iteritems():
signal.signal(sig, handler)

class ReactorTestCase(unittest.TestCase):
def setUp(self):
self._saved_signals = save_signal_handlers()
self._io_loop = IOLoop()
self._reactor = TornadoReactor(self._io_loop)

def tearDown(self):
self._io_loop.close(all_fds=True)
restore_signal_handlers(self._saved_signals)


class ReactorWhenRunningTest(ReactorTestCase):
Expand Down Expand Up @@ -304,12 +317,14 @@ def testNoWriter(self):

class CompatibilityTests(unittest.TestCase):
def setUp(self):
self.saved_signals = save_signal_handlers()
self.io_loop = IOLoop()
self.reactor = TornadoReactor(self.io_loop)

def tearDown(self):
self.reactor.disconnectAll()
self.io_loop.close(all_fds=True)
restore_signal_handlers(self.saved_signals)

def start_twisted_server(self):
class HelloResource(Resource):
Expand Down Expand Up @@ -485,6 +500,10 @@ def make_test_subclass(test_class):
class TornadoTest(test_class):
_reactors = ["tornado.platform.twisted._TestReactor"]

def buildReactor(self):
self.__saved_signals = save_signal_handlers()
return test_class.buildReactor(self)

def unbuildReactor(self, reactor):
test_class.unbuildReactor(self, reactor)
# Clean up file descriptors (especially epoll/kqueue
Expand All @@ -493,6 +512,8 @@ def unbuildReactor(self, reactor):
# since twisted expects to be able to unregister
# connections in a post-shutdown hook.
reactor._io_loop.close(all_fds=True)
restore_signal_handlers(self.__saved_signals)

TornadoTest.__name__ = test_class.__name__
return TornadoTest
test_subclass = make_test_subclass(test_class)
Expand Down
4 changes: 2 additions & 2 deletions tornado/web.py
Expand Up @@ -1882,7 +1882,7 @@ def html_body(self):

class URLSpec(object):
"""Specifies mappings between URLs and handlers."""
def __init__(self, pattern, handler_class, kwargs={}, name=None):
def __init__(self, pattern, handler_class, kwargs=None, name=None):
"""Creates a URLSpec.
Parameters:
Expand All @@ -1906,7 +1906,7 @@ def __init__(self, pattern, handler_class, kwargs={}, name=None):
("groups in url regexes must either be all named or all "
"positional: %r" % self.regex.pattern)
self.handler_class = handler_class
self.kwargs = kwargs
self.kwargs = kwargs or {}
self.name = name
self._path, self._group_count = self._find_groups()

Expand Down

0 comments on commit ac40ded

Please sign in to comment.