Skip to content

Commit

Permalink
refactor(core)
Browse files Browse the repository at this point in the history
  • Loading branch information
h2non committed Oct 13, 2016
1 parent b7b1f44 commit 48c6444
Show file tree
Hide file tree
Showing 10 changed files with 133 additions and 67 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# pook [![Build Status](https://travis-ci.org/h2non/pook.svg?branch=master)](https://travis-ci.org/h2non/pook) [![PyPI](https://img.shields.io/pypi/v/pook.svg?maxAge=2592000?style=flat-square)](https://pypi.python.org/pypi/pook) [![API](https://img.shields.io/badge/api-docs-green.svg)](https://h2non.github.io/pook) [![Documentation Status](https://readthedocs.org/projects/pook/badge/?version=latest)](http://pook.readthedocs.io/en/latest/?badge=latest)
# pook [![Build Status](https://travis-ci.org/h2non/pook.svg?branch=master)](https://travis-ci.org/h2non/pook) [![PyPI](https://img.shields.io/pypi/v/pook.svg?maxAge=2592000?style=flat-square)](https://pypi.python.org/pypi/pook) [![Coverage Status](https://coveralls.io/repos/github/h2non/pook/badge.svg?branch=master)](https://coveralls.io/github/h2non/pook?branch=master) [![Documentation Status](https://readthedocs.org/projects/pook/badge/?version=latest)](http://pook.readthedocs.io/en/latest/?badge=latest)

Simply and expressive utility library for mocking and expectations for HTTP traffic in [Python](http://python.org).

Expand Down
6 changes: 4 additions & 2 deletions examples/basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,20 @@

if __name__ == '__main__':
m = pook.get('http://httpbin.org/ip?foo=bar')
# m.type('application/json')
m.type('application/json')
m.type('application/xml')
m.reply(404).json({'error': 'not found'})

# Testing
pook.activate()

res = requests.get('http://httpbin.org/ip?foo=bar')
res = requests.get('http://httpbin.org/ip?foo=bar&baz=foo')
print('Status:', res.status_code)
print('Headers:', res.headers)
print('Body:', res.text)

# res = requests.get('http://httpbin.org/ip?foo=bar')
print('Mock:', m)
pook.disable()
# res = requests.get('http://httpbin.org/ip')
# print('Status:', res.status_code)
Expand Down
2 changes: 1 addition & 1 deletion pook/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ def mock(url=None, **kwargs):
"""
Registers a new mock for GET method.
"""
mock = Mock(url, **kwargs)
mock = Mock(url=url, **kwargs)
engine.add_mock(mock)
return mock

Expand Down
19 changes: 19 additions & 0 deletions pook/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,22 @@ def wrapper(self, *args, **kwargs):
result = fn(self, *args, **kwargs)
return self if result is None else result
return wrapper


def expectation(fn):
@functools.wraps(fn)
def wrapper(self, *args, **kwargs):
if not hasattr(self, 'expectations'):
self.expectations = {}

try:
return fn(self, *args, **kwargs)
finally:
name = fn.__name__
store = self.expectations.get(name)
if not store:
store = []
store.append(*args)
self.expectations[name] = store

return wrapper
4 changes: 2 additions & 2 deletions pook/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ def on_request(self, request):
# If mock matches, return the response object
if mock.match(request):
return mock.response
except PockExpiredMock:
except PookExpiredMock:
self.mocks.remove(mock)

if not self.networking:
raise PockNoMatches('Cannot match any mock for request:', request)
raise PookNoMatches('Cannot match any mock for request:', request)
4 changes: 2 additions & 2 deletions pook/headers.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,8 +175,8 @@ def extend(self, *args, **kwargs):

def getlist(self, key):
"""
Returns a list of all the values for the named field. Returns an
empty list if the key doesn't exist.
Returns a list of all the values for the named field.
Returns an empty list if the key doesn't exist.
"""
try:
vals = self._container[key.lower()]
Expand Down
18 changes: 9 additions & 9 deletions pook/interceptors/urllib3.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@
if sys.version_info < (3,): # Python 2
from httplib import responses as http_reasons
from cStringIO import StringIO as BytesIO
from urlparse import urlparse, parse_qsl
from urlparse import urlparse
else: # Python 3
from http.client import responses as http_reasons
from io import BytesIO
from urllib.parse import urlparse, parse_qsl
from urllib.parse import urlparse

PATCHES = (
'requests.packages.urllib3.connectionpool.HTTPConnectionPool.urlopen',
Expand Down Expand Up @@ -69,18 +69,18 @@ class Urllib3Interceptor(BaseInterceptor):
def _on_request(self, pool, method, url,
body=None, headers=None, **kwargs):
req = Request(method)
req.url = urlparse(pool.scheme + '://' + pool.host + ':' +
str(pool.port) + url)
req.url = (pool.scheme + '://' + pool.host + ':' +
str(pool.port) + url)
req.headers = headers
req.body = body

print('REQ:', pool, method, url)
print('URL:', req.url)
print('HEADERS:', req.headers)
print('BODY:', body)
# print('REQ:', pool, method, url)
# print('URL:', req.url)
# print('HEADERS:', req.headers)
# print('BODY:', body)

mock = self.engine.on_request(req)
print('MOCK BODY:', body_io(mock._body).getvalue())
# print('MOCK BODY:', body_io(mock._body).getvalue())

headers = []
for key in mock._headers:
Expand Down
4 changes: 3 additions & 1 deletion pook/matchers/headers.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
class HeadersMatcher(object):
def __init__(self, headers):
self.headers = {}
if not isinstance(headers, dict):
raise TypeError('headers must be a dictionary')
self.headers = headers

def match(self, req):
return True
95 changes: 52 additions & 43 deletions pook/mock.py
Original file line number Diff line number Diff line change
@@ -1,106 +1,112 @@
import re
from .matchers import * # noqa
from .decorators import fluent
from .decorators import fluent, expectation
from .response import Response
from .request import Request
from .matcher import MatcherEngine
from .utils import trigger_methods
from .exceptions import PookExpiredMock


# class CallList(Sequence, Sized):
# def __init__(self):
# self._calls = []

# def __iter__(self):
# return iter(self._calls)

# def __len__(self):
# return len(self._calls)

# def __getitem__(self, idx):
# return self._calls[idx]

# def add(self, request, response):
# self._calls.append(Call(request, response))

# def reset(self):
# self._calls = []


class Mock(object):
"""
Mock represents the HTTP request mock definition
and expectation DSL.
"""
def __init__(self, url,
path='',
method='GET',
params=None,
headers=None,
body=None,
def __init__(self,
# url='', path='',
# method='GET', params=None,
# headers=None, body=None,
request=None, response=None,
**args):
self._calls = 0
self._times = 1
self._persist = False
self.request = Request()
self.response = None
self.request = request
self.response = response or Response()
self.matchers = MatcherEngine()
self.method(method)
self.url(url)
self.params = {}
self.expectations = {}

# Triggers instance methods based on argument names
trigger_methods(self, args)

@fluent
@expectation
def protocol(self, value):
self.add_matcher(URLProtocolMatcher(value))

@fluent
@expectation
def url(self, url):
if not url:
raise Exception('url argument cannot be empty')

self.add_matcher(URLMatcher(url))

@fluent
@expectation
def method(self, method):
self.add_matcher(MethodMatcher(method))

@fluent
@expectation
def path(self, path):
self.add_matcher(PathMatcher(path))

@fluent
@expectation
def header(self, name, value):
headers = (name, value)
self.add_matcher(HeadersMatcher(*headers))

@fluent
@expectation
def headers(self, *args):
self.add_matcher(HeadersMatcher(*args))

@fluent
@expectation
def header_present(self, name):
headers = (name, re.compile('(.*)'))
self.add_matcher(HeadersMatcher(*headers))
headers = {name: re.compile('(.*)')}
self.add_matcher(HeadersMatcher(headers))

@fluent
def headers_present(self, name):
headers
@expectation
def headers_present(self, headers):
headers = {name: re.compile('(.*)') for name in headers}
self.add_matcher(HeadersMatcher(headers))

@fluent
@expectation
def type(self, value):
self.add_matcher(HeadersMatcher(('Content-Type', value)))
self.add_matcher(HeadersMatcher({'Content-Type': value}))

@fluent
def query_param(self, name, value):
query = (name, value)
self.add_matcher(QueryMatcher(query))
@expectation
def param(self, name, value):
self.params({name: value})

@fluent
def query_exists(self, name):
query = (name, '(.*)')
self.add_matcher(QueryMatcher(query))
@expectation
def param_exists(self, name):
self.params({name: '(.*)'})

@fluent
@expectation
def params(self, params):
self.add_matcher(QueryMatcher(params))

@fluent
def json(self, body):
self.add_matcher(JSONMatcher(data))

@fluent
def json_schema(self, schema):
self.add_matcher(JSONSchemaMatcher(schema))

@fluent
def xml(self, body):
self.add_matcher(XMLMatcher(data))

@fluent
def add_matcher(self, matcher):
Expand Down Expand Up @@ -139,3 +145,6 @@ def match(self, req):
self._times -= 1

return True

def __repr__(self):
return 'Mock({})'.format(self.expectations)
46 changes: 40 additions & 6 deletions pook/request.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,50 @@
import sys
from .headers import HTTPHeaderDict
from .utils import trigger_methods

if sys.version_info < (3,): # Python 2
from urlparse import urlparse
else: # Python 3
from urllib.parse import urlparse


class Request(object):
"""
Request object representing the mock request expectation.
Request object representing the request mock expectation.
"""
def __init__(self, method='GET', **args):
self.method = method
self.headers = HTTPHeaderDict()
self.url = ''
self.params = ''
self.body = None
self._method = method
self._headers = HTTPHeaderDict()
self._url = None
self._body = None
# Call methods
trigger_methods(self, args)

@property
def method(self):
return self._method

@property
def headers(self):
return self._headers

@headers.setter
def headers(self, headers):
self._headers.extend(headers)

@property
def url(self, url):
return self._url

@url.setter
def url(self, url):
self._url = urlparse(url)
print('Parsed:', self._url)

@property
def body(self, data):
return self._body

@body.setter
def body(self, body):
self._body = body

0 comments on commit 48c6444

Please sign in to comment.