@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.
Files changed (106) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +53 -0
  2. package/.github/ISSUE_TEMPLATE/feature_request.md +38 -0
  3. package/.github/workflows/deploy-docs.yml +62 -0
  4. package/.github/workflows/publish-github.yml +52 -0
  5. package/.github/workflows/publish.yml +44 -0
  6. package/.github/workflows/test.yml +32 -0
  7. package/.pre-commit-config.yaml +157 -0
  8. package/ALTERNATIVE_PUBLISHING.md +175 -0
  9. package/ARCHITECTURE.md +1011 -0
  10. package/CHANGELOG.md +99 -0
  11. package/CODE_OF_CONDUCT.md +41 -0
  12. package/CONTRIBUTING.md +427 -0
  13. package/Dockerfile +22 -0
  14. package/LICENSE +21 -0
  15. package/MCP_CONFIG.md +505 -0
  16. package/PUBLISHING.md +210 -0
  17. package/README.md +400 -0
  18. package/SECURITY.md +61 -0
  19. package/docs/README.md +41 -0
  20. package/docs/blog/2019-05-28-first-blog-post.md +12 -0
  21. package/docs/blog/2019-05-29-long-blog-post.md +44 -0
  22. package/docs/blog/2021-08-01-mdx-blog-post.mdx +24 -0
  23. package/docs/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg +0 -0
  24. package/docs/blog/2021-08-26-welcome/index.md +29 -0
  25. package/docs/blog/authors.yml +25 -0
  26. package/docs/blog/tags.yml +19 -0
  27. package/docs/docs/api/overview.md +183 -0
  28. package/docs/docs/installation.md +252 -0
  29. package/docs/docs/intro.md +87 -0
  30. package/docs/docs/tutorial-basics/_category_.json +8 -0
  31. package/docs/docs/tutorial-basics/congratulations.md +23 -0
  32. package/docs/docs/tutorial-basics/create-a-blog-post.md +34 -0
  33. package/docs/docs/tutorial-basics/create-a-document.md +57 -0
  34. package/docs/docs/tutorial-basics/create-a-page.md +43 -0
  35. package/docs/docs/tutorial-basics/deploy-your-site.md +31 -0
  36. package/docs/docs/tutorial-basics/markdown-features.mdx +152 -0
  37. package/docs/docs/tutorial-extras/_category_.json +7 -0
  38. package/docs/docs/tutorial-extras/img/docsVersionDropdown.png +0 -0
  39. package/docs/docs/tutorial-extras/img/localeDropdown.png +0 -0
  40. package/docs/docs/tutorial-extras/manage-docs-versions.md +55 -0
  41. package/docs/docs/tutorial-extras/translate-your-site.md +88 -0
  42. package/docs/docs/tutorials/quickstart.md +365 -0
  43. package/docs/docusaurus.config.ts +163 -0
  44. package/docs/package-lock.json +17493 -0
  45. package/docs/package.json +48 -0
  46. package/docs/sidebars.ts +33 -0
  47. package/docs/src/components/HomepageFeatures/index.tsx +71 -0
  48. package/docs/src/components/HomepageFeatures/styles.module.css +11 -0
  49. package/docs/src/css/custom.css +30 -0
  50. package/docs/src/pages/index.module.css +23 -0
  51. package/docs/src/pages/index.tsx +44 -0
  52. package/docs/src/pages/markdown-page.md +7 -0
  53. package/docs/static/.nojekyll +0 -0
  54. package/docs/static/img/docusaurus-social-card.jpg +0 -0
  55. package/docs/static/img/docusaurus.png +0 -0
  56. package/docs/static/img/favicon.ico +0 -0
  57. package/docs/static/img/logo.svg +1 -0
  58. package/docs/static/img/undraw_docusaurus_mountain.svg +171 -0
  59. package/docs/static/img/undraw_docusaurus_react.svg +170 -0
  60. package/docs/static/img/undraw_docusaurus_tree.svg +40 -0
  61. package/docs/tsconfig.json +8 -0
  62. package/examples/README.md +48 -0
  63. package/examples/auto_save_demo.py +206 -0
  64. package/examples/auto_save_overwrite.py +201 -0
  65. package/examples/basic_usage.py +135 -0
  66. package/examples/demo.py +139 -0
  67. package/examples/history_demo.py +317 -0
  68. package/examples/test_default_autosave.py +124 -0
  69. package/examples/update_consignee_example.py +179 -0
  70. package/package.json +51 -0
  71. package/plans/2026-04-19-fastmcp3-migration-plan.md +1045 -0
  72. package/pyproject.toml +331 -0
  73. package/requirements-dev.txt +30 -0
  74. package/requirements.txt +22 -0
  75. package/scripts/publish.py +67 -0
  76. package/smithery.yaml +15 -0
  77. package/specs/2026-04-19-fastmcp3-migration-design.md +243 -0
  78. package/src/csv_editor/__init__.py +8 -0
  79. package/src/csv_editor/models/__init__.py +39 -0
  80. package/src/csv_editor/models/auto_save.py +246 -0
  81. package/src/csv_editor/models/csv_session.py +468 -0
  82. package/src/csv_editor/models/data_models.py +244 -0
  83. package/src/csv_editor/models/history_manager.py +456 -0
  84. package/src/csv_editor/prompts/__init__.py +0 -0
  85. package/src/csv_editor/prompts/data_prompts.py +13 -0
  86. package/src/csv_editor/resources/__init__.py +0 -0
  87. package/src/csv_editor/resources/csv_resources.py +22 -0
  88. package/src/csv_editor/server.py +640 -0
  89. package/src/csv_editor/tools/__init__.py +5 -0
  90. package/src/csv_editor/tools/analytics.py +700 -0
  91. package/src/csv_editor/tools/auto_save_operations.py +235 -0
  92. package/src/csv_editor/tools/data_operations.py +3 -0
  93. package/src/csv_editor/tools/history_operations.py +315 -0
  94. package/src/csv_editor/tools/io_operations.py +431 -0
  95. package/src/csv_editor/tools/transformations.py +663 -0
  96. package/src/csv_editor/tools/validation.py +822 -0
  97. package/src/csv_editor/utils/__init__.py +0 -0
  98. package/src/csv_editor/utils/validators.py +205 -0
  99. package/tests/README.md +65 -0
  100. package/tests/__init__.py +7 -0
  101. package/tests/conftest.py +50 -0
  102. package/tests/test_auto_save.py +378 -0
  103. package/tests/test_basic.py +103 -0
  104. package/tests/test_integration.py +356 -0
  105. package/tests/test_server_boot.py +50 -0
  106. package/tests/test_settings.py +184 -0
@@ -0,0 +1,317 @@
1
+ #!/usr/bin/env python3
2
+ """Demonstration of history tracking with undo/redo functionality."""
3
+
4
+ import asyncio
5
+ import tempfile
6
+ import os
7
+ import pandas as pd
8
+ from pathlib import Path
9
+ import json
10
+
11
+ # Setup path for imports
12
+ import sys
13
+ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
14
+
15
+ from src.csv_editor.models.csv_session import CSVSession
16
+ from src.csv_editor.models.history_manager import HistoryStorage
17
+ from src.csv_editor.models.data_models import OperationType
18
+
19
+
20
+ async def demonstrate_history():
21
+ """Demonstrate history tracking with undo/redo capabilities."""
22
+
23
+ print("=" * 60)
24
+ print("CSV Editor History & Undo/Redo Demonstration")
25
+ print("=" * 60)
26
+
27
+ # Create initial data
28
+ initial_data = pd.DataFrame({
29
+ 'product': ['Laptop', 'Mouse', 'Keyboard', 'Monitor'],
30
+ 'price': [999.99, 29.99, 79.99, 299.99],
31
+ 'stock': [50, 200, 150, 75],
32
+ 'category': ['Electronics', 'Accessories', 'Accessories', 'Electronics']
33
+ })
34
+
35
+ # Create session with history enabled (JSON persistence)
36
+ session = CSVSession(
37
+ enable_history=True,
38
+ history_storage=HistoryStorage.JSON
39
+ )
40
+
41
+ # Load data
42
+ temp_file = os.path.join(tempfile.gettempdir(), "demo_data.csv")
43
+ initial_data.to_csv(temp_file, index=False)
44
+ session.load_data(initial_data, file_path=temp_file)
45
+
46
+ print(f"\n✓ Created session with history tracking")
47
+ print(f"Initial data:\n{session.df}\n")
48
+
49
+ # Perform a series of operations
50
+ print("-" * 40)
51
+ print("Performing operations...")
52
+ print("-" * 40)
53
+
54
+ # Operation 1: Increase prices
55
+ print("\n1. Increasing all prices by 10%")
56
+ session.df['price'] = session.df['price'] * 1.1
57
+ session.record_operation(OperationType.TRANSFORM, {"operation": "price_increase_10%"})
58
+ print(f"Prices after increase:\n{session.df['price'].tolist()}")
59
+
60
+ # Operation 2: Filter low stock
61
+ print("\n2. Filtering items with stock >= 100")
62
+ session.df = session.df[session.df['stock'] >= 100]
63
+ session.record_operation(OperationType.FILTER, {"condition": "stock >= 100"})
64
+ print(f"Products after filter: {session.df['product'].tolist()}")
65
+
66
+ # Operation 3: Add discount column
67
+ print("\n3. Adding discount column")
68
+ session.df['discount'] = 0.15
69
+ session.record_operation(OperationType.ADD_COLUMN, {"column": "discount", "value": 0.15})
70
+ print(f"Columns: {session.df.columns.tolist()}")
71
+
72
+ # Operation 4: Sort by price
73
+ print("\n4. Sorting by price (descending)")
74
+ session.df = session.df.sort_values('price', ascending=False)
75
+ session.record_operation(OperationType.SORT, {"column": "price", "ascending": False})
76
+ print(f"Products after sort: {session.df['product'].tolist()}")
77
+
78
+ # Show current history
79
+ print("\n" + "=" * 60)
80
+ print("Current History")
81
+ print("=" * 60)
82
+ history_result = session.get_history()
83
+ if history_result["success"]:
84
+ print(f"Total operations: {history_result['statistics']['total_operations']}")
85
+ print(f"Current position: {history_result['statistics']['current_position']}")
86
+ print(f"Can undo: {history_result['statistics']['can_undo']}")
87
+ print(f"Can redo: {history_result['statistics']['can_redo']}")
88
+
89
+ print("\nOperations:")
90
+ for op in history_result['history']:
91
+ print(f" [{op['index']}] {op['operation_type']} - {op['timestamp']}")
92
+ if op['is_current']:
93
+ print(f" ^ Current position")
94
+
95
+ # Demonstrate undo
96
+ print("\n" + "=" * 60)
97
+ print("Undo Operations")
98
+ print("=" * 60)
99
+
100
+ print("\n1. Undoing last operation (sort)...")
101
+ undo_result = await session.undo()
102
+ if undo_result["success"]:
103
+ print(f"✓ {undo_result['message']}")
104
+ print(f"Products order: {session.df['product'].tolist() if session.df is not None else 'N/A'}")
105
+
106
+ print("\n2. Undoing again (add discount column)...")
107
+ undo_result = await session.undo()
108
+ if undo_result["success"]:
109
+ print(f"✓ {undo_result['message']}")
110
+ print(f"Columns: {session.df.columns.tolist() if session.df is not None else 'N/A'}")
111
+
112
+ print("\n3. Undoing once more (filter)...")
113
+ undo_result = await session.undo()
114
+ if undo_result["success"]:
115
+ print(f"✓ {undo_result['message']}")
116
+ print(f"Row count: {len(session.df) if session.df is not None else 0}")
117
+ print(f"Products: {session.df['product'].tolist() if session.df is not None else 'N/A'}")
118
+
119
+ # Show history after undo
120
+ history_result = session.get_history()
121
+ print(f"\nCurrent position after undos: {history_result['statistics']['current_position']}")
122
+ print(f"Can redo: {history_result['statistics']['can_redo']}")
123
+
124
+ # Demonstrate redo
125
+ print("\n" + "=" * 60)
126
+ print("Redo Operations")
127
+ print("=" * 60)
128
+
129
+ print("\n1. Redoing (re-apply filter)...")
130
+ redo_result = await session.redo()
131
+ if redo_result["success"]:
132
+ print(f"✓ {redo_result['message']}")
133
+ print(f"Row count: {len(session.df) if session.df is not None else 0}")
134
+
135
+ print("\n2. Redoing again (re-add discount column)...")
136
+ redo_result = await session.redo()
137
+ if redo_result["success"]:
138
+ print(f"✓ {redo_result['message']}")
139
+ print(f"Columns: {session.df.columns.tolist() if session.df is not None else 'N/A'}")
140
+
141
+ # Perform a new operation (clears redo stack)
142
+ print("\n" + "=" * 60)
143
+ print("New Operation (clears redo stack)")
144
+ print("=" * 60)
145
+
146
+ print("\nAdding a new column 'in_stock'...")
147
+ session.df['in_stock'] = True
148
+ session.record_operation(OperationType.ADD_COLUMN, {"column": "in_stock", "value": True})
149
+ print(f"Columns: {session.df.columns.tolist()}")
150
+
151
+ history_result = session.get_history()
152
+ print(f"\nCan still redo? {history_result['statistics']['can_redo']} (should be False)")
153
+
154
+ # Demonstrate restore to specific operation
155
+ print("\n" + "=" * 60)
156
+ print("Restore to Specific Operation")
157
+ print("=" * 60)
158
+
159
+ # Get first operation ID
160
+ history_result = session.get_history()
161
+ if history_result["success"] and history_result["history"]:
162
+ first_op = history_result["history"][0]
163
+ print(f"\nRestoring to first operation: {first_op['operation_type']}")
164
+
165
+ restore_result = await session.restore_to_operation(first_op['operation_id'])
166
+ if restore_result["success"]:
167
+ print(f"✓ {restore_result['message']}")
168
+ print(f"Data shape: {restore_result['shape']}")
169
+ print(f"Current data:\n{session.df}")
170
+
171
+ # Export history
172
+ print("\n" + "=" * 60)
173
+ print("Export History")
174
+ print("=" * 60)
175
+
176
+ # Export as JSON
177
+ history_file = os.path.join(tempfile.gettempdir(), "history_export.json")
178
+ if session.history_manager:
179
+ success = session.history_manager.export_history(history_file, "json")
180
+ if success:
181
+ print(f"✓ History exported to: {history_file}")
182
+
183
+ # Show exported content
184
+ with open(history_file, 'r') as f:
185
+ history_data = json.load(f)
186
+ print(f" Total operations: {history_data['total_operations']}")
187
+ print(f" Exported at: {history_data['exported_at']}")
188
+
189
+ # Show history statistics
190
+ print("\n" + "=" * 60)
191
+ print("History Statistics")
192
+ print("=" * 60)
193
+
194
+ if session.history_manager:
195
+ stats = session.history_manager.get_statistics()
196
+ print(f"Total operations: {stats['total_operations']}")
197
+ print(f"Current position: {stats['current_position']}")
198
+ print(f"Snapshots saved: {stats['snapshots_count']}")
199
+ print(f"Storage type: {stats['storage_type']}")
200
+ print("\nOperation breakdown:")
201
+ for op_type, count in stats['operation_types'].items():
202
+ print(f" {op_type}: {count}")
203
+
204
+ # Show persistence
205
+ print("\n" + "=" * 60)
206
+ print("History Persistence")
207
+ print("=" * 60)
208
+
209
+ history_dir = session.history_manager.history_dir if session.history_manager else None
210
+ if history_dir:
211
+ print(f"History directory: {history_dir}")
212
+
213
+ # List history files
214
+ history_files = list(Path(history_dir).glob(f"*{session.session_id}*"))
215
+ print(f"History files created: {len(history_files)}")
216
+ for hf in history_files:
217
+ print(f" - {hf.name}")
218
+
219
+ # Check snapshot directory
220
+ snapshot_dir = Path(history_dir) / "snapshots" / session.session_id
221
+ if snapshot_dir.exists():
222
+ snapshots = list(snapshot_dir.glob("*.pkl"))
223
+ print(f"Snapshots saved: {len(snapshots)}")
224
+
225
+ print("\n✅ History demonstration completed!")
226
+
227
+ return session
228
+
229
+
230
+ async def demonstrate_history_recovery():
231
+ """Demonstrate recovering history from a previous session."""
232
+
233
+ print("\n" + "=" * 60)
234
+ print("History Recovery from Previous Session")
235
+ print("=" * 60)
236
+
237
+ # Create a session and perform operations
238
+ print("\n1. Creating initial session with operations...")
239
+ session1 = CSVSession(
240
+ session_id="demo-recovery-session",
241
+ enable_history=True,
242
+ history_storage=HistoryStorage.JSON
243
+ )
244
+
245
+ data = pd.DataFrame({
246
+ 'item': ['A', 'B', 'C'],
247
+ 'value': [10, 20, 30]
248
+ })
249
+
250
+ session1.load_data(data)
251
+
252
+ # Perform operations
253
+ session1.df['value'] = session1.df['value'] * 2
254
+ session1.record_operation(OperationType.TRANSFORM, {"operation": "double_values"})
255
+
256
+ session1.df['status'] = 'active'
257
+ session1.record_operation(OperationType.ADD_COLUMN, {"column": "status"})
258
+
259
+ print(f" Operations performed: 2")
260
+ print(f" Final data:\n{session1.df}")
261
+
262
+ # Simulate session end
263
+ session1_id = session1.session_id
264
+ del session1
265
+
266
+ print("\n2. Session ended. Creating new session with same ID...")
267
+
268
+ # Create new session with same ID - should load history
269
+ session2 = CSVSession(
270
+ session_id=session1_id,
271
+ enable_history=True,
272
+ history_storage=HistoryStorage.JSON
273
+ )
274
+
275
+ # Load the data (in real scenario, would load from file)
276
+ session2.load_data(data)
277
+
278
+ # Check if history was recovered
279
+ history_result = session2.get_history()
280
+ if history_result["success"]:
281
+ print(f"✓ History recovered! Found {history_result['statistics']['total_operations']} operations")
282
+
283
+ # Can we undo operations from previous session?
284
+ print("\n3. Testing undo on recovered history...")
285
+ if history_result['statistics']['can_undo']:
286
+ undo_result = await session2.undo()
287
+ if undo_result["success"]:
288
+ print(f"✓ Successfully undid operation from previous session!")
289
+ print(f" Operation: {undo_result['operation']['operation_type']}")
290
+
291
+ print("\n✅ History recovery demonstration completed!")
292
+
293
+
294
+ async def main():
295
+ """Run all history demonstrations."""
296
+
297
+ # Demo 1: Basic history with undo/redo
298
+ session = await demonstrate_history()
299
+
300
+ # Demo 2: History recovery
301
+ await demonstrate_history_recovery()
302
+
303
+ print("\n" + "=" * 60)
304
+ print("Key Features Demonstrated")
305
+ print("=" * 60)
306
+ print("✓ Operation history tracking with timestamps")
307
+ print("✓ Persistent history storage (JSON/Pickle)")
308
+ print("✓ Undo/Redo functionality")
309
+ print("✓ Restore to any previous operation")
310
+ print("✓ History export (JSON/CSV)")
311
+ print("✓ History recovery across sessions")
312
+ print("✓ Automatic snapshots for data recovery")
313
+ print("✓ History statistics and analysis")
314
+
315
+
316
+ if __name__ == "__main__":
317
+ asyncio.run(main())
@@ -0,0 +1,124 @@
1
+ #!/usr/bin/env python3
2
+ """Test that auto-save is enabled by default and works correctly."""
3
+
4
+ import asyncio
5
+ import tempfile
6
+ import os
7
+ import pandas as pd
8
+ from pathlib import Path
9
+
10
+ # Setup path for imports
11
+ import sys
12
+ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
13
+
14
+ from src.csv_editor.models.csv_session import CSVSession
15
+ from src.csv_editor.models.data_models import OperationType
16
+
17
+
18
+ async def test_default_autosave():
19
+ """Test that auto-save is enabled by default."""
20
+
21
+ print("=" * 60)
22
+ print("Testing Default Auto-Save Configuration")
23
+ print("=" * 60)
24
+
25
+ # Create a temporary CSV file
26
+ temp_dir = tempfile.mkdtemp()
27
+ test_file = os.path.join(temp_dir, "test_data.csv")
28
+
29
+ # Create initial data
30
+ initial_data = pd.DataFrame({
31
+ 'id': [1, 2, 3],
32
+ 'name': ['Alice', 'Bob', 'Charlie'],
33
+ 'value': [100, 200, 300]
34
+ })
35
+
36
+ # Save initial data
37
+ initial_data.to_csv(test_file, index=False)
38
+ print(f"\n✓ Created test file: {test_file}")
39
+ print(f"Initial content:\n{initial_data}")
40
+
41
+ # Create session WITHOUT explicitly configuring auto-save
42
+ session = CSVSession() # Using defaults
43
+
44
+ # Check default auto-save configuration
45
+ print("\n" + "-" * 40)
46
+ print("Default Auto-Save Configuration:")
47
+ print("-" * 40)
48
+ config = session.auto_save_config
49
+ print(f"Enabled: {config.enabled}")
50
+ print(f"Mode: {config.mode.value}")
51
+ print(f"Strategy: {config.strategy.value}")
52
+
53
+ # Verify defaults
54
+ assert config.enabled == True, "Auto-save should be enabled by default"
55
+ assert config.mode.value == "after_operation", "Mode should be 'after_operation' by default"
56
+ assert config.strategy.value == "overwrite", "Strategy should be 'overwrite' by default"
57
+ print("\n✅ All defaults are correct!")
58
+
59
+ # Load the CSV file
60
+ df = pd.read_csv(test_file)
61
+ session.load_data(df, file_path=test_file)
62
+
63
+ print("\n" + "-" * 40)
64
+ print("Testing Auto-Save on Operations:")
65
+ print("-" * 40)
66
+
67
+ # Perform an operation
68
+ print("\n1. Doubling all values...")
69
+ session.df['value'] = session.df['value'] * 2
70
+ session.record_operation(OperationType.TRANSFORM, {"operation": "double_values"})
71
+
72
+ # Trigger auto-save (should happen automatically after operation)
73
+ result = await session.trigger_auto_save_if_needed()
74
+ print(f" Auto-save triggered: {result is not None}")
75
+
76
+ # Read the file to verify it was updated
77
+ saved_df = pd.read_csv(test_file)
78
+ print(f" Values in file after operation: {saved_df['value'].tolist()}")
79
+ assert saved_df['value'].tolist() == [200, 400, 600], "File should be auto-saved with new values"
80
+ print(" ✓ File was automatically updated!")
81
+
82
+ # Perform another operation
83
+ print("\n2. Adding a new column...")
84
+ session.df['status'] = 'active'
85
+ session.record_operation(OperationType.ADD_COLUMN, {"column": "status"})
86
+
87
+ # Trigger auto-save again
88
+ result = await session.trigger_auto_save_if_needed()
89
+ print(f" Auto-save triggered: {result is not None}")
90
+
91
+ # Verify the file has the new column
92
+ saved_df = pd.read_csv(test_file)
93
+ print(f" Columns in file: {saved_df.columns.tolist()}")
94
+ assert 'status' in saved_df.columns, "New column should be in the saved file"
95
+ print(" ✓ New column was automatically saved!")
96
+
97
+ # Show final file content
98
+ print("\n" + "-" * 40)
99
+ print("Final File Content:")
100
+ print("-" * 40)
101
+ print(saved_df)
102
+
103
+ print("\n" + "=" * 60)
104
+ print("✅ Default Auto-Save Test Passed!")
105
+ print("=" * 60)
106
+ print("\nSummary:")
107
+ print("• Auto-save is ENABLED by default")
108
+ print("• Mode is 'after_operation' by default")
109
+ print("• Strategy is 'overwrite' by default")
110
+ print("• Original file is automatically updated after each operation")
111
+ print("• No manual configuration needed!")
112
+
113
+ return test_file
114
+
115
+
116
+ async def main():
117
+ """Run the test."""
118
+ test_file = await test_default_autosave()
119
+ print(f"\nTest file location: {test_file}")
120
+ print("(File has been automatically saved with all changes)")
121
+
122
+
123
+ if __name__ == "__main__":
124
+ asyncio.run(main())
@@ -0,0 +1,179 @@
1
+ #!/usr/bin/env python3
2
+ """Example showing how to update the consignee name field using the new update_column function."""
3
+
4
+ import asyncio
5
+ from mcp_client import MCPClient
6
+
7
+
8
+ async def update_consignee_example():
9
+ """Example of updating consignee field to keep only the company name."""
10
+
11
+ # Initialize MCP client
12
+ client = MCPClient()
13
+ await client.connect("csv-editor")
14
+
15
+ # Load the CSV file
16
+ print("Loading CSV file...")
17
+ result = await client.call_tool(
18
+ "load_csv",
19
+ {
20
+ "file_path": "/home/santosh/projects/csv-editor/tests/sample_data/123456/1753698447530_BOL_Lubecon USA LLC_GHY 1.csv"
21
+ }
22
+ )
23
+ session_id = result["session_id"]
24
+ print(f"Session created: {session_id}")
25
+ print(f"Loaded {result['row_count']} rows with {result['column_count']} columns")
26
+
27
+ # Get current consignee value
28
+ print("\nGetting current consignee value...")
29
+ info_result = await client.call_tool(
30
+ "get_session_info",
31
+ {"session_id": session_id}
32
+ )
33
+ print(f"Columns: {info_result['columns'][:10]}...") # Show first 10 columns
34
+
35
+ # Method 1: Using 'split' operation to extract company name
36
+ print("\n--- Method 1: Using 'split' operation ---")
37
+ print("Extracting company name (everything before the dash)...")
38
+
39
+ result = await client.call_tool(
40
+ "update_column",
41
+ {
42
+ "session_id": session_id,
43
+ "column": "Consignee Name",
44
+ "operation": "split",
45
+ "pattern": " -", # Split on " -" (space and dash)
46
+ "value": 0 # Take the first part (index 0)
47
+ }
48
+ )
49
+
50
+ if result["success"]:
51
+ print(f"✓ Column updated successfully!")
52
+ print(f" Original: {result['original_sample'][0] if result['original_sample'] else 'N/A'}")
53
+ print(f" Updated: {result['updated_sample'][0] if result['updated_sample'] else 'N/A'}")
54
+
55
+ # Export the result
56
+ print("\nExporting updated CSV...")
57
+ export_result = await client.call_tool(
58
+ "export_csv",
59
+ {
60
+ "session_id": session_id,
61
+ "file_path": "/tmp/updated_bol_method1.csv",
62
+ "format": "csv"
63
+ }
64
+ )
65
+ print(f"✓ Exported to: {export_result['file_path']}")
66
+
67
+ # Close session
68
+ await client.call_tool("close_session", {"session_id": session_id})
69
+
70
+ # Method 2: Using 'replace' operation with regex
71
+ print("\n--- Method 2: Using 'replace' with regex ---")
72
+
73
+ # Load the file again
74
+ result = await client.call_tool(
75
+ "load_csv",
76
+ {
77
+ "file_path": "/home/santosh/projects/csv-editor/tests/sample_data/123456/1753698447530_BOL_Lubecon USA LLC_GHY 1.csv"
78
+ }
79
+ )
80
+ session_id = result["session_id"]
81
+
82
+ print("Removing location information using regex...")
83
+ result = await client.call_tool(
84
+ "update_column",
85
+ {
86
+ "session_id": session_id,
87
+ "column": "Consignee Name",
88
+ "operation": "replace",
89
+ "pattern": r"\s*-.*$", # Match everything from " -" to end of string
90
+ "replacement": "" # Replace with empty string
91
+ }
92
+ )
93
+
94
+ if result["success"]:
95
+ print(f"✓ Column updated successfully!")
96
+ print(f" Original: {result['original_sample'][0] if result['original_sample'] else 'N/A'}")
97
+ print(f" Updated: {result['updated_sample'][0] if result['updated_sample'] else 'N/A'}")
98
+
99
+ # Export the result
100
+ export_result = await client.call_tool(
101
+ "export_csv",
102
+ {
103
+ "session_id": session_id,
104
+ "file_path": "/tmp/updated_bol_method2.csv",
105
+ "format": "csv"
106
+ }
107
+ )
108
+ print(f"✓ Exported to: {export_result['file_path']}")
109
+
110
+ # Close session
111
+ await client.call_tool("close_session", {"session_id": session_id})
112
+
113
+ # Method 3: Using 'extract' operation
114
+ print("\n--- Method 3: Using 'extract' with regex ---")
115
+
116
+ # Load the file again
117
+ result = await client.call_tool(
118
+ "load_csv",
119
+ {
120
+ "file_path": "/home/santosh/projects/csv-editor/tests/sample_data/123456/1753698447530_BOL_Lubecon USA LLC_GHY 1.csv"
121
+ }
122
+ )
123
+ session_id = result["session_id"]
124
+
125
+ print("Extracting company name using regex...")
126
+ result = await client.call_tool(
127
+ "update_column",
128
+ {
129
+ "session_id": session_id,
130
+ "column": "Consignee Name",
131
+ "operation": "extract",
132
+ "pattern": r"^([^-]+)", # Extract everything before the first dash
133
+ }
134
+ )
135
+
136
+ if result["success"]:
137
+ print(f"✓ Column updated successfully!")
138
+ print(f" Original: {result['original_sample'][0] if result['original_sample'] else 'N/A'}")
139
+ print(f" Updated: {result['updated_sample'][0] if result['updated_sample'] else 'N/A'}")
140
+
141
+ # Also clean up any trailing spaces
142
+ print("\nCleaning up whitespace...")
143
+ result = await client.call_tool(
144
+ "update_column",
145
+ {
146
+ "session_id": session_id,
147
+ "column": "Consignee Name",
148
+ "operation": "strip"
149
+ }
150
+ )
151
+ print(f"✓ Whitespace cleaned")
152
+
153
+ # Export the result
154
+ export_result = await client.call_tool(
155
+ "export_csv",
156
+ {
157
+ "session_id": session_id,
158
+ "file_path": "/tmp/updated_bol_method3.csv",
159
+ "format": "csv"
160
+ }
161
+ )
162
+ print(f"✓ Exported to: {export_result['file_path']}")
163
+
164
+ # Close session
165
+ await client.call_tool("close_session", {"session_id": session_id})
166
+
167
+ await client.disconnect()
168
+
169
+ print("\n✅ All methods completed successfully!")
170
+ print("\nThe new 'update_column' function makes it simple to:")
171
+ print(" • Extract parts of text (split, extract)")
172
+ print(" • Clean up text (strip, replace)")
173
+ print(" • Transform text (upper, lower)")
174
+ print(" • Fill missing values")
175
+ print("\nCheck the exported files in /tmp/ to see the results.")
176
+
177
+
178
+ if __name__ == "__main__":
179
+ asyncio.run(update_consignee_example())
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@mseep/csv-editor",
3
+ "version": "1.0.0",
4
+ "description": "MCP server for comprehensive CSV file operations",
5
+ "keywords": [
6
+ "mcp",
7
+ "mcp-server",
8
+ "csv",
9
+ "excel",
10
+ "data",
11
+ "pandas",
12
+ "model-context-protocol",
13
+ "ai",
14
+ "claude",
15
+ "llm",
16
+ "mseep"
17
+ ],
18
+ "homepage": "https://github.com/santoshray02/csv-editor#readme",
19
+ "bugs": {
20
+ "url": "https://github.com/santoshray02/csv-editor/issues"
21
+ },
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "git+https://github.com/santoshray02/csv-editor.git"
25
+ },
26
+ "license": "MIT",
27
+ "author": {
28
+ "name": "Santosh Ray",
29
+ "email": "rayskumar02@gmail.com"
30
+ },
31
+ "type": "module",
32
+ "scripts": {
33
+ "build": "echo 'Python package - no JS build needed'",
34
+ "prepare": "echo 'Installing Python dependencies...'"
35
+ },
36
+ "mcp": {
37
+ "serverType": "python",
38
+ "command": "python",
39
+ "args": [
40
+ "-m",
41
+ "csv_editor.server"
42
+ ],
43
+ "env": {},
44
+ "capabilities": {
45
+ "tools": true,
46
+ "resources": true,
47
+ "prompts": true
48
+ }
49
+ },
50
+ "publisher": "mseep"
51
+ }