signalwire-agents 0.1.27__py3-none-any.whl → 0.1.28__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,219 +0,0 @@
1
- """
2
- Copyright (c) 2025 SignalWire
3
-
4
- This file is part of the SignalWire AI Agents SDK.
5
-
6
- Licensed under the MIT License.
7
- See LICENSE file in the project root for full license information.
8
- """
9
-
10
- """
11
- File-based implementation of the StateManager interface
12
- """
13
-
14
- import os
15
- import json
16
- import tempfile
17
- import time
18
- from datetime import datetime, timedelta
19
- from typing import Dict, Any, Optional, List
20
-
21
- from .state_manager import StateManager
22
-
23
-
24
- class FileStateManager(StateManager):
25
- """
26
- File-based state manager implementation
27
-
28
- This implementation stores state data as JSON files in a directory.
29
- Each call's state is stored in a separate file named by call_id.
30
- Files older than expiry_days are automatically cleaned up.
31
-
32
- This is suitable for development and low-volume deployments.
33
- For production, consider using database or Redis implementations.
34
- """
35
-
36
- def __init__(
37
- self,
38
- storage_dir: Optional[str] = None,
39
- expiry_days: float = 1.0
40
- ):
41
- """
42
- Initialize the file state manager
43
-
44
- Args:
45
- storage_dir: Directory to store state files (default: system temp dir)
46
- expiry_days: Days after which state files are considered expired
47
- """
48
- self.storage_dir = storage_dir or os.path.join(tempfile.gettempdir(), "signalwire_state")
49
- self.expiry_days = expiry_days
50
-
51
- # Create storage directory if it doesn't exist
52
- if not os.path.exists(self.storage_dir):
53
- os.makedirs(self.storage_dir)
54
-
55
- def _get_file_path(self, call_id: str) -> str:
56
- """Get the file path for a call_id"""
57
- # Sanitize call_id to ensure it's safe for a filename
58
- sanitized_id = "".join(c for c in call_id if c.isalnum() or c in "-_.")
59
- return os.path.join(self.storage_dir, f"{sanitized_id}.json")
60
-
61
- def store(self, call_id: str, data: Dict[str, Any]) -> bool:
62
- """
63
- Store state data for a call
64
-
65
- Args:
66
- call_id: Unique identifier for the call
67
- data: Dictionary of state data to store
68
-
69
- Returns:
70
- True if successful, False otherwise
71
- """
72
- try:
73
- # Add metadata including timestamp
74
- state_data = {
75
- "call_id": call_id,
76
- "created_at": datetime.now().isoformat(),
77
- "last_updated": datetime.now().isoformat(),
78
- "data": data
79
- }
80
-
81
- file_path = self._get_file_path(call_id)
82
- with open(file_path, "w") as f:
83
- json.dump(state_data, f, indent=2)
84
- return True
85
- except Exception as e:
86
- print(f"Error storing state for call {call_id}: {e}")
87
- return False
88
-
89
- def retrieve(self, call_id: str) -> Optional[Dict[str, Any]]:
90
- """
91
- Retrieve state data for a call
92
-
93
- Args:
94
- call_id: Unique identifier for the call
95
-
96
- Returns:
97
- Dictionary of state data or None if not found
98
- """
99
- file_path = self._get_file_path(call_id)
100
- if not os.path.exists(file_path):
101
- return None
102
-
103
- try:
104
- with open(file_path, "r") as f:
105
- state_data = json.load(f)
106
-
107
- # Check if the file is expired
108
- created_at = datetime.fromisoformat(state_data["created_at"])
109
- if (datetime.now() - created_at) > timedelta(days=self.expiry_days):
110
- # Expired, so delete it and return None
111
- os.remove(file_path)
112
- return None
113
-
114
- return state_data["data"]
115
- except Exception as e:
116
- print(f"Error retrieving state for call {call_id}: {e}")
117
- return None
118
-
119
- def update(self, call_id: str, data: Dict[str, Any]) -> bool:
120
- """
121
- Update state data for a call
122
-
123
- Args:
124
- call_id: Unique identifier for the call
125
- data: Dictionary of state data to update (merged with existing)
126
-
127
- Returns:
128
- True if successful, False otherwise
129
- """
130
- file_path = self._get_file_path(call_id)
131
- if not os.path.exists(file_path):
132
- # If no existing data, just store new data
133
- return self.store(call_id, data)
134
-
135
- try:
136
- # Read existing data
137
- with open(file_path, "r") as f:
138
- state_data = json.load(f)
139
-
140
- # Update the data (deep merge)
141
- existing_data = state_data["data"]
142
- self._deep_update(existing_data, data)
143
-
144
- # Update metadata
145
- state_data["last_updated"] = datetime.now().isoformat()
146
- state_data["data"] = existing_data
147
-
148
- # Write back to file
149
- with open(file_path, "w") as f:
150
- json.dump(state_data, f, indent=2)
151
-
152
- return True
153
- except Exception as e:
154
- print(f"Error updating state for call {call_id}: {e}")
155
- return False
156
-
157
- def delete(self, call_id: str) -> bool:
158
- """
159
- Delete state data for a call
160
-
161
- Args:
162
- call_id: Unique identifier for the call
163
-
164
- Returns:
165
- True if successful, False otherwise
166
- """
167
- file_path = self._get_file_path(call_id)
168
- if not os.path.exists(file_path):
169
- return False
170
-
171
- try:
172
- os.remove(file_path)
173
- return True
174
- except Exception as e:
175
- print(f"Error deleting state for call {call_id}: {e}")
176
- return False
177
-
178
- def cleanup_expired(self) -> int:
179
- """
180
- Clean up expired state files
181
-
182
- Returns:
183
- Number of expired files cleaned up
184
- """
185
- count = 0
186
- try:
187
- # Get all state files
188
- for filename in os.listdir(self.storage_dir):
189
- if not filename.endswith(".json"):
190
- continue
191
-
192
- file_path = os.path.join(self.storage_dir, filename)
193
-
194
- try:
195
- # Read the file to check creation time
196
- with open(file_path, "r") as f:
197
- state_data = json.load(f)
198
-
199
- # Check if the file is expired
200
- created_at = datetime.fromisoformat(state_data["created_at"])
201
- if (datetime.now() - created_at) > timedelta(days=self.expiry_days):
202
- os.remove(file_path)
203
- count += 1
204
- except Exception:
205
- # Skip files that can't be processed
206
- continue
207
-
208
- return count
209
- except Exception as e:
210
- print(f"Error cleaning up expired state files: {e}")
211
- return count
212
-
213
- def _deep_update(self, d: Dict, u: Dict) -> None:
214
- """Recursively update a nested dictionary"""
215
- for k, v in u.items():
216
- if isinstance(v, dict) and k in d and isinstance(d[k], dict):
217
- self._deep_update(d[k], v)
218
- else:
219
- d[k] = v
@@ -1,101 +0,0 @@
1
- """
2
- Copyright (c) 2025 SignalWire
3
-
4
- This file is part of the SignalWire AI Agents SDK.
5
-
6
- Licensed under the MIT License.
7
- See LICENSE file in the project root for full license information.
8
- """
9
-
10
- """
11
- Abstract base class for state management
12
- """
13
-
14
- from abc import ABC, abstractmethod
15
- from typing import Dict, Any, Optional
16
-
17
-
18
- class StateManager(ABC):
19
- """
20
- Abstract base class for state management
21
-
22
- This defines the interface that all state manager implementations
23
- must follow. State managers are responsible for storing, retrieving,
24
- and managing call-specific state data.
25
- """
26
-
27
- @abstractmethod
28
- def store(self, call_id: str, data: Dict[str, Any]) -> bool:
29
- """
30
- Store state data for a call
31
-
32
- Args:
33
- call_id: Unique identifier for the call
34
- data: Dictionary of state data to store
35
-
36
- Returns:
37
- True if successful, False otherwise
38
- """
39
- pass
40
-
41
- @abstractmethod
42
- def retrieve(self, call_id: str) -> Optional[Dict[str, Any]]:
43
- """
44
- Retrieve state data for a call
45
-
46
- Args:
47
- call_id: Unique identifier for the call
48
-
49
- Returns:
50
- Dictionary of state data or None if not found
51
- """
52
- pass
53
-
54
- @abstractmethod
55
- def update(self, call_id: str, data: Dict[str, Any]) -> bool:
56
- """
57
- Update state data for a call
58
-
59
- Args:
60
- call_id: Unique identifier for the call
61
- data: Dictionary of state data to update (merged with existing)
62
-
63
- Returns:
64
- True if successful, False otherwise
65
- """
66
- pass
67
-
68
- @abstractmethod
69
- def delete(self, call_id: str) -> bool:
70
- """
71
- Delete state data for a call
72
-
73
- Args:
74
- call_id: Unique identifier for the call
75
-
76
- Returns:
77
- True if successful, False otherwise
78
- """
79
- pass
80
-
81
- @abstractmethod
82
- def cleanup_expired(self) -> int:
83
- """
84
- Clean up expired state data
85
-
86
- Returns:
87
- Number of expired items cleaned up
88
- """
89
- pass
90
-
91
- def exists(self, call_id: str) -> bool:
92
- """
93
- Check if state exists for a call
94
-
95
- Args:
96
- call_id: Unique identifier for the call
97
-
98
- Returns:
99
- True if state exists, False otherwise
100
- """
101
- return self.retrieve(call_id) is not None