starting with a proof of concept. It IS possible to monkey patch socket module

This commit is contained in:
Gabriel Falcão
2011-01-28 23:16:39 -02:00
commit cd64f821e9
10 changed files with 474 additions and 0 deletions

10
.gitignore vendored Normal file
View File

@@ -0,0 +1,10 @@
*.pyc
.coverage
docs/_build/
httpretty.egg-info/
build/
dist/
.DS_Store
*.swp
.#*
#*

22
COPYING Normal file
View File

@@ -0,0 +1,22 @@
Copyright (C) <2011> Gabriel Falcão <gabriel@nacaolivre.org>
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

40
Makefile Normal file
View File

@@ -0,0 +1,40 @@
all: check_dependencies unit functional integration doctest
filename=httpretty-`python -c 'import httpretty;print httpretty.version'`.tar.gz
#export PYTHONPATH:= ${PWD}
export HTTPRETTY_DEPENDENCIES:= nose sure
check_dependencies:
@echo "Checking for dependencies to run tests ..."
@for dependency in `echo $$HTTPRETTY_DEPENDENCIES`; do \
python -c "import $$dependency" 2>/dev/null || (echo "You must install $$dependency in order to run httpretty's tests" && exit 3) ; \
done
unit: clean
@echo "Running unit tests ..."
@nosetests -s --verbosity=2 --with-coverage --cover-erase --cover-inclusive tests/unit --cover-package=httpretty
functional: clean
@echo "Running functional tests ..."
@nosetests -s --verbosity=2 --with-coverage --cover-erase --cover-inclusive tests/functional --cover-package=httpretty
integration: clean
@echo "Running integration tests ..."
@nosetests -s --verbosity=2 tests/integration
doctest: clean
@cd docs && make doctest
documentation:
@cd docs && make html
clean:
@printf "Cleaning up files that are already in .gitignore... "
@for pattern in `cat .gitignore`; do rm -rf $$pattern; done
@echo "OK!"
release: clean unit functional integration doctest deploy-documentation
@printf "Exporting to $(filename)... "
@tar czf $(filename) httpretty setup.py README.md COPYING
@echo "DONE!"

89
README.md Normal file
View File

@@ -0,0 +1,89 @@
# HTTPretty
> Version 0.1
# What
HTTPretty is a HTTP client mock library for Python 100% inspired on ruby's [FakeWeb](http://fakeweb.rubyforge.org/)
# Motivation
When building systems that access external resources such as RESTful
webservices, XMLRPC or even simple HTTP requests, we stumble in the
problem:
"I'm gonna need to mock all those requests"
It brings a lot of hassle, you will need to use a generic mocking
tool, mess with scope and so on.
## The idea behind HTTPretty (how it works)
HTTPretty [monkey matches](http://en.wikipedia.org/wiki/Monkey_patch)
Python's [socket](http://docs.python.org/library/socket.html) core
module, reimplementing the HTTP protocol, by mocking requests and
responses.
This is a nice thing, if you consider that mostly python http module
will be "mockable".
# Usage
from httpretty import HTTPretty
HTTPretty.register_uri(HTTPretty.GET, "http://globo.com/",
body="The biggest portal in Brazil")
fd = urllib2.urlopen('http://globo.com')
got = fd.read()
fd.close()
print got
**:: output ::**
The biggest portal in Brazil
# Dependencies
you will need **ONLY** if you decide to contribute to HTTPretty which means you're gonna need run our test suite
* [nose](http://code.google.com/p/python-nose/)
> [sudo] pip install nose
* [sure](http://github.com/gabrielfalcao/sure/)
> [sudo] pip install sure
# Contributing
1. fork and clone the project
2. install the dependencies above
3. run the tests with make:
> make unit functional integration
4. hack at will
5. commit, push etc
6. send a pull request
# License
<HTTPretty - HTTP client mock for Python>
Copyright (C) <2011> Gabriel Falcão <gabriel@nacaolivre.org>
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

144
httpretty/__init__.py Normal file
View File

@@ -0,0 +1,144 @@
# #!/usr/bin/env python
# -*- coding: utf-8 -*-
# <HTTPretty - HTTP client mock for Python>
# Copyright (C) <2011> Gabriel Falcão <gabriel@nacaolivre.org>
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation
# files (the "Software"), to deal in the Software without
# restriction, including without limitation the rights to use,
# copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following
# conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.
import re
import socket
from urlparse import urlsplit
from StringIO import StringIO
class fakesock(object):
old_socket = socket.socket
class socket(object):
_entry = None
def __init__(self, family, type, protocol):
self.family = family
self.type = type
self.protocol = protocol
def connect(self, address):
self._address = (self._host, self._port) = address
self._closed = False
def close(self):
self._closed = True
def makefile(self, mode, bufsize):
self._mode = mode
self._bufsize = bufsize
fd = StringIO()
if self._entry:
fd.write(self._entry.body.strip())
fd.seek(0)
return fd
def sendall(self, data):
verb, headers_string = data.split('\n', 1)
method, path, version = re.split('\s+', verb.strip(), 3)
info = URIInfo(hostname=self._host, port=self._port, path=path)
entry = HTTPretty._entries.get(info, None)
if not entry:
return
if entry.method == method:
self._entry = entry
def create_fake_connection(address, timeout=socket._GLOBAL_DEFAULT_TIMEOUT):
s = fakesock.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP)
if timeout is not socket._GLOBAL_DEFAULT_TIMEOUT:
s.settimeout(timeout)
s.connect(address)
return s
class Entry(object):
def __init__(self, method, uri, body):
self.method = method
self.uri = uri
self.body = body
class URIInfo(object):
def __init__(self, username='', password='', hostname='', port=80, path='/', query='', fragment=''):
self.username = username or ''
self.password = password or ''
self.hostname = hostname or ''
self.port = int(port) != 80 and str(port) or ''
self.path = path or ''
self.query = query or ''
self.fragment = fragment or ''
def __unicode__(self):
attrs = 'username', 'password', 'hostname', 'port', 'path', 'query', 'fragment'
return ur'<httpretty.URIInfo(%s)>' % ", ".join(['%s="%s"' % (k, getattr(self, k, '')) for k in attrs])
def __repr__(self):
return unicode(self)
def __hash__(self):
return hash(unicode(self))
def __eq__(self, other):
return unicode(self) == unicode(other)
@classmethod
def from_uri(cls, uri):
result = urlsplit(uri)
return cls(result.username,
result.password,
result.hostname,
result.port or 80,
result.path,
result.query,
result.fragment)
class HTTPretty(object):
u"""The URI registration class"""
_entries = {}
GET = 'GET'
PUT = 'PUT'
POST = 'POST'
DELETE = 'DELETE'
HEAD = 'HEAD'
@classmethod
def register_uri(self, method, uri, body):
self._entries[URIInfo.from_uri(uri)] = Entry(method, uri, body)
def __repr__(self):
return u'<HTTPretty with %d URI entries>' % len(self._entries)
socket.socket = fakesock.socket
socket.create_connection = create_fake_connection
socket.create = fakesock.socket
socket.__dict__.update(fakesock.__dict__)

49
setup.py Executable file
View File

@@ -0,0 +1,49 @@
# #!/usr/bin/env python
# -*- coding: utf-8 -*-
# <HTTPretty - HTTP client mock for Python>
# Copyright (C) <2011> Gabriel Falcão <gabriel@nacaolivre.org>
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation
# files (the "Software"), to deal in the Software without
# restriction, including without limitation the rights to use,
# copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following
# conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.
import os
from httpretty import version
from setuptools import setup
def get_packages():
# setuptools can't do the job :(
packages = []
for root, dirnames, filenames in os.walk('httpretty'):
if '__init__.py' in filenames:
packages.append(".".join(os.path.split(root)).strip("."))
return packages
setup(name='httpretty',
version=version,
description='HTTP client mock for Python',
author=u'Gabriel Falcao',
author_email='gabriel@nacaolivre.org',
url='http://github.com/gabrielfalcao/httpretty',
packages=get_packages()
)

View File

@@ -0,0 +1,26 @@
# #!/usr/bin/env python
# -*- coding: utf-8 -*-
# <HTTPretty - HTTP client mock for Python>
# Copyright (C) <2011> Gabriel Falcão <gabriel@nacaolivre.org>
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation
# files (the "Software"), to deal in the Software without
# restriction, including without limitation the rights to use,
# copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following
# conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,42 @@
# #!/usr/bin/env python
# -*- coding: utf-8 -*-
# <HTTPretty - HTTP client mock for Python>
# Copyright (C) <2011> Gabriel Falcão <gabriel@nacaolivre.org>
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation
# files (the "Software"), to deal in the Software without
# restriction, including without limitation the rights to use,
# copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following
# conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.
import urllib2
from sure import that, within, microseconds
from httpretty import HTTPretty
#@within(five=microseconds) # ensure time to timeout actual request
def test_httpretty_should_mock_a_simple_get_with_urllib2_read():
u"HTTPretty should mock a simple GET with urllib2.read()"
HTTPretty.register_uri(HTTPretty.GET, "http://globo.com/",
body="The biggest portal in Brazil")
fd = urllib2.urlopen('http://globo.com')
got = fd.read()
fd.close()
assert that(got).equals('The biggest portal in Brazil')

View File

@@ -0,0 +1,26 @@
# #!/usr/bin/env python
# -*- coding: utf-8 -*-
# <HTTPretty - HTTP client mock for Python>
# Copyright (C) <2011> Gabriel Falcão <gabriel@nacaolivre.org>
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation
# files (the "Software"), to deal in the Software without
# restriction, including without limitation the rights to use,
# copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following
# conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.

26
tests/unit/__init__.py Normal file
View File

@@ -0,0 +1,26 @@
# #!/usr/bin/env python
# -*- coding: utf-8 -*-
# <HTTPretty - HTTP client mock for Python>
# Copyright (C) <2011> Gabriel Falcão <gabriel@nacaolivre.org>
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation
# files (the "Software"), to deal in the Software without
# restriction, including without limitation the rights to use,
# copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following
# conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.