"""
call_factory
Meta web api wrapper.
Usage:
.. code-block:: python
from prompy.networkio.call_factory import CallRoute, Caller
from prompy.threadio.promise_queue import PromiseQueuePool
class Api(Caller):
def call_home(self, **kwargs):
return CallRoute('/')
def call_data(self, **kwargs):
return CallRoute('/data', method='POST')
pool = PromiseQueuePool(start=True)
api = Api(base_url='http://localhost:5000', promise_container=pool)
api.call_data(data={'num': 6}).then(print).catch(print)
"""
import json
import re
import functools
from typing import Any, Optional, Union, Dict, List
from prompy.container import BasePromiseContainer
from prompy.networkio import urlcall, http_constants
from prompy.networkio.url_tools import encode_url_params
from prompy.promise import Promise
_route_params_pattern = re.compile('<(\w*)>')
[docs]class CallRoute:
"""Route object used by :class:`Caller`"""
[docs] def __init__(self,
route: str,
method: str=http_constants.GET,
content_type: str=http_constants.CONTENT_TYPE_JSON):
"""
Route data object with format methods.
:param route: url to call, with optional templating.
:param method: http method
:param content_type:
"""
self.route = route
self.method = method
self.route_params = _route_params_pattern.findall(self.route)
self.route_params_length = len(self.route_params)
self.content_type = content_type
class _MetaCall(type):
def __new__(mcs, name, bases, attributes):
new_attrs = dict(**attributes)
def _route_wrap(func):
@functools.wraps(func)
def _inner(self: Caller, *args, **kwargs):
route: CallRoute = func(self, *args, **kwargs)
promise = self.call(route, args, **kwargs)
return promise
return _inner
for route_name in filter(lambda k: k.startswith('call_'), attributes.keys()):
new_attrs[route_name] = _route_wrap(attributes[route_name])
return type.__new__(mcs, name, bases, new_attrs)
[docs]class Caller(metaclass=_MetaCall):
"""
Wraps all method starting with `call_` with a call.
**route methods must:**
- return a CallRoute object.
- kwargs must be there if you want Caller.call and route params kwargs.
"""
[docs] def __init__(self, base_url: str='',
promise_container: BasePromiseContainer=None,
prom_type=Promise,
prom_args: dict=None):
"""
:param base_url:
:param promise_container:
:param prom_type:
:param prom_args:
"""
self.base_url = base_url
self.promise_container = promise_container
self.prom_type = prom_type
self.prom_args = prom_args or {}
[docs] def call(self,
route: CallRoute,
route_params: list=None,
params: dict=None,
headers: dict=None,
origin_req_host=None,
unverifiable: bool=False,
data=None,
**kwargs) -> Promise:
"""
Call a route, used by the wrapped route methods.
:param route:
:param route_params:
:param params:
:param headers:
:param origin_req_host:
:param unverifiable:
:param data:
:param kwargs:
:return:
"""
url = f'{self.base_url}{route.format_route_params(*route_params)}'
if params:
url = encode_url_params(url, params)
self.before_call(route, route_params, params)
headers = headers or {}
headers['Content-Type'] = route.content_type
_data = route.format_data(data)
promise = urlcall.url_call(url,
data=_data,
method=route.method,
headers=headers,
origin_req_host=origin_req_host,
unverifiable=unverifiable,
prom_type=self.prom_type,
**self.prom_args)
promise.complete(lambda result, error: self.after_call(route, route_params, params, result, error))
if self.promise_container:
self.promise_container.add_promise(promise)
return promise
[docs] def before_call(self, route: CallRoute, route_params: list, params: dict):
"""
global before call callback.
:param route: The route that was called.
:param route_params: The params of the route if any
:param params: The url params
:return:
"""
pass
[docs] def after_call(self, route: CallRoute, route_params: list, params: dict, result: Any, error: Any):
"""
global after call callback
:param route: The route that was called.
:param route_params: The params of the route if any
:param params: The url params
:param result: The result of the call if any
:param error: The error of the call if any
:return:
"""
pass