summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGabriel Falcão <gabriel@nacaolivre.org>2018-04-26 20:49:36 -0300
committerGabriel Falcão <gabriel@nacaolivre.org>2018-04-26 20:49:36 -0300
commitba3a893d4d3feed5b3b9d57e4d3f3d1bfc6db810 (patch)
treed45ed6e23c5e54c4a6e0e4c9296a58c2f87a00a9
parentb2a6666bd5f45a08467bbc0bf00fea165bc7e5a4 (diff)
downloadhttpretty-ba3a893d4d3feed5b3b9d57e4d3f3d1bfc6db810.tar.gz
more documentation
-rw-r--r--Makefile4
-rw-r--r--Pipfile.lock4
-rw-r--r--docs/source/api.rst135
-rw-r--r--docs/source/conf.py6
-rw-r--r--docs/source/contributing.rst12
-rw-r--r--httpretty/__init__.py54
-rw-r--r--httpretty/core.py263
-rw-r--r--tests/unit/test_httpretty.py15
8 files changed, 411 insertions, 82 deletions
diff --git a/Makefile b/Makefile
index 6f6d33f..bf3decc 100644
--- a/Makefile
+++ b/Makefile
@@ -42,6 +42,10 @@ clean:
@printf "Cleaning up files that are already in .gitignore... "
@for pattern in `cat .gitignore`; do rm -rf $$pattern; done
@echo "OK!"
+ @printf "Deleting built documentation"
+ @rm -rf docs/build
+ @printf "Deleting dist files"
+ @rm -rf dist
release: lint unit functional docs
@rm -rf dist/*
diff --git a/Pipfile.lock b/Pipfile.lock
index 49f6273..387e867 100644
--- a/Pipfile.lock
+++ b/Pipfile.lock
@@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
- "sha256": "5fee12257c42c2db084c56469f71433f38ac66d58e299f2dfd9cd7822298b22e"
+ "sha256": "2598ebbdf276f48b6164053d0f52af007f02ae6224a4cb156d6bdd2791a5bbcb"
},
"pipfile-spec": 6,
"requires": {
@@ -146,7 +146,7 @@
"sha256:6bd0f6ad48ec2aa117d3d141940d484deccda84d4fcd884f5c3d93c23ecd8c79",
"sha256:8ad8c4783bf61ded74527bffb48ed9b54166685e4230386a9ed9b1279e2df5b1"
],
- "markers": "python_version == '2.7'",
+ "markers": "python_version < '3.4'",
"version": "==1.1.6"
},
"flake8": {
diff --git a/docs/source/api.rst b/docs/source/api.rst
index 9896a76..1f34a37 100644
--- a/docs/source/api.rst
+++ b/docs/source/api.rst
@@ -3,11 +3,129 @@
API Reference
=============
-.. autosummary::
- :toctree: stubs
+.. _httpretty:
+register_uri
+------------
-.. _api core:
+.. automethod:: httpretty.core.httpretty.register_uri
+
+enable
+------
+
+.. automethod:: httpretty.core.httpretty.enable
+
+disable
+-------
+
+.. automethod:: httpretty.core.httpretty.disable
+
+is_enabled
+----------
+
+.. automethod:: httpretty.core.httpretty.is_enabled
+
+last_request
+------------
+
+.. autofunction:: httpretty.last_request
+
+
+.. _httprettified:
+
+httprettified
+-------------
+
+.. autofunction:: httpretty.core.httprettified
+
+
+.. _httprettized:
+
+httprettized
+------------
+
+.. autoclass:: httpretty.core.httprettized
+ :members:
+
+
+.. _HTTPrettyRequest:
+
+HTTPrettyRequest
+----------------
+
+.. autoclass:: httpretty.core.HTTPrettyRequest
+ :members:
+
+
+.. _HTTPrettyRequestEmpty:
+
+HTTPrettyRequestEmpty
+---------------------
+
+.. autoclass:: httpretty.core.HTTPrettyRequestEmpty
+ :members:
+
+.. _FakeSockFile:
+
+FakeSockFile
+------------
+
+.. autoclass:: httpretty.core.FakeSockFile
+ :members:
+
+
+.. _FakeSSLSocket:
+
+FakeSSLSocket
+------------
+
+.. autoclass:: httpretty.core.FakeSSLSocket
+ :members:
+
+
+.. _FakeSockFile:
+
+FakeSockFile
+------------
+
+.. autoclass:: httpretty.core.FakeSockFile
+ :members:
+
+
+
+.. _URIInfo:
+
+URIInfo
+-------
+
+.. autoclass:: httpretty.URIInfo
+ :members:
+
+
+.. _URIMatcher:
+
+URIMatcher
+----------
+
+.. autoclass:: httpretty.URIMatcher
+ :members:
+
+
+.. _Entry:
+
+Entry
+-----
+
+.. autoclass:: httpretty.Entry
+ :members:
+
+
+.. _api modules:
+
+Modules
+=======
+
+.. _api module core:
Core
----
@@ -15,12 +133,15 @@ Core
.. automodule:: httpretty.core
:members:
+.. _api module http:
+
Http
----
.. automodule:: httpretty.http
:members:
+.. _api module utils:
Utils
-----
@@ -28,16 +149,10 @@ Utils
.. automodule:: httpretty.utils
:members:
+.. _api module exceptions:
Exceptions
----------
.. automodule:: httpretty.errors
:members:
-
-
-Compat
-------
-
-.. automodule:: httpretty.compat
- :members:
diff --git a/docs/source/conf.py b/docs/source/conf.py
index 733f5e1..8679c2d 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
+
import sys
import sphinx_rtd_theme
try:
@@ -14,8 +15,8 @@ from httpretty.version import version # noqa
project = 'HTTPretty'
-copyright = '2018, Gabriel Falcão'
-author = 'Gabriel Falcão'
+copyright = '2018, Gabriel Falcao'
+author = 'Gabriel Falcao'
# The short X.Y version
version = version
@@ -31,6 +32,7 @@ extensions = [
'sphinx.ext.ifconfig',
'sphinx.ext.viewcode',
'sphinx.ext.githubpages',
+ 'sphinx.ext.autosummary',
]
templates_path = ['_templates']
diff --git a/docs/source/contributing.rst b/docs/source/contributing.rst
index e8588bc..6246852 100644
--- a/docs/source/contributing.rst
+++ b/docs/source/contributing.rst
@@ -59,11 +59,11 @@ License
Main contributors
=================
-HTTPretty has received `many contributions <https://github.com/gabrielfalcao/HTTPretty/graphs/contributors>`
+HTTPretty has received `many contributions <https://github.com/gabrielfalcao/HTTPretty/graphs/contributors>`_
but some folks made remarkable contributions and deserve extra credit:
-- Andrew Gross ~> @andrewgross
-- Hugh Saunders ~> @hughsaunders
-- James Rowe ~> @JNRowe
-- Matt Luongo ~> @mhluongo
-- Steve Pulec ~> @spulec
+- Andrew Gross ~> `@andrewgross <https://github.com/andrewgross>`_
+- Hugh Saunders ~> `@hughsaunders <https://github.com/hughsaunders>`_
+- James Rowe ~> `@JNRowe <https://github.com/JNRowe>`_
+- Matt Luongo ~> `@mhluongo <https://github.com/mhluongo>`_
+- Steve Pulec ~> `@spulec <https://github.com/spulec>`_
diff --git a/httpretty/__init__.py b/httpretty/__init__.py
index b306a3e..e4f2bed 100644
--- a/httpretty/__init__.py
+++ b/httpretty/__init__.py
@@ -23,21 +23,29 @@
# 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.
-from __future__ import unicode_literals
+# flake8: noqa
-from .core import httpretty
-from .core import httprettified
-from .core import EmptyRequestHeaders
-from .core import HTTPrettyRequestEmpty # noqa
-from .core import URIInfo
+from . import core
from .errors import HTTPrettyError, UnmockedError
from .version import version
__version__ = version
+# aliases
+EmptyRequestHeaders = core.EmptyRequestHeaders
+Entry = core.Entry
+HTTPrettyRequestEmpty = core.HTTPrettyRequestEmpty
+URIInfo = core.URIInfo
+URIMatcher = core.URIMatcher
+httprettified = core.httprettified
+httprettized = core.httprettized
+httpretty = core.httpretty
+
HTTPretty = httpretty
activate = httprettified
+enabled = httprettized
+
enable = httpretty.enable
register_uri = httpretty.register_uri
disable = httpretty.disable
@@ -56,36 +64,14 @@ CONNECT = httpretty.CONNECT
def last_request():
- """returns the last request"""
+ """
+ :returns: the last :py:class:`~httpretty.core.HTTPrettyRequest`
+ """
return httpretty.last_request
def has_request():
- """returns a boolean indicating whether any request has been made"""
+ """
+ :returns: bool - whether any request has been made
+ """
return not isinstance(httpretty.last_request.headers, EmptyRequestHeaders)
-
-
-__all__ = [
- 'last_request',
- 'has_request',
- 'GET',
- 'PUT',
- 'POST',
- 'DELETE',
- 'HEAD',
- 'PATCH',
- 'OPTIONS',
- 'CONNECT',
- 'register_uri',
- 'enable',
- 'disable',
- 'is_enabled',
- 'reset',
- 'Response',
- 'URIInfo',
- 'UnmockedError',
- 'HTTPrettyError',
- 'httpretty',
- 'httprettified',
- 'EmptyRequestHeaders',
-]
diff --git a/httpretty/core.py b/httpretty/core.py
index da07206..9c604b0 100644
--- a/httpretty/core.py
+++ b/httpretty/core.py
@@ -122,9 +122,10 @@ def FALLBACK_FUNCTION(x):
class HTTPrettyRequest(BaseHTTPRequestHandler, BaseClass):
- """Represents a HTTP request. It takes a valid multi-line, ``\r\n``
- separated string with HTTP headers and parse them out using the
- internal `parse_request` method.
+ """
+ Represents a HTTP request. It takes a valid multi-line,
+ ``\r\n`` separated string with HTTP headers and parse them out using
+ the internal `parse_request` method.
It also replaces the `rfile` and `wfile` attributes with StringIO
instances so that we guarantee that it won't make any I/O, neighter
@@ -141,17 +142,14 @@ class HTTPrettyRequest(BaseHTTPRequestHandler, BaseClass):
attributes. Please notice that if you need a single value from a
query string you will need to get it manually like:
- ::
+ .. testcode::
>>> request.querystring
{'name': ['Gabriel Falcao']}
>>> print request.querystring['name'][0]
- ``parsed_body`` -> a dictionary containing parsed request body or
- None if HTTPrettyRequest doesn't know how to parse it. It
- currently supports parsing body data that was sent under the
- ``content`-type` headers values: ``application/json`` or
- ``application/x-www-form-urlencoded``
+
+
"""
def __init__(self, headers, body=''):
# first of all, lets make sure that if headers or body are
@@ -197,6 +195,12 @@ class HTTPrettyRequest(BaseHTTPRequestHandler, BaseClass):
# And the body will be attempted to be parsed as
# `application/json` or `application/x-www-form-urlencoded`
+ """a dictionary containing parsed request body or None if
+ HTTPrettyRequest doesn't know how to parse it. It currently
+ supports parsing body data that was sent under the
+ ``content`-type` headers values: ``application/json`` or
+ ``application/x-www-form-urlencoded``
+ """
self.parsed_body = self.parse_request_body(self._body)
@property
@@ -585,6 +589,8 @@ class fakesock(object):
def fake_wrap_socket(orig_wrap_socket_fn, *args, **kw):
+ """drop-in replacement for py:func:`ssl.wrap_socket`
+ """
server_hostname = kw.get('server_hostname')
if server_hostname is not None:
matcher = httpretty.match_https_hostname(server_hostname)
@@ -1028,7 +1034,8 @@ class URIMatcher(object):
class httpretty(HttpBaseClass):
- """The URI registration class"""
+ """manages HTTPretty's internal request/response registry and request matching.
+ """
_entries = {}
latest_requests = []
@@ -1038,6 +1045,10 @@ class httpretty(HttpBaseClass):
@classmethod
def match_uriinfo(cls, info):
+ """
+ :param info: an :py:class:`~httpretty.core.URIInfo`
+ :returns: a 2-item tuple: (:py:class:`~httpretty.core.URLMatcher`, :py:class:`~httpretty.core.URIInfo`) or ``(None, [])``
+ """
items = sorted(
cls._entries.items(),
key=lambda matcher_entries: matcher_entries[0].priority,
@@ -1051,6 +1062,10 @@ class httpretty(HttpBaseClass):
@classmethod
def match_https_hostname(cls, hostname):
+ """
+ :param hostname: a string
+ :returns: an :py:class:`~httpretty.core.URLMatcher` or ``None``
+ """
items = sorted(
cls._entries.items(),
key=lambda matcher_entries: matcher_entries[0].priority,
@@ -1071,6 +1086,11 @@ class httpretty(HttpBaseClass):
@classmethod
def match_http_address(cls, hostname, port):
+ """
+ :param hostname: a string
+ :param port: an integer
+ :returns: an :py:class:`~httpretty.core.URLMatcher` or ``None``
+ """
items = sorted(
cls._entries.items(),
key=lambda matcher_entries: matcher_entries[0].priority,
@@ -1099,6 +1119,26 @@ class httpretty(HttpBaseClass):
@classmethod
@contextlib.contextmanager
def record(cls, filename, indentation=4, encoding='utf-8'):
+ """
+ .. testcode::
+
+ import io
+ import json
+ import requests
+ import httpretty
+
+ with httpretty.record('/tmp/ip.json'):
+ data = requests.get('https://httpbin.org/ip').json()
+
+ with io.open('/tmp/ip.json') as fd:
+ assert data == json.load(fd)
+
+ :param filename: a string
+ :param indentation: an integer, defaults to **4**
+ :param encoding: a string, defaults to **"utf-8"**
+
+ :returns: a `context-manager <https://docs.python.org/3/reference/datamodel.html#context-managers>`_
+ """
try:
import urllib3
except ImportError:
@@ -1147,10 +1187,27 @@ class httpretty(HttpBaseClass):
@classmethod
@contextlib.contextmanager
- def playback(cls, origin):
+ def playback(cls, filename):
+ """
+ .. testcode::
+
+ import io
+ import json
+ import requests
+ import httpretty
+
+ with httpretty.record('/tmp/ip.json'):
+ data = requests.get('https://httpbin.org/ip').json()
+
+ with io.open('/tmp/ip.json') as fd:
+ assert data == json.load(fd)
+
+ :param filename: a string
+ :returns: a `context-manager <https://docs.python.org/3/reference/datamodel.html#context-managers>`_
+ """
cls.enable()
- data = json.loads(open(origin).read())
+ data = json.loads(open(filename).read())
for item in data:
uri = item['request']['uri']
method = item['request']['method']
@@ -1163,6 +1220,8 @@ class httpretty(HttpBaseClass):
@classmethod
def reset(cls):
+ """resets the internal state of HTTPretty, unregistering all URLs
+ """
POTENTIAL_HTTP_PORTS.intersection_update(DEFAULT_HTTP_PORTS)
POTENTIAL_HTTPS_PORTS.intersection_update(DEFAULT_HTTPS_PORTS)
cls._entries.clear()
@@ -1171,6 +1230,18 @@ class httpretty(HttpBaseClass):
@classmethod
def historify_request(cls, headers, body='', append=True):
+ """appends request to a list for later retrieval
+
+ .. testcode::
+
+ import httpretty
+
+ httpretty.register_uri(httpretty.GET, 'https://httpbin.org/ip', body='')
+ with httpretty.enabled():
+ requests.get('https://httpbin.org/ip')
+
+ assert httpretty.latest_requests[-1].url == 'https://httpbin.org/ip'
+ """
request = HTTPrettyRequest(headers, body)
cls.last_request = request
if append or not cls.latest_requests:
@@ -1180,14 +1251,38 @@ class httpretty(HttpBaseClass):
return request
@classmethod
- def register_uri(cls, method, uri, body='HTTPretty :)',
+ def register_uri(cls, method, uri, body='{"message": "HTTPretty :)"}',
adding_headers=None,
forcing_headers=None,
status=200,
- responses=None, match_querystring=False,
+ responses=None,
+ match_querystring=False,
priority=0,
**headers):
+ """
+ .. testcode::
+
+ import httpretty
+
+ httpretty.register_uri(httpretty.GET, 'https://httpbin.org/ip', body='')
+ with httpretty.enabled():
+ requests.get('https://httpbin.org/ip')
+
+ assert httpretty.latest_requests[-1].url == 'https://httpbin.org/ip'
+
+
+ :param method: one of ``httpretty.GET``, ``httpretty.PUT``, ``httpretty.POST``, ``httpretty.DELETE``, ``httpretty.HEAD``, ``httpretty.PATCH``, ``httpretty.OPTIONS``, ``httpretty.CONNECT``
+ :param uri: a string (e.g.: **"https://httpbin.org/ip"**)
+ :param body: a string, defaults to ``{"message": "HTTPretty :)"}``
+ :param adding_headers: dict - headers to be added to the response
+ :param forcing_headers: dict - headers to be forcefully set in the response
+ :param status: an integer, defaults to **200**
+ :param responses: a list of entries, ideally each created with :py:meth:`~httpretty.core.httpretty.Response`
+ :param priority: an integer, useful for setting higher priority over previously registered urls. defaults to zero
+ :param match_querystring: bool - whether to take the querystring into account when matching an URL
+ :param headers: headers to be added to the response
+ """
uri_is_string = isinstance(uri, basestring)
if uri_is_string and re.search(r'^\w+://[^/]+[.]\w{2,}$', uri):
@@ -1228,17 +1323,49 @@ class httpretty(HttpBaseClass):
forcing_headers=None,
status=200,
streaming=False,
- **headers):
-
- headers[str('body')] = body
- headers[str('adding_headers')] = adding_headers
- headers[str('forcing_headers')] = forcing_headers
- headers[str('status')] = int(status)
- headers[str('streaming')] = streaming
- return Entry(method, uri, **headers)
+ **kw):
+ """
+ shortcut to create an :py:class:`~httpretty.core.Entry` that takes the body as first positional argument
+
+ .. seealso:: the parameters of this function match those of the :py:class:`~httpretty.core.Entry` constructor
+ :param body:
+ :param method: one of ``httpretty.GET``, ``httpretty.PUT``, ``httpretty.POST``, ``httpretty.DELETE``, ``httpretty.HEAD``, ``httpretty.PATCH``, ``httpretty.OPTIONS``, ``httpretty.CONNECT``
+ :param uri:
+ :param adding_headers:
+ :param forcing_headers:
+ :param status: defaults to **200**
+ :param streaming: defaults to **False**
+ :param kw: keyword-arguments passed onto the :py:class:`~httpretty.core.Entry`
+ :returns: an :py:class:`~httpretty.core.Entry`
+ """
+ kw['body'] = body
+ kw['adding_headers'] = adding_headers
+ kw['forcing_headers'] = forcing_headers
+ kw['status'] = int(status)
+ kw['streaming'] = streaming
+ return Entry(method, uri, **kw)
@classmethod
def disable(cls):
+ """Disables HTTPretty entirely, putting the original :py:mod:`socket`
+ module back in its place.
+
+
+ .. code::
+
+ import re, json
+ import httpretty
+
+ httpretty.enable()
+ # request passes through fake socket
+ response = requests.get('https://httpbin.org')
+
+ httpretty.disable()
+ # request uses real python socket module
+ response = requests.get('https://httpbin.org')
+
+ .. note:: This method does not call :py:meth:`httpretty.core.reset` automatically.
+ """
cls._is_enabled = False
socket.socket = old_socket
socket.SocketType = old_SocketType
@@ -1280,10 +1407,48 @@ class httpretty(HttpBaseClass):
@classmethod
def is_enabled(cls):
+ """Check if HTTPretty is enabled
+
+ :returns: bool
+
+ .. testcode::
+
+ import httpretty
+
+ httpretty.enable()
+ assert httpretty.is_enabled() == True
+
+ httpretty.disable()
+ assert httpretty.is_enabled() == False
+ """
return cls._is_enabled
@classmethod
def enable(cls):
+ """Enables HTTPretty.
+
+ .. testcode::
+
+ import re, json
+ import httpretty
+
+ httpretty.enable()
+
+ httpretty.register_uri(
+ httpretty.GET,
+ re.compile(r'http://.*'),
+ body=json.dumps({'man': 'in', 'the': 'middle'})
+ )
+
+ response = requests.get('https://foo.bar/foo/bar')
+
+ response.json().should.equal({
+ "man": "in",
+ "the": "middle",
+ })
+
+ .. warning:: after calling this method the original :py:mod:`socket` is replaced with :py:class:`httpretty.core.fakesock`. Make sure to call :py:meth:`~httpretty.disable` after done with your tests or use the :py:class:`httpretty.enabled` as decorator or `context-manager <https://docs.python.org/3/reference/datamodel.html#context-managers>`_
+ """
cls._is_enabled = True
# Some versions of python internally shadowed the
# SocketType variable incorrectly https://bugs.python.org/issue20386
@@ -1332,7 +1497,18 @@ class httpretty(HttpBaseClass):
class httprettized(object):
+ """`context-manager <https://docs.python.org/3/reference/datamodel.html#context-managers>`_ for enabling HTTPretty.
+
+ .. testcode::
+
+ import httpretty
+
+ httpretty.register_uri(httpretty.GET, 'https://httpbin.org/ip', body='')
+ with httpretty.enabled():
+ requests.get('https://httpbin.org/ip')
+ assert httpretty.latest_requests[-1].url == 'https://httpbin.org/ip'
+ """
def __enter__(self):
httpretty.reset()
httpretty.enable()
@@ -1343,7 +1519,50 @@ class httprettized(object):
def httprettified(test):
+ """decorator for test functions
+
+ :param test: a callable
+
+
+ example usage with `nosetests <https://nose.readthedocs.io/en/latest/>`_
+
+ .. testcode::
+ import sure
+ from httpretty import httprettified
+
+ @httprettified
+ def test_using_nosetests():
+ httpretty.register_uri(
+ httpretty.GET,
+ 'https://httpbin.org/ip'
+ )
+
+ response = requests.get('https://httpbin.org/ip')
+
+ response.json().should.equal({
+ "message": "HTTPretty :)"
+ })
+
+ example usage with `unittest module <https://docs.python.org/3/library/unittest.html>`_
+
+ .. testcode::
+
+ import unittest
+ from sure import expect
+ from httpretty import httprettified
+
+ @httprettified
+ class TestWithPyUnit(unittest.TestCase):
+ def test_httpbin(self):
+ httpretty.register_uri(httpretty.GET, 'https://httpbin.org/ip')
+ response = requests.get('https://httpbin.org/ip')
+ expect(response.json()).to.equal({
+ "message": "HTTPretty :)"
+ })
+
+
+ """
def decorate_unittest_TestCase_setUp(klass):
# Prefer addCleanup (added in python 2.7), but fall back
diff --git a/tests/unit/test_httpretty.py b/tests/unit/test_httpretty.py
index 097efae..a99bd03 100644
--- a/tests/unit/test_httpretty.py
+++ b/tests/unit/test_httpretty.py
@@ -27,7 +27,10 @@
from __future__ import unicode_literals
import json
from sure import expect
-from httpretty import HTTPretty, HTTPrettyError, core
+import httpretty
+from httpretty import HTTPretty
+from httpretty import HTTPrettyError
+from httpretty import core
from httpretty.core import URIInfo, BaseClass, Entry, FakeSockFile, HTTPrettyRequest
from httpretty.http import STATUSES
@@ -298,11 +301,11 @@ def test_fake_socket_passes_through_setblocking():
def test_fake_socket_passes_through_fileno():
import socket
- HTTPretty.enable()
- s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- s.truesock = MagicMock()
- expect(s.fileno).called_with().should_not.throw(AttributeError)
- s.truesock.fileno.assert_called_with()
+ with httpretty.enabled():
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ s.truesock = MagicMock()
+ expect(s.fileno).called_with().should_not.throw(AttributeError)
+ s.truesock.fileno.assert_called_with()
def test_fake_socket_passes_through_getsockopt():