0
# State Management
1
2
Persistent state management across conversation turns with support for different scopes (user, conversation, private conversation) and custom storage implementations. Includes state property accessors and automatic state persistence.
3
4
## Capabilities
5
6
### BotState Base Class
7
8
Abstract base class that provides the foundation for all state management in the Bot Framework, implementing property management, loading, and saving state data with storage backend integration.
9
10
```python { .api }
11
class BotState:
12
def __init__(self, storage, context_service_key: str):
13
"""
14
Initialize bot state.
15
16
Args:
17
storage (Storage): Storage implementation
18
context_service_key (str): Key for storing state in turn context
19
"""
20
21
def create_property(self, name: str):
22
"""
23
Create a state property accessor.
24
25
Args:
26
name (str): Name of the property
27
28
Returns:
29
StatePropertyAccessor: Property accessor
30
"""
31
32
async def load(self, turn_context: TurnContext, force: bool = False):
33
"""
34
Load state from storage.
35
36
Args:
37
turn_context (TurnContext): Current turn context
38
force (bool): Force reload even if already loaded
39
"""
40
41
async def save_changes(self, turn_context: TurnContext, force: bool = False):
42
"""
43
Save state changes to storage.
44
45
Args:
46
turn_context (TurnContext): Current turn context
47
force (bool): Force save even if no changes detected
48
"""
49
50
async def clear_state(self, turn_context: TurnContext):
51
"""
52
Clear current state.
53
54
Args:
55
turn_context (TurnContext): Current turn context
56
"""
57
58
def get_storage_key(self, turn_context: TurnContext):
59
"""
60
Get storage key for this state. Must be implemented by derived classes.
61
62
Args:
63
turn_context (TurnContext): Current turn context
64
65
Returns:
66
str: Storage key
67
"""
68
69
async def delete(self, turn_context: TurnContext):
70
"""
71
Delete state from storage.
72
73
Args:
74
turn_context (TurnContext): Current turn context
75
"""
76
```
77
78
### ConversationState
79
80
Manages conversation-scoped state that persists across turns within a single conversation. Data is shared among all participants in the conversation.
81
82
```python { .api }
83
class ConversationState(BotState):
84
def __init__(self, storage):
85
"""
86
Initialize conversation state.
87
88
Args:
89
storage (Storage): Storage implementation for persistence
90
"""
91
92
def get_storage_key(self, turn_context: TurnContext):
93
"""
94
Get storage key based on conversation ID.
95
96
Args:
97
turn_context (TurnContext): Current turn context
98
99
Returns:
100
str: Conversation-scoped storage key
101
"""
102
```
103
104
### UserState
105
106
Manages user-scoped state that persists across conversations for a specific user. Data follows the user regardless of which conversation they're in.
107
108
```python { .api }
109
class UserState(BotState):
110
def __init__(self, storage):
111
"""
112
Initialize user state.
113
114
Args:
115
storage (Storage): Storage implementation for persistence
116
"""
117
118
def get_storage_key(self, turn_context: TurnContext):
119
"""
120
Get storage key based on user ID.
121
122
Args:
123
turn_context (TurnContext): Current turn context
124
125
Returns:
126
str: User-scoped storage key
127
"""
128
```
129
130
### PrivateConversationState
131
132
Manages private conversation state that is scoped to both user and conversation. Provides user-specific state within a particular conversation context.
133
134
```python { .api }
135
class PrivateConversationState(BotState):
136
def __init__(self, storage):
137
"""
138
Initialize private conversation state.
139
140
Args:
141
storage (Storage): Storage implementation for persistence
142
"""
143
144
def get_storage_key(self, turn_context: TurnContext):
145
"""
146
Get storage key based on user and conversation ID.
147
148
Args:
149
turn_context (TurnContext): Current turn context
150
151
Returns:
152
str: Private conversation-scoped storage key
153
"""
154
```
155
156
### StatePropertyAccessor
157
158
Provides type-safe access to individual properties within bot state, handling default values, property getting/setting, and deletion.
159
160
```python { .api }
161
class StatePropertyAccessor:
162
def __init__(self, state: BotState, name: str):
163
"""
164
Initialize property accessor.
165
166
Args:
167
state (BotState): Parent state object
168
name (str): Property name
169
"""
170
171
async def get(self, turn_context: TurnContext, default_value_or_factory=None):
172
"""
173
Get property value.
174
175
Args:
176
turn_context (TurnContext): Current turn context
177
default_value_or_factory: Default value or factory function
178
179
Returns:
180
object: Property value or default
181
"""
182
183
async def set(self, turn_context: TurnContext, value):
184
"""
185
Set property value.
186
187
Args:
188
turn_context (TurnContext): Current turn context
189
value: Value to set
190
"""
191
192
async def delete(self, turn_context: TurnContext):
193
"""
194
Delete property.
195
196
Args:
197
turn_context (TurnContext): Current turn context
198
"""
199
```
200
201
### BotStateSet
202
203
Collection of bot states that can be managed together, providing convenient methods for loading and saving multiple state objects.
204
205
```python { .api }
206
class BotStateSet:
207
def __init__(self, *bot_states):
208
"""
209
Initialize bot state set.
210
211
Args:
212
*bot_states: Variable number of BotState objects
213
"""
214
215
def add(self, bot_state: BotState):
216
"""
217
Add a bot state to the set.
218
219
Args:
220
bot_state (BotState): State to add
221
"""
222
223
async def load_all(self, turn_context: TurnContext, force: bool = False):
224
"""
225
Load all states in the set.
226
227
Args:
228
turn_context (TurnContext): Current turn context
229
force (bool): Force reload even if already loaded
230
"""
231
232
async def save_all_changes(self, turn_context: TurnContext, force: bool = False):
233
"""
234
Save changes for all states in the set.
235
236
Args:
237
turn_context (TurnContext): Current turn context
238
force (bool): Force save even if no changes detected
239
"""
240
```
241
242
### StatePropertyInfo
243
244
Provides information about state properties for introspection and debugging purposes.
245
246
```python { .api }
247
class StatePropertyInfo:
248
def __init__(self, name: str, type_name: str = None):
249
"""
250
Initialize property info.
251
252
Args:
253
name (str): Property name
254
type_name (str, optional): Type name for the property
255
"""
256
self.name = name
257
self.type_name = type_name
258
```
259
260
## Usage Examples
261
262
### Basic State Setup
263
264
```python
265
from botbuilder.core import (
266
ActivityHandler, TurnContext, ConversationState,
267
UserState, MemoryStorage, MessageFactory
268
)
269
270
class StateBot(ActivityHandler):
271
def __init__(self):
272
# Create storage
273
memory_storage = MemoryStorage()
274
275
# Create state objects
276
self.conversation_state = ConversationState(memory_storage)
277
self.user_state = UserState(memory_storage)
278
279
# Create property accessors
280
self.user_profile_accessor = self.user_state.create_property("UserProfile")
281
self.conversation_data_accessor = self.conversation_state.create_property("ConversationData")
282
283
async def on_message_activity(self, turn_context: TurnContext):
284
# Get user profile
285
user_profile = await self.user_profile_accessor.get(
286
turn_context,
287
lambda: {"name": None, "message_count": 0}
288
)
289
290
# Get conversation data
291
conversation_data = await self.conversation_data_accessor.get(
292
turn_context,
293
lambda: {"turn_count": 0}
294
)
295
296
# Update data
297
user_profile["message_count"] += 1
298
conversation_data["turn_count"] += 1
299
300
# Save changes
301
await self.conversation_state.save_changes(turn_context)
302
await self.user_state.save_changes(turn_context)
303
304
# Send response
305
reply = f"User messages: {user_profile['message_count']}, Turn: {conversation_data['turn_count']}"
306
await turn_context.send_activity(MessageFactory.text(reply))
307
```
308
309
### Complex State Management
310
311
```python
312
class UserProfile:
313
def __init__(self):
314
self.name = None
315
self.age = None
316
self.preferences = {}
317
self.last_activity = None
318
319
class ConversationData:
320
def __init__(self):
321
self.topic = None
322
self.turn_count = 0
323
self.participants = []
324
325
class AdvancedStateBot(ActivityHandler):
326
def __init__(self):
327
memory_storage = MemoryStorage()
328
329
self.conversation_state = ConversationState(memory_storage)
330
self.user_state = UserState(memory_storage)
331
self.private_conversation_state = PrivateConversationState(memory_storage)
332
333
# Create typed property accessors
334
self.user_profile_accessor = self.user_state.create_property("UserProfile")
335
self.conversation_data_accessor = self.conversation_state.create_property("ConversationData")
336
self.private_data_accessor = self.private_conversation_state.create_property("PrivateData")
337
338
async def on_message_activity(self, turn_context: TurnContext):
339
# Get state data with default objects
340
user_profile = await self.user_profile_accessor.get(turn_context, UserProfile)
341
conversation_data = await self.conversation_data_accessor.get(turn_context, ConversationData)
342
private_data = await self.private_data_accessor.get(turn_context, lambda: {"notes": []})
343
344
# Update state based on message
345
text = turn_context.activity.text.lower()
346
347
if text.startswith("my name is"):
348
user_profile.name = text[11:].strip()
349
await turn_context.send_activity(MessageFactory.text(f"Nice to meet you, {user_profile.name}!"))
350
351
elif text.startswith("topic:"):
352
conversation_data.topic = text[6:].strip()
353
await turn_context.send_activity(MessageFactory.text(f"Changed topic to: {conversation_data.topic}"))
354
355
else:
356
# Add private note
357
private_data["notes"].append(f"Turn {conversation_data.turn_count}: {text}")
358
359
# Update turn count
360
conversation_data.turn_count += 1
361
362
# Save all changes
363
await self.user_state.save_changes(turn_context)
364
await self.conversation_state.save_changes(turn_context)
365
await self.private_conversation_state.save_changes(turn_context)
366
```
367
368
### Using BotStateSet
369
370
```python
371
class StateSetBot(ActivityHandler):
372
def __init__(self):
373
memory_storage = MemoryStorage()
374
375
self.conversation_state = ConversationState(memory_storage)
376
self.user_state = UserState(memory_storage)
377
378
# Create state set for batch operations
379
self.state_set = BotStateSet(self.conversation_state, self.user_state)
380
381
self.user_name_accessor = self.user_state.create_property("UserName")
382
self.turn_count_accessor = self.conversation_state.create_property("TurnCount")
383
384
async def on_message_activity(self, turn_context: TurnContext):
385
# Load all states at once
386
await self.state_set.load_all(turn_context)
387
388
# Work with state properties
389
user_name = await self.user_name_accessor.get(turn_context, lambda: "Anonymous")
390
turn_count = await self.turn_count_accessor.get(turn_context, lambda: 0)
391
392
# Update data
393
turn_count += 1
394
await self.turn_count_accessor.set(turn_context, turn_count)
395
396
if turn_context.activity.text.startswith("call me"):
397
new_name = turn_context.activity.text[8:].strip()
398
await self.user_name_accessor.set(turn_context, new_name)
399
user_name = new_name
400
401
# Save all states at once
402
await self.state_set.save_all_changes(turn_context)
403
404
reply = f"Hello {user_name}! This is turn #{turn_count}"
405
await turn_context.send_activity(MessageFactory.text(reply))
406
```
407
408
### State Clearing and Deletion
409
410
```python
411
async def on_message_activity(self, turn_context: TurnContext):
412
text = turn_context.activity.text.lower()
413
414
if text == "reset user":
415
# Clear user state
416
await self.user_state.clear_state(turn_context)
417
await self.user_state.save_changes(turn_context)
418
await turn_context.send_activity(MessageFactory.text("User state cleared!"))
419
420
elif text == "reset conversation":
421
# Clear conversation state
422
await self.conversation_state.clear_state(turn_context)
423
await self.conversation_state.save_changes(turn_context)
424
await turn_context.send_activity(MessageFactory.text("Conversation state cleared!"))
425
426
elif text == "delete my data":
427
# Delete specific property
428
await self.user_profile_accessor.delete(turn_context)
429
await self.user_state.save_changes(turn_context)
430
await turn_context.send_activity(MessageFactory.text("User profile deleted!"))
431
```
432
433
### Custom Default Value Factory
434
435
```python
436
def create_default_user_profile():
437
return {
438
"name": "New User",
439
"join_date": datetime.now().isoformat(),
440
"settings": {
441
"notifications": True,
442
"theme": "light"
443
}
444
}
445
446
async def on_message_activity(self, turn_context: TurnContext):
447
# Use factory function for complex default values
448
user_profile = await self.user_profile_accessor.get(
449
turn_context,
450
create_default_user_profile
451
)
452
453
# Now user_profile is guaranteed to have the structure we need
454
await turn_context.send_activity(
455
MessageFactory.text(f"Welcome back, {user_profile['name']}!")
456
)
457
```
458
459
## Types
460
461
```python { .api }
462
class PropertyManager:
463
"""Base class for managing properties."""
464
pass
465
466
class StoreItem:
467
"""Base class for items stored in bot state."""
468
def __init__(self):
469
self.e_tag = "*"
470
```