src/pook/api.py
import functools
import re
from contextlib import contextmanager
from inspect import isfunction
from .engine import Engine
from .matcher import MatcherEngine
from .mock import Mock
from .mock_engine import MockEngine
from .request import Request
from .response import Response
from asyncio import iscoroutinefunction
from .activate_async import activate_async
# Public API symbols to export
__all__ = (
"activate",
"on",
"disable",
"off",
"reset",
"engine",
"use_network",
"enable_network",
"disable_network",
"get",
"post",
"put",
"patch",
"head",
"use",
"set_mock_engine",
"delete",
"options",
"pending",
"ispending",
"mock",
"pending_mocks",
"unmatched_requests",
"isunmatched",
"unmatched",
"isactive",
"isdone",
"regex",
"Engine",
"Mock",
"Request",
"Response",
"MatcherEngine",
"MockEngine",
"use_network_filter",
)
# Default singleton mock engine to be used
_engine = Engine()
def debug(enable=True):
"""
Enables or disables debug mode in the current mock engine.
Arguments:
enable (bool): ``True`` to enable debug mode. Otherwise ``False``.
"""
_engine.debug = enable
def engine():
"""
Returns the current running mock engine.
Returns:
pook.Engine: current used engine.
"""
return _engine
def set_mock_engine(engine):
"""
Sets a custom mock engine, replacing the built-in one.
This is particularly useful if you want to replace the built-in
HTTP traffic mock interceptor engine with your custom one.
For mock engine implementation details, see `pook.MockEngine`.
Arguments:
engine (pook.MockEngine): custom mock engine to use.
"""
_engine.set_mock_engine(engine)
def activate(fn=None):
"""
Enables the HTTP traffic interceptors.
This function can be used as decorator.
Arguments:
fn (function|coroutinefunction): Optional function argument
if used as decorator.
Returns:
function: decorator wrapper function, only if called as decorator,
otherwise ``None``.
Example::
# Standard use case
pook.activate()
pook.mock('server.com/foo').reply(404)
res = requests.get('server.com/foo')
assert res.status_code == 404
pook.disable()
# Decorator use case
@pook.activate
def test_request():
pook.mock('server.com/foo').reply(404)
res = requests.get('server.com/foo')
assert res.status_code == 404
"""
# If not used as decorator, activate the engine and exit
if not isfunction(fn):
_engine.activate()
return None
# If used as decorator for an async coroutine, wrap it
if iscoroutinefunction(fn):
return activate_async(fn, _engine)
@functools.wraps(fn)
def wrapper(*args, **kw):
_engine.activate()
try:
fn(*args, **kw)
finally:
_engine.disable()
return wrapper
def on(fn=None):
"""
Enables the HTTP traffic interceptors.
Alias to ``pook.activate()``.
Arguments:
fn (function|coroutinefunction): Optional function argument
if used as decorator.
Returns:
function: decorator wrapper function, only if called as decorator.
Example::
# 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)
def disable():
"""
Disables HTTP traffic interceptors.
"""
_engine.disable()
def off():
"""
Disables mock engine, HTTP traffic interceptors and flushed all the
registered mocks.
Internally, it calls ``pook.disable()`` and ``pook.off()``.
"""
disable()
reset()
def reset():
"""
Resets current mock engine state, flushing all the registered mocks.
This action will not disable the mock engine.
"""
_engine.reset()
@contextmanager
def use(network=False):
"""
Creates a new isolated mock engine to be used via context manager.
Example::
with pook.use() as engine:
pook.mock('server.com/foo').reply(404)
res = requests.get('server.com/foo')
assert res.status_code == 404
"""
global _engine
# Create temporal engine
__engine = _engine
activated = __engine.active
if activated:
__engine.disable()
_engine = Engine(network=network)
_engine.activate()
# Yield engine to be used by the context manager
yield _engine
# Restore engine state
_engine.disable()
if network:
_engine.disable_network()
# Restore previous engine
_engine = __engine
if activated:
_engine.activate()
@contextmanager
def context(network=False):
"""
Create a new isolated mock engine to be used via context manager.
Semantic alias to ``pook.context()``.
Example::
with pook.use() as engine:
pook.mock('server.com/foo').reply(404)
res = requests.get('server.com/foo')
assert res.status_code == 404
"""
with use(network=network) as engine:
yield engine
@contextmanager
def use_network():
"""
Creates a new mock engine to be used as context manager
Example::
with pook.use_network() as engine:
pook.mock('server.com/foo').reply(404)
res = requests.get('server.com/foo')
assert res.status_code == 404
"""
with use(network=True) as engine:
yield engine
def enable_network(*hostnames):
"""
Enables real networking mode for unmatched mocks in the current
mock engine.
"""
_engine.enable_network(*hostnames)
def disable_network():
"""
Disables real traffic networking mode in the current mock engine.
"""
_engine.disable_network()
def use_network_filter(*fn):
"""
Adds network filters to determine if certain outgoing
unmatched HTTP traffic can stablish real network connections.
Arguments:
*fn (function): variadic function filter arguments to be used.
"""
_engine.use_network_filter(*fn)
def flush_network_filters():
"""
Flushes registered real networking filters in the current
mock engine.
"""
_engine.flush_network_filters()
def mock(url=None, **kw):
"""
Creates and register a new HTTP mock.
Arguments:
url (str): request URL to mock.
activate (bool): force mock engine activation.
Defaults to ``False``.
**kw (mixed): variadic keyword arguments.
Returns:
pook.Mock: mock instance
"""
return _engine.mock(url, **kw)
def get(url, **kw):
"""
Registers a new mock HTTP request with GET method.
Arguments:
url (str): request URL to mock.
activate (bool): force mock engine activation.
Defaults to ``False``.
**kw (mixed): variadic arguments to ``pook.Mock`` constructor.
Returns:
pook.Mock: mock instance
"""
return mock(url, method="GET", **kw)
def post(url, **kw):
"""
Registers a new mock HTTP request with POST method.
Arguments:
url (str): request URL to mock.
activate (bool): force mock engine activation.
Defaults to ``False``.
**kw (mixed): variadic arguments to ``pook.Mock`` constructor.
Returns:
pook.Mock: mock instance
"""
return mock(url, method="POST", **kw)
def put(url, **kw):
"""
Registers a new mock HTTP request with PUT method.
Arguments:
url (str): request URL to mock.
activate (bool): force mock engine activation.
Defaults to ``False``.
**kw (mixed): variadic arguments to ``pook.Mock`` constructor.
Returns:
pook.Mock: mock instance
"""
return mock(url, method="PUT", **kw)
def delete(url, **kw):
"""
Registers a new mock HTTP request with DELETE method.
Arguments:
url (str): request URL to mock.
activate (bool): force mock engine activation.
Defaults to ``False``.
**kw (mixed): variadic arguments to ``pook.Mock`` constructor.
Returns:
pook.Mock: mock instance
"""
return mock(url, method="DELETE", **kw)
def head(url, **kw):
"""
Registers a new mock HTTP request with HEAD method.
Arguments:
url (str): request URL to mock.
activate (bool): force mock engine activation.
Defaults to ``False``.
**kw (mixed): variadic arguments to ``pook.Mock`` constructor.
Returns:
pook.Mock: mock instance
"""
return mock(url, method="HEAD", **kw)
def patch(url=None, **kw):
"""
Registers a new mock HTTP request with PATCH method.
Arguments:
url (str): request URL to mock.
activate (bool): force mock engine activation.
Defaults to ``False``.
**kw (mixed): variadic arguments to ``pook.Mock`` constructor.
Returns:
pook.Mock: new mock instance.
"""
return mock(url, method="PATCH", **kw)
def options(url=None, **kw):
"""
Registers a new mock HTTP request with OPTIONS method.
Arguments:
url (str): request URL to mock.
activate (bool): force mock engine activation.
Defaults to ``False``.
**kw (mixed): variadic arguments to ``pook.Mock`` constructor.
Returns:
pook.Mock: new mock instance.
"""
return mock(url, method="OPTIONS", **kw)
def pending():
"""
Returns the numbers of pending mocks to be matched.
Returns:
int: number of pending mocks to match.
"""
return _engine.pending()
def ispending():
"""
Returns the numbers of pending mocks to be matched.
Returns:
int: number of pending mocks to match.
"""
return _engine.ispending()
def pending_mocks():
"""
Returns pending mocks to be matched.
Returns:
list: pending mock instances.
"""
return _engine.pending_mocks()
def unmatched_requests():
"""
Returns a ``tuple`` of unmatched requests.
Unmatched requests will be registered only if ``networking``
mode has been enabled.
Returns:
list: unmatched intercepted requests.
"""
return _engine.unmatched_requests()
def unmatched():
"""
Returns the total number of unmatched requests intercepted by pook.
Unmatched requests will be registered only if ``networking``
mode has been enabled.
Returns:
int: total number of unmatched requests.
"""
return _engine.unmatched()
def isunmatched():
"""
Returns ``True`` if there are unmatched requests. Otherwise ``False``.
Unmatched requests will be registered only if ``networking``
mode has been enabled.
Returns:
bool
"""
return _engine.isunmatched()
def isactive():
"""
Returns ``True`` if pook is active and intercepting traffic.
Otherwise ``False``.
Returns:
bool: True if pook is active, otherwise False.
"""
return _engine.isactive()
def isdone():
"""
Returns True if all the registered mocks has been triggered.
Returns:
bool: True if all the registered mocks are gone, otherwise False.
"""
return _engine.isdone()
def regex(expression, flags=re.IGNORECASE):
"""
Convenient shortcut to ``re.compile()`` for fast, easy to use
regular expression compilation without an extra import statement.
Arguments:
expression (str): regular expression value.
flags (int): optional regular expression flags.
Defaults to ``re.IGNORECASE``
Returns:
expression (str): string based regular expression.
Raises:
Exception: in case of regular expression compilation error
Example::
(pook
.get('api.com/foo')
.header('Content-Type', pook.regex('[a-z]{1,4}')))
"""
return re.compile(expression, flags=flags)