Coverage for src/lexigram/web/pipes/decorators.py: 42%
19 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-25 04:37 +0800
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-25 04:37 +0800
1"""Pipes decorators for Lexigram Framework.
3Provides @use_pipes for applying pipes to controllers and handlers.
4"""
6from __future__ import annotations
8from collections.abc import Callable
9import inspect
10from typing import Any, TypeVar, cast
12from lexigram.web.protocols import PipeProtocol
14T = TypeVar("T")
17def use_pipes(*pipe_instances: PipeProtocol) -> Callable[[T], T]:
18 """Decorator to apply pipes to a class or method.
20 Stores pipe metadata on the target for runtime resolution by the ParameterBinder.
21 Pipes applied at the class level are inherited by all handler methods.
23 Example::
25 @use_pipes(ValidationPipe())
26 class UserController(Controller):
27 @post("/users")
28 async def create(self, user: User):
29 ...
31 Args:
32 *pipe_instances: PipeProtocol instances to apply.
34 Returns:
35 The decorated class or function.
36 """
38 def decorator(target: T) -> T:
39 if inspect.isclass(target):
40 # Apply to all methods of the class
41 for name, method in inspect.getmembers(
42 target,
43 predicate=inspect.isfunction,
44 ):
45 if not name.startswith("_"): # Skip private methods
46 existing: list[PipeProtocol] = (
47 getattr(method, "_lexigram_pipes", None) or []
48 )
49 # Prepend class-level pipes so they run before method-level ones
50 cast("Any", method)._lexigram_pipes = (
51 list(pipe_instances) + existing
52 )
53 return cast("T", target)
55 # Apply to a single function/method
56 existing_pipes: list[PipeProtocol] = (
57 getattr(target, "_lexigram_pipes", None) or []
58 )
59 cast("Any", target)._lexigram_pipes = existing_pipes + list(pipe_instances)
60 return target
62 return decorator
65__all__ = ["use_pipes"]