Coverage for src / dataknobs_bots / bot / context.py: 74%

19 statements  

« prev     ^ index     » next       coverage.py v7.13.0, created at 2025-12-16 10:13 -0700

1"""Bot execution context.""" 

2 

3from dataclasses import dataclass, field 

4from typing import Any 

5 

6 

7@dataclass 

8class BotContext: 

9 """Runtime context for bot execution. 

10 

11 Supports dict-like access for dynamic attributes via request_metadata. 

12 Use `context["key"]` or `context.get("key")` for dynamic data. 

13 

14 Attributes: 

15 conversation_id: Unique identifier for the conversation 

16 client_id: Identifier for the client/tenant 

17 user_id: Optional user identifier 

18 session_metadata: Metadata for the session 

19 request_metadata: Metadata for the current request (also used for dict-like access) 

20 """ 

21 

22 conversation_id: str 

23 client_id: str 

24 user_id: str | None = None 

25 session_metadata: dict[str, Any] = field(default_factory=dict) 

26 request_metadata: dict[str, Any] = field(default_factory=dict) 

27 

28 def __getitem__(self, key: str) -> Any: 

29 """Get item from request_metadata using dict-like access. 

30 

31 Args: 

32 key: Key to retrieve 

33 

34 Returns: 

35 Value from request_metadata 

36 

37 Raises: 

38 KeyError: If key not found in request_metadata 

39 """ 

40 return self.request_metadata[key] 

41 

42 def __setitem__(self, key: str, value: Any) -> None: 

43 """Set item in request_metadata using dict-like access. 

44 

45 Args: 

46 key: Key to set 

47 value: Value to store 

48 """ 

49 self.request_metadata[key] = value 

50 

51 def __contains__(self, key: str) -> bool: 

52 """Check if key exists in request_metadata. 

53 

54 Args: 

55 key: Key to check 

56 

57 Returns: 

58 True if key exists in request_metadata 

59 """ 

60 return key in self.request_metadata 

61 

62 def get(self, key: str, default: Any = None) -> Any: 

63 """Get item from request_metadata with optional default. 

64 

65 Args: 

66 key: Key to retrieve 

67 default: Default value if key not found 

68 

69 Returns: 

70 Value from request_metadata or default 

71 """ 

72 return self.request_metadata.get(key, default) 

73 

74 def copy(self, **overrides: Any) -> "BotContext": 

75 """Create a copy of this context with optional field overrides. 

76 

77 Creates shallow copies of session_metadata and request_metadata dicts 

78 to avoid mutation issues between the original and copy. 

79 

80 Args: 

81 **overrides: Field values to override in the copy 

82 

83 Returns: 

84 New BotContext instance with copied values 

85 

86 Example: 

87 >>> ctx = BotContext(conversation_id="conv-1", client_id="client-1") 

88 >>> ctx2 = ctx.copy(conversation_id="conv-2") 

89 >>> ctx2.conversation_id 

90 'conv-2' 

91 """ 

92 return BotContext( 

93 conversation_id=overrides.get("conversation_id", self.conversation_id), 

94 client_id=overrides.get("client_id", self.client_id), 

95 user_id=overrides.get("user_id", self.user_id), 

96 session_metadata=overrides.get( 

97 "session_metadata", dict(self.session_metadata) 

98 ), 

99 request_metadata=overrides.get( 

100 "request_metadata", dict(self.request_metadata) 

101 ), 

102 )