diff --git a/.travis.yml b/.travis.yml index 3219e4c..a9ee381 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,11 @@ language: python python: + - 2.6 + - 2.7 - 3.2 + - 3.3 script: make test + +install: + - pip install --use-mirrors \ + `python -c 'from setup import META; print(" ".join(META["extras_require"]["testing"]))'` diff --git a/setup.py b/setup.py index a202be0..9467566 100644 --- a/setup.py +++ b/setup.py @@ -1,15 +1,25 @@ from setuptools import setup, find_packages -setup( - name = 'wsgi_intercept', - version = '0.1', - author = 'Titus Brown, Kumar McMillan, Chris Dent', - author_email = 'cdent@peermore.com', - description = 'wsgi_intercept installs a WSGI application in place of a real URI for testing.', +META = { + 'name': 'wsgi_intercept', + 'version': '0.2', + 'author': 'Titus Brown, Kumar McMillan, Chris Dent', + 'author_email': 'cdent@peermore.com', + 'description': 'wsgi_intercept installs a WSGI application in place of a real URI for testing.', # What will the name be? #url="http://pypi.python.org/pypi/wsgi_intercept", - long_description = open('README.md').read(), - license = 'MIT License', - packages = find_packages(), - ) + 'long_description': open('README.md').read(), + 'license': 'MIT License', + 'packages': find_packages(), + 'extras_require': { + 'testing': [ + 'pytest', + 'httplib2', + 'requests' + ], + }, +} + +if __name__ == '__main__': + setup(**META) diff --git a/test/test_http_client.py b/test/test_http_client.py index a807a55..aeab580 100644 --- a/test/test_http_client.py +++ b/test/test_http_client.py @@ -1,22 +1,22 @@ import pytest +import sys from wsgi_intercept import http_client_intercept -from socket import gaierror import wsgi_intercept from test import wsgi_app -import http.client -_saved_debuglevel = None +try: + import http.client as http_lib +except ImportError: + import httplib as http_lib def http_install(port=80): - _saved_debuglevel, wsgi_intercept.debuglevel = wsgi_intercept.debuglevel, 1 http_client_intercept.install() wsgi_intercept.add_wsgi_intercept( 'some_hopefully_nonexistant_domain', port, wsgi_app.create_fn) def http_uninstall(port=80): - wsgi_intercept.debuglevel = _saved_debuglevel wsgi_intercept.remove_wsgi_intercept( 'some_hopefully_nonexistant_domain', port) http_client_intercept.uninstall() @@ -24,7 +24,7 @@ def http_uninstall(port=80): def test_http_success(): http_install() - http_client = http.client.HTTPConnection( + http_client = http_lib.HTTPConnection( 'some_hopefully_nonexistant_domain') http_client.request('GET', '/') content = http_client.getresponse().read() @@ -33,15 +33,17 @@ def test_http_success(): http_uninstall() -# https and http.client are not happy because of a recursion problem -# HTTPSConnection calls super in __init__ -@pytest.mark.xfail def test_https_success(): http_install(443) - http_client = http.client.HTTPSConnection( - 'some_hopefully_nonexistant_domain') - http_client.request('GET', '/') - content = http_client.getresponse().read() - assert content == b'WSGI intercept successful!\n' - assert wsgi_app.success() + if sys.version_info[0] < 3: + http_client = http_lib.HTTPSConnection( + 'some_hopefully_nonexistant_domain') + http_client.request('GET', '/') + content = http_client.getresponse().read() + assert content == b'WSGI intercept successful!\n' + assert wsgi_app.success() + else: + with pytest.raises(NotImplementedError): + http_client = http_lib.HTTPSConnection( + 'some_hopefully_nonexistant_domain') http_uninstall(443) diff --git a/test/test_httplib2.py b/test/test_httplib2.py index 8cad816..4cce476 100644 --- a/test/test_httplib2.py +++ b/test/test_httplib2.py @@ -7,11 +7,7 @@ import httplib2 import py.test -_saved_debuglevel = None - - def install(port=80): - _saved_debuglevel, wsgi_intercept.debuglevel = wsgi_intercept.debuglevel, 1 httplib2_intercept.install() wsgi_intercept.add_wsgi_intercept( 'some_hopefully_nonexistant_domain', @@ -19,7 +15,6 @@ def install(port=80): def uninstall(): - wsgi_intercept.debuglevel = _saved_debuglevel httplib2_intercept.uninstall() @@ -35,7 +30,6 @@ def test_success(): def test_bogus_domain(): install() - wsgi_intercept.debuglevel = 1 py.test.raises(gaierror, 'httplib2_intercept.HTTP_WSGIInterceptorWithTimeout("_nonexistant_domain_").connect()') uninstall() diff --git a/test/test_urllib.py b/test/test_urllib.py index 0b6f02e..05c045d 100644 --- a/test/test_urllib.py +++ b/test/test_urllib.py @@ -1,29 +1,27 @@ import pytest -import urllib.request +try: + import urllib.request as url_lib +except ImportError: + import urllib2 as url_lib from wsgi_intercept import urllib_intercept import wsgi_intercept from test import wsgi_app -_saved_debuglevel = None - - def add_http_intercept(port=80): - _saved_debuglevel, wsgi_intercept.debuglevel = wsgi_intercept.debuglevel, 1 wsgi_intercept.add_wsgi_intercept( 'some_hopefully_nonexistant_domain', port, wsgi_app.create_fn) def remove_intercept(): - wsgi_intercept.debuglevel = _saved_debuglevel wsgi_intercept.remove_wsgi_intercept() def test_http(): add_http_intercept() urllib_intercept.install_opener() - urllib.request.urlopen('http://some_hopefully_nonexistant_domain:80/') + url_lib.urlopen('http://some_hopefully_nonexistant_domain:80/') assert wsgi_app.success() remove_intercept() @@ -31,7 +29,7 @@ def test_http(): def test_http_default_port(): add_http_intercept() urllib_intercept.install_opener() - urllib.request.urlopen('http://some_hopefully_nonexistant_domain/') + url_lib.urlopen('http://some_hopefully_nonexistant_domain/') assert wsgi_app.success() remove_intercept() @@ -39,7 +37,7 @@ def test_http_default_port(): def test_https(): add_http_intercept(443) urllib_intercept.install_opener() - urllib.request.urlopen('https://some_hopefully_nonexistant_domain:443/') + url_lib.urlopen('https://some_hopefully_nonexistant_domain:443/') assert wsgi_app.success() remove_intercept() @@ -47,6 +45,6 @@ def test_https(): def test_https_default_port(): add_http_intercept(443) urllib_intercept.install_opener() - urllib.request.urlopen('https://some_hopefully_nonexistant_domain/') + url_lib.urlopen('https://some_hopefully_nonexistant_domain/') assert wsgi_app.success() remove_intercept() diff --git a/test/test_wsgi_compliance.py b/test/test_wsgi_compliance.py index 47a4605..576c30e 100644 --- a/test/test_wsgi_compliance.py +++ b/test/test_wsgi_compliance.py @@ -5,18 +5,13 @@ from test import wsgi_app import httplib2 -_saved_debuglevel = None - - def http_install(): - _saved_debuglevel, wsgi_intercept.debuglevel = wsgi_intercept.debuglevel, 1 install() wsgi_intercept.add_wsgi_intercept( 'some_hopefully_nonexistant_domain', 80, wsgi_app.create_fn) def http_uninstall(): - wsgi_intercept.debuglevel = _saved_debuglevel wsgi_intercept.remove_wsgi_intercept( 'some_hopefully_nonexistant_domain', 80) uninstall() @@ -77,6 +72,6 @@ def test_encoding_errors(): with py.test.raises(UnicodeEncodeError): response, content = http.request( 'http://some_hopefully_nonexistant_domain/boom/baz', 'GET', - headers={'Accept': 'application/\u2603'}) + headers={'Accept': u'application/\u2603'}) http_uninstall() diff --git a/test/wsgi_app.py b/test/wsgi_app.py index f0a057f..18f23d2 100644 --- a/test/wsgi_app.py +++ b/test/wsgi_app.py @@ -4,6 +4,11 @@ Simple WSGI applications for testing. from pprint import pformat +try: + bytes +except ImportError: + bytes = str + _app_was_hit = False _internals = {} @@ -46,4 +51,4 @@ def more_interesting_app(environ, start_response): _internals = environ start_response('200 OK', [('Content-type', 'text/plain')]) - return [bytes(pformat(environ), encoding='utf-8')] + return [pformat(environ).encode('utf-8')] diff --git a/wsgi_intercept/__init__.py b/wsgi_intercept/__init__.py index e90831a..1196ef9 100644 --- a/wsgi_intercept/__init__.py +++ b/wsgi_intercept/__init__.py @@ -106,11 +106,22 @@ failing tests, et cetera using the Issue Tracker. .. _GitHub: http://github.com/cdent/python3-wsgi-intercept """ +from __future__ import print_function + __version__ = '0.0.1' + import sys -from http.client import HTTPConnection -from io import BytesIO +try: + from http.client import HTTPConnection, HTTPSConnection +except ImportError: + from httplib import HTTPConnection, HTTPSConnection + +try: + from io import BytesIO +except ImportError: + from StringIO import StringIO as BytesIO + import traceback debuglevel = 0 @@ -404,17 +415,18 @@ class wsgi_fake_socket: # return the concatenated results. return BytesIO(self.output.getvalue()) - def sendall(self, str): + def sendall(self, content): """ Save all the traffic to self.inp. """ if debuglevel >= 2: - print(">>>", str, ">>>") + print(">>>", content, ">>>") try: - self.inp.write(str) - except TypeError: - self.inp.write(bytes([str]).decode('utf-8')) + self.inp.write(content) + except TypeError as exc: + print('type error', exc) + self.inp.write(content.decode('utf-8')) def close(self): "Do nothing, for now." @@ -474,53 +486,48 @@ class WSGI_HTTPConnection(HTTPConnection): # WSGI_HTTPSConnection # -try: - from http.client import HTTPSConnection -except ImportError: - pass -else: - class WSGI_HTTPSConnection(HTTPSConnection, WSGI_HTTPConnection): +class WSGI_HTTPSConnection(HTTPSConnection, WSGI_HTTPConnection): + """ + Intercept all traffic to certain hosts & redirect into a WSGI + application object. + """ + def get_app(self, host, port): """ - Intercept all traffic to certain hosts & redirect into a WSGI - application object. + Return the app object for the given (host, port). """ - def get_app(self, host, port): - """ - Return the app object for the given (host, port). - """ - key = (host, int(port)) + key = (host, int(port)) - app, script_name = None, None + app, script_name = None, None - if key in _wsgi_intercept: - (app_fn, script_name) = _wsgi_intercept[key] - app = app_fn() + if key in _wsgi_intercept: + (app_fn, script_name) = _wsgi_intercept[key] + app = app_fn() - return app, script_name + return app, script_name - def connect(self): - """ - Override the connect() function to intercept calls to certain - host/ports. + def connect(self): + """ + Override the connect() function to intercept calls to certain + host/ports. - If no app at host/port has been registered for interception then - a normal HTTPSConnection is made. - """ - if debuglevel: - sys.stderr.write('connect: %s, %s\n' % (self.host, self.port,)) + If no app at host/port has been registered for interception then + a normal HTTPSConnection is made. + """ + if debuglevel: + sys.stderr.write('connect: %s, %s\n' % (self.host, self.port,)) - try: - (app, script_name) = self.get_app(self.host, self.port) - if app: - if debuglevel: - sys.stderr.write('INTERCEPTING call to %s:%s\n' % - (self.host, self.port,)) - self.sock = wsgi_fake_socket(app, self.host, self.port, - script_name) - else: - HTTPSConnection.connect(self) + try: + (app, script_name) = self.get_app(self.host, self.port) + if app: + if debuglevel: + sys.stderr.write('INTERCEPTING call to %s:%s\n' % + (self.host, self.port,)) + self.sock = wsgi_fake_socket(app, self.host, self.port, + script_name) + else: + HTTPSConnection.connect(self) - except Exception as e: - if debuglevel: # intercept & print out tracebacks - traceback.print_exc() - raise + except Exception as e: + if debuglevel: # intercept & print out tracebacks + traceback.print_exc() + raise diff --git a/wsgi_intercept/http_client_intercept.py b/wsgi_intercept/http_client_intercept.py index 3fd03c5..f1b5a4d 100644 --- a/wsgi_intercept/http_client_intercept.py +++ b/wsgi_intercept/http_client_intercept.py @@ -8,20 +8,42 @@ # XXX: HTTPSConnection is currently not allowed as attempting # to override it causes a recursion error. -import http.client -import wsgi_intercept -import sys -from http.client import ( - HTTPConnection as OriginalHTTPConnection, - #HTTPSConnection as OriginalHTTPSConnection -) +SKIP_SSL = False + +try: + import http.client as http_lib + SKIP_SSL = True +except ImportError: + import httplib as http_lib + +from . import WSGI_HTTPConnection, WSGI_HTTPSConnection + +try: + from http.client import ( + HTTPConnection as OriginalHTTPConnection, + HTTPSConnection as OriginalHTTPSConnection + ) +except ImportError: + from httplib import ( + HTTPConnection as OriginalHTTPConnection, + HTTPSConnection as OriginalHTTPSConnection + ) + + +class Error_HTTPSConnection(object): + + def __init__(self, *args, **kwargs): + raise NotImplementedError('HTTPS temporarily not implemented') def install(): - http.client.HTTPConnection = wsgi_intercept.WSGI_HTTPConnection - #http.client.HTTPSConnection = wsgi_intercept.WSGI_HTTPSConnection + http_lib.HTTPConnection = WSGI_HTTPConnection + if SKIP_SSL: + http_lib.HTTPSConnection = Error_HTTPSConnection + else: + http_lib.HTTPSConnection = WSGI_HTTPSConnection def uninstall(): - http.client.HTTPConnection = OriginalHTTPConnection - #http.client.HTTPSConnection = OriginalHTTPSConnection + http_lib.HTTPConnection = OriginalHTTPConnection + http_lib.HTTPSConnection = OriginalHTTPSConnection diff --git a/wsgi_intercept/urllib_intercept.py b/wsgi_intercept/urllib_intercept.py index 63cb989..6ff6d0b 100644 --- a/wsgi_intercept/urllib_intercept.py +++ b/wsgi_intercept/urllib_intercept.py @@ -1,10 +1,18 @@ -from wsgi_intercept import WSGI_HTTPConnection, WSGI_HTTPSConnection -import urllib.request -from urllib.request import HTTPHandler, HTTPSHandler +try: + import urllib.request as url_lib +except ImportError: + import urllib2 as url_lib + +try: + from urllib.request import HTTPHandler, HTTPSHandler +except ImportError: + from urllib2 import HTTPHandler, HTTPSHandler + +from . import WSGI_HTTPConnection, WSGI_HTTPSConnection -class WSGI_HTTPHandler(HTTPHandler): +class WSGI_HTTPHandler(url_lib.HTTPHandler): """ Override the default HTTPHandler class with one that uses the WSGI_HTTPConnection class to open HTTP URLs. @@ -13,7 +21,7 @@ class WSGI_HTTPHandler(HTTPHandler): return self.do_open(WSGI_HTTPConnection, req) -class WSGI_HTTPSHandler(HTTPSHandler): +class WSGI_HTTPSHandler(url_lib.HTTPSHandler): """ Override the default HTTPSHandler class with one that uses the WSGI_HTTPConnection class to open HTTPS URLs. @@ -26,11 +34,11 @@ def install_opener(): handlers = [WSGI_HTTPHandler()] if WSGI_HTTPSHandler is not None: handlers.append(WSGI_HTTPSHandler()) - opener = urllib.request.build_opener(*handlers) - urllib.request.install_opener(opener) + opener = url_lib.build_opener(*handlers) + url_lib.install_opener(opener) return opener def uninstall_opener(): - urllib.request.install_opener(None) + url_lib.install_opener(None)