PK!f^^formation/__init__.pyfrom .__version__ import __version__ # noqa from .formation import wrap __all__ = ["wrap"] PK!#jbformation/__version__.py__version__ = "0.1.27"PK!*. formation/for_requests.pyimport requests from requests.compat import urljoin from .formation import wrap, _REQ_HTTP, _RES_HTTP, _SESSION from attr import attrib, attrs 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", self.__class__.base_uri) self.request = build( middleware=kwargs.get("middleware", self.__class__.middleware), base_uri=base_uri, ) 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) def build_sender(middleware=[], base_uri=None): wrapped = wrap(requests_adapter, middleware=middleware) def sender(method, url, session_context={}, **kwargs): ctx = { _REQ_HTTP: FormationHttpRequest( url=urljoin(base_uri, url), method=method, **kwargs ), _SESSION: session_context, } ctx = wrapped(ctx) return ctx[_RES_HTTP] 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): return Sender(build_sender(middleware=middleware, base_uri=base_uri)) # 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 ) 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! /m]formation/middleware.pyfrom .formation import ( _REQ_HTTP, _CONTEXT, _SESSION, _RES_HTTP, _RETRY, _REQ_ID, _REQ_PARENT_ID, _REQ_DURATION, _UID, ) from six.moves import _thread as thread import datetime from uuid import uuid4 from toolz.curried import valfilter, get_in import pybreaker import os def default_stack(logger): return [ create_request_id(), create_context(), create_request_duration(), create_request_logger(logger), ] def create_request_id(key="x-request-id", idgen=uuid4): def requests_id(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 requests_id def create_request_duration(now=datetime.datetime.now): def request_duration(ctx, next): start = now() ctx = next(ctx) end = now() - start ctx["req.duration_us"] = end.microseconds return ctx return request_duration 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 create_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(ctx, call): request_id = ctx[_REQ_ID] 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 def create_request_logger(logger): no_nones = valfilter(lambda x: x) def request_logger(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 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 create_circuit_breaker(logger, name): breaker = pybreaker.CircuitBreaker(name=name, listeners=[breaker_logger(logger)]) def circuit_breaker(ctx, call): context = ctx.get(_CONTEXT, {}) log = logger.bind(**context) if breaker.current_state == "open": log.info("circuitbreaker.middleware.open", name=breaker.name) call = breaker(call) try: ctx = call(ctx) return ctx except pybreaker.CircuitBreakerError: return ctx return circuit_breaker 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: raise ex ctx[_RETRY] = 1 + retries # TODO exponential backoff res = retry_middleware(ctx, call) return res return retry_middleware PK!H9VWX formation-0.1.27.dist-info/WHEEL A н#f."jm)!fb҅~ܴA,mTD}E n0H饹*|D[¬c i=0(q3PK!H#Uy ? #formation-0.1.27.dist-info/METADATAVnF}WL I뤈Q'Mm} kҒZ{-Ĩ`93sf ))4JN1eVa{y$gUSU®tJ9k*i*%eKaάp6op a 譩xP k7sf~4Z?SP)kI3Jp(tnly3yE "16^hz/<p%T9.ЯpDgT4/WtZܲ `}kr+J.@AU!\O}ɓ{[Dו1 ﭣ'ϓQqsLƔq@}|^-$yO@nd@}),ȩvy6k U%)o_KhЃɺƬy^!&}!RGуu\TbB6uދGU?B4AIPibE٬^G5aAW' RUm'mbҊ{kM>$gǩ_)eG_mz{Ce !,#%Н@TGuE`kOdc۔jw07;Bzilyt.fB"A'жQS!ny Upm,ؗyJZ kHelƭƃ@sVZ6P+PixXhAFfRpPi8P78ܝq4 ð*TaDB:3ڪ^]Rht|򜦯@Pdv3M/޼zso.F[,CDPK!HDP(b!formation-0.1.27.dist-info/RECORD}ѻv@>F(NAnAj #>IeIWkdm1m񪛹$ [fm-ox&GyP!p2N^u͆8FARt.}(}O=}PշHNՐocb(.C_q{V\)^WuZ4#ao;=a(= |Ԃ6F(H899X vIO4MO#CXM | z>m-0uB3eҔMp/Cל$K__R:N