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

1"""Bot execution context."""

2

3 from dataclasses import dataclass, field

4 from typing import Any

5

6

7@dataclass

8 class 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 )