railtracks.middleware
1from railtracks.middleware.after import after_node 2from railtracks.middleware.core import ( 3 Middleware, 4 wrap_node, 5) 6from railtracks.middleware.post import post_node 7from railtracks.middleware.verdict import Verdict, VerifierRejectedError 8 9__all__ = [ 10 "post_node", 11 "after_node", 12 "Middleware", 13 "wrap_node", 14 "Verdict", 15 "VerifierRejectedError", 16]
31def post_node( 32 fn: Callable[[_R], Awaitable[_R]] | Callable[[_R], _R] | None = None, 33 /, 34 *, 35 name: str | None = None, 36) -> ( 37 Middleware[..., _R] 38 | Callable[ 39 [Callable[[_R], Awaitable[_R]] | Callable[[_R], _R]], Middleware[..., _R] 40 ] 41): 42 """ 43 Special decorator to create a middleware that runs after the node completes. The wrapped function will run and then your post function will be called upon successful completion of the function. 44 45 NOTE: This middleware will not run if the node raises an exception. 46 """ 47 48 if fn is None: 49 return lambda f: wrap_node(_wrapper(f), name=name) 50 51 return wrap_node(_wrapper(fn), name=name)
Special decorator to create a middleware that runs after the node completes. The wrapped function will run and then your post function will be called upon successful completion of the function.
NOTE: This middleware will not run if the node raises an exception.
28def after_node( 29 fn: Callable[[_R], Awaitable[_R]] | Callable[[_R], _R] | None = None, 30 /, 31 *, 32 name: str | None = None, 33) -> Any: 34 """Deprecated: Use ``rt.post_node`` instead.""" 35 warn_pending_change( 36 "rt.after_node", 37 change="is renamed", 38 instead="rt.post_node", 39 detail="The function itself is unchanged.", 40 ) 41 if fn is None: 42 return post_node(name=name) 43 return post_node(fn, name=name)
Deprecated: Use rt.post_node instead.
41class Middleware(Generic[_P, _R]): 42 """Execution-control middleware: wraps a callable to control how it is invoked. 43 44 Built from an async call-style function ``fn(call, *args, **kwargs)`` where 45 ``call`` is the next callable in the chain:: 46 47 @wrap_node 48 async def retry(call, *args, **kwargs): 49 for _ in range(3): 50 try: 51 return await call(*args, **kwargs) 52 except Exception: 53 pass 54 raise RuntimeError("All retries exhausted") 55 56 ``fn`` must be ``async``; passing a plain ``def`` raises ``TypeError``. 57 """ 58 59 def __init__( 60 self, 61 fn: Callable[Concatenate[Callable[_P, Awaitable[_R]], _P], Awaitable[_R]], 62 name: str | None = None, 63 ) -> None: 64 _require_async(fn, "Middleware function") 65 self._fn = fn 66 self.name = name if name is not None else fn.__name__ 67 self.type_id = str( 68 uuid.uuid4() 69 ) # identifies this middleware definition, shared by every invocation 70 71 self._has_registered = False 72 73 async def start_creation_task(self): 74 if self._has_registered: 75 return 76 77 self._has_registered = True 78 event = MiddlewareCreationEvent( 79 middleware_type_id=self.type_id, 80 middleware_name=self.name, 81 ) 82 return await emit(event) 83 84 def wrap(self, inner: Callable[_P, Awaitable[_R]]) -> Callable[_P, Awaitable[_R]]: 85 """Compose this middleware onto ``inner``, returning a new callable with the same signature.""" 86 fn = self._fn 87 88 async def wrapped(*args: _P.args, **kwargs: _P.kwargs) -> _R: 89 return await fn(inner, *args, **kwargs) 90 91 return wrapped 92 93 def __repr__(self) -> str: 94 return f"Middleware({self._fn.__name__})" 95 96 def middleware_type(self) -> str: 97 return "General"
Execution-control middleware: wraps a callable to control how it is invoked.
Built from an async call-style function fn(call, *args, **kwargs) where
call is the next callable in the chain::
@wrap_node
async def retry(call, *args, **kwargs):
for _ in range(3):
try:
return await call(*args, **kwargs)
except Exception:
pass
raise RuntimeError("All retries exhausted")
fn must be async; passing a plain def raises TypeError.
59 def __init__( 60 self, 61 fn: Callable[Concatenate[Callable[_P, Awaitable[_R]], _P], Awaitable[_R]], 62 name: str | None = None, 63 ) -> None: 64 _require_async(fn, "Middleware function") 65 self._fn = fn 66 self.name = name if name is not None else fn.__name__ 67 self.type_id = str( 68 uuid.uuid4() 69 ) # identifies this middleware definition, shared by every invocation 70 71 self._has_registered = False
84 def wrap(self, inner: Callable[_P, Awaitable[_R]]) -> Callable[_P, Awaitable[_R]]: 85 """Compose this middleware onto ``inner``, returning a new callable with the same signature.""" 86 fn = self._fn 87 88 async def wrapped(*args: _P.args, **kwargs: _P.kwargs) -> _R: 89 return await fn(inner, *args, **kwargs) 90 91 return wrapped
Compose this middleware onto inner, returning a new callable with the same signature.
10@dataclass 11class Verdict(Generic[_R]): 12 """The result of an approve callable's review of a node call. 13 14 - accept: ``accepted=True``, ``comment=None`` 15 - accept with comments: ``accepted=True``, ``comment=<str>``, optionally 16 ``args``/``kwargs`` (pre-call) or ``result`` (post-call) set to override 17 what gets forwarded. 18 - decline: ``accepted=False``, ``comment=None`` 19 - decline with comments: ``accepted=False``, ``comment=<str>`` 20 21 ``args``/``kwargs`` mean "forward these into the call instead" — used by 22 pre-call verifiers, which review before the node runs. ``result`` means 23 "propagate this instead of what the call produced" — used by post-call 24 verifiers, which review after the node has already run and can no longer 25 change what was passed in, only what continues onward. 26 27 ``Verdict`` is generic over ``result``'s type, matching the wrapped 28 node's return type, so a ``result=`` override of the wrong type is a 29 type-checker error rather than a silent runtime mismatch. There is no 30 runtime validation of ``args``/``kwargs`` overrides against the node's 31 signature — a bad override surfaces as a ``TypeError`` from the call 32 itself. 33 """ 34 35 accepted: bool 36 comment: str | None = None 37 args: tuple | None = None 38 kwargs: dict | None = None 39 result: _R | None = None
The result of an approve callable's review of a node call.
- accept:
accepted=True,comment=None - accept with comments:
accepted=True,comment=<str>, optionallyargs/kwargs(pre-call) orresult(post-call) set to override what gets forwarded. - decline:
accepted=False,comment=None - decline with comments:
accepted=False,comment=<str>
args/kwargs mean "forward these into the call instead" — used by
pre-call verifiers, which review before the node runs. result means
"propagate this instead of what the call produced" — used by post-call
verifiers, which review after the node has already run and can no longer
change what was passed in, only what continues onward.
Verdict is generic over result's type, matching the wrapped
node's return type, so a result= override of the wrong type is a
type-checker error rather than a silent runtime mismatch. There is no
runtime validation of args/kwargs overrides against the node's
signature — a bad override surfaces as a TypeError from the call
itself.
42class VerifierRejectedError(Exception): 43 """Raised when a verifier's approve callable declines a node call."""
Raised when a verifier's approve callable declines a node call.