@mseep/csv-editor 1.0.0
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.
- package/.github/ISSUE_TEMPLATE/bug_report.md +53 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +38 -0
- package/.github/workflows/deploy-docs.yml +62 -0
- package/.github/workflows/publish-github.yml +52 -0
- package/.github/workflows/publish.yml +44 -0
- package/.github/workflows/test.yml +32 -0
- package/.pre-commit-config.yaml +157 -0
- package/ALTERNATIVE_PUBLISHING.md +175 -0
- package/ARCHITECTURE.md +1011 -0
- package/CHANGELOG.md +99 -0
- package/CODE_OF_CONDUCT.md +41 -0
- package/CONTRIBUTING.md +427 -0
- package/Dockerfile +22 -0
- package/LICENSE +21 -0
- package/MCP_CONFIG.md +505 -0
- package/PUBLISHING.md +210 -0
- package/README.md +400 -0
- package/SECURITY.md +61 -0
- package/docs/README.md +41 -0
- package/docs/blog/2019-05-28-first-blog-post.md +12 -0
- package/docs/blog/2019-05-29-long-blog-post.md +44 -0
- package/docs/blog/2021-08-01-mdx-blog-post.mdx +24 -0
- package/docs/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg +0 -0
- package/docs/blog/2021-08-26-welcome/index.md +29 -0
- package/docs/blog/authors.yml +25 -0
- package/docs/blog/tags.yml +19 -0
- package/docs/docs/api/overview.md +183 -0
- package/docs/docs/installation.md +252 -0
- package/docs/docs/intro.md +87 -0
- package/docs/docs/tutorial-basics/_category_.json +8 -0
- package/docs/docs/tutorial-basics/congratulations.md +23 -0
- package/docs/docs/tutorial-basics/create-a-blog-post.md +34 -0
- package/docs/docs/tutorial-basics/create-a-document.md +57 -0
- package/docs/docs/tutorial-basics/create-a-page.md +43 -0
- package/docs/docs/tutorial-basics/deploy-your-site.md +31 -0
- package/docs/docs/tutorial-basics/markdown-features.mdx +152 -0
- package/docs/docs/tutorial-extras/_category_.json +7 -0
- package/docs/docs/tutorial-extras/img/docsVersionDropdown.png +0 -0
- package/docs/docs/tutorial-extras/img/localeDropdown.png +0 -0
- package/docs/docs/tutorial-extras/manage-docs-versions.md +55 -0
- package/docs/docs/tutorial-extras/translate-your-site.md +88 -0
- package/docs/docs/tutorials/quickstart.md +365 -0
- package/docs/docusaurus.config.ts +163 -0
- package/docs/package-lock.json +17493 -0
- package/docs/package.json +48 -0
- package/docs/sidebars.ts +33 -0
- package/docs/src/components/HomepageFeatures/index.tsx +71 -0
- package/docs/src/components/HomepageFeatures/styles.module.css +11 -0
- package/docs/src/css/custom.css +30 -0
- package/docs/src/pages/index.module.css +23 -0
- package/docs/src/pages/index.tsx +44 -0
- package/docs/src/pages/markdown-page.md +7 -0
- package/docs/static/.nojekyll +0 -0
- package/docs/static/img/docusaurus-social-card.jpg +0 -0
- package/docs/static/img/docusaurus.png +0 -0
- package/docs/static/img/favicon.ico +0 -0
- package/docs/static/img/logo.svg +1 -0
- package/docs/static/img/undraw_docusaurus_mountain.svg +171 -0
- package/docs/static/img/undraw_docusaurus_react.svg +170 -0
- package/docs/static/img/undraw_docusaurus_tree.svg +40 -0
- package/docs/tsconfig.json +8 -0
- package/examples/README.md +48 -0
- package/examples/auto_save_demo.py +206 -0
- package/examples/auto_save_overwrite.py +201 -0
- package/examples/basic_usage.py +135 -0
- package/examples/demo.py +139 -0
- package/examples/history_demo.py +317 -0
- package/examples/test_default_autosave.py +124 -0
- package/examples/update_consignee_example.py +179 -0
- package/package.json +51 -0
- package/plans/2026-04-19-fastmcp3-migration-plan.md +1045 -0
- package/pyproject.toml +331 -0
- package/requirements-dev.txt +30 -0
- package/requirements.txt +22 -0
- package/scripts/publish.py +67 -0
- package/smithery.yaml +15 -0
- package/specs/2026-04-19-fastmcp3-migration-design.md +243 -0
- package/src/csv_editor/__init__.py +8 -0
- package/src/csv_editor/models/__init__.py +39 -0
- package/src/csv_editor/models/auto_save.py +246 -0
- package/src/csv_editor/models/csv_session.py +468 -0
- package/src/csv_editor/models/data_models.py +244 -0
- package/src/csv_editor/models/history_manager.py +456 -0
- package/src/csv_editor/prompts/__init__.py +0 -0
- package/src/csv_editor/prompts/data_prompts.py +13 -0
- package/src/csv_editor/resources/__init__.py +0 -0
- package/src/csv_editor/resources/csv_resources.py +22 -0
- package/src/csv_editor/server.py +640 -0
- package/src/csv_editor/tools/__init__.py +5 -0
- package/src/csv_editor/tools/analytics.py +700 -0
- package/src/csv_editor/tools/auto_save_operations.py +235 -0
- package/src/csv_editor/tools/data_operations.py +3 -0
- package/src/csv_editor/tools/history_operations.py +315 -0
- package/src/csv_editor/tools/io_operations.py +431 -0
- package/src/csv_editor/tools/transformations.py +663 -0
- package/src/csv_editor/tools/validation.py +822 -0
- package/src/csv_editor/utils/__init__.py +0 -0
- package/src/csv_editor/utils/validators.py +205 -0
- package/tests/README.md +65 -0
- package/tests/__init__.py +7 -0
- package/tests/conftest.py +50 -0
- package/tests/test_auto_save.py +378 -0
- package/tests/test_basic.py +103 -0
- package/tests/test_integration.py +356 -0
- package/tests/test_server_boot.py +50 -0
- package/tests/test_settings.py +184 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
"""Auto-save operations for CSV sessions."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from fastmcp import Context
|
|
7
|
+
|
|
8
|
+
from ..models.csv_session import get_session_manager
|
|
9
|
+
from ..models.data_models import OperationResult
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
async def configure_auto_save(
|
|
15
|
+
session_id: str,
|
|
16
|
+
enabled: bool = True,
|
|
17
|
+
mode: str = "after_operation",
|
|
18
|
+
strategy: str = "backup",
|
|
19
|
+
interval_seconds: int | None = None,
|
|
20
|
+
max_backups: int | None = None,
|
|
21
|
+
backup_dir: str | None = None,
|
|
22
|
+
custom_path: str | None = None,
|
|
23
|
+
format: str = "csv",
|
|
24
|
+
encoding: str = "utf-8",
|
|
25
|
+
ctx: Context = None,
|
|
26
|
+
) -> dict[str, Any]:
|
|
27
|
+
"""
|
|
28
|
+
Configure auto-save settings for a session.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
session_id: Session identifier
|
|
32
|
+
enabled: Whether auto-save is enabled
|
|
33
|
+
mode: Auto-save mode ('disabled', 'after_operation', 'periodic', 'hybrid')
|
|
34
|
+
strategy: Save strategy ('overwrite', 'backup', 'versioned', 'custom')
|
|
35
|
+
interval_seconds: Interval for periodic saves (default 300)
|
|
36
|
+
max_backups: Maximum number of backup files to keep (default 10)
|
|
37
|
+
backup_dir: Directory for backup files
|
|
38
|
+
custom_path: Custom path for saves (when strategy='custom')
|
|
39
|
+
format: Export format ('csv', 'tsv', 'json', 'excel', 'parquet')
|
|
40
|
+
encoding: File encoding (default 'utf-8')
|
|
41
|
+
ctx: FastMCP context
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
Dict with success status and configuration
|
|
45
|
+
"""
|
|
46
|
+
try:
|
|
47
|
+
manager = get_session_manager()
|
|
48
|
+
session = manager.get_session(session_id)
|
|
49
|
+
|
|
50
|
+
if not session:
|
|
51
|
+
return OperationResult(
|
|
52
|
+
success=False,
|
|
53
|
+
message="Session not found",
|
|
54
|
+
error=f"No session with ID: {session_id}",
|
|
55
|
+
).model_dump()
|
|
56
|
+
|
|
57
|
+
if ctx:
|
|
58
|
+
await ctx.info(f"Configuring auto-save for session {session_id}")
|
|
59
|
+
|
|
60
|
+
# Build configuration
|
|
61
|
+
config = {
|
|
62
|
+
"enabled": enabled,
|
|
63
|
+
"mode": mode,
|
|
64
|
+
"strategy": strategy,
|
|
65
|
+
"format": format,
|
|
66
|
+
"encoding": encoding,
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if interval_seconds is not None:
|
|
70
|
+
config["interval_seconds"] = interval_seconds
|
|
71
|
+
if max_backups is not None:
|
|
72
|
+
config["max_backups"] = max_backups
|
|
73
|
+
if backup_dir is not None:
|
|
74
|
+
config["backup_dir"] = backup_dir
|
|
75
|
+
if custom_path is not None:
|
|
76
|
+
config["custom_path"] = custom_path
|
|
77
|
+
|
|
78
|
+
# Apply configuration
|
|
79
|
+
result = await session.enable_auto_save(config)
|
|
80
|
+
|
|
81
|
+
if result["success"]:
|
|
82
|
+
if ctx:
|
|
83
|
+
await ctx.info(f"Auto-save configured: {mode} mode, {strategy} strategy")
|
|
84
|
+
|
|
85
|
+
return OperationResult(
|
|
86
|
+
success=True,
|
|
87
|
+
message="Auto-save configured successfully",
|
|
88
|
+
session_id=session_id,
|
|
89
|
+
data=result["config"],
|
|
90
|
+
).model_dump()
|
|
91
|
+
else:
|
|
92
|
+
return OperationResult(
|
|
93
|
+
success=False, message="Failed to configure auto-save", error=result.get("error")
|
|
94
|
+
).model_dump()
|
|
95
|
+
|
|
96
|
+
except Exception as e:
|
|
97
|
+
logger.error(f"Error configuring auto-save: {e!s}")
|
|
98
|
+
if ctx:
|
|
99
|
+
await ctx.error(f"Failed to configure auto-save: {e!s}")
|
|
100
|
+
return OperationResult(
|
|
101
|
+
success=False, message="Failed to configure auto-save", error=str(e)
|
|
102
|
+
).model_dump()
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
async def disable_auto_save(session_id: str, ctx: Context = None) -> dict[str, Any]:
|
|
106
|
+
"""
|
|
107
|
+
Disable auto-save for a session.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
session_id: Session identifier
|
|
111
|
+
ctx: FastMCP context
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
Dict with success status
|
|
115
|
+
"""
|
|
116
|
+
try:
|
|
117
|
+
manager = get_session_manager()
|
|
118
|
+
session = manager.get_session(session_id)
|
|
119
|
+
|
|
120
|
+
if not session:
|
|
121
|
+
return OperationResult(
|
|
122
|
+
success=False,
|
|
123
|
+
message="Session not found",
|
|
124
|
+
error=f"No session with ID: {session_id}",
|
|
125
|
+
).model_dump()
|
|
126
|
+
|
|
127
|
+
result = await session.disable_auto_save()
|
|
128
|
+
|
|
129
|
+
if result["success"]:
|
|
130
|
+
if ctx:
|
|
131
|
+
await ctx.info(f"Auto-save disabled for session {session_id}")
|
|
132
|
+
|
|
133
|
+
return OperationResult(
|
|
134
|
+
success=True, message="Auto-save disabled", session_id=session_id
|
|
135
|
+
).model_dump()
|
|
136
|
+
else:
|
|
137
|
+
return OperationResult(
|
|
138
|
+
success=False, message="Failed to disable auto-save", error=result.get("error")
|
|
139
|
+
).model_dump()
|
|
140
|
+
|
|
141
|
+
except Exception as e:
|
|
142
|
+
logger.error(f"Error disabling auto-save: {e!s}")
|
|
143
|
+
if ctx:
|
|
144
|
+
await ctx.error(f"Failed to disable auto-save: {e!s}")
|
|
145
|
+
return OperationResult(
|
|
146
|
+
success=False, message="Failed to disable auto-save", error=str(e)
|
|
147
|
+
).model_dump()
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
async def get_auto_save_status(session_id: str, ctx: Context = None) -> dict[str, Any]:
|
|
151
|
+
"""
|
|
152
|
+
Get auto-save status for a session.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
session_id: Session identifier
|
|
156
|
+
ctx: FastMCP context
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
Dict with auto-save status
|
|
160
|
+
"""
|
|
161
|
+
try:
|
|
162
|
+
manager = get_session_manager()
|
|
163
|
+
session = manager.get_session(session_id)
|
|
164
|
+
|
|
165
|
+
if not session:
|
|
166
|
+
return OperationResult(
|
|
167
|
+
success=False,
|
|
168
|
+
message="Session not found",
|
|
169
|
+
error=f"No session with ID: {session_id}",
|
|
170
|
+
).model_dump()
|
|
171
|
+
|
|
172
|
+
status = session.get_auto_save_status()
|
|
173
|
+
|
|
174
|
+
if ctx:
|
|
175
|
+
await ctx.info(f"Auto-save status retrieved for session {session_id}")
|
|
176
|
+
|
|
177
|
+
return OperationResult(
|
|
178
|
+
success=True, message="Auto-save status retrieved", session_id=session_id, data=status
|
|
179
|
+
).model_dump()
|
|
180
|
+
|
|
181
|
+
except Exception as e:
|
|
182
|
+
logger.error(f"Error getting auto-save status: {e!s}")
|
|
183
|
+
if ctx:
|
|
184
|
+
await ctx.error(f"Failed to get auto-save status: {e!s}")
|
|
185
|
+
return OperationResult(
|
|
186
|
+
success=False, message="Failed to get auto-save status", error=str(e)
|
|
187
|
+
).model_dump()
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
async def trigger_manual_save(session_id: str, ctx: Context = None) -> dict[str, Any]:
|
|
191
|
+
"""
|
|
192
|
+
Manually trigger a save for a session.
|
|
193
|
+
|
|
194
|
+
Args:
|
|
195
|
+
session_id: Session identifier
|
|
196
|
+
ctx: FastMCP context
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
Dict with save result
|
|
200
|
+
"""
|
|
201
|
+
try:
|
|
202
|
+
manager = get_session_manager()
|
|
203
|
+
session = manager.get_session(session_id)
|
|
204
|
+
|
|
205
|
+
if not session:
|
|
206
|
+
return OperationResult(
|
|
207
|
+
success=False,
|
|
208
|
+
message="Session not found",
|
|
209
|
+
error=f"No session with ID: {session_id}",
|
|
210
|
+
).model_dump()
|
|
211
|
+
|
|
212
|
+
if ctx:
|
|
213
|
+
await ctx.info(f"Triggering manual save for session {session_id}")
|
|
214
|
+
|
|
215
|
+
result = await session.manual_save()
|
|
216
|
+
|
|
217
|
+
if result["success"]:
|
|
218
|
+
if ctx:
|
|
219
|
+
await ctx.info(f"Manual save completed: {result.get('save_path')}")
|
|
220
|
+
|
|
221
|
+
return OperationResult(
|
|
222
|
+
success=True, message="Manual save completed", session_id=session_id, data=result
|
|
223
|
+
).model_dump()
|
|
224
|
+
else:
|
|
225
|
+
return OperationResult(
|
|
226
|
+
success=False, message="Manual save failed", error=result.get("error")
|
|
227
|
+
).model_dump()
|
|
228
|
+
|
|
229
|
+
except Exception as e:
|
|
230
|
+
logger.error(f"Error in manual save: {e!s}")
|
|
231
|
+
if ctx:
|
|
232
|
+
await ctx.error(f"Failed to trigger manual save: {e!s}")
|
|
233
|
+
return OperationResult(
|
|
234
|
+
success=False, message="Failed to trigger manual save", error=str(e)
|
|
235
|
+
).model_dump()
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
"""History operations for CSV sessions."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from fastmcp import Context
|
|
7
|
+
|
|
8
|
+
from ..models.csv_session import get_session_manager
|
|
9
|
+
from ..models.data_models import OperationResult
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
async def undo_operation(session_id: str, ctx: Context = None) -> dict[str, Any]:
|
|
15
|
+
"""
|
|
16
|
+
Undo the last operation in a session.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
session_id: Session identifier
|
|
20
|
+
ctx: FastMCP context
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
Dict with success status and undo result
|
|
24
|
+
"""
|
|
25
|
+
try:
|
|
26
|
+
manager = get_session_manager()
|
|
27
|
+
session = manager.get_session(session_id)
|
|
28
|
+
|
|
29
|
+
if not session:
|
|
30
|
+
return OperationResult(
|
|
31
|
+
success=False,
|
|
32
|
+
message="Session not found",
|
|
33
|
+
error=f"No session with ID: {session_id}",
|
|
34
|
+
).model_dump()
|
|
35
|
+
|
|
36
|
+
if ctx:
|
|
37
|
+
await ctx.info(f"Undoing last operation for session {session_id}")
|
|
38
|
+
|
|
39
|
+
result = await session.undo()
|
|
40
|
+
|
|
41
|
+
if result["success"]:
|
|
42
|
+
if ctx:
|
|
43
|
+
await ctx.info(f"Successfully undid operation: {result.get('message')}")
|
|
44
|
+
|
|
45
|
+
return OperationResult(
|
|
46
|
+
success=True, message=result["message"], session_id=session_id, data=result
|
|
47
|
+
).model_dump()
|
|
48
|
+
else:
|
|
49
|
+
return OperationResult(
|
|
50
|
+
success=False, message="Failed to undo operation", error=result.get("error")
|
|
51
|
+
).model_dump()
|
|
52
|
+
|
|
53
|
+
except Exception as e:
|
|
54
|
+
logger.error(f"Error undoing operation: {e!s}")
|
|
55
|
+
if ctx:
|
|
56
|
+
await ctx.error(f"Failed to undo operation: {e!s}")
|
|
57
|
+
return OperationResult(
|
|
58
|
+
success=False, message="Failed to undo operation", error=str(e)
|
|
59
|
+
).model_dump()
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
async def redo_operation(session_id: str, ctx: Context = None) -> dict[str, Any]:
|
|
63
|
+
"""
|
|
64
|
+
Redo a previously undone operation.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
session_id: Session identifier
|
|
68
|
+
ctx: FastMCP context
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
Dict with success status and redo result
|
|
72
|
+
"""
|
|
73
|
+
try:
|
|
74
|
+
manager = get_session_manager()
|
|
75
|
+
session = manager.get_session(session_id)
|
|
76
|
+
|
|
77
|
+
if not session:
|
|
78
|
+
return OperationResult(
|
|
79
|
+
success=False,
|
|
80
|
+
message="Session not found",
|
|
81
|
+
error=f"No session with ID: {session_id}",
|
|
82
|
+
).model_dump()
|
|
83
|
+
|
|
84
|
+
if ctx:
|
|
85
|
+
await ctx.info(f"Redoing operation for session {session_id}")
|
|
86
|
+
|
|
87
|
+
result = await session.redo()
|
|
88
|
+
|
|
89
|
+
if result["success"]:
|
|
90
|
+
if ctx:
|
|
91
|
+
await ctx.info(f"Successfully redid operation: {result.get('message')}")
|
|
92
|
+
|
|
93
|
+
return OperationResult(
|
|
94
|
+
success=True, message=result["message"], session_id=session_id, data=result
|
|
95
|
+
).model_dump()
|
|
96
|
+
else:
|
|
97
|
+
return OperationResult(
|
|
98
|
+
success=False, message="Failed to redo operation", error=result.get("error")
|
|
99
|
+
).model_dump()
|
|
100
|
+
|
|
101
|
+
except Exception as e:
|
|
102
|
+
logger.error(f"Error redoing operation: {e!s}")
|
|
103
|
+
if ctx:
|
|
104
|
+
await ctx.error(f"Failed to redo operation: {e!s}")
|
|
105
|
+
return OperationResult(
|
|
106
|
+
success=False, message="Failed to redo operation", error=str(e)
|
|
107
|
+
).model_dump()
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
async def get_operation_history(
|
|
111
|
+
session_id: str, limit: int | None = None, ctx: Context = None
|
|
112
|
+
) -> dict[str, Any]:
|
|
113
|
+
"""
|
|
114
|
+
Get operation history for a session.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
session_id: Session identifier
|
|
118
|
+
limit: Maximum number of operations to return
|
|
119
|
+
ctx: FastMCP context
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
Dict with history and statistics
|
|
123
|
+
"""
|
|
124
|
+
try:
|
|
125
|
+
manager = get_session_manager()
|
|
126
|
+
session = manager.get_session(session_id)
|
|
127
|
+
|
|
128
|
+
if not session:
|
|
129
|
+
return OperationResult(
|
|
130
|
+
success=False,
|
|
131
|
+
message="Session not found",
|
|
132
|
+
error=f"No session with ID: {session_id}",
|
|
133
|
+
).model_dump()
|
|
134
|
+
|
|
135
|
+
if ctx:
|
|
136
|
+
await ctx.info(f"Getting operation history for session {session_id}")
|
|
137
|
+
|
|
138
|
+
result = session.get_history(limit)
|
|
139
|
+
|
|
140
|
+
if result["success"]:
|
|
141
|
+
return OperationResult(
|
|
142
|
+
success=True,
|
|
143
|
+
message="History retrieved successfully",
|
|
144
|
+
session_id=session_id,
|
|
145
|
+
data=result,
|
|
146
|
+
).model_dump()
|
|
147
|
+
else:
|
|
148
|
+
return OperationResult(
|
|
149
|
+
success=False, message="Failed to get history", error=result.get("error")
|
|
150
|
+
).model_dump()
|
|
151
|
+
|
|
152
|
+
except Exception as e:
|
|
153
|
+
logger.error(f"Error getting history: {e!s}")
|
|
154
|
+
if ctx:
|
|
155
|
+
await ctx.error(f"Failed to get history: {e!s}")
|
|
156
|
+
return OperationResult(
|
|
157
|
+
success=False, message="Failed to get history", error=str(e)
|
|
158
|
+
).model_dump()
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
async def restore_to_operation(
|
|
162
|
+
session_id: str, operation_id: str, ctx: Context = None
|
|
163
|
+
) -> dict[str, Any]:
|
|
164
|
+
"""
|
|
165
|
+
Restore session data to a specific operation point.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
session_id: Session identifier
|
|
169
|
+
operation_id: Operation ID to restore to
|
|
170
|
+
ctx: FastMCP context
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
Dict with success status and restore result
|
|
174
|
+
"""
|
|
175
|
+
try:
|
|
176
|
+
manager = get_session_manager()
|
|
177
|
+
session = manager.get_session(session_id)
|
|
178
|
+
|
|
179
|
+
if not session:
|
|
180
|
+
return OperationResult(
|
|
181
|
+
success=False,
|
|
182
|
+
message="Session not found",
|
|
183
|
+
error=f"No session with ID: {session_id}",
|
|
184
|
+
).model_dump()
|
|
185
|
+
|
|
186
|
+
if ctx:
|
|
187
|
+
await ctx.info(f"Restoring session {session_id} to operation {operation_id}")
|
|
188
|
+
|
|
189
|
+
result = await session.restore_to_operation(operation_id)
|
|
190
|
+
|
|
191
|
+
if result["success"]:
|
|
192
|
+
if ctx:
|
|
193
|
+
await ctx.info(f"Successfully restored to operation {operation_id}")
|
|
194
|
+
|
|
195
|
+
return OperationResult(
|
|
196
|
+
success=True, message=result["message"], session_id=session_id, data=result
|
|
197
|
+
).model_dump()
|
|
198
|
+
else:
|
|
199
|
+
return OperationResult(
|
|
200
|
+
success=False, message="Failed to restore to operation", error=result.get("error")
|
|
201
|
+
).model_dump()
|
|
202
|
+
|
|
203
|
+
except Exception as e:
|
|
204
|
+
logger.error(f"Error restoring to operation: {e!s}")
|
|
205
|
+
if ctx:
|
|
206
|
+
await ctx.error(f"Failed to restore to operation: {e!s}")
|
|
207
|
+
return OperationResult(
|
|
208
|
+
success=False, message="Failed to restore to operation", error=str(e)
|
|
209
|
+
).model_dump()
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
async def clear_history(session_id: str, ctx: Context = None) -> dict[str, Any]:
|
|
213
|
+
"""
|
|
214
|
+
Clear all operation history for a session.
|
|
215
|
+
|
|
216
|
+
Args:
|
|
217
|
+
session_id: Session identifier
|
|
218
|
+
ctx: FastMCP context
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
Dict with success status
|
|
222
|
+
"""
|
|
223
|
+
try:
|
|
224
|
+
manager = get_session_manager()
|
|
225
|
+
session = manager.get_session(session_id)
|
|
226
|
+
|
|
227
|
+
if not session:
|
|
228
|
+
return OperationResult(
|
|
229
|
+
success=False,
|
|
230
|
+
message="Session not found",
|
|
231
|
+
error=f"No session with ID: {session_id}",
|
|
232
|
+
).model_dump()
|
|
233
|
+
|
|
234
|
+
if not session.history_manager:
|
|
235
|
+
return OperationResult(
|
|
236
|
+
success=False,
|
|
237
|
+
message="History is not enabled for this session",
|
|
238
|
+
error="History management is disabled",
|
|
239
|
+
).model_dump()
|
|
240
|
+
|
|
241
|
+
if ctx:
|
|
242
|
+
await ctx.info(f"Clearing history for session {session_id}")
|
|
243
|
+
|
|
244
|
+
session.history_manager.clear_history()
|
|
245
|
+
|
|
246
|
+
return OperationResult(
|
|
247
|
+
success=True, message="History cleared successfully", session_id=session_id
|
|
248
|
+
).model_dump()
|
|
249
|
+
|
|
250
|
+
except Exception as e:
|
|
251
|
+
logger.error(f"Error clearing history: {e!s}")
|
|
252
|
+
if ctx:
|
|
253
|
+
await ctx.error(f"Failed to clear history: {e!s}")
|
|
254
|
+
return OperationResult(
|
|
255
|
+
success=False, message="Failed to clear history", error=str(e)
|
|
256
|
+
).model_dump()
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
async def export_history(
|
|
260
|
+
session_id: str, file_path: str, format: str = "json", ctx: Context = None
|
|
261
|
+
) -> dict[str, Any]:
|
|
262
|
+
"""
|
|
263
|
+
Export operation history to a file.
|
|
264
|
+
|
|
265
|
+
Args:
|
|
266
|
+
session_id: Session identifier
|
|
267
|
+
file_path: Path to export history to
|
|
268
|
+
format: Export format ('json' or 'csv')
|
|
269
|
+
ctx: FastMCP context
|
|
270
|
+
|
|
271
|
+
Returns:
|
|
272
|
+
Dict with success status
|
|
273
|
+
"""
|
|
274
|
+
try:
|
|
275
|
+
manager = get_session_manager()
|
|
276
|
+
session = manager.get_session(session_id)
|
|
277
|
+
|
|
278
|
+
if not session:
|
|
279
|
+
return OperationResult(
|
|
280
|
+
success=False,
|
|
281
|
+
message="Session not found",
|
|
282
|
+
error=f"No session with ID: {session_id}",
|
|
283
|
+
).model_dump()
|
|
284
|
+
|
|
285
|
+
if not session.history_manager:
|
|
286
|
+
return OperationResult(
|
|
287
|
+
success=False,
|
|
288
|
+
message="History is not enabled for this session",
|
|
289
|
+
error="History management is disabled",
|
|
290
|
+
).model_dump()
|
|
291
|
+
|
|
292
|
+
if ctx:
|
|
293
|
+
await ctx.info(f"Exporting history for session {session_id} to {file_path}")
|
|
294
|
+
|
|
295
|
+
success = session.history_manager.export_history(file_path, format)
|
|
296
|
+
|
|
297
|
+
if success:
|
|
298
|
+
return OperationResult(
|
|
299
|
+
success=True,
|
|
300
|
+
message=f"History exported to {file_path}",
|
|
301
|
+
session_id=session_id,
|
|
302
|
+
data={"file_path": file_path, "format": format},
|
|
303
|
+
).model_dump()
|
|
304
|
+
else:
|
|
305
|
+
return OperationResult(
|
|
306
|
+
success=False, message="Failed to export history", error="Export operation failed"
|
|
307
|
+
).model_dump()
|
|
308
|
+
|
|
309
|
+
except Exception as e:
|
|
310
|
+
logger.error(f"Error exporting history: {e!s}")
|
|
311
|
+
if ctx:
|
|
312
|
+
await ctx.error(f"Failed to export history: {e!s}")
|
|
313
|
+
return OperationResult(
|
|
314
|
+
success=False, message="Failed to export history", error=str(e)
|
|
315
|
+
).model_dump()
|