Coverage for src/lexigram/graphql/directives/registry.py: 100%

23 statements  

« prev     ^ index     » next       coverage.py v7.15.4, created at 2026-08-25 04:37 +0800

1"""Registry implementation for GraphQL directive handlers. 

2 

3This module was split out of the package root to keep ``__init__`` focused 

4on exports. ``DirectiveRegistry`` satisfies 

5:class:`~lexigram.contracts.graphql.DirectiveHandler`. 

6""" 

7 

8from __future__ import annotations 

9 

10from typing import Any 

11 

12 

13class DirectiveRegistry: 

14 """Registry-based implementation of :class:`~lexigram.contracts.graphql.DirectiveHandler`. 

15 

16 Maps directive names to handler callables via :meth:`on` (decorator syntax 

17 or explicit :meth:`register` call). When :meth:`apply_directive` is invoked 

18 with an unknown directive name, the target is returned unchanged if no 

19 default handler is registered, or the default handler is called. 

20 

21 Args: 

22 default_handler: Optional fallback for unrecognised directives. 

23 Receives the same ``(directive_name, args, target)`` signature. 

24 When ``None`` (default), unknown directives are silently ignored. 

25 """ 

26 

27 def __init__( 

28 self, 

29 default_handler: Any | None = None, 

30 ) -> None: 

31 self._handlers: dict[str, Any] = {} 

32 self._default = default_handler 

33 

34 def register( 

35 self, 

36 directive_name: str, 

37 handler: Any, 

38 ) -> None: 

39 """Register a handler callable for a directive. 

40 

41 Args: 

42 directive_name: Name of the GraphQL directive (without ``@``). 

43 handler: Callable ``(directive_name, args, target) -> target``. 

44 """ 

45 self._handlers[directive_name] = handler 

46 

47 def on(self, directive_name: str) -> Any: 

48 """Decorator that registers a handler for *directive_name*. 

49 

50 Example:: 

51 

52 @registry.on("auth") 

53 def apply_auth(name, args, target): 

54 target.__roles__ = args.get("roles", []) 

55 return target 

56 

57 Args: 

58 directive_name: Name of the directive. 

59 

60 Returns: 

61 Decorator that registers the decorated function. 

62 """ 

63 

64 def decorator(func: Any) -> Any: 

65 self.register(directive_name, func) 

66 return func 

67 

68 return decorator 

69 

70 def apply_directive( 

71 self, 

72 directive_name: str, 

73 args: dict[str, Any], 

74 target: Any, 

75 ) -> Any: 

76 """Apply a registered directive handler to *target*. 

77 

78 If no handler is registered under *directive_name* the default handler 

79 is called if one was supplied; otherwise *target* is returned as-is. 

80 

81 Args: 

82 directive_name: Directive to apply (without ``@``). 

83 args: Directive arguments from the schema. 

84 target: Schema element to transform. 

85 

86 Returns: 

87 The (possibly transformed) *target*. 

88 """ 

89 handler = self._handlers.get(directive_name, self._default) 

90 if handler is None: 

91 return target 

92 return handler(directive_name, args, target) 

93 

94 def __contains__(self, directive_name: str) -> bool: 

95 """Return ``True`` if a handler is registered for *directive_name*.""" 

96 return directive_name in self._handlers 

97 

98 def __repr__(self) -> str: 

99 names = sorted(self._handlers) 

100 return f"DirectiveRegistry(directives={names!r})"