toolplane-python-client 0.1.0__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.
Files changed (81) hide show
  1. toolplane/__init__.py +106 -0
  2. toolplane/common/__init__.py +93 -0
  3. toolplane/common/base_config.py +129 -0
  4. toolplane/common/base_connection_manager.py +171 -0
  5. toolplane/common/base_session_manager.py +321 -0
  6. toolplane/common/base_tool_manager.py +347 -0
  7. toolplane/common/constants.py +47 -0
  8. toolplane/common/utils.py +310 -0
  9. toolplane/core/__init__.py +67 -0
  10. toolplane/core/config.py +107 -0
  11. toolplane/core/connection.py +285 -0
  12. toolplane/core/errors.py +298 -0
  13. toolplane/core/machine.py +480 -0
  14. toolplane/core/request.py +775 -0
  15. toolplane/core/session.py +332 -0
  16. toolplane/core/session_context.py +514 -0
  17. toolplane/core/task.py +130 -0
  18. toolplane/core/tool.py +329 -0
  19. toolplane/http_core/__init__.py +37 -0
  20. toolplane/http_core/http_config.py +97 -0
  21. toolplane/http_core/http_connection.py +409 -0
  22. toolplane/http_core/http_machine.py +298 -0
  23. toolplane/http_core/http_request.py +748 -0
  24. toolplane/http_core/http_session.py +348 -0
  25. toolplane/http_core/http_session_context.py +491 -0
  26. toolplane/http_core/http_task.py +101 -0
  27. toolplane/http_core/http_tool.py +400 -0
  28. toolplane/interfaces/__init__.py +27 -0
  29. toolplane/interfaces/client_interface.py +122 -0
  30. toolplane/interfaces/connection_interface.py +193 -0
  31. toolplane/interfaces/event_interface.py +290 -0
  32. toolplane/interfaces/request_interface.py +439 -0
  33. toolplane/interfaces/session_interface.py +288 -0
  34. toolplane/interfaces/tool_interface.py +441 -0
  35. toolplane/proto/__init__.py +0 -0
  36. toolplane/proto/service_pb2.py +315 -0
  37. toolplane/proto/service_pb2_grpc.py +2240 -0
  38. toolplane/provider_cli.py +268 -0
  39. toolplane/provider_registry.py +77 -0
  40. toolplane/provider_runtime.py +302 -0
  41. toolplane/toolkits/__init__.py +0 -0
  42. toolplane/toolkits/standalone_tools/__init__.py +0 -0
  43. toolplane/toolkits/standalone_tools/create_directory.py +94 -0
  44. toolplane/toolkits/standalone_tools/create_file.py +124 -0
  45. toolplane/toolkits/standalone_tools/file_search.py +229 -0
  46. toolplane/toolkits/standalone_tools/grep_search.py +372 -0
  47. toolplane/toolkits/standalone_tools/launcher.py +146 -0
  48. toolplane/toolkits/standalone_tools/list_dir.py +395 -0
  49. toolplane/toolkits/standalone_tools/read_file.py +346 -0
  50. toolplane/toolkits/standalone_tools/replace_string_in_file.py +407 -0
  51. toolplane/toolkits/standalone_tools/run_tests.py +66 -0
  52. toolplane/toolkits/standalone_tools/semantic_search.py +485 -0
  53. toolplane/toolkits/standalone_tools/standalone_toolkit.py +979 -0
  54. toolplane/toolkits/standalone_tools/test_failure_analysis.py +618 -0
  55. toolplane/toolkits/standalone_tools/test_standalone_toolkit.py +517 -0
  56. toolplane/toolkits/swe/__init__.py +35 -0
  57. toolplane/toolkits/swe/create_directory.py +15 -0
  58. toolplane/toolkits/swe/create_file.py +15 -0
  59. toolplane/toolkits/swe/descriptions.py +273 -0
  60. toolplane/toolkits/swe/execute_bash.py +93 -0
  61. toolplane/toolkits/swe/file_editor.py +775 -0
  62. toolplane/toolkits/swe/file_search.py +16 -0
  63. toolplane/toolkits/swe/finish.py +50 -0
  64. toolplane/toolkits/swe/grep_search.py +19 -0
  65. toolplane/toolkits/swe/list_dir.py +407 -0
  66. toolplane/toolkits/swe/read_file.py +18 -0
  67. toolplane/toolkits/swe/replace_string_in_file.py +17 -0
  68. toolplane/toolkits/swe/search.py +260 -0
  69. toolplane/toolkits/swe/semantic_search.py +20 -0
  70. toolplane/toolkits/swe/str_replace_editor.py +647 -0
  71. toolplane/toolkits/swe/submit.py +29 -0
  72. toolplane/toolkits/swe/swe_toolkit.py +1296 -0
  73. toolplane/toolplane_client.py +686 -0
  74. toolplane/toolplane_http_client.py +681 -0
  75. toolplane/utils/__init__.py +3 -0
  76. toolplane/utils/schema.py +146 -0
  77. toolplane_python_client-0.1.0.dist-info/METADATA +543 -0
  78. toolplane_python_client-0.1.0.dist-info/RECORD +81 -0
  79. toolplane_python_client-0.1.0.dist-info/WHEEL +5 -0
  80. toolplane_python_client-0.1.0.dist-info/entry_points.txt +2 -0
  81. toolplane_python_client-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,407 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Description: Replace strings in files with enhanced features and safety checks.
4
+
5
+ This tool performs string replacements in files with validation, backup options,
6
+ and various safety features to prevent accidental data loss.
7
+
8
+ Parameters:
9
+ file_path (string, required): The absolute path to the file to edit.
10
+ old_string (string, required): The string to be replaced (must match exactly).
11
+ new_string (string, required): The replacement string.
12
+ create_backup (boolean, optional): Create a backup before editing (default: True).
13
+ dry_run (boolean, optional): Show what would be changed without making changes (default: False).
14
+ whole_word (boolean, optional): Only replace whole words (default: False).
15
+ ignore_case (boolean, optional): Perform case-insensitive matching (default: False).
16
+ max_replacements (integer, optional): Maximum number of replacements to make (default: unlimited).
17
+ """
18
+
19
+ import argparse
20
+ import os
21
+ import re
22
+ import shutil
23
+ import sys
24
+ import tempfile
25
+ import time
26
+ from pathlib import Path
27
+ from typing import Any, Dict, Optional
28
+
29
+
30
+ def replace_string_in_file(
31
+ file_path: str,
32
+ old_string: str,
33
+ new_string: str,
34
+ create_backup: bool = True,
35
+ dry_run: bool = False,
36
+ whole_word: bool = False,
37
+ ignore_case: bool = False,
38
+ max_replacements: Optional[int] = None,
39
+ ) -> Dict[str, Any]:
40
+ """
41
+ Replace strings in a file with enhanced features and safety checks.
42
+
43
+ Args:
44
+ file_path: The absolute path to the file to edit
45
+ old_string: The string to be replaced
46
+ new_string: The replacement string
47
+ create_backup: Create a backup before editing
48
+ dry_run: Show what would be changed without making changes
49
+ whole_word: Only replace whole words
50
+ ignore_case: Perform case-insensitive matching
51
+ max_replacements: Maximum number of replacements to make
52
+
53
+ Returns:
54
+ Dictionary containing operation results and statistics
55
+ """
56
+ try:
57
+ path = Path(file_path)
58
+
59
+ # Validate input
60
+ if not path.exists():
61
+ return {
62
+ "success": False,
63
+ "error": f"File does not exist: {file_path}",
64
+ "replacements_made": 0,
65
+ }
66
+
67
+ if not path.is_file():
68
+ return {
69
+ "success": False,
70
+ "error": f"Path is not a file: {file_path}",
71
+ "replacements_made": 0,
72
+ }
73
+
74
+ # Read the file
75
+ try:
76
+ with open(path, "r", encoding="utf-8", errors="replace") as f:
77
+ original_content = f.read()
78
+ except UnicodeDecodeError:
79
+ # Try with different encodings
80
+ for encoding in ["latin-1", "cp1252"]:
81
+ try:
82
+ with open(path, "r", encoding=encoding) as f:
83
+ original_content = f.read()
84
+ break
85
+ except UnicodeDecodeError:
86
+ continue
87
+ else:
88
+ return {
89
+ "success": False,
90
+ "error": f"Could not read file with any supported encoding: {file_path}",
91
+ "replacements_made": 0,
92
+ }
93
+
94
+ # Prepare search pattern
95
+ if whole_word:
96
+ if ignore_case:
97
+ pattern = re.compile(
98
+ r"\b" + re.escape(old_string) + r"\b", re.IGNORECASE
99
+ )
100
+ else:
101
+ pattern = re.compile(r"\b" + re.escape(old_string) + r"\b")
102
+ else:
103
+ if ignore_case:
104
+ pattern = re.compile(re.escape(old_string), re.IGNORECASE)
105
+ else:
106
+ pattern = re.compile(re.escape(old_string))
107
+
108
+ # Find all matches
109
+ matches = list(pattern.finditer(original_content))
110
+
111
+ if not matches:
112
+ return {
113
+ "success": True,
114
+ "message": f"No matches found for '{old_string}' in {file_path}",
115
+ "replacements_made": 0,
116
+ "dry_run": dry_run,
117
+ }
118
+
119
+ # Apply max_replacements limit
120
+ if max_replacements is not None and len(matches) > max_replacements:
121
+ matches = matches[:max_replacements]
122
+
123
+ # Perform replacements
124
+ replacements_made = len(matches)
125
+
126
+ if dry_run:
127
+ # Show what would be changed
128
+ changes = []
129
+ for i, match in enumerate(matches[:5]): # Show first 5 matches
130
+ start = match.start()
131
+ end = match.end()
132
+
133
+ # Get context around the match
134
+ context_start = max(0, start - 50)
135
+ context_end = min(len(original_content), end + 50)
136
+
137
+ before = original_content[context_start:context_end]
138
+ after = before.replace(match.group(), new_string)
139
+
140
+ changes.append(
141
+ {
142
+ "match_number": i + 1,
143
+ "position": start,
144
+ "before": before,
145
+ "after": after,
146
+ "line_number": original_content[:start].count("\n") + 1,
147
+ }
148
+ )
149
+
150
+ return {
151
+ "success": True,
152
+ "message": f"DRY RUN: Would replace {replacements_made} occurrences of '{old_string}' with '{new_string}' in {file_path}",
153
+ "replacements_made": replacements_made,
154
+ "dry_run": True,
155
+ "changes_preview": changes,
156
+ }
157
+
158
+ # Create backup if requested
159
+ backup_path = None
160
+ if create_backup:
161
+ backup_path = create_file_backup(path)
162
+
163
+ # Apply replacements
164
+ new_content = pattern.sub(
165
+ new_string, original_content, count=max_replacements or 0
166
+ )
167
+
168
+ # Write the modified content
169
+ try:
170
+ with open(path, "w", encoding="utf-8") as f:
171
+ f.write(new_content)
172
+ except Exception as e:
173
+ # Restore backup if write fails
174
+ if backup_path and backup_path.exists():
175
+ shutil.copy2(backup_path, path)
176
+ raise e
177
+
178
+ result = {
179
+ "success": True,
180
+ "message": f"Successfully replaced {replacements_made} occurrences of '{old_string}' with '{new_string}' in {file_path}",
181
+ "replacements_made": replacements_made,
182
+ "dry_run": False,
183
+ "backup_created": backup_path is not None,
184
+ "backup_path": str(backup_path) if backup_path else None,
185
+ "original_size": len(original_content),
186
+ "new_size": len(new_content),
187
+ "size_change": len(new_content) - len(original_content),
188
+ }
189
+
190
+ return result
191
+
192
+ except PermissionError:
193
+ return {
194
+ "success": False,
195
+ "error": f"Permission denied: {file_path}",
196
+ "replacements_made": 0,
197
+ }
198
+ except Exception as e:
199
+ return {
200
+ "success": False,
201
+ "error": f"Error processing file: {e}",
202
+ "replacements_made": 0,
203
+ }
204
+
205
+
206
+ def create_file_backup(file_path: Path) -> Optional[Path]:
207
+ """Create a backup of the file with timestamp."""
208
+ try:
209
+ timestamp = time.strftime("%Y%m%d_%H%M%S")
210
+ backup_path = file_path.with_suffix(f".backup_{timestamp}{file_path.suffix}")
211
+
212
+ shutil.copy2(file_path, backup_path)
213
+ return backup_path
214
+
215
+ except Exception:
216
+ return None
217
+
218
+
219
+ def validate_old_string_uniqueness(file_path: str, old_string: str) -> Dict[str, Any]:
220
+ """Validate that the old_string appears uniquely in the file."""
221
+ try:
222
+ path = Path(file_path)
223
+
224
+ with open(path, "r", encoding="utf-8", errors="replace") as f:
225
+ content = f.read()
226
+
227
+ occurrences = content.count(old_string)
228
+
229
+ if occurrences == 0:
230
+ return {
231
+ "unique": False,
232
+ "count": 0,
233
+ "message": f"String '{old_string}' not found in file",
234
+ }
235
+ elif occurrences == 1:
236
+ return {
237
+ "unique": True,
238
+ "count": 1,
239
+ "message": f"String '{old_string}' found once (unique)",
240
+ }
241
+ else:
242
+ # Find all occurrences and their contexts
243
+ contexts = []
244
+ start = 0
245
+ for i in range(min(occurrences, 5)): # Show first 5 occurrences
246
+ pos = content.find(old_string, start)
247
+ if pos == -1:
248
+ break
249
+
250
+ context_start = max(0, pos - 50)
251
+ context_end = min(len(content), pos + len(old_string) + 50)
252
+ context = content[context_start:context_end]
253
+ line_number = content[:pos].count("\n") + 1
254
+
255
+ contexts.append(
256
+ {"position": pos, "line_number": line_number, "context": context}
257
+ )
258
+
259
+ start = pos + 1
260
+
261
+ return {
262
+ "unique": False,
263
+ "count": occurrences,
264
+ "message": f"String '{old_string}' found {occurrences} times (not unique)",
265
+ "contexts": contexts,
266
+ }
267
+
268
+ except Exception as e:
269
+ return {
270
+ "unique": False,
271
+ "count": 0,
272
+ "message": f"Error checking uniqueness: {e}",
273
+ }
274
+
275
+
276
+ def main():
277
+ parser = argparse.ArgumentParser(
278
+ description="Replace strings in files with enhanced features and safety checks."
279
+ )
280
+ parser.add_argument(
281
+ "file_path", type=str, help="The absolute path to the file to edit"
282
+ )
283
+ parser.add_argument(
284
+ "old_string", type=str, help="The string to be replaced (must match exactly)"
285
+ )
286
+ parser.add_argument("new_string", type=str, help="The replacement string")
287
+ parser.add_argument(
288
+ "--create_backup",
289
+ action="store_true",
290
+ default=True,
291
+ help="Create a backup before editing (default: True)",
292
+ )
293
+ parser.add_argument(
294
+ "--no_backup",
295
+ action="store_true",
296
+ default=False,
297
+ help="Don't create a backup before editing",
298
+ )
299
+ parser.add_argument(
300
+ "--dry_run",
301
+ action="store_true",
302
+ default=False,
303
+ help="Show what would be changed without making changes (default: False)",
304
+ )
305
+ parser.add_argument(
306
+ "--whole_word",
307
+ action="store_true",
308
+ default=False,
309
+ help="Only replace whole words (default: False)",
310
+ )
311
+ parser.add_argument(
312
+ "--ignore_case",
313
+ action="store_true",
314
+ default=False,
315
+ help="Perform case-insensitive matching (default: False)",
316
+ )
317
+ parser.add_argument(
318
+ "--max_replacements",
319
+ type=int,
320
+ help="Maximum number of replacements to make (default: unlimited)",
321
+ )
322
+ parser.add_argument(
323
+ "--check_uniqueness",
324
+ action="store_true",
325
+ default=False,
326
+ help="Check if the old_string is unique before replacing",
327
+ )
328
+ parser.add_argument(
329
+ "--output_format",
330
+ choices=["default", "json"],
331
+ default="default",
332
+ help="Output format (default: default)",
333
+ )
334
+
335
+ args = parser.parse_args()
336
+
337
+ # Handle backup flag
338
+ create_backup = args.create_backup and not args.no_backup
339
+
340
+ # Check uniqueness if requested
341
+ if args.check_uniqueness:
342
+ uniqueness_result = validate_old_string_uniqueness(
343
+ args.file_path, args.old_string
344
+ )
345
+
346
+ if args.output_format == "json":
347
+ import json
348
+
349
+ print(json.dumps(uniqueness_result, indent=2))
350
+ else:
351
+ print(uniqueness_result["message"])
352
+
353
+ if not uniqueness_result["unique"] and uniqueness_result["count"] > 1:
354
+ print(f"\nOccurrences found:")
355
+ for i, ctx in enumerate(uniqueness_result.get("contexts", []), 1):
356
+ print(
357
+ f"{i}. Line {ctx['line_number']}, Position {ctx['position']}:"
358
+ )
359
+ print(f" Context: {ctx['context'][:100]}...")
360
+ print()
361
+
362
+ if not uniqueness_result["unique"] and uniqueness_result["count"] > 1:
363
+ print(
364
+ "String is not unique. Use --dry_run to see all changes before applying."
365
+ )
366
+
367
+ # Perform replacement
368
+ result = replace_string_in_file(
369
+ args.file_path,
370
+ args.old_string,
371
+ args.new_string,
372
+ create_backup=create_backup,
373
+ dry_run=args.dry_run,
374
+ whole_word=args.whole_word,
375
+ ignore_case=args.ignore_case,
376
+ max_replacements=args.max_replacements,
377
+ )
378
+
379
+ if args.output_format == "json":
380
+ import json
381
+
382
+ print(json.dumps(result, indent=2))
383
+ else:
384
+ if result["success"]:
385
+ print(result["message"])
386
+
387
+ if args.dry_run and "changes_preview" in result:
388
+ print("\nChanges preview:")
389
+ for change in result["changes_preview"]:
390
+ print(
391
+ f"\nMatch {change['match_number']} at line {change['line_number']}:"
392
+ )
393
+ print(f"Before: {change['before']}")
394
+ print(f"After: {change['after']}")
395
+
396
+ if result.get("backup_created"):
397
+ print(f"Backup created: {result['backup_path']}")
398
+
399
+ else:
400
+ print(f"Error: {result['error']}", file=sys.stderr)
401
+
402
+ # Exit with error code if operation failed
403
+ sys.exit(0 if result["success"] else 1)
404
+
405
+
406
+ if __name__ == "__main__":
407
+ main()
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Test runner script for standalone tools.
4
+
5
+ This script provides an easy way to run the test suite with various options.
6
+ """
7
+
8
+ import argparse
9
+ import subprocess
10
+ import sys
11
+ from pathlib import Path
12
+
13
+
14
+ def run_tests(test_pattern=None, verbose=False, coverage=False, markers=None):
15
+ """Run the test suite with specified options."""
16
+ cmd = [sys.executable, "-m", "pytest"]
17
+
18
+ if verbose:
19
+ cmd.append("-v")
20
+
21
+ if coverage:
22
+ cmd.extend(["--cov=.", "--cov-report=html", "--cov-report=term"])
23
+
24
+ if markers:
25
+ cmd.extend(["-m", markers])
26
+
27
+ if test_pattern:
28
+ cmd.extend(["-k", test_pattern])
29
+
30
+ # Add the test file
31
+ cmd.append("test_standalone_toolkit.py")
32
+
33
+ print(f"Running: {' '.join(cmd)}")
34
+ return subprocess.run(cmd).returncode
35
+
36
+
37
+ def main():
38
+ parser = argparse.ArgumentParser(description="Run standalone tools test suite")
39
+ parser.add_argument("-v", "--verbose", action="store_true", help="Verbose output")
40
+ parser.add_argument(
41
+ "-c", "--coverage", action="store_true", help="Run with coverage analysis"
42
+ )
43
+ parser.add_argument("-k", "--pattern", help="Run tests matching pattern")
44
+ parser.add_argument(
45
+ "-m", "--markers", help="Run tests with specific markers (e.g., 'not slow')"
46
+ )
47
+ parser.add_argument(
48
+ "--install-deps", action="store_true", help="Install test dependencies first"
49
+ )
50
+
51
+ args = parser.parse_args()
52
+
53
+ if args.install_deps:
54
+ print("Installing test dependencies...")
55
+ subprocess.run([sys.executable, "-m", "pip", "install", "pytest", "pytest-cov"])
56
+
57
+ return run_tests(
58
+ test_pattern=args.pattern,
59
+ verbose=args.verbose,
60
+ coverage=args.coverage,
61
+ markers=args.markers,
62
+ )
63
+
64
+
65
+ if __name__ == "__main__":
66
+ sys.exit(main())