# Source code for pook.mock

    import functools
    from inspect import isfunction, ismethod

    from furl import furl

    from .constants import TYPES
    from .helpers import trigger_methods
    from .matcher import MatcherEngine
    from .matchers import init as matcher
    from .request import Request
    from .response import Response

    def _append_funcs(target, items):
        """
        Helper function to append functions into a given list.

        Arguments:
            target (list): receptor list to append functions.
            items (iterable): iterable that yields elements to append.
        """
        [target.append(item) for item in items if isfunction(item) or ismethod(item)]

    def _trigger_request(instance, request):
        """
        Triggers request mock definition methods dynamically based on input
        keyword arguments passed to `pook.Mock` constructor.

        This is used to provide a more Pythonic interface vs chainable API
        approach.
        """
        if not isinstance(request, Request):
            raise TypeError("request must be instance of pook.Request")

        # Register request matchers
        for key in request.keys:
            if hasattr(instance, key):
                getattr(instance, key)(getattr(request, key))

    class Mock:
        """
        Mock is used to declare and compose the HTTP request/response mock
        definition and matching expectations, which provides fluent API DSL.

        Arguments:
            url (str): URL to match.
                E.g: ``server.com/api?foo=bar``.
            method (str): HTTP method name to match.
                E.g: ``GET``.
            path (str): URL path to match.
                E.g: ``/api/users``.
            headers (dict): Header values to match.
                E.g: ``{'server': 'nginx'}``.
            header_present (str): Matches is a header is present.
            headers_present (list|tuple): Matches if multiple headers are present.
            type (str): Matches MIME ``Content-Type`` header.
                E.g: ``json``, ``xml``, ``html``, ``text/plain``
            content (str): Same as ``type`` argument.
            params (dict): Matches the given URL params.
            param_exists (str): Matches if a given URL param exists.
            params_exists (list|tuple): Matches if a given URL params exists.
            body (str|regex): Matches the payload body by regex or
                strict comparison.
            json (dict|list|str|regex): Matches the payload body against the given
                JSON or regular expression.
            jsonschema (dict|str): Matches the payload body against the given
                JSONSchema.
            xml (str|regex): matches the payload body against the given XML string
                or regular expression.
            file (str): Disk file path to load body from. Analog to ``body`` param.
            times (int): Mock TTL or maximum number of times that the mock can be
                matched.
            persist (bool): Enable persistent mode. Mock won't be flushed even if
                it matched one or multiple times.
            delay (int): Optional network delay simulation (only applicable when
                using ``aiohttp`` HTTP client).
            callback (function): optional callback function called every time the
                mock is matched.
            reply (int): Mock response status. Defaults to ``200``.
            response_status (int): Mock response status. Alias to ``reply`` param.
            response_headers (dict): Response headers to use.
            response_type (str): Response MIME type expression or alias.
                Analog to ``type`` param. E.g: ``json``, ``xml``, ``text/plain``.
            response_body (str): Response body to use.
            response_json (dict|list|str): Response JSON to use. If Python is
                passed, it will be serialized as JSON transparently.
            response_xml (str): XML body string to use.
            request (pook.Request): Optional. Request mock definition object.
            response (pook.Response): Optional. Response mock definition
                object.

        Returns:
            pook.Mock
        """

        _KEY_ORDER = (
            "add_matcher",
            "body",
            "callback",
            "calls",
            "content",
            "delay",
            "done",
            "error",
            "file",
            "filter",
            "header",
            "header_present",
            "headers",
            "headers_present",
            "isdone",
            "ismatched",
            "json",
            "jsonschema",
            "map",
            "match",
            "matched",
            "matches",
            "method",
            "url",
            "param",
            "param_exists",
            "params",
            "path",
            "persist",
            "reply",
            "response",
            "status",
            "times",
            "total_matches",
            "type",
            "use",
            "xml",
        )

        def __init__(self, request=None, response=None, **kw):
            # Stores the number of times the mock should live
            self._times = 1
            # Stores the number of times the mock has been matched
            self._matches = 0
            # Stores the simulated error exception
            self._error = None
            # Stores the optional network delay in milliseconds
            self._delay = 0
            # Stores the mock persistance mode. `True` means it will live forever
            self._persist = False
            # Optional binded engine where the mock belongs to
            self._engine = None
            # Store request-response mock matched calls
            self._calls = []
            # Stores the input request instance
            self._request = request or Request()
            # Stores the response mock instance
            self._response = response or Response()
            # Stores the mock matcher engine used for outgoing traffic matching
            self.matchers = MatcherEngine()
            # Stores filters used to filter outgoing HTTP requests.
            self.filters = []
            # Stores HTTP request mappers used by the mock.
            self.mappers = []
            # Stores callback functions that will be triggered if the mock
            # matches outgoing traffic.
            self.callbacks = []

            # Triggers instance methods based on argument names
            trigger_methods(self, kw, self._KEY_ORDER)

            # Trigger matchers based on predefined request object, if needed
            if request:
                _trigger_request(self, request)

        def url(self, url):
            """
            Defines the mock URL to match.
            It can be a full URL with path and query params.

            Protocol schema is optional, defaults to ``http://``.

            Arguments:
                url (str): mock URL to match. E.g: ``server.com/api``.

            Returns:
                self: current Mock instance.
            """
            self._request.url = url
            self.add_matcher(matcher("URLMatcher", url))
            return self

        def method(self, method):
            """
            Defines the HTTP method to match.
            Use ``*`` to match any method.

            Arguments:
                method (str): method value to match. E.g: ``GET``.

            Returns:
                self: current Mock instance.
            """
            self._request.method = method
            self.add_matcher(matcher("MethodMatcher", method))
            return self

        def path(self, path):
            """
            Defines a URL path to match.

            Only call this method if the URL has no path already defined.

            Arguments:
                path (str): URL path value to match. E.g: ``/api/users``.

            Returns:
                self: current Mock instance.
            """
            url = furl(self._request.rawurl)
            url.path = path
            self._request.url = url.url
            self.add_matcher(matcher("PathMatcher", path))
            return self

        def header(self, name, value):
            """
            Defines a URL path to match.

            Only call this method if the URL has no path already defined.

            Arguments:
                path (str): URL path value to match. E.g: ``/api/users``.

            Returns:
                self: current Mock instance.
            """
            headers = {name: value}
            self._request.headers = headers
            self.add_matcher(matcher("HeadersMatcher", headers))
            return self

        def headers(self, headers=None, **kw):
            """
            Defines a dictionary of arguments.

            Header keys are case insensitive.

            Arguments:
                headers (dict): headers to match.
                **headers (dict): headers to match as variadic keyword arguments.

            Returns:
                self: current Mock instance.
            """
            headers = kw if kw else headers
            self._request.headers = headers
            self.add_matcher(matcher("HeadersMatcher", headers))
            return self

        def header_present(self, *names):
            """
            Defines a new header matcher expectation that must be present in the
            outgoing request in order to be satisfied, no matter what value it
            hosts.

            Header keys are case insensitive.

            Arguments:
                *names (str): header or headers names to match.

            Returns:
                self: current Mock instance.

            Example::

                (pook.get('server.com/api')
                    .header_present('content-type'))
            """
            return self.headers_present(names)

        def headers_present(self, headers):
            """
            Defines a list of headers that must be present in the
            outgoing request in order to satisfy the matcher, no matter what value
            the headers hosts.

            Header keys are case insensitive.

            Arguments:
                headers (list|tuple): header keys to match.

            Returns:
                self: current Mock instance.

            Example::

                (pook.get('server.com/api')
                    .headers_present(['content-type', 'Authorization']))
            """
            if not headers:
                raise ValueError("`headers` must not be empty")

            for header in headers:
                self.add_matcher(matcher("HeaderExistsMatcher", header))
            return self

        def type(self, value):
            """
            Defines the request ``Content-Type`` header to match.

            You can pass one of the following aliases instead of the full
            MIME type representation:

            - ``json`` = ``application/json``
            - ``xml`` = ``application/xml``
            - ``html`` = ``text/html``
            - ``text`` = ``text/plain``
            - ``urlencoded`` = ``application/x-www-form-urlencoded``
            - ``form`` = ``application/x-www-form-urlencoded``
            - ``form-data`` = ``application/x-www-form-urlencoded``

            Arguments:
                value (str): type alias or header value to match.

            Returns:
                self: current Mock instance.
            """
            self.content(value)
            return self

        def content(self, value):
            """
            Defines the ``Content-Type`` outgoing header value to match.

            You can pass one of the following type aliases instead of the full
            MIME type representation:

            - ``json`` = ``application/json``
            - ``xml`` = ``application/xml``
            - ``html`` = ``text/html``
            - ``text`` = ``text/plain``
            - ``urlencoded`` = ``application/x-www-form-urlencoded``
            - ``form`` = ``application/x-www-form-urlencoded``
            - ``form-data`` = ``application/x-www-form-urlencoded``

            Arguments:
                value (str): type alias or header value to match.

            Returns:
                self: current Mock instance.
            """
            header = {"Content-Type": TYPES.get(value, value)}
            self._request.headers = header
            self.add_matcher(matcher("HeadersMatcher", header))
            return self

        def param(self, name, value):
            """
            Defines an URL param key and value to match.

            Arguments:
                name (str): param name value to match.
                value (str): param name value to match.

            Returns:
                self: current Mock instance.
            """
            self.params({name: value})
            return self

        def param_exists(self, name, allow_empty=False):
            """
            Checks if a given URL param name is present in the URL.

            Arguments:
                name (str): param name to check existence.
                allow_empty (bool): whether to allow an empty value of the param

            Returns:
                self: current Mock instance.
            """
            self.add_matcher(matcher("QueryParameterExistsMatcher", name, allow_empty))
            return self

        def params(self, params):
            """
            Defines a set of URL query params to match.

            Arguments:
                params (dict): set of params to match.

            Returns:
                self: current Mock instance.
            """
            url = furl(self._request.rawurl)
            url = url.add(params)
            self._request.url = url.url
            self.add_matcher(matcher("QueryMatcher", params))
            return self

        def body(self, body):
            """
            Defines the body data to match.

            ``body`` argument can be a ``str``, ``bytes`` or a regular expression.

            Arguments:
                body (str|bytes|regex): body data to match.

            Returns:
                self: current Mock instance.
            """
            if hasattr(body, "encode"):
                body = body.encode("utf-8", "backslashreplace")

            self._request.body = body
            self.add_matcher(matcher("BodyMatcher", body))
            return self

        def json(self, json):
            """
            Defines the JSON body to match.

            ``json`` argument can be an JSON string, a JSON serializable
            Python structure, such as a ``dict`` or ``list`` or it can be
            a regular expression used to match the body.

            Arguments:
                json (str|dict|list|regex): body JSON to match.

            Returns:
                self: current Mock instance.
            """
            self._request.json = json
            self.add_matcher(matcher("JSONMatcher", json))
            return self

        def jsonschema(self, schema):
            """
            Defines a JSONSchema representation to be used for body matching.

            Arguments:
                schema (str|dict): dict or JSONSchema string to use.

            Returns:
                self: current Mock instance.
            """
            self.add_matcher(matcher("JSONSchemaMatcher", schema))
            return self

        def xml(self, xml):
            """
            Defines a XML body value to match.

            Arguments:
                xml (str|regex): body XML to match.

            Returns:
                self: current Mock instance.
            """
            self._request.xml = xml
            self.add_matcher(matcher("XMLMatcher", xml))
            return self

        def file(self, path):
            """
            Reads the body to match from a disk file.

            Arguments:
                path (str): relative or absolute path to file to read from.

            Returns:
                self: current Mock instance.
            """
            with open(path, "rb") as f:
                return self.body(f.read())

        def add_matcher(self, matcher):
            """
            Adds one or multiple custom matchers instances.

            Matchers must implement the following interface:

            - ``.__init__(expectation)``
            - ``.match(request)``
            - ``.name = str``

            Matchers can optionally inherit from ``pook.matchers.BaseMatcher``.

            Arguments:
                *matchers (pook.matchers.BaseMatcher): matchers to add.

            Returns:
                self: current Mock instance.
            """
            self.matchers.add(matcher)
            return self

        def use(self, *matchers):
            """
            Adds one or multiple custom matchers instances.

            Matchers must implement the following interface:

            - ``.__init__(expectation)``
            - ``.match(request)``
            - ``.name = str``

            Matchers can optionally inherit from ``pook.matchers.BaseMatcher``.

            Arguments:
                *matchers (pook.matchers.BaseMatcher): matchers to add.

            Returns:
                self: current Mock instance.
            """
            [self.add_matcher(matcher) for matcher in matchers]
            return self

        def times(self, times=1):
            """
            Defines the TTL limit for the current mock.

            The TTL number will determine the maximum number of times that the
            current mock can be matched and therefore consumed.

            Arguments:
                times (int): TTL number. Defaults to ``1``.

            Returns:
                self: current Mock instance.
            """
            self._times = times
            return self

        def persist(self, status=None):
            """
            Enables persistent mode for the current mock.

            Returns:
                self: current Mock instance.
            """
            self._persist = status if isinstance(status, bool) else True
            return self

        def filter(self, *filters):
            """
            Registers one o multiple request filters used during the matching
            phase.

            Arguments:
                *mappers (function): variadic mapper functions.

            Returns:
                self: current Mock instance.
            """
            _append_funcs(self.filters, filters)
            return self

        def map(self, *mappers):
            """
            Registers one o multiple request mappers used during the mapping
            phase.

            Arguments:
                *mappers (function): variadic mapper functions.

            Returns:
                self: current Mock instance.
            """
            _append_funcs(self.mappers, mappers)
            return self

        def callback(self, *callbacks):
            """
            Registers one or multiple callback that will be called every time the
            current mock matches an outgoing HTTP request.

            Arguments:
                *callbacks (function): callback functions to call.

            Returns:
                self: current Mock instance.
            """
            _append_funcs(self.callbacks, callbacks)
            return self

        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.

            Returns:
                self: current Mock instance.
            """
            self._delay = int(delay)
            return self

        def error(self, error):
            """
            Defines a simulated exception error that will be raised.

            Arguments:
                error (str|Exception): error to raise.

            Returns:
                self: current Mock instance.
            """
            self._error = RuntimeError(error) if isinstance(error, str) else error
            return self

        def reply(self, status=200, new_response=False, **kw):
            """
            Defines the mock response.

            Arguments:
                status (int, optional): response status code. Defaults to ``200``.
                **kw (dict): optional keyword arguments passed to ``pook.Response``
                    constructor.

            Returns:
                pook.Response: mock response definition instance.
            """
            # Use or create a Response mock instance
            res = Response(**kw) if new_response else self._response
            # Define HTTP mandatory response status
            res.status(status or res._status)
            # Expose current mock instance in response for self-reference
            res.mock = self
            # Define mock response
            self._response = res
            # Return response
            return res

        def status(self, code=200):
            """
            Defines the response status code.
            Equivalent to ``self.reply(code)``.

            Arguments:
                code (int): response status code. Defaults to ``200``.

            Returns:
                pook.Response: mock response definition instance.
            """
            return self.reply(status=code)

        def response(self, status=200, **kw):
            """
            Defines the mock response. Alias to ``.reply()``

            Arguments:
                status (int): response status code. Defaults to ``200``.
                **kw (dict): optional keyword arguments passed to ``pook.Response``
                    constructor.

            Returns:
                pook.Response: mock response definition instance.
            """
            return self.reply(status=status, **kw)

        def isdone(self):
            """
            Returns ``True`` if the mock has been matched by outgoing HTTP traffic.

            Returns:
                bool: ``True`` if the mock was matched succesfully.
            """
            return (self._persist and self._matches > 0) or self._times <= 0

        def ismatched(self):
            """
            Returns ``True`` if the mock has been matched at least once time.

            Returns:
                bool
            """
            return self._matches > 0

        @property
        def done(self):
            """
            Attribute accessor that would be ``True`` if the current mock
            is done, and therefore have been matched multiple times.

            Returns:
                bool
            """
            return self.isdone()

        @property
        def matched(self):
            """
            Accessor property that would be ``True`` if the current mock
            have been matched at least once.

            See ``Mock.total_matches`` for more information.

            Returns:
                bool
            """
            return self._matches > 0

        @property
        def total_matches(self):
            """
            Accessor property to retrieve the total number of times that the
            current mock has been matched.

            Returns:
                int
            """
            return self._matches

        @property
        def matches(self):
            """
            Accessor to retrieve the mock match calls registry.

            Returns:
                list[MockCall]
            """
            return self._calls

        @property
        def calls(self):
            """
            Accessor to retrieve the amount of mock matched calls.

            Returns:
                int
            """
            return len(self.matches)

        def match(self, request):
            """
            Matches an outgoing HTTP request against the current mock matchers.

            This method acts like a delegator to `pook.MatcherEngine`.

            Arguments:
                request (pook.Request): request instance to match.

            Raises:
                Exception: if the mock has an exception defined.

            Returns:
                tuple(bool, list[Exception]): ``True`` if the mock matches
                    the outgoing HTTP request, otherwise ``False``. Also returns
                    an optional list of error exceptions.
            """
            # Trigger mock filters
            for test in self.filters:
                if not test(request, self):
                    return False, []

            # Trigger mock mappers
            for mapper in self.mappers:
                request = mapper(request, self)
                if not request:
                    raise ValueError("map function must return a request object")

            # Match incoming request against registered mock matchers
            matches, errors = self.matchers.match(request)

            # If not matched, return False
            if not matches:
                return False, errors

            if self._times <= 0:
                return False, [f"Mock matches request but is expired.\n{self!r}"]

            # Register matched request for further inspecion and reference
            self._calls.append(request)

            # Increase mock call counter
            self._matches += 1
            if not self._persist:
                self._times -= 1

            # Raise simulated error
            if self._error:
                raise self._error

            # Trigger callback when matched
            for callback in self.callbacks:
                callback(request, self)

            return True, []

        def __call__(self, fn):
            """
            Overload Mock instance as callable object in order to be used
            as decorator definition syntax.

            Arguments:
                fn (function): function to decorate.

            Returns:
                function or pook.Mock
            """
            # Support chain sequences of mock definitions
            if isinstance(fn, Response):
                return fn.mock
            if isinstance(fn, Mock):
                return fn

            # Force type assertion and raise an error if it is not a function
            if not isfunction(fn) and not ismethod(fn):
                raise TypeError("first argument must be a method or function")

            # Remove mock to prevent decorator definition scope collision
            self._engine.remove_mock(self)

            @functools.wraps(fn)
            def decorator(*args, **kw):
                # Re-register mock on decorator call
                self._engine.add_mock(self)

                # Force engine activation, if available
                # This prevents state issue while declaring mocks as decorators.
                # This might be removed in the future.
                engine_active = self._engine.active
                if not engine_active:
                    self._engine.activate()

                # Call decorated target function
                try:
                    return fn(*args, **kw)
                finally:
                    # Finally remove mock after function execution
                    # to prevent shared state
                    self._engine.remove_mock(self)

                    # If the engine was not previously active, disable it
                    if not engine_active:
                        self._engine.disable()

            return decorator

        def __repr__(self):
            """
            Returns an human friendly readable instance data representation.

            Returns:
                str
            """
            keys = ("matches", "times", "persist", "matchers", "response")

            args = []
            for key in keys:
                if key == "matchers":
                    value = repr(self.matchers).replace("\n  ", "\n    ")
                    value = value[:-2] + "  ])"
                elif key == "response":
                    value = repr(self._response)
                    value = value[:-1] + "  )"
                else:
                    value = repr(getattr(self, "_" + key))
                args.append(f"{key}={value}")

            args = "(\n  {}\n)".format(",\n  ".join(args))

            return type(self).__name__ + args

        def __enter__(self):
            """
            Implements context manager enter interface.
            """
            # Make mock persistent if using default times
            if self._times == 1:
                self._persist = True

            # Automatically enable the mock engine, if needed
            if not self._engine.active:
                self._engine.activate()
                self._disable_engine = True

            return self

        def __exit__(self, etype, value, traceback):
            """
            Implements context manager exit interface.
            """
            # Force disable mock
            self._times = 0

            # Automatically disable the mock engine, if needed
            if getattr(self, "_disable_engine", False):
                self._disable_engine = False
                self._engine.disable()

            if etype is not None:
                raise value