Skip to content

Commit

Permalink
feat(#12, #14):
Browse files Browse the repository at this point in the history
  • Loading branch information
h2non committed Nov 21, 2016
1 parent 6b42a47 commit 470b20b
Show file tree
Hide file tree
Showing 8 changed files with 89 additions and 65 deletions.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
122 changes: 65 additions & 57 deletions pook/interceptors/aiohttp.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from urllib.parse import urlunparse
from http.client import responses as http_reasons

if sys.version_info >= (3, 4): # Python 3.4+
if sys.version_info >= (3, 4, 2): # Python 3.4.2+
import asyncio
else:
asyncio = None
Expand Down Expand Up @@ -52,60 +52,66 @@ class AIOHTTPInterceptor(BaseInterceptor):
def _url(self, url):
return yarl.URL(url) if yarl else None

def _on_request(self, _request, session, method, url,
data=None, headers=None, **kw):
# Create request contract based on incoming params
req = Request(method)
req.headers = headers or {}
req.body = data

# Expose extra variadic arguments
req.extra = kw

# Compose URL
req.url = str(url)

# Match the request against the registered mocks in pook
mock = self.engine.match(req)

# If cannot match any mock, run real HTTP request if networking
# or silent model are enabled, otherwise this statement won't
# be reached (an exception will be raised before).
if not mock:
return _request(session, method, url,
data=data, headers=headers, **kw)

# Shortcut to mock response
res = mock._response

# Aggregate headers as list of tuples for interface compatibility
headers = []
for key in res._headers:
headers.append((key, res._headers[key]))

# Create mock equivalent HTTP response
_res = HTTPResponse(req.method, self._url(urlunparse(req.url)))

# response status
_res.version = (1, 1)
_res.status = res.status
_res.reason = http_reasons.get(res.status)
_res._should_close = False

# Add response headers
_res.raw_headers = tuple(headers)
_res.headers = multidict.CIMultiDictProxy(
multidict.CIMultiDict(headers)
)

# Define `_content` attribute with an empty string to
# force do not read from stream (which won't exists)
_res._content = ''
if res.body:
_res._content = res.body.encode('utf-8', errors='replace')

# Return response based on mock definition
return _res
if asyncio:
@asyncio.coroutine
def _on_request(self, _request, session, method, url,
data=None, headers=None, **kw):
# Create request contract based on incoming params
req = Request(method)
req.headers = headers or {}
req.body = data

# Expose extra variadic arguments
req.extra = kw

# Compose URL
req.url = str(url)

# Match the request against the registered mocks in pook
mock = self.engine.match(req)

# If cannot match any mock, run real HTTP request if networking
# or silent model are enabled, otherwise this statement won't
# be reached (an exception will be raised before).
if not mock:
return _request(session, method, url,
data=data, headers=headers, **kw)

# Simulate network delay
if mock._delay:
yield from asyncio.sleep(mock._delay / 1000)

# Shortcut to mock response
res = mock._response

# Aggregate headers as list of tuples for interface compatibility
headers = []
for key in res._headers:
headers.append((key, res._headers[key]))

# Create mock equivalent HTTP response
_res = HTTPResponse(req.method, self._url(urlunparse(req.url)))

# response status
_res.version = (1, 1)
_res.status = res.status
_res.reason = http_reasons.get(res.status)
_res._should_close = False

# Add response headers
_res.raw_headers = tuple(headers)
_res.headers = multidict.CIMultiDictProxy(
multidict.CIMultiDict(headers)
)

# Define `_content` attribute with an empty string to
# force do not read from stream (which won't exists)
_res._content = ''
if res.body:
_res._content = res.body.encode('utf-8', errors='replace')

# Return response based on mock definition
return _res

def _patch(self, path):
# If not modern Python, just ignore patch
Expand All @@ -114,8 +120,10 @@ def _patch(self, path):

@asyncio.coroutine
def handler(session, method, url, data=None, headers=None, **kw):
return self._on_request(_request, session, method, url,
data=data, headers=headers, **kw)
return (yield from self._on_request(
_request, session, method, url,
data=data, headers=headers, **kw)
)

try:
# Create a new patcher for Urllib3 urlopen function
Expand Down
4 changes: 3 additions & 1 deletion pook/interceptors/base.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
from abc import abstractmethod, ABCMeta


class BaseInterceptor(metaclass=ABCMeta):
class BaseInterceptor(object):
"""
BaseInterceptor provides a base class for HTTP traffic
interceptors implementations.
"""

__metaclass__ = ABCMeta

def __init__(self, engine):
self.patchers = []
self.engine = engine
Expand Down
16 changes: 9 additions & 7 deletions pook/matchers/base.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import abc
import functools
from copy import deepcopy
from abc import abstractmethod, ABCMeta


class BaseMatcher(metaclass=abc.ABCMeta):
class BaseMatcher(object):
"""
BaseMatcher implements the basic HTTP request matching interface.
"""

__metaclass__ = ABCMeta

# Negate matching if necessary
negate = False
# Defines if the matching supports regular expression matching
Expand All @@ -23,18 +25,18 @@ def __init__(self, expectation, negate=False):
def name(self):
return type(self).__name__

@abc.abstractmethod
@property
def expectation(self):
return self._expectation if hasattr(self, '_expectation') else None

@abstractmethod
def match(self, request):
"""
Match performs the value matching.
This method must be implemented by child classes.
"""
pass

@property
def expectation(self):
return self._expectation if hasattr(self, '_expectation') else None

@expectation.setter
def expectation(self, value):
self._expectation = value
Expand Down
12 changes: 12 additions & 0 deletions pook/mock.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ def __init__(self, request=None, response=None, **kw):
self._calls = 0
self._times = 1
self._error = None
self._delay = 0
self._persist = False
self._request = request
self._response = response or Response()
Expand Down Expand Up @@ -151,6 +152,17 @@ def map(self, *mappers):
def callback(self, *callbacks):
append_funcs(self.callbacks, callbacks)

@fluent
def delay(self, delay=1000):
"""
Delay network response with certain milliseconds.
Only supported by asynchronous HTTP clients, such as ``aiohttp``.
Arguments:
delay (int): milliseconds to delay response.
"""
self._delay = int(delay)

@fluent
def error(self, error):
"""
Expand Down

0 comments on commit 470b20b

Please sign in to comment.