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
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-16 10:13 -0700
1"""Bot execution context."""
3from dataclasses import dataclass, field
4from typing import Any
7@dataclass
8class BotContext:
9 """Runtime context for bot execution.
11 Supports dict-like access for dynamic attributes via request_metadata.
12 Use `context["key"]` or `context.get("key")` for dynamic data.
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 """
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)
28 def __getitem__(self, key: str) -> Any:
29 """Get item from request_metadata using dict-like access.
31 Args:
32 key: Key to retrieve
34 Returns:
35 Value from request_metadata
37 Raises:
38 KeyError: If key not found in request_metadata
39 """
40 return self.request_metadata[key]
42 def __setitem__(self, key: str, value: Any) -> None:
43 """Set item in request_metadata using dict-like access.
45 Args:
46 key: Key to set
47 value: Value to store
48 """
49 self.request_metadata[key] = value
51 def __contains__(self, key: str) -> bool:
52 """Check if key exists in request_metadata.
54 Args:
55 key: Key to check
57 Returns:
58 True if key exists in request_metadata
59 """
60 return key in self.request_metadata
62 def get(self, key: str, default: Any = None) -> Any:
63 """Get item from request_metadata with optional default.
65 Args:
66 key: Key to retrieve
67 default: Default value if key not found
69 Returns:
70 Value from request_metadata or default
71 """
72 return self.request_metadata.get(key, default)
74 def copy(self, **overrides: Any) -> "BotContext":
75 """Create a copy of this context with optional field overrides.
77 Creates shallow copies of session_metadata and request_metadata dicts
78 to avoid mutation issues between the original and copy.
80 Args:
81 **overrides: Field values to override in the copy
83 Returns:
84 New BotContext instance with copied values
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 )