Skip to content

Commit

Permalink
feat(core): improve API and docs. update docs. add examples
Browse files Browse the repository at this point in the history
  • Loading branch information
h2non committed Nov 26, 2016
1 parent 8a0ff34 commit 123cca4
Show file tree
Hide file tree
Showing 25 changed files with 556 additions and 50 deletions.
7 changes: 7 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[run]
branch = True

[report]
exclude_lines =
@abc.abstractmethod
@abc.abstractproperty
4 changes: 2 additions & 2 deletions History.rst
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
History
=======

0.1.0 (2016-11-25)
0.1.0 (2016-11-27)
-----------------

- First version.
- First version (beta)
8 changes: 7 additions & 1 deletion MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
include README.rst LICENSE History.rst requirements-dev.txt
include README.rst LICENSE History.rst

include requirements.txt
include requirements-dev.txt

prune docs/_build
prune tasks
73 changes: 66 additions & 7 deletions README.rst
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
pook |Build Status| |PyPI| |Coverage Status| |Documentation Status| |Stability| |Quality| |Versions|
====================================================================================================

Versatile and expressive utility library for simple HTTP traffic mocking and expectations in `Python`_.
Versatile, expressive and hackable utility library for HTTP traffic mocking and expectations in `Python`_.

``pook`` is HTTP client agnostic and works with most popular HTTP packages via adapters.
If someone is not supported yet, it can be in a future via interceptor adapter.
``pook`` can intercept and mock traffic for most popular HTTP client implementations in Python ecosystem.

pook was heavily inspired by `gock`_ Go package.
The API is highly hackable and simple to use, giving you the ability to support new HTTP clients and
custom HTTP mock matchers very easily.

**Note**: work in progress.
pook was heavily inspired by `gock`_, its Go equivalent package.

**Still beta**: report any issue you may experiment.

Features
--------
Expand All @@ -21,13 +23,16 @@ Features
- HTTP client agnostic via adapters (works with most popular HTTP packages).
- Easily simulate error
- Supports JSON Schema body matching.
- Works with any testing framework or engine.
- Works with any testing framework or engine (unittest, pytest, nosetests...)
- Usable in both runtime and testing environments.
- Can be used as decorator and/or via context managers.
- Extensible by design: write your own components and plug in.
- Pluggable and hackable API.
- Work with Python +2.7 and +3.0.
- Does not support WebSocket traffic mocking.
- Work with Python +2.7 and +3.0 (including PyPy).
- Just one dependency = JSONSchema validator.


Supported HTTP clients
----------------------

Expand All @@ -36,6 +41,7 @@ Supported HTTP clients
- [✔] urllib / http.client (experimental)
- [x] pycurl (pending, see `#16`_)


Installation
------------

Expand All @@ -51,14 +57,24 @@ Or install the latest sources from Github::
pip install -e git+git://github.com/h2non/pook.git#egg=pook
Documentation
------------

See RTD documentation: |Documentation Status|


API
---

See `API reference`_ documention.


Examples
--------

See `examples/`_ directory for full featured code and usage case examples.

Basic mocking
^^^^^^^^^^^^^

Expand Down Expand Up @@ -100,6 +116,48 @@ Using the chainable API
assert mock.calls[0].request.url == 'http://twitter.com/api/1/foobar'
assert mock.calls[0].response.text == '{"error": "not found"}'
Usage as decorator
^^^^^^^^^^^^^^^^^^

.. code:: python
import pook
import requests
@pook.get('http://httpbin.org/status/500', reply=204)
@pook.get('http://httpbin.org/status/400', reply=200)
def fetch(url):
return requests.get(url)
res = fetch('http://httpbin.org/status/400')
print('#1 status:', res.status_code)
res = fetch('http://httpbin.org/status/500')
print('#2 status:', res.status_code)
Example using Hy language (Lisp dialect for Python)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

.. code:: hy
(import [pook])
(import [requests])
(defn request [url &optional [status 404]]
(doto (.mock pook url) (.reply status))
(let [res (.get requests url)]
(. res status_code)))
(defn run []
(with [(.use pook)]
(print "Status:" (request "http://server.com/foo" :status 204))))
;; Run test program
(defmain [&args] (run))
License
-------

Expand All @@ -109,6 +167,7 @@ MIT - Tomas Aparicio
.. _gock: https://github.com/h2non/gock
.. _annotated API reference: http://pook.rtfd.io
.. #16: https://github.com/h2non/pook/issues/16
.. examples/: https://github.com/h2non/pook/tree/master/examples
.. |Build Status| image:: https://travis-ci.org/h2non/pook.svg?branch=master
Expand Down
2 changes: 1 addition & 1 deletion examples/aiohttp_client.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# flake8: noqa

import pook
import aiohttp
import asyncio
import async_timeout
import pook


async def fetch(session, url, data):
Expand Down
17 changes: 17 additions & 0 deletions examples/decorator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import pook
import requests


@pook.get('http://httpbin.org/status/500', reply=204)
@pook.get('http://httpbin.org/status/400', reply=200)
def fetch(url):
return requests.get(url)

res = fetch('http://httpbin.org/status/400')
print('#1 status:', res.status_code)

res = fetch('http://httpbin.org/status/500')
print('#2 status:', res.status_code)

print('Is done:', pook.isdone())
print('Pending mocks:', pook.pending_mocks())
File renamed without changes.
23 changes: 23 additions & 0 deletions examples/match_callback.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import pook
import requests


def on_match(request, mock):
print('On match:', request, mock)


with pook.use():
pook.get('httpbin.org/ip',
reply=403, response_type='json',
response_headers={'pepe': 'lopez'},
response_json={'error': 'not found'},
callback=on_match)

res = requests.get('http://httpbin.org/ip')
print('Status:', res.status_code)
print('Headers:', res.headers)
print('Body:', res.json())

print('Is done:', pook.isdone())
print('Pending mocks:', pook.pending_mocks())
print('Unmatched requests:', pook.unmatched_requests())
7 changes: 3 additions & 4 deletions examples/requests_client.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,22 @@
import requests
import pook
import requests


# Use context
with pook.use():
def on_match(request, mock):
print('On match:', request, mock)

pook.get('http://httpbin.org/ip',
pook.get('httpbin.org/ip',
reply=403, response_type='json',
response_headers={'pepe': 'lopez'},
response_json={'error': 'not found'},
callback=on_match)

pook.get('http://httpbin.org/headers',
pook.get('httpbin.org/headers',
reply=404, response_type='json',
response_headers={'pepe': 'lopez'},
response_json={'error': 'not found'},
error=Exception('foo'),
callback=on_match)

res = requests.get('http://httpbin.org/ip')
Expand Down
14 changes: 14 additions & 0 deletions examples/simulated_error.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import pook
import requests

# Simulated error exception
error = Exception('simulated error')

with pook.use():
# Define mock to intercept
pook.get('httpbin.org/status/500', error=error)

try:
requests.get('http://httpbin.org/status/500')
except Exception as err:
print('Error:', err)
17 changes: 17 additions & 0 deletions examples/urllib3_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import pook
import urllib3


# Mock HTTP traffic only in the given context
with pook.use():
pook.get('http://httpbin.org/status/404').reply(204)

# Intercept request
http = urllib3.PoolManager()
r = http.request('GET', 'http://httpbin.org/status/404')
print('#1 status:', r.status)

# Real request outside of the context manager
http = urllib3.PoolManager()
r = http.request('GET', 'http://httpbin.org/status/404')
print('#2 status:', r.status)
74 changes: 69 additions & 5 deletions pook/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@
from contextlib import contextmanager
from .engine import Engine

# Explicit symbols to export.
__all__ = (
'activate', 'on', 'disable', 'off', 'engine',
'use_network', 'enable_network', 'use', 'mock',
'get', 'post', 'put', 'patch', 'head',
'delete', 'options', 'pending', 'ispending',
'pending_mocks', 'unmatched_requests', 'isunmatched',
'unmatched', 'isactive', 'isdone', 'regex'
)

# Singleton mock engine
engine = Engine()

Expand All @@ -13,6 +23,29 @@ def activate(fn=None):
Enables the HTTP traffic interceptors.
This function can be used as decorator.
Arguments:
fn (function): Optional function argument if used as decorator.
Returns:
function: decorator wrapper function, only if called as decorator.
Usage::
# Standard usage
pook.activate()
pook.mock('server.com/foo').reply(404)
res = requests.get('server.com/foo')
assert res.status_code == 404
# Usage as decorator
@pook.activate
def test_request():
pook.mock('server.com/foo').reply(404)
res = requests.get('server.com/foo')
assert res.status_code == 404
"""
engine.activate()

Expand All @@ -34,7 +67,28 @@ def wrapper(*args, **kw):
def on(fn=None):
"""
Enables the HTTP traffic interceptors.
Alias to pook.activate().
Alias to ``pook.activate()``.
Arguments:
fn (function): Optional function argument if used as decorator.
Returns:
function: decorator wrapper function, only if called as decorator.
# Standard usage
pook.on()
pook.mock('server.com/foo').reply(404)
res = requests.get('server.com/foo')
assert res.status_code == 404
# Usage as decorator
@pook.on
def test_request():
pook.mock('server.com/foo').reply(404)
res = requests.get('server.com/foo')
assert res.status_code == 404
"""
return activate(fn)

Expand All @@ -49,7 +103,7 @@ def disable():
def off():
"""
Disables HTTP traffic interceptors.
Alias to pook.disable().
Alias to ``pook.disable()``.
"""
disable()

Expand Down Expand Up @@ -189,15 +243,25 @@ def head(url, **kw):

def patch(url=None, **kw):
"""
Registers a new mock.
Alias to mock()
Creates a new mock for the given URL. Alias to `mock()`.
Returns:
pook.Mock: mock instance
pook.Mock: new mock instance.
"""
return mock(url, method='PATCH', **kw)


def options(url=None, **kw):
"""
Creates a new mock for the given URL with the OPTIONS HTTP verb.
Alias to `mock()`.
Returns:
pook.Mock: new mock instance.
"""
return mock(url, method='OPTIONS', **kw)


def pending():
"""
Returns the numbers of pending mocks to be matched.
Expand Down

0 comments on commit 123cca4

Please sign in to comment.