Skip to content

Commit

Permalink
refactor(core)
Browse files Browse the repository at this point in the history
  • Loading branch information
h2non committed Oct 10, 2016
1 parent 6a5bc12 commit b7b1f44
Show file tree
Hide file tree
Showing 10 changed files with 113 additions and 25 deletions.
14 changes: 12 additions & 2 deletions pook/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def activate(fn=None):
engine.activate()

if not isfunction(fn):
return None
return fn

def wrapper(*args, **kwargs):
try:
Expand Down Expand Up @@ -76,10 +76,20 @@ def use():


# Public API
def mock(url, method='GET', **kwargs):
def mock(url=None, **kwargs):
"""
Registers a new mock for GET method.
"""
mock = Mock(url, **kwargs)
engine.add_mock(mock)
return mock


def patch(url=None, method='GET', **kwargs):
"""
Registers a new mock.
Alias to mock()
"""
mock = Mock(url, method, **kwargs)
engine.add_mock(mock)
return mock
Expand Down
4 changes: 4 additions & 0 deletions pook/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,7 @@ class PookNoMatches(Exception):

class PookExpiredMock(Exception):
pass


class PookInvalidArgument(Exception):
pass
16 changes: 11 additions & 5 deletions pook/headers.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,10 @@ def __iter__(self):
yield vals[0]

def pop(self, key, default=__marker):
'''
"""
D.pop(k[,d]) -> v, remove specified key and return the corresponding value.
If key is not found, d is returned if given, otherwise KeyError is raised.
'''
"""
# Using the MutableMapping function directly fails due to the private marker.
# Using ordinary dict.pop would expose the internal structures.
# So let's reinvent the wheel.
Expand Down Expand Up @@ -210,14 +210,18 @@ def copy(self):
return clone

def iteritems(self):
"""Iterate over all header lines, including duplicate ones."""
"""
Iterate over all header lines, including duplicate ones.
"""
for key in self:
vals = self._container[key.lower()]
for val in vals[1:]:
yield vals[0], val

def itermerged(self):
"""Iterate over all headers, merging duplicate ones together."""
"""
Iterate over all headers, merging duplicate ones together.
"""
for key in self:
val = self._container[key.lower()]
yield val[0], ', '.join(val[1:])
Expand All @@ -227,7 +231,9 @@ def items(self):

@classmethod
def from_httplib(cls, message): # Python 2
"""Read headers from a Python 2 httplib message object."""
"""
Read headers from a Python 2 httplib message object.
"""
# python2.7 does not expose a proper API for exporting multiheaders
# efficiently. This function re-reads raw lines from the message
# object and extracts the multiheaders properly.
Expand Down
16 changes: 15 additions & 1 deletion pook/interceptors/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
from .urllib3 import Urllib3Interceptor
from .base import BaseInterceptor # noqa

# Store built-in interceptors.
# Explicit module exports
__all__ = [
'store',
'add',
'BaseInterceptor'
]

# Store built-in interceptors in pook.
# Note: order is intentional.
store = [
# UrllibInterceptor,
Expand All @@ -12,5 +20,11 @@
def add(interceptor):
"""
Registers a new HTTP client interceptor.
Arguments:
interceptor (interceptor): interceptor class to be added.
Returns:
None
"""
store.append(interceptor)
22 changes: 22 additions & 0 deletions pook/interceptors/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
class BaseInterceptor(object):
"""
BaseInterceptor provides a base class for HTTP traffic
interceptors implementations.
"""
def __init__(self, engine):
self.patchers = []
self.engine = engine

def activate(self):
"""
Activates the traffic interceptor.
This method must be implemented by any interceptor.
"""
raise NotImplemented('not implemented yet')

def disable(self):
"""
Disables the traffic interceptor.
This method must be implemented by any interceptor.
"""
raise NotImplemented('not implemented yet')
7 changes: 2 additions & 5 deletions pook/interceptors/urllib3.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import io
import sys
from ..request import Request
from .base import BaseInterceptor

# Support Python 2/3
try:
Expand Down Expand Up @@ -61,14 +62,10 @@ def map(self, req):
req.url = self.req.url


class Urllib3Interceptor(object):
class Urllib3Interceptor(BaseInterceptor):
"""
urllib3 HTTP traffic interceptor.
"""
def __init__(self, engine):
self.patchers = []
self.engine = engine

def _on_request(self, pool, method, url,
body=None, headers=None, **kwargs):
req = Request(method)
Expand Down
13 changes: 7 additions & 6 deletions pook/mock.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from .response import Response
from .request import Request
from .matcher import MatcherEngine
from .utils import trigger_methods
from .exceptions import PookExpiredMock


Expand Down Expand Up @@ -37,18 +38,18 @@ def __init__(self, url,
method='GET',
params=None,
headers=None,
body=None):
body=None,
**args):
self._calls = 0
self._times = 1
self._persist = False
self.request = Request()
self.response = Response()
self.response = None
self.matchers = MatcherEngine()
self.method(method)
self.url(url)
self.params = {}
self.body = body
# self.args = kwargs
trigger_methods(self, args)

@fluent
def protocol(self, value):
Expand Down Expand Up @@ -117,8 +118,8 @@ def times(self, num=1):
def persist(self):
self._persist = True

def reply(self, status=200):
self.response._status = status
def reply(self, status=200, **args):
self.response = Response(status=status, **args)
return self.response

def match(self, req):
Expand Down
7 changes: 5 additions & 2 deletions pook/request.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
from .headers import HTTPHeaderDict
from .utils import trigger_methods


class Request(object):
"""
Request object represents.
Request object representing the mock request expectation.
"""
def __init__(self, method='GET'):
def __init__(self, method='GET', **args):
self.method = method
self.headers = HTTPHeaderDict()
self.url = ''
self.params = ''
self.body = None
# Call methods
trigger_methods(self, args)
26 changes: 22 additions & 4 deletions pook/response.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import json
from .headers import HTTPHeaderDict
from .utils import trigger_methods

TYPE_ALIASES = {
'html': 'text/html',
Expand All @@ -12,14 +13,23 @@


class Response(object):
def __init__(self, status=200):
def __init__(self, **args):
self._mock = None
self._body = None
self._status = status
self._headers = HTTPHeaderDict()
# Call methods
trigger_methods(self, args)

def status(seld, status=200):
def status(self, status=200):
self._status = status

def header(self, key, value):
if type(key) is tuple:
key, value = str(key[0]), key[1]

headers = {key: value}
self._headers.extend(headers)

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

Expand All @@ -29,7 +39,15 @@ def type(self, name):

def json(self, data):
self._headers['Content-Type'] = 'application/json'
self._body = json.dumps(data)
self._body = json.dumps(data, indent=4)

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

@property
def mock(self):
return self._mock

@mock.setter
def mock(self, mock):
self._mock = mock
13 changes: 13 additions & 0 deletions pook/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from inspect import isfunction
from .exceptions import PookInvalidArgument

def trigger_methods(instance, args):
""""
Triggers the required class methods using simple reflection
based on the given arguments dictionary.
"""
for key, arg in args.items():
method = getattr(instance, key, None)
if isfunction(method):
raise PookInvalidArgument('Unsupported argument: {}'.format(key))
method(arg)

0 comments on commit b7b1f44

Please sign in to comment.