PK!2ssformation/__init__.pyfrom .__version__ import __version__ # noqa from .formation import wrap, _CONTEXT __all__ = ["wrap", "_CONTEXT"] PK!Ӗformation/__version__.py__version__ = "0.1.30"PK!Mw:formation/for_requests.pyimport requests from requests.compat import urljoin from .formation import wrap, _REQ_HTTP, _RES_HTTP, _SESSION from attr import attrib, attrs from lxml import html from toolz.curried import keyfilter, reduce import xmltodict import datetime __all__ = ["build_sender", "build", "client"] def client(cls=None): def client_decorator(cls): original_init = cls.__init__ def now_iso(self): return datetime.datetime.utcnow().isoformat() def path(self, p): return requests.compat.urljoin(self.base_uri, p) def init(self, *args, **kwargs): original_init(self, *args, **kwargs) base_uri = kwargs.get( "base_uri", getattr(self.__class__, "base_uri", "http://localhost") ) response_as = kwargs.get( "response_as", getattr(self.__class__, "response_as", None) ) self.request = build( middleware=kwargs.get( "middleware", getattr(self.__class__, "middleware", []) ), base_uri=base_uri, response_as=response_as, ) self.base_uri = base_uri cls.path = path cls.now_iso = now_iso cls.__init__ = init return cls if cls: return client_decorator(cls) return client_decorator @attrs class FormationHttpRequest(object): url = attrib() method = attrib(default="get") headers = attrib(default={}) params = attrib(default={}) auth = attrib(default=None) data = attrib(default=None) timeout = attrib(default=None) def params_filter(p): return p.startswith(":") def not_params_filter(p): return not params_filter(p) def apply_params(url, params): route_params = keyfilter(params_filter, params) return ( reduce(lambda acc, kv: acc.replace(kv[0], kv[1]), route_params.items(), url), keyfilter(not_params_filter, params), ) def get_response(ctx): return ctx.get(_RES_HTTP, None) @staticmethod def raw_response(ctx): res = get_response(ctx) if not res: return (None, None, None) return (res, res.status_code, res.headers) @staticmethod def json_response(ctx): res = get_response(ctx) if not res: return (None, None, None) return (res.json(), res.status_code, res.headers) @staticmethod def xmltodict_response(ctx): res = get_response(ctx) if not res: return (None, None, None) return (xmltodict.parse(res.text), res.status_code, res.headers) @staticmethod def html_response(ctx): res = get_response(ctx) if not res: return (None, None, None) return (html.fromstring(res.content), res.status_code, res.headers) @staticmethod def text_response(ctx): res = get_response(ctx) if not res: return (None, None, None) return (res.text, res.status_code, res.headers) def build_sender(middleware=[], base_uri=None, response_as=None): wrapped = wrap(requests_adapter, middleware=middleware) def sender(method, url, session_context={}, params={}, **kwargs): resolved_response_as = kwargs.get("response_as", response_as) or raw_response params = params if isinstance(params, dict) else params.to_dict() (url, params) = apply_params(url, params) ctx = { _REQ_HTTP: FormationHttpRequest( url=urljoin(base_uri, url), method=method, params=params, **kwargs ), _SESSION: session_context, } ctx = wrapped(ctx) return resolved_response_as(ctx) return sender class Sender(object): def __init__(self, send): self.send = send def get(self, path, **kwargs): return self.send("get", path, **kwargs) def post(self, path, **kwargs): return self.send("post", path, **kwargs) def put(self, path, **kwargs): return self.send("put", path, **kwargs) def build(middleware=[], base_uri=None, response_as=None): return Sender( build_sender(middleware=middleware, base_uri=base_uri, response_as=response_as) ) # TODO: timeout (middleware) # TODO: pass more requests vars via req (e.g. timeout, retry) def requests_adapter(ctx): req = ctx[_REQ_HTTP] meth = getattr(requests, req.method.lower()) # TODO ship var as kwargs and not explicitly res = meth( req.url, headers=req.headers, params=req.params, auth=req.auth, data=req.data, timeout=req.timeout, ) ctx[_RES_HTTP] = res return ctx PK!formation/formation.pyfrom toolz import reduce _REQ_HTTP = "fmtn.req.http" _RES_HTTP = "fmtn.res.http" _CONTEXT = "fmtn.context" _SESSION = "fmtn.session" _RETRY = "fmtn.retry" _REQ_ID = "req.id" _UID = "uid" _REQ_PARENT_ID = "req.parent.id" _REQ_DURATION = "req.duration_us" def wrap(call, middleware=[]): return reduce( lambda acc, m: lambda ctx: m(ctx, acc), reversed(middleware), lambda ctx: call(ctx), ) PK!( formation/middleware/__init__.pyfrom .breaker import circuit_breaker, trigger_breaker_if # noqa from .context import context from .context_logger import context_logger # noqa from .logger import request_logger from .request_duration import request_duration from .request_id import request_id from .retry import retry # noqa from .ua import ua # noqa from .accept import accept # noqa from .timeout import timeout # noqa def default_stack(logger): return [request_id(), context(), request_duration(), request_logger(logger)] PK! "formation/middleware/accept.pyfrom ..formation import _REQ_HTTP def accept(mime_type): def accept_middleware(ctx, call): req = ctx.get(_REQ_HTTP) req.headers["Content-Type"] = mime_type return call(ctx) return accept_middleware PK!;  formation/middleware/breaker.pyimport pybreaker from ..formation import _CONTEXT, _RES_HTTP class BreakerTriggerException(Exception): pass def breaker_logger(logger): class LogListener(pybreaker.CircuitBreakerListener): "Listener used to log circuit breaker events." def state_change(self, cb, old_state, new_state): logger.warn( "circuitbreaker.state_changed", name=cb.name, old_state=old_state.name, new_state=new_state.name, ) return LogListener() def trigger_breaker_if(trigger): def trigger_breaker_middleware(ctx, call): ctx = call(ctx) if trigger(ctx.get(_RES_HTTP)): raise BreakerTriggerException return trigger_breaker_middleware def circuit_breaker( logger, name, fail_max=5, reset_timeout=60, state_storage=None, exclude=[] ): breaker = pybreaker.CircuitBreaker( name=name, listeners=[breaker_logger(logger)], exclude=exclude, fail_max=fail_max, reset_timeout=reset_timeout, state_storage=state_storage, ) def circuit_breaker_middleware(ctx, call): context = ctx.get(_CONTEXT, {}) log = logger.bind(**context) if breaker.current_state == "open": log.info("circuitbreaker.open", name=breaker.name) call = breaker(call) try: ctx = call(ctx) return ctx except pybreaker.CircuitBreakerError: return ctx return circuit_breaker_middleware PK!formation/middleware/context.pyfrom ..formation import _CONTEXT, _REQ_ID, _REQ_PARENT_ID, _SESSION, _UID import os from six.moves import _thread as thread from toolz.curried import get_in def get_context( request_id=None, request_parent_id=None, namespace="service", env="local", sha="dev", version="0.0.1", scope="service", uid=None, getpid=os.getpid, gettid=thread.get_ident, ): pid = getpid() tid = gettid() return { "v": version, "sha": sha, "env": env, "pid": pid, "tid": tid, "uid": uid, "scope": scope, "ns": namespace, "rid": request_id, "rid_p": request_parent_id, } def context( context_fn=get_context, namespace="service", scope="all", env="local", sha="dev", version="0.01", getpid=os.getpid, gettid=thread.get_ident, ): def context_middleware(ctx, call): request_id = ctx.get(_REQ_ID, None) request_parent_id = ctx.get(_REQ_PARENT_ID, None) uid = get_in([_SESSION, _UID], None) ctx[_CONTEXT] = context_fn( env=env, sha=sha, version=version, request_id=request_id, request_parent_id=request_parent_id, scope=scope, uid=uid, getpid=getpid, gettid=gettid, ) ctx = call(ctx) return ctx return context_middleware PK!$3::&formation/middleware/context_logger.pyfrom ..formation import _REQ_HTTP, _RES_HTTP, _CONTEXT, _REQ_DURATION def context_logger(logger): def context_logger_middleware(ctx, next): context = ctx.get(_CONTEXT, {}) logger.bind(**context).info("context") ctx = next(ctx) return ctx return context_logger_middleware PK!%βformation/middleware/logger.pyfrom ..formation import _REQ_HTTP, _RES_HTTP, _CONTEXT, _REQ_DURATION from toolz.curried import valfilter def request_logger(logger): no_nones = valfilter(lambda x: x) def request_logger_middleware(ctx, next): req = ctx[_REQ_HTTP] context = ctx.get(_CONTEXT, {}) msg = "request.http" log = logger.bind(**context) log.info(msg, url=req.url, method=req.method, params=no_nones(req.params)) log.debug(msg, headers=req.headers) ctx = next(ctx) res = ctx[_RES_HTTP] msg = "response.http" log.info( msg, url=res.request.url, status=res.status_code, method=res.request.method, elapsed=res.elapsed, size=len(res.content), duration_us=ctx.get(_REQ_DURATION, None), ) log.debug(msg, headers=res.headers) return ctx return request_logger_middleware PK!**(formation/middleware/request_duration.pyimport datetime def request_duration(now=datetime.datetime.now): def request_duration_middleware(ctx, next): start = now() ctx = next(ctx) end = now() - start ctx["req.duration_us"] = end.microseconds return ctx return request_duration_middleware PK!oo"formation/middleware/request_id.pyfrom ..formation import _REQ_HTTP, _REQ_ID from uuid import uuid4 def request_id(key="x-request-id", idgen=uuid4): def request_id_middleware(ctx, next): headers = ctx[_REQ_HTTP].headers headers[key] = headers.get(key, str(idgen())) ctx[_REQ_ID] = headers[key] ctx = next(ctx) return ctx return request_id_middleware PK!Ztformation/middleware/retry.pyfrom ..formation import _RETRY def retry(max_retries=3): def retry_middleware(ctx, call): try: res = call(ctx) return res except Exception as ex: retries = ctx.get(_RETRY, 0) if retries >= max_retries - 1: raise ex ctx[_RETRY] = 1 + retries # TODO exponential backoff res = retry_middleware(ctx, call) return res return retry_middleware PK!&formation/middleware/timeout.pyfrom ..formation import _REQ_HTTP def timeout(timeout=None): def timeout_middleware(ctx, call): req = ctx.get(_REQ_HTTP) if timeout: req.timeout = timeout return call(ctx) return timeout_middleware PK!"Bformation/middleware/ua.pyfrom ..formation import _REQ_HTTP def ua(user_agent="formation/1.0.0"): def ua_middleware(ctx, call): req = ctx.get(_REQ_HTTP) req.headers["User-Agent"] = user_agent return call(ctx) return ua_middleware PK!H9VWX formation-0.1.30.dist-info/WHEEL A н#f."jm)!fb҅~ܴA,mTD}E n0H饹*|D[¬c i=0(q3PK!Hp/u6 #formation-0.1.30.dist-info/METADATAVao6_qm.l9vڵ3YڡҮKCĴxXKFRa}e ݻ߱RxSFi1͍-h8'uU®tF)k*y9Jʜ2)=y[%WxkGoMRy_`*U81¥JX;]Ly4V:p̅[GSӳgλގAo+:َB{UY 15Q~Htʲ7t?уӓ8~aNÓ=|?A۩bdF1r}Agei=?1`MjEQ(%RPƀo8'ߊE@+ҼYy+Ɣ+wz:<>Gd  0_y@= e`z? ![(!W-oJ|7QJ9߮.`kMJ_qiƮ5Ī2F{O%0 i:v J ƥNQ~ںǬG4,Q4Nu4 =*Jc=ild7K+ն7)(Lܵ%7,PN-8:Y1uz{ U'yG^5TabQFLJn!!ʷɇvڨvTYB|;`U[uS&q;,unJK*yu SZ:화jynu +YW\QDnt*u5[ɭ&adÃ&y nϡ `&dh+'w |.]u mk7zN~,H8I1&Jlw~$]eM۳6=ƫFA0l11JK\N-TlMiIźN3yP|!~WQTƓşq*]RsJ5k/`5]VEM`"$J|a7F=Êes!4Gy(g۶jXFCj~wV 9"@N:A009V:Mv(J5֯!q ^ Ki+ 6q}6ЎjIP!:Ma?Ui Pc4]~h #RP9//v?  oFmbOJ&Ԫvp8;}'0""8u+iU