alita-sdk 0.3.154__py3-none-any.whl → 0.3.156__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,14 +1,15 @@
1
1
  import json
2
2
  import logging
3
3
  import re
4
- from typing import Any, Dict, List, Optional, Union
4
+ from typing import Any, Dict, List, Optional
5
5
  from traceback import format_exc
6
6
 
7
7
  import requests
8
8
  from langchain_core.tools import ToolException
9
- from pydantic import Field, PrivateAttr, model_validator, create_model, SecretStr
9
+ from pydantic import Field, model_validator, create_model, SecretStr
10
10
 
11
11
  from ..elitea_base import BaseToolApiWrapper
12
+ from .postman_analysis import PostmanAnalyzer
12
13
 
13
14
  logger = logging.getLogger(__name__)
14
15
 
@@ -18,21 +19,17 @@ PostmanGetCollections = create_model(
18
19
  )
19
20
 
20
21
  PostmanGetCollection = create_model(
21
- "PostmanGetCollection",
22
- collection_id=(str, Field(
23
- description="The ID of the collection to retrieve"))
22
+ "PostmanGetCollection"
24
23
  )
25
24
 
26
25
  PostmanGetFolder = create_model(
27
26
  "PostmanGetFolder",
28
- collection_id=(str, Field(description="The ID of the collection")),
29
27
  folder_path=(str, Field(
30
28
  description="The path to the folder (e.g., 'API/Users' for nested folders)"))
31
29
  )
32
30
 
33
31
  PostmanGetFolderRequests = create_model(
34
32
  "PostmanGetFolderRequests",
35
- collection_id=(str, Field(description="The ID of the collection")),
36
33
  folder_path=(str, Field(description="The path to the folder")),
37
34
  include_details=(bool, Field(
38
35
  description="Include detailed request information", default=False))
@@ -40,8 +37,6 @@ PostmanGetFolderRequests = create_model(
40
37
 
41
38
  PostmanSearchRequests = create_model(
42
39
  "PostmanSearchRequests",
43
- collection_id=(str, Field(
44
- description="The ID of the collection to search in")),
45
40
  query=(str, Field(description="The search query")),
46
41
  search_in=(str, Field(
47
42
  description="Where to search: name, url, description, all", default="all")),
@@ -51,20 +46,22 @@ PostmanSearchRequests = create_model(
51
46
 
52
47
  PostmanAnalyzeCollection = create_model(
53
48
  "PostmanAnalyzeCollection",
54
- collection_id=(str, Field(
55
- description="The ID of the collection to analyze"))
49
+ include_improvements=(bool, Field(
50
+ description="Include improvement suggestions in the analysis", default=False))
56
51
  )
57
52
 
58
53
  PostmanAnalyzeFolder = create_model(
59
54
  "PostmanAnalyzeFolder",
60
- collection_id=(str, Field(description="The ID of the collection")),
61
- folder_path=(str, Field(description="The path to the folder to analyze"))
55
+ folder_path=(str, Field(description="The path to the folder to analyze")),
56
+ include_improvements=(bool, Field(
57
+ description="Include improvement suggestions in the analysis", default=False))
62
58
  )
63
59
 
64
- PostmanGetImprovementSuggestions = create_model(
65
- "PostmanGetImprovementSuggestions",
66
- collection_id=(str, Field(
67
- description="The ID of the collection to get improvements for"))
60
+ PostmanAnalyzeRequest = create_model(
61
+ "PostmanAnalyzeRequest",
62
+ request_path=(str, Field(description="The path to the request to analyze")),
63
+ include_improvements=(bool, Field(
64
+ description="Include improvement suggestions in the analysis", default=False))
68
65
  )
69
66
 
70
67
  PostmanCreateCollection = create_model(
@@ -78,36 +75,37 @@ PostmanCreateCollection = create_model(
78
75
  description="Optional default authentication", default=None))
79
76
  )
80
77
 
81
- PostmanUpdateCollection = create_model(
82
- "PostmanUpdateCollection",
83
- collection_id=(str, Field(
84
- description="The ID of the collection to update")),
85
- name=(Optional[str], Field(
86
- description="New name for the collection", default=None)),
87
- description=(Optional[str], Field(
88
- description="New description for the collection", default=None)),
89
- variables=(Optional[List[Dict]], Field(
90
- description="Updated collection variables", default=None)),
91
- auth=(Optional[Dict], Field(
92
- description="Updated authentication settings", default=None))
78
+ PostmanUpdateCollectionName = create_model(
79
+ "PostmanUpdateCollectionName",
80
+ name=(str, Field(description="New name for the collection"))
81
+ )
82
+
83
+ PostmanUpdateCollectionDescription = create_model(
84
+ "PostmanUpdateCollectionDescription",
85
+ description=(str, Field(description="New description for the collection"))
86
+ )
87
+
88
+ PostmanUpdateCollectionVariables = create_model(
89
+ "PostmanUpdateCollectionVariables",
90
+ variables=(List[Dict], Field(description="Updated collection variables"))
91
+ )
92
+
93
+ PostmanUpdateCollectionAuth = create_model(
94
+ "PostmanUpdateCollectionAuth",
95
+ auth=(Dict, Field(description="Updated authentication settings"))
93
96
  )
94
97
 
95
98
  PostmanDeleteCollection = create_model(
96
- "PostmanDeleteCollection",
97
- collection_id=(str, Field(
98
- description="The ID of the collection to delete"))
99
+ "PostmanDeleteCollection"
99
100
  )
100
101
 
101
102
  PostmanDuplicateCollection = create_model(
102
103
  "PostmanDuplicateCollection",
103
- collection_id=(str, Field(
104
- description="The ID of the collection to duplicate")),
105
104
  new_name=(str, Field(description="Name for the new collection copy"))
106
105
  )
107
106
 
108
107
  PostmanCreateFolder = create_model(
109
108
  "PostmanCreateFolder",
110
- collection_id=(str, Field(description="The ID of the collection")),
111
109
  name=(str, Field(description="Name of the new folder")),
112
110
  description=(Optional[str], Field(
113
111
  description="Optional description for the folder", default=None)),
@@ -119,7 +117,6 @@ PostmanCreateFolder = create_model(
119
117
 
120
118
  PostmanUpdateFolder = create_model(
121
119
  "PostmanUpdateFolder",
122
- collection_id=(str, Field(description="The ID of the collection")),
123
120
  folder_path=(str, Field(description="Path to the folder to update")),
124
121
  name=(Optional[str], Field(
125
122
  description="New name for the folder", default=None)),
@@ -131,13 +128,11 @@ PostmanUpdateFolder = create_model(
131
128
 
132
129
  PostmanDeleteFolder = create_model(
133
130
  "PostmanDeleteFolder",
134
- collection_id=(str, Field(description="The ID of the collection")),
135
131
  folder_path=(str, Field(description="Path to the folder to delete"))
136
132
  )
137
133
 
138
134
  PostmanMoveFolder = create_model(
139
135
  "PostmanMoveFolder",
140
- collection_id=(str, Field(description="The ID of the collection")),
141
136
  source_path=(str, Field(description="Current path of the folder to move")),
142
137
  target_path=(Optional[str], Field(
143
138
  description="New parent folder path", default=None))
@@ -145,7 +140,6 @@ PostmanMoveFolder = create_model(
145
140
 
146
141
  PostmanCreateRequest = create_model(
147
142
  "PostmanCreateRequest",
148
- collection_id=(str, Field(description="The ID of the collection")),
149
143
  folder_path=(Optional[str], Field(
150
144
  description="Path to the folder", default=None)),
151
145
  name=(str, Field(description="Name of the new request")),
@@ -165,38 +159,67 @@ PostmanCreateRequest = create_model(
165
159
  description="Optional pre-request script code", default=None))
166
160
  )
167
161
 
168
- PostmanUpdateRequest = create_model(
169
- "PostmanUpdateRequest",
170
- collection_id=(str, Field(description="The ID of the collection")),
171
- request_path=(str, Field(
172
- description="Path to the request (folder/requestName)")),
173
- name=(Optional[str], Field(
174
- description="New name for the request", default=None)),
175
- method=(Optional[str], Field(
176
- description="HTTP method for the request", default=None)),
177
- url=(Optional[str], Field(
178
- description="URL for the request", default=None)),
179
- description=(Optional[str], Field(
180
- description="Description for the request", default=None)),
181
- headers=(Optional[List[Dict]], Field(
182
- description="Request headers", default=None)),
183
- body=(Optional[Dict], Field(description="Request body", default=None)),
184
- auth=(Optional[Dict], Field(
185
- description="Request authentication", default=None)),
186
- tests=(Optional[str], Field(description="Test script code", default=None)),
187
- pre_request_script=(Optional[str], Field(
188
- description="Pre-request script code", default=None))
162
+ PostmanUpdateRequestName = create_model(
163
+ "PostmanUpdateRequestName",
164
+ request_path=(str, Field(description="Path to the request (folder/requestName)")),
165
+ name=(str, Field(description="New name for the request"))
166
+ )
167
+
168
+ PostmanUpdateRequestMethod = create_model(
169
+ "PostmanUpdateRequestMethod",
170
+ request_path=(str, Field(description="Path to the request (folder/requestName)")),
171
+ method=(str, Field(description="HTTP method for the request"))
172
+ )
173
+
174
+ PostmanUpdateRequestUrl = create_model(
175
+ "PostmanUpdateRequestUrl",
176
+ request_path=(str, Field(description="Path to the request (folder/requestName)")),
177
+ url=(str, Field(description="URL for the request"))
178
+ )
179
+
180
+ PostmanUpdateRequestDescription = create_model(
181
+ "PostmanUpdateRequestDescription",
182
+ request_path=(str, Field(description="Path to the request (folder/requestName)")),
183
+ description=(str, Field(description="Description for the request"))
184
+ )
185
+
186
+ PostmanUpdateRequestHeaders = create_model(
187
+ "PostmanUpdateRequestHeaders",
188
+ request_path=(str, Field(description="Path to the request (folder/requestName)")),
189
+ headers=(List[Dict], Field(description="Request headers"))
190
+ )
191
+
192
+ PostmanUpdateRequestBody = create_model(
193
+ "PostmanUpdateRequestBody",
194
+ request_path=(str, Field(description="Path to the request (folder/requestName)")),
195
+ body=(Dict, Field(description="Request body"))
196
+ )
197
+
198
+ PostmanUpdateRequestAuth = create_model(
199
+ "PostmanUpdateRequestAuth",
200
+ request_path=(str, Field(description="Path to the request (folder/requestName)")),
201
+ auth=(Dict, Field(description="Request authentication"))
202
+ )
203
+
204
+ PostmanUpdateRequestTests = create_model(
205
+ "PostmanUpdateRequestTests",
206
+ request_path=(str, Field(description="Path to the request (folder/requestName)")),
207
+ tests=(str, Field(description="Test script code"))
208
+ )
209
+
210
+ PostmanUpdateRequestPreScript = create_model(
211
+ "PostmanUpdateRequestPreScript",
212
+ request_path=(str, Field(description="Path to the request (folder/requestName)")),
213
+ pre_request_script=(str, Field(description="Pre-request script code"))
189
214
  )
190
215
 
191
216
  PostmanDeleteRequest = create_model(
192
217
  "PostmanDeleteRequest",
193
- collection_id=(str, Field(description="The ID of the collection")),
194
218
  request_path=(str, Field(description="Path to the request to delete"))
195
219
  )
196
220
 
197
221
  PostmanDuplicateRequest = create_model(
198
222
  "PostmanDuplicateRequest",
199
- collection_id=(str, Field(description="The ID of the collection")),
200
223
  source_path=(str, Field(description="Path to the request to duplicate")),
201
224
  new_name=(str, Field(description="Name for the duplicated request")),
202
225
  target_path=(Optional[str], Field(
@@ -205,7 +228,6 @@ PostmanDuplicateRequest = create_model(
205
228
 
206
229
  PostmanMoveRequest = create_model(
207
230
  "PostmanMoveRequest",
208
- collection_id=(str, Field(description="The ID of the collection")),
209
231
  source_path=(str, Field(
210
232
  description="Current path of the request to move")),
211
233
  target_path=(Optional[str], Field(
@@ -221,7 +243,12 @@ class PostmanApiWrapper(BaseToolApiWrapper):
221
243
  collection_id: Optional[str] = None
222
244
  workspace_id: Optional[str] = None
223
245
  timeout: int = 30
224
- _session: requests.Session = PrivateAttr()
246
+ session: Any = None
247
+ analyzer: PostmanAnalyzer = None
248
+
249
+ model_config = {
250
+ "arbitrary_types_allowed": True
251
+ }
225
252
 
226
253
  @model_validator(mode='before')
227
254
  @classmethod
@@ -233,16 +260,14 @@ class PostmanApiWrapper(BaseToolApiWrapper):
233
260
  "`requests` package not found, please run "
234
261
  "`pip install requests`"
235
262
  )
263
+ values["session"] = requests.Session()
264
+ values["session"].headers.update({
265
+ 'X-API-Key': values.get('api_key'),
266
+ 'Content-Type': 'application/json'
267
+ })
268
+ values["analyzer"] = PostmanAnalyzer()
236
269
  return values
237
270
 
238
- def __init__(self, **data):
239
- super().__init__(**data)
240
- self._session = requests.Session()
241
- self._session.headers.update({
242
- 'X-API-Key': self.api_key.get_secret_value(),
243
- 'Content-Type': 'application/json',
244
- })
245
- # Removed ineffective timeout assignment. Timeout will be enforced in `_make_request`.
246
271
 
247
272
  def _make_request(self, method: str, endpoint: str, **kwargs) -> Dict[str, Any]:
248
273
  """Make HTTP request to Postman API."""
@@ -250,7 +275,7 @@ class PostmanApiWrapper(BaseToolApiWrapper):
250
275
 
251
276
  try:
252
277
  logger.info(f"Making {method.upper()} request to {url}")
253
- response = self._session.request(method, url, timeout=self.timeout, **kwargs)
278
+ response = self.session.request(method, url, timeout=self.timeout, **kwargs)
254
279
  response.raise_for_status()
255
280
 
256
281
  if response.content:
@@ -318,11 +343,11 @@ class PostmanApiWrapper(BaseToolApiWrapper):
318
343
  "ref": self.analyze_folder
319
344
  },
320
345
  {
321
- "name": "get_improvement_suggestions",
322
- "mode": "get_improvement_suggestions",
323
- "description": "Get improvement suggestions for a collection",
324
- "args_schema": PostmanGetImprovementSuggestions,
325
- "ref": self.get_improvement_suggestions
346
+ "name": "analyze_request",
347
+ "mode": "analyze_request",
348
+ "description": "Analyze a specific request within a collection",
349
+ "args_schema": PostmanAnalyzeRequest,
350
+ "ref": self.analyze_request
326
351
  },
327
352
  {
328
353
  "name": "create_collection",
@@ -332,19 +357,40 @@ class PostmanApiWrapper(BaseToolApiWrapper):
332
357
  "ref": self.create_collection
333
358
  },
334
359
  {
335
- "name": "update_collection",
336
- "mode": "update_collection",
337
- "description": "Update an existing collection (name, description, variables, auth)",
338
- "args_schema": PostmanUpdateCollection,
339
- "ref": self.update_collection
360
+ "name": "update_collection_name",
361
+ "mode": "update_collection_name",
362
+ "description": "Update collection name",
363
+ "args_schema": PostmanUpdateCollectionName,
364
+ "ref": self.update_collection_name
340
365
  },
341
366
  {
342
- "name": "delete_collection",
343
- "mode": "delete_collection",
344
- "description": "Delete a collection permanently",
345
- "args_schema": PostmanDeleteCollection,
346
- "ref": self.delete_collection
367
+ "name": "update_collection_description",
368
+ "mode": "update_collection_description",
369
+ "description": "Update collection description",
370
+ "args_schema": PostmanUpdateCollectionDescription,
371
+ "ref": self.update_collection_description
347
372
  },
373
+ {
374
+ "name": "update_collection_variables",
375
+ "mode": "update_collection_variables",
376
+ "description": "Update collection variables",
377
+ "args_schema": PostmanUpdateCollectionVariables,
378
+ "ref": self.update_collection_variables
379
+ },
380
+ {
381
+ "name": "update_collection_auth",
382
+ "mode": "update_collection_auth",
383
+ "description": "Update collection authentication settings",
384
+ "args_schema": PostmanUpdateCollectionAuth,
385
+ "ref": self.update_collection_auth
386
+ },
387
+ # {
388
+ # "name": "delete_collection",
389
+ # "mode": "delete_collection",
390
+ # "description": "Delete a collection permanently",
391
+ # "args_schema": PostmanDeleteCollection,
392
+ # "ref": self.delete_collection
393
+ # },
348
394
  {
349
395
  "name": "duplicate_collection",
350
396
  "mode": "duplicate_collection",
@@ -367,13 +413,13 @@ class PostmanApiWrapper(BaseToolApiWrapper):
367
413
  "args_schema": PostmanUpdateFolder,
368
414
  "ref": self.update_folder
369
415
  },
370
- {
371
- "name": "delete_folder",
372
- "mode": "delete_folder",
373
- "description": "Delete a folder and all its contents permanently",
374
- "args_schema": PostmanDeleteFolder,
375
- "ref": self.delete_folder
376
- },
416
+ # {
417
+ # "name": "delete_folder",
418
+ # "mode": "delete_folder",
419
+ # "description": "Delete a folder and all its contents permanently",
420
+ # "args_schema": PostmanDeleteFolder,
421
+ # "ref": self.delete_folder
422
+ # },
377
423
  {
378
424
  "name": "move_folder",
379
425
  "mode": "move_folder",
@@ -389,19 +435,75 @@ class PostmanApiWrapper(BaseToolApiWrapper):
389
435
  "ref": self.create_request
390
436
  },
391
437
  {
392
- "name": "update_request",
393
- "mode": "update_request",
394
- "description": "Update an existing API request",
395
- "args_schema": PostmanUpdateRequest,
396
- "ref": self.update_request
438
+ "name": "update_request_name",
439
+ "mode": "update_request_name",
440
+ "description": "Update request name",
441
+ "args_schema": PostmanUpdateRequestName,
442
+ "ref": self.update_request_name
443
+ },
444
+ {
445
+ "name": "update_request_method",
446
+ "mode": "update_request_method",
447
+ "description": "Update request HTTP method",
448
+ "args_schema": PostmanUpdateRequestMethod,
449
+ "ref": self.update_request_method
450
+ },
451
+ {
452
+ "name": "update_request_url",
453
+ "mode": "update_request_url",
454
+ "description": "Update request URL",
455
+ "args_schema": PostmanUpdateRequestUrl,
456
+ "ref": self.update_request_url
457
+ },
458
+ {
459
+ "name": "update_request_description",
460
+ "mode": "update_request_description",
461
+ "description": "Update request description",
462
+ "args_schema": PostmanUpdateRequestDescription,
463
+ "ref": self.update_request_description
464
+ },
465
+ {
466
+ "name": "update_request_headers",
467
+ "mode": "update_request_headers",
468
+ "description": "Update request headers",
469
+ "args_schema": PostmanUpdateRequestHeaders,
470
+ "ref": self.update_request_headers
471
+ },
472
+ {
473
+ "name": "update_request_body",
474
+ "mode": "update_request_body",
475
+ "description": "Update request body",
476
+ "args_schema": PostmanUpdateRequestBody,
477
+ "ref": self.update_request_body
478
+ },
479
+ {
480
+ "name": "update_request_auth",
481
+ "mode": "update_request_auth",
482
+ "description": "Update request authentication",
483
+ "args_schema": PostmanUpdateRequestAuth,
484
+ "ref": self.update_request_auth
485
+ },
486
+ {
487
+ "name": "update_request_tests",
488
+ "mode": "update_request_tests",
489
+ "description": "Update request test scripts",
490
+ "args_schema": PostmanUpdateRequestTests,
491
+ "ref": self.update_request_tests
397
492
  },
398
493
  {
399
- "name": "delete_request",
400
- "mode": "delete_request",
401
- "description": "Delete an API request permanently",
402
- "args_schema": PostmanDeleteRequest,
403
- "ref": self.delete_request
494
+ "name": "update_request_pre_script",
495
+ "mode": "update_request_pre_script",
496
+ "description": "Update request pre-request scripts",
497
+ "args_schema": PostmanUpdateRequestPreScript,
498
+ "ref": self.update_request_pre_script
404
499
  },
500
+ # {
501
+ # "name": "delete_request",
502
+ # "mode": "delete_request",
503
+ # "description": "Delete an API request permanently",
504
+ # "args_schema": PostmanDeleteRequest,
505
+ # "ref": self.delete_request
506
+ # },
405
507
  {
406
508
  "name": "duplicate_request",
407
509
  "mode": "duplicate_request",
@@ -432,25 +534,25 @@ class PostmanApiWrapper(BaseToolApiWrapper):
432
534
  logger.error(f"Exception when getting collections: {stacktrace}")
433
535
  raise ToolException(f"Unable to get collections: {str(e)}")
434
536
 
435
- def get_collection(self, collection_id: str, **kwargs) -> str:
537
+ def get_collection(self, **kwargs) -> str:
436
538
  """Get a specific collection by ID."""
437
539
  try:
438
540
  response = self._make_request(
439
- 'GET', f'/collections/{collection_id}')
541
+ 'GET', f'/collections/{self.collection_id}')
440
542
  return json.dumps(response, indent=2)
441
543
  except Exception as e:
442
544
  stacktrace = format_exc()
443
545
  logger.error(
444
- f"Exception when getting collection {collection_id}: {stacktrace}")
546
+ f"Exception when getting collection {self.collection_id}: {stacktrace}")
445
547
  raise ToolException(
446
- f"Unable to get collection {collection_id}: {str(e)}")
548
+ f"Unable to get collection {self.collection_id}: {str(e)}")
447
549
 
448
- def get_folder(self, collection_id: str, folder_path: str, **kwargs) -> str:
550
+ def get_folder(self, folder_path: str, **kwargs) -> str:
449
551
  """Get folders from a collection by path."""
450
552
  try:
451
553
  collection = self._make_request(
452
- 'GET', f'/collections/{collection_id}')
453
- folders = self._find_folders_by_path(
554
+ 'GET', f'/collections/{self.collection_id}')
555
+ folders = self.analyzer.find_folders_by_path(
454
556
  collection['collection']['item'], folder_path)
455
557
  return json.dumps(folders, indent=2)
456
558
  except Exception as e:
@@ -458,21 +560,21 @@ class PostmanApiWrapper(BaseToolApiWrapper):
458
560
  logger.error(
459
561
  f"Exception when getting folder {folder_path}: {stacktrace}")
460
562
  raise ToolException(
461
- f"Unable to get folder {folder_path} from collection {collection_id}: {str(e)}")
563
+ f"Unable to get folder {folder_path} from collection {self.collection_id}: {str(e)}")
462
564
 
463
- def get_folder_requests(self, collection_id: str, folder_path: str, include_details: bool = False, **kwargs) -> str:
565
+ def get_folder_requests(self, folder_path: str, include_details: bool = False, **kwargs) -> str:
464
566
  """Get detailed information about all requests in a folder."""
465
567
  try:
466
568
  collection = self._make_request(
467
- 'GET', f'/collections/{collection_id}')
468
- folders = self._find_folders_by_path(
569
+ 'GET', f'/collections/{self.collection_id}')
570
+ folders = self.analyzer.find_folders_by_path(
469
571
  collection['collection']['item'], folder_path)
470
572
 
471
573
  if not folders:
472
- raise ToolException(f"Folder '{folder_path}' not found in collection '{collection_id}'.")
574
+ raise ToolException(f"Folder '{folder_path}' not found in collection '{self.collection_id}'.")
473
575
 
474
576
  folder = folders[0]
475
- requests = self._extract_requests_from_items(
577
+ requests = self.analyzer.extract_requests_from_items(
476
578
  folder.get('item', []), include_details)
477
579
 
478
580
  result = {
@@ -490,16 +592,16 @@ class PostmanApiWrapper(BaseToolApiWrapper):
490
592
  raise ToolException(
491
593
  f"Unable to get requests from folder {folder_path}: {str(e)}")
492
594
 
493
- def search_requests(self, collection_id: str, query: str, search_in: str = "all", method: str = None, **kwargs) -> str:
595
+ def search_requests(self, query: str, search_in: str = "all", method: str = None, **kwargs) -> str:
494
596
  """Search for requests across the collection."""
495
597
  try:
496
598
  collection = self._make_request(
497
- 'GET', f'/collections/{collection_id}')
498
- requests = self._search_requests_in_items(
599
+ 'GET', f'/collections/{self.collection_id}')
600
+ requests = self.analyzer.search_requests_in_items(
499
601
  collection['collection']['item'], query, search_in, method)
500
602
 
501
603
  result = {
502
- "collection_id": collection_id,
604
+ "collection_id": self.collection_id,
503
605
  "query": query,
504
606
  "search_in": search_in,
505
607
  "method_filter": method,
@@ -512,27 +614,33 @@ class PostmanApiWrapper(BaseToolApiWrapper):
512
614
  stacktrace = format_exc()
513
615
  logger.error(f"Exception when searching requests: {stacktrace}")
514
616
  raise ToolException(
515
- f"Unable to search requests in collection {collection_id}: {str(e)}")
617
+ f"Unable to search requests in collection {self.collection_id}: {str(e)}")
516
618
 
517
- def analyze_collection(self, collection_id: str, **kwargs) -> str:
619
+ def analyze_collection(self, include_improvements: bool = False, **kwargs) -> str:
518
620
  """Analyze a collection for API quality and best practices."""
519
621
  try:
520
622
  collection = self._make_request(
521
- 'GET', f'/collections/{collection_id}')
522
- analysis = self._perform_collection_analysis(collection)
623
+ 'GET', f'/collections/{self.collection_id}')
624
+ analysis = self.analyzer.perform_collection_analysis(collection)
625
+
626
+ if include_improvements:
627
+ improvements = self.analyzer.generate_improvements(analysis)
628
+ analysis["improvements"] = improvements
629
+ analysis["improvement_count"] = len(improvements)
630
+
523
631
  return json.dumps(analysis, indent=2)
524
632
  except Exception as e:
525
633
  stacktrace = format_exc()
526
634
  logger.error(f"Exception when analyzing collection: {stacktrace}")
527
635
  raise ToolException(
528
- f"Unable to analyze collection {collection_id}: {str(e)}")
636
+ f"Unable to analyze collection {self.collection_id}: {str(e)}")
529
637
 
530
- def analyze_folder(self, collection_id: str, folder_path: str, **kwargs) -> str:
638
+ def analyze_folder(self, folder_path: str, include_improvements: bool = False, **kwargs) -> str:
531
639
  """Analyze a specific folder within a collection."""
532
640
  try:
533
641
  collection = self._make_request(
534
- 'GET', f'/collections/{collection_id}')
535
- folders = self._find_folders_by_path(
642
+ 'GET', f'/collections/{self.collection_id}')
643
+ folders = self.analyzer.find_folders_by_path(
536
644
  collection['collection']['item'], folder_path)
537
645
 
538
646
  if not folders:
@@ -540,7 +648,13 @@ class PostmanApiWrapper(BaseToolApiWrapper):
540
648
 
541
649
  folder_analyses = []
542
650
  for folder in folders:
543
- analysis = self._perform_folder_analysis(folder, folder_path)
651
+ analysis = self.analyzer.perform_folder_analysis(folder, folder_path)
652
+
653
+ if include_improvements:
654
+ improvements = self.analyzer.generate_folder_improvements(analysis)
655
+ analysis["improvements"] = improvements
656
+ analysis["improvement_count"] = len(improvements)
657
+
544
658
  folder_analyses.append(analysis)
545
659
 
546
660
  return json.dumps(folder_analyses, indent=2)
@@ -550,28 +664,35 @@ class PostmanApiWrapper(BaseToolApiWrapper):
550
664
  raise ToolException(
551
665
  f"Unable to analyze folder {folder_path}: {str(e)}")
552
666
 
553
- def get_improvement_suggestions(self, collection_id: str, **kwargs) -> str:
554
- """Get improvement suggestions for a collection."""
667
+ def analyze_request(self, request_path: str, include_improvements: bool = False, **kwargs) -> str:
668
+ """Analyze a specific request within a collection."""
555
669
  try:
556
670
  collection = self._make_request(
557
- 'GET', f'/collections/{collection_id}')
558
- analysis = self._perform_collection_analysis(collection)
559
- improvements = self._generate_improvements(analysis)
671
+ 'GET', f'/collections/{self.collection_id}')
672
+ collection_data = collection["collection"]
560
673
 
561
- result = {
562
- "collection_id": collection_id,
563
- "collection_name": analysis["collection_name"],
564
- "improvement_count": len(improvements),
565
- "improvements": improvements
566
- }
674
+ # Find the request
675
+ request_item = self.analyzer.find_request_by_path(
676
+ collection_data["item"], request_path)
677
+ if not request_item:
678
+ raise ToolException(f"Request '{request_path}' not found")
567
679
 
568
- return json.dumps(result, indent=2)
680
+ # Perform request analysis
681
+ analysis = self.analyzer.perform_request_analysis(request_item)
682
+ analysis["request_path"] = request_path
683
+ analysis["collection_id"] = self.collection_id
684
+
685
+ if include_improvements:
686
+ improvements = self.analyzer.generate_request_improvements(analysis)
687
+ analysis["improvements"] = improvements
688
+ analysis["improvement_count"] = len(improvements)
689
+
690
+ return json.dumps(analysis, indent=2)
569
691
  except Exception as e:
570
692
  stacktrace = format_exc()
571
- logger.error(
572
- f"Exception when generating improvements: {stacktrace}")
693
+ logger.error(f"Exception when analyzing request: {stacktrace}")
573
694
  raise ToolException(
574
- f"Unable to generate improvements for collection {collection_id}: {str(e)}")
695
+ f"Unable to analyze request {request_path}: {str(e)}")
575
696
 
576
697
  # =================================================================
577
698
  # COLLECTION MANAGEMENT METHODS
@@ -608,52 +729,104 @@ class PostmanApiWrapper(BaseToolApiWrapper):
608
729
  raise ToolException(
609
730
  f"Unable to create collection '{name}': {str(e)}")
610
731
 
611
- def update_collection(self, collection_id: str, name: str = None, description: str = None,
612
- variables: List[Dict] = None, auth: Dict = None, **kwargs) -> str:
613
- """Update an existing collection."""
732
+ def update_collection_name(self, name: str, **kwargs) -> str:
733
+ """Update collection name."""
614
734
  try:
615
735
  # Get current collection
616
736
  current = self._make_request(
617
- 'GET', f'/collections/{collection_id}')
737
+ 'GET', f'/collections/{self.collection_id}')
618
738
  collection_data = current["collection"]
619
739
 
620
- # Update fields if provided
621
- if name:
622
- collection_data["info"]["name"] = name
623
- if description is not None:
624
- collection_data["info"]["description"] = description
625
- if variables is not None:
626
- collection_data["variable"] = variables
627
- if auth is not None:
628
- collection_data["auth"] = auth
740
+ # Update name
741
+ collection_data["info"]["name"] = name
629
742
 
630
- response = self._make_request('PUT', f'/collections/{collection_id}',
743
+ response = self._make_request('PUT', f'/collections/{self.collection_id}',
631
744
  json={"collection": collection_data})
632
745
  return json.dumps(response, indent=2)
633
746
  except Exception as e:
634
747
  stacktrace = format_exc()
635
- logger.error(f"Exception when updating collection: {stacktrace}")
748
+ logger.error(f"Exception when updating collection name: {stacktrace}")
636
749
  raise ToolException(
637
- f"Unable to update collection {collection_id}: {str(e)}")
750
+ f"Unable to update collection {self.collection_id} name: {str(e)}")
638
751
 
639
- def delete_collection(self, collection_id: str, **kwargs) -> str:
752
+ def update_collection_description(self, description: str, **kwargs) -> str:
753
+ """Update collection description."""
754
+ try:
755
+ # Get current collection
756
+ current = self._make_request(
757
+ 'GET', f'/collections/{self.collection_id}')
758
+ collection_data = current["collection"]
759
+
760
+ # Update description
761
+ collection_data["info"]["description"] = description
762
+
763
+ response = self._make_request('PUT', f'/collections/{self.collection_id}',
764
+ json={"collection": collection_data})
765
+ return json.dumps(response, indent=2)
766
+ except Exception as e:
767
+ stacktrace = format_exc()
768
+ logger.error(f"Exception when updating collection description: {stacktrace}")
769
+ raise ToolException(
770
+ f"Unable to update collection {self.collection_id} description: {str(e)}")
771
+
772
+ def update_collection_variables(self, variables: List[Dict], **kwargs) -> str:
773
+ """Update collection variables."""
774
+ try:
775
+ # Get current collection
776
+ current = self._make_request(
777
+ 'GET', f'/collections/{self.collection_id}')
778
+ collection_data = current["collection"]
779
+
780
+ # Update variables
781
+ collection_data["variable"] = variables
782
+
783
+ response = self._make_request('PUT', f'/collections/{self.collection_id}',
784
+ json={"collection": collection_data})
785
+ return json.dumps(response, indent=2)
786
+ except Exception as e:
787
+ stacktrace = format_exc()
788
+ logger.error(f"Exception when updating collection variables: {stacktrace}")
789
+ raise ToolException(
790
+ f"Unable to update collection {self.collection_id} variables: {str(e)}")
791
+
792
+ def update_collection_auth(self, auth: Dict, **kwargs) -> str:
793
+ """Update collection authentication settings."""
794
+ try:
795
+ # Get current collection
796
+ current = self._make_request(
797
+ 'GET', f'/collections/{self.collection_id}')
798
+ collection_data = current["collection"]
799
+
800
+ # Update auth
801
+ collection_data["auth"] = auth
802
+
803
+ response = self._make_request('PUT', f'/collections/{self.collection_id}',
804
+ json={"collection": collection_data})
805
+ return json.dumps(response, indent=2)
806
+ except Exception as e:
807
+ stacktrace = format_exc()
808
+ logger.error(f"Exception when updating collection auth: {stacktrace}")
809
+ raise ToolException(
810
+ f"Unable to update collection {self.collection_id} auth: {str(e)}")
811
+
812
+ def delete_collection(self, **kwargs) -> str:
640
813
  """Delete a collection permanently."""
641
814
  try:
642
815
  response = self._make_request(
643
- 'DELETE', f'/collections/{collection_id}')
644
- return json.dumps({"message": f"Collection {collection_id} deleted successfully"}, indent=2)
816
+ 'DELETE', f'/collections/{self.collection_id}')
817
+ return json.dumps({"message": f"Collection {self.collection_id} deleted successfully"}, indent=2)
645
818
  except Exception as e:
646
819
  stacktrace = format_exc()
647
820
  logger.error(f"Exception when deleting collection: {stacktrace}")
648
821
  raise ToolException(
649
- f"Unable to delete collection {collection_id}: {str(e)}")
822
+ f"Unable to delete collection {self.collection_id}: {str(e)}")
650
823
 
651
- def duplicate_collection(self, collection_id: str, new_name: str, **kwargs) -> str:
824
+ def duplicate_collection(self, new_name: str, **kwargs) -> str:
652
825
  """Create a copy of an existing collection."""
653
826
  try:
654
827
  # Get the original collection
655
828
  original = self._make_request(
656
- 'GET', f'/collections/{collection_id}')
829
+ 'GET', f'/collections/{self.collection_id}')
657
830
  collection_data = original["collection"]
658
831
 
659
832
  # Update the name and remove IDs to create a new collection
@@ -662,7 +835,7 @@ class PostmanApiWrapper(BaseToolApiWrapper):
662
835
  del collection_data["info"]["_postman_id"]
663
836
 
664
837
  # Remove item IDs recursively
665
- self._remove_item_ids(collection_data.get("item", []))
838
+ self.analyzer.remove_item_ids(collection_data.get("item", []))
666
839
 
667
840
  response = self._make_request(
668
841
  'POST', '/collections', json={"collection": collection_data})
@@ -672,19 +845,19 @@ class PostmanApiWrapper(BaseToolApiWrapper):
672
845
  logger.error(
673
846
  f"Exception when duplicating collection: {stacktrace}")
674
847
  raise ToolException(
675
- f"Unable to duplicate collection {collection_id}: {str(e)}")
848
+ f"Unable to duplicate collection {self.collection_id}: {str(e)}")
676
849
 
677
850
  # =================================================================
678
851
  # FOLDER MANAGEMENT METHODS
679
852
  # =================================================================
680
853
 
681
- def create_folder(self, collection_id: str, name: str, description: str = None,
854
+ def create_folder(self, name: str, description: str = None,
682
855
  parent_path: str = None, auth: Dict = None, **kwargs) -> str:
683
856
  """Create a new folder in a collection."""
684
857
  try:
685
858
  # Get current collection
686
859
  collection = self._make_request(
687
- 'GET', f'/collections/{collection_id}')
860
+ 'GET', f'/collections/{self.collection_id}')
688
861
  collection_data = collection["collection"]
689
862
 
690
863
  # Create folder item
@@ -700,7 +873,7 @@ class PostmanApiWrapper(BaseToolApiWrapper):
700
873
 
701
874
  # Add folder to appropriate location
702
875
  if parent_path:
703
- parent_folders = self._find_folders_by_path(
876
+ parent_folders = self.analyzer.find_folders_by_path(
704
877
  collection_data["item"], parent_path)
705
878
  if not parent_folders:
706
879
  raise ToolException(
@@ -710,7 +883,7 @@ class PostmanApiWrapper(BaseToolApiWrapper):
710
883
  collection_data["item"].append(folder_item)
711
884
 
712
885
  # Update collection
713
- response = self._make_request('PUT', f'/collections/{collection_id}',
886
+ response = self._make_request('PUT', f'/collections/{self.collection_id}',
714
887
  json={"collection": collection_data})
715
888
  return json.dumps({"message": f"Folder '{name}' created successfully"}, indent=2)
716
889
  except Exception as e:
@@ -718,17 +891,17 @@ class PostmanApiWrapper(BaseToolApiWrapper):
718
891
  logger.error(f"Exception when creating folder: {stacktrace}")
719
892
  raise ToolException(f"Unable to create folder '{name}': {str(e)}")
720
893
 
721
- def update_folder(self, collection_id: str, folder_path: str, name: str = None,
894
+ def update_folder(self, folder_path: str, name: str = None,
722
895
  description: str = None, auth: Dict = None, **kwargs) -> str:
723
896
  """Update folder properties."""
724
897
  try:
725
898
  # Get current collection
726
899
  collection = self._make_request(
727
- 'GET', f'/collections/{collection_id}')
900
+ 'GET', f'/collections/{self.collection_id}')
728
901
  collection_data = collection["collection"]
729
902
 
730
903
  # Find the folder
731
- folders = self._find_folders_by_path(
904
+ folders = self.analyzer.find_folders_by_path(
732
905
  collection_data["item"], folder_path)
733
906
  if not folders:
734
907
  raise ToolException(f"Folder '{folder_path}' not found")
@@ -744,7 +917,7 @@ class PostmanApiWrapper(BaseToolApiWrapper):
744
917
  folder["auth"] = auth
745
918
 
746
919
  # Update collection
747
- response = self._make_request('PUT', f'/collections/{collection_id}',
920
+ response = self._make_request('PUT', f'/collections/{self.collection_id}',
748
921
  json={"collection": collection_data})
749
922
  return json.dumps({"message": f"Folder '{folder_path}' updated successfully"}, indent=2)
750
923
  except Exception as e:
@@ -753,18 +926,18 @@ class PostmanApiWrapper(BaseToolApiWrapper):
753
926
  raise ToolException(
754
927
  f"Unable to update folder '{folder_path}': {str(e)}")
755
928
 
756
- def delete_folder(self, collection_id: str, folder_path: str, **kwargs) -> str:
929
+ def delete_folder(self, folder_path: str, **kwargs) -> str:
757
930
  """Delete a folder and all its contents permanently."""
758
931
  try:
759
932
  # Get current collection
760
933
  collection = self._make_request(
761
- 'GET', f'/collections/{collection_id}')
934
+ 'GET', f'/collections/{self.collection_id}')
762
935
  collection_data = collection["collection"]
763
936
 
764
937
  # Find and remove the folder
765
- if self._remove_folder_by_path(collection_data["item"], folder_path):
938
+ if self.analyzer.remove_folder_by_path(collection_data["item"], folder_path):
766
939
  # Update collection
767
- response = self._make_request('PUT', f'/collections/{collection_id}',
940
+ response = self._make_request('PUT', f'/collections/{self.collection_id}',
768
941
  json={"collection": collection_data})
769
942
  return json.dumps({"message": f"Folder '{folder_path}' deleted successfully"}, indent=2)
770
943
  else:
@@ -775,16 +948,16 @@ class PostmanApiWrapper(BaseToolApiWrapper):
775
948
  raise ToolException(
776
949
  f"Unable to delete folder '{folder_path}': {str(e)}")
777
950
 
778
- def move_folder(self, collection_id: str, source_path: str, target_path: str = None, **kwargs) -> str:
951
+ def move_folder(self, source_path: str, target_path: str = None, **kwargs) -> str:
779
952
  """Move a folder to a different location within the collection."""
780
953
  try:
781
954
  # Get current collection
782
955
  collection = self._make_request(
783
- 'GET', f'/collections/{collection_id}')
956
+ 'GET', f'/collections/{self.collection_id}')
784
957
  collection_data = collection["collection"]
785
958
 
786
959
  # Find source folder
787
- source_folder = self._find_folders_by_path(
960
+ source_folder = self.analyzer.find_folders_by_path(
788
961
  collection_data["item"], source_path)
789
962
  if not source_folder:
790
963
  raise ToolException(f"Source folder '{source_path}' not found")
@@ -792,11 +965,11 @@ class PostmanApiWrapper(BaseToolApiWrapper):
792
965
  folder_data = source_folder[0].copy()
793
966
 
794
967
  # Remove from source location
795
- self._remove_folder_by_path(collection_data["item"], source_path)
968
+ self.analyzer.remove_folder_by_path(collection_data["item"], source_path)
796
969
 
797
970
  # Add to target location
798
971
  if target_path:
799
- target_folders = self._find_folders_by_path(
972
+ target_folders = self.analyzer.find_folders_by_path(
800
973
  collection_data["item"], target_path)
801
974
  if not target_folders:
802
975
  raise ToolException(
@@ -806,7 +979,7 @@ class PostmanApiWrapper(BaseToolApiWrapper):
806
979
  collection_data["item"].append(folder_data)
807
980
 
808
981
  # Update collection
809
- response = self._make_request('PUT', f'/collections/{collection_id}',
982
+ response = self._make_request('PUT', f'/collections/{self.collection_id}',
810
983
  json={"collection": collection_data})
811
984
  return json.dumps({"message": f"Folder moved from '{source_path}' to '{target_path or 'root'}'"}, indent=2)
812
985
  except Exception as e:
@@ -819,7 +992,7 @@ class PostmanApiWrapper(BaseToolApiWrapper):
819
992
  # REQUEST MANAGEMENT METHODS
820
993
  # =================================================================
821
994
 
822
- def create_request(self, collection_id: str, name: str, method: str, url: str,
995
+ def create_request(self, name: str, method: str, url: str,
823
996
  folder_path: str = None, description: str = None, headers: List[Dict] = None,
824
997
  body: Dict = None, auth: Dict = None, tests: str = None,
825
998
  pre_request_script: str = None, **kwargs) -> str:
@@ -827,7 +1000,7 @@ class PostmanApiWrapper(BaseToolApiWrapper):
827
1000
  try:
828
1001
  # Get current collection
829
1002
  collection = self._make_request(
830
- 'GET', f'/collections/{collection_id}')
1003
+ 'GET', f'/collections/{self.collection_id}')
831
1004
  collection_data = collection["collection"]
832
1005
 
833
1006
  # Create request item
@@ -870,7 +1043,7 @@ class PostmanApiWrapper(BaseToolApiWrapper):
870
1043
 
871
1044
  # Add request to appropriate location
872
1045
  if folder_path:
873
- folders = self._find_folders_by_path(
1046
+ folders = self.analyzer.find_folders_by_path(
874
1047
  collection_data["item"], folder_path)
875
1048
  if not folders:
876
1049
  raise ToolException(f"Folder '{folder_path}' not found")
@@ -879,7 +1052,7 @@ class PostmanApiWrapper(BaseToolApiWrapper):
879
1052
  collection_data["item"].append(request_item)
880
1053
 
881
1054
  # Update collection
882
- response = self._make_request('PUT', f'/collections/{collection_id}',
1055
+ response = self._make_request('PUT', f'/collections/{self.collection_id}',
883
1056
  json={"collection": collection_data})
884
1057
  return json.dumps({"message": f"Request '{name}' created successfully"}, indent=2)
885
1058
  except Exception as e:
@@ -887,92 +1060,283 @@ class PostmanApiWrapper(BaseToolApiWrapper):
887
1060
  logger.error(f"Exception when creating request: {stacktrace}")
888
1061
  raise ToolException(f"Unable to create request '{name}': {str(e)}")
889
1062
 
890
- def update_request(self, collection_id: str, request_path: str, name: str = None,
891
- method: str = None, url: str = None, description: str = None,
892
- headers: List[Dict] = None, body: Dict = None, auth: Dict = None,
893
- tests: str = None, pre_request_script: str = None, **kwargs) -> str:
894
- """Update an existing API request."""
1063
+ def update_request_name(self, request_path: str, name: str, **kwargs) -> str:
1064
+ """Update request name."""
895
1065
  try:
896
1066
  # Get current collection
897
1067
  collection = self._make_request(
898
- 'GET', f'/collections/{collection_id}')
1068
+ 'GET', f'/collections/{self.collection_id}')
899
1069
  collection_data = collection["collection"]
900
1070
 
901
1071
  # Find the request
902
- request_item = self._find_request_by_path(
1072
+ request_item = self.analyzer.find_request_by_path(
903
1073
  collection_data["item"], request_path)
904
1074
  if not request_item:
905
1075
  raise ToolException(f"Request '{request_path}' not found")
906
1076
 
907
- # Update fields if provided
908
- if name:
909
- request_item["name"] = name
910
- if method:
911
- request_item["request"]["method"] = method.upper()
912
- if url:
913
- request_item["request"]["url"] = url
914
- if description is not None:
915
- request_item["request"]["description"] = description
916
- if headers is not None:
917
- request_item["request"]["header"] = headers
918
- if body is not None:
919
- request_item["request"]["body"] = body
920
- if auth is not None:
921
- request_item["request"]["auth"] = auth
1077
+ # Update name
1078
+ request_item["name"] = name
922
1079
 
923
- # Update events
924
- if tests is not None or pre_request_script is not None:
925
- events = request_item.get("event", [])
926
-
927
- if pre_request_script is not None:
928
- # Remove existing prerequest events
929
- events = [e for e in events if e.get(
930
- "listen") != "prerequest"]
931
- if pre_request_script:
932
- events.append({
933
- "listen": "prerequest",
934
- "script": {
935
- "exec": pre_request_script.split('\n'),
936
- "type": "text/javascript"
937
- }
938
- })
939
-
940
- if tests is not None:
941
- # Remove existing test events
942
- events = [e for e in events if e.get("listen") != "test"]
943
- if tests:
944
- events.append({
945
- "listen": "test",
946
- "script": {
947
- "exec": tests.split('\n'),
948
- "type": "text/javascript"
949
- }
950
- })
1080
+ # Update collection
1081
+ response = self._make_request('PUT', f'/collections/{self.collection_id}',
1082
+ json={"collection": collection_data})
1083
+ return json.dumps({"message": f"Request '{request_path}' name updated successfully"}, indent=2)
1084
+ except Exception as e:
1085
+ stacktrace = format_exc()
1086
+ logger.error(f"Exception when updating request name: {stacktrace}")
1087
+ raise ToolException(
1088
+ f"Unable to update request '{request_path}' name: {str(e)}")
951
1089
 
952
- request_item["event"] = events
1090
+ def update_request_method(self, request_path: str, method: str, **kwargs) -> str:
1091
+ """Update request HTTP method."""
1092
+ try:
1093
+ # Get current collection
1094
+ collection = self._make_request(
1095
+ 'GET', f'/collections/{self.collection_id}')
1096
+ collection_data = collection["collection"]
1097
+
1098
+ # Find the request
1099
+ request_item = self.analyzer.find_request_by_path(
1100
+ collection_data["item"], request_path)
1101
+ if not request_item:
1102
+ raise ToolException(f"Request '{request_path}' not found")
1103
+
1104
+ # Update method
1105
+ request_item["request"]["method"] = method.upper()
1106
+
1107
+ # Update collection
1108
+ response = self._make_request('PUT', f'/collections/{self.collection_id}',
1109
+ json={"collection": collection_data})
1110
+ return json.dumps({"message": f"Request '{request_path}' method updated successfully"}, indent=2)
1111
+ except Exception as e:
1112
+ stacktrace = format_exc()
1113
+ logger.error(f"Exception when updating request method: {stacktrace}")
1114
+ raise ToolException(
1115
+ f"Unable to update request '{request_path}' method: {str(e)}")
1116
+
1117
+ def update_request_url(self, request_path: str, url: str, **kwargs) -> str:
1118
+ """Update request URL."""
1119
+ try:
1120
+ # Get current collection
1121
+ collection = self._make_request(
1122
+ 'GET', f'/collections/{self.collection_id}')
1123
+ collection_data = collection["collection"]
1124
+
1125
+ # Find the request
1126
+ request_item = self.analyzer.find_request_by_path(
1127
+ collection_data["item"], request_path)
1128
+ if not request_item:
1129
+ raise ToolException(f"Request '{request_path}' not found")
1130
+
1131
+ # Update URL
1132
+ request_item["request"]["url"] = url
953
1133
 
954
1134
  # Update collection
955
- response = self._make_request('PUT', f'/collections/{collection_id}',
1135
+ response = self._make_request('PUT', f'/collections/{self.collection_id}',
956
1136
  json={"collection": collection_data})
957
- return json.dumps({"message": f"Request '{request_path}' updated successfully"}, indent=2)
1137
+ return json.dumps({"message": f"Request '{request_path}' URL updated successfully"}, indent=2)
958
1138
  except Exception as e:
959
1139
  stacktrace = format_exc()
960
- logger.error(f"Exception when updating request: {stacktrace}")
1140
+ logger.error(f"Exception when updating request URL: {stacktrace}")
961
1141
  raise ToolException(
962
- f"Unable to update request '{request_path}': {str(e)}")
1142
+ f"Unable to update request '{request_path}' URL: {str(e)}")
1143
+
1144
+ def update_request_description(self, request_path: str, description: str, **kwargs) -> str:
1145
+ """Update request description."""
1146
+ try:
1147
+ # Get current collection
1148
+ collection = self._make_request(
1149
+ 'GET', f'/collections/{self.collection_id}')
1150
+ collection_data = collection["collection"]
1151
+
1152
+ # Find the request
1153
+ request_item = self.analyzer.find_request_by_path(
1154
+ collection_data["item"], request_path)
1155
+ if not request_item:
1156
+ raise ToolException(f"Request '{request_path}' not found")
1157
+
1158
+ # Update description
1159
+ request_item["request"]["description"] = description
963
1160
 
964
- def delete_request(self, collection_id: str, request_path: str, **kwargs) -> str:
1161
+ # Update collection
1162
+ response = self._make_request('PUT', f'/collections/{self.collection_id}',
1163
+ json={"collection": collection_data})
1164
+ return json.dumps({"message": f"Request '{request_path}' description updated successfully"}, indent=2)
1165
+ except Exception as e:
1166
+ stacktrace = format_exc()
1167
+ logger.error(f"Exception when updating request description: {stacktrace}")
1168
+ raise ToolException(
1169
+ f"Unable to update request '{request_path}' description: {str(e)}")
1170
+
1171
+ def update_request_headers(self, request_path: str, headers: List[Dict], **kwargs) -> str:
1172
+ """Update request headers."""
1173
+ try:
1174
+ # Get current collection
1175
+ collection = self._make_request(
1176
+ 'GET', f'/collections/{self.collection_id}')
1177
+ collection_data = collection["collection"]
1178
+
1179
+ # Find the request
1180
+ request_item = self.analyzer.find_request_by_path(
1181
+ collection_data["item"], request_path)
1182
+ if not request_item:
1183
+ raise ToolException(f"Request '{request_path}' not found")
1184
+
1185
+ # Update headers
1186
+ request_item["request"]["header"] = headers
1187
+
1188
+ # Update collection
1189
+ response = self._make_request('PUT', f'/collections/{self.collection_id}',
1190
+ json={"collection": collection_data})
1191
+ return json.dumps({"message": f"Request '{request_path}' headers updated successfully"}, indent=2)
1192
+ except Exception as e:
1193
+ stacktrace = format_exc()
1194
+ logger.error(f"Exception when updating request headers: {stacktrace}")
1195
+ raise ToolException(
1196
+ f"Unable to update request '{request_path}' headers: {str(e)}")
1197
+
1198
+ def update_request_body(self, request_path: str, body: Dict, **kwargs) -> str:
1199
+ """Update request body."""
1200
+ try:
1201
+ # Get current collection
1202
+ collection = self._make_request(
1203
+ 'GET', f'/collections/{self.collection_id}')
1204
+ collection_data = collection["collection"]
1205
+
1206
+ # Find the request
1207
+ request_item = self.analyzer.find_request_by_path(
1208
+ collection_data["item"], request_path)
1209
+ if not request_item:
1210
+ raise ToolException(f"Request '{request_path}' not found")
1211
+
1212
+ # Update body
1213
+ request_item["request"]["body"] = body
1214
+
1215
+ # Update collection
1216
+ response = self._make_request('PUT', f'/collections/{self.collection_id}',
1217
+ json={"collection": collection_data})
1218
+ return json.dumps({"message": f"Request '{request_path}' body updated successfully"}, indent=2)
1219
+ except Exception as e:
1220
+ stacktrace = format_exc()
1221
+ logger.error(f"Exception when updating request body: {stacktrace}")
1222
+ raise ToolException(
1223
+ f"Unable to update request '{request_path}' body: {str(e)}")
1224
+
1225
+ def update_request_auth(self, request_path: str, auth: Dict, **kwargs) -> str:
1226
+ """Update request authentication."""
1227
+ try:
1228
+ # Get current collection
1229
+ collection = self._make_request(
1230
+ 'GET', f'/collections/{self.collection_id}')
1231
+ collection_data = collection["collection"]
1232
+
1233
+ # Find the request
1234
+ request_item = self.analyzer.find_request_by_path(
1235
+ collection_data["item"], request_path)
1236
+ if not request_item:
1237
+ raise ToolException(f"Request '{request_path}' not found")
1238
+
1239
+ # Update auth
1240
+ request_item["request"]["auth"] = auth
1241
+
1242
+ # Update collection
1243
+ response = self._make_request('PUT', f'/collections/{self.collection_id}',
1244
+ json={"collection": collection_data})
1245
+ return json.dumps({"message": f"Request '{request_path}' auth updated successfully"}, indent=2)
1246
+ except Exception as e:
1247
+ stacktrace = format_exc()
1248
+ logger.error(f"Exception when updating request auth: {stacktrace}")
1249
+ raise ToolException(
1250
+ f"Unable to update request '{request_path}' auth: {str(e)}")
1251
+
1252
+ def update_request_tests(self, request_path: str, tests: str, **kwargs) -> str:
1253
+ """Update request test scripts."""
1254
+ try:
1255
+ # Get current collection
1256
+ collection = self._make_request(
1257
+ 'GET', f'/collections/{self.collection_id}')
1258
+ collection_data = collection["collection"]
1259
+
1260
+ # Find the request
1261
+ request_item = self.analyzer.find_request_by_path(
1262
+ collection_data["item"], request_path)
1263
+ if not request_item:
1264
+ raise ToolException(f"Request '{request_path}' not found")
1265
+
1266
+ # Update test events
1267
+ events = request_item.get("event", [])
1268
+ # Remove existing test events
1269
+ events = [e for e in events if e.get("listen") != "test"]
1270
+ if tests:
1271
+ events.append({
1272
+ "listen": "test",
1273
+ "script": {
1274
+ "exec": tests.split('\n'),
1275
+ "type": "text/javascript"
1276
+ }
1277
+ })
1278
+ request_item["event"] = events
1279
+
1280
+ # Update collection
1281
+ response = self._make_request('PUT', f'/collections/{self.collection_id}',
1282
+ json={"collection": collection_data})
1283
+ return json.dumps({"message": f"Request '{request_path}' tests updated successfully"}, indent=2)
1284
+ except Exception as e:
1285
+ stacktrace = format_exc()
1286
+ logger.error(f"Exception when updating request tests: {stacktrace}")
1287
+ raise ToolException(
1288
+ f"Unable to update request '{request_path}' tests: {str(e)}")
1289
+
1290
+ def update_request_pre_script(self, request_path: str, pre_request_script: str, **kwargs) -> str:
1291
+ """Update request pre-request scripts."""
1292
+ try:
1293
+ # Get current collection
1294
+ collection = self._make_request(
1295
+ 'GET', f'/collections/{self.collection_id}')
1296
+ collection_data = collection["collection"]
1297
+
1298
+ # Find the request
1299
+ request_item = self.analyzer.find_request_by_path(
1300
+ collection_data["item"], request_path)
1301
+ if not request_item:
1302
+ raise ToolException(f"Request '{request_path}' not found")
1303
+
1304
+ # Update prerequest events
1305
+ events = request_item.get("event", [])
1306
+ # Remove existing prerequest events
1307
+ events = [e for e in events if e.get("listen") != "prerequest"]
1308
+ if pre_request_script:
1309
+ events.append({
1310
+ "listen": "prerequest",
1311
+ "script": {
1312
+ "exec": pre_request_script.split('\n'),
1313
+ "type": "text/javascript"
1314
+ }
1315
+ })
1316
+ request_item["event"] = events
1317
+
1318
+ # Update collection
1319
+ response = self._make_request('PUT', f'/collections/{self.collection_id}',
1320
+ json={"collection": collection_data})
1321
+ return json.dumps({"message": f"Request '{request_path}' pre-script updated successfully"}, indent=2)
1322
+ except Exception as e:
1323
+ stacktrace = format_exc()
1324
+ logger.error(f"Exception when updating request pre-script: {stacktrace}")
1325
+ raise ToolException(
1326
+ f"Unable to update request '{request_path}' pre-script: {str(e)}")
1327
+
1328
+ def delete_request(self, request_path: str, **kwargs) -> str:
965
1329
  """Delete an API request permanently."""
966
1330
  try:
967
1331
  # Get current collection
968
1332
  collection = self._make_request(
969
- 'GET', f'/collections/{collection_id}')
1333
+ 'GET', f'/collections/{self.collection_id}')
970
1334
  collection_data = collection["collection"]
971
1335
 
972
1336
  # Find and remove the request
973
- if self._remove_request_by_path(collection_data["item"], request_path):
1337
+ if self.analyzer.remove_request_by_path(collection_data["item"], request_path):
974
1338
  # Update collection
975
- response = self._make_request('PUT', f'/collections/{collection_id}',
1339
+ response = self._make_request('PUT', f'/collections/{self.collection_id}',
976
1340
  json={"collection": collection_data})
977
1341
  return json.dumps({"message": f"Request '{request_path}' deleted successfully"}, indent=2)
978
1342
  else:
@@ -983,17 +1347,17 @@ class PostmanApiWrapper(BaseToolApiWrapper):
983
1347
  raise ToolException(
984
1348
  f"Unable to delete request '{request_path}': {str(e)}")
985
1349
 
986
- def duplicate_request(self, collection_id: str, source_path: str, new_name: str,
1350
+ def duplicate_request(self, source_path: str, new_name: str,
987
1351
  target_path: str = None, **kwargs) -> str:
988
1352
  """Create a copy of an existing API request."""
989
1353
  try:
990
1354
  # Get current collection
991
1355
  collection = self._make_request(
992
- 'GET', f'/collections/{collection_id}')
1356
+ 'GET', f'/collections/{self.collection_id}')
993
1357
  collection_data = collection["collection"]
994
1358
 
995
1359
  # Find source request
996
- source_request = self._find_request_by_path(
1360
+ source_request = self.analyzer.find_request_by_path(
997
1361
  collection_data["item"], source_path)
998
1362
  if not source_request:
999
1363
  raise ToolException(
@@ -1009,7 +1373,7 @@ class PostmanApiWrapper(BaseToolApiWrapper):
1009
1373
 
1010
1374
  # Add to target location
1011
1375
  if target_path:
1012
- folders = self._find_folders_by_path(
1376
+ folders = self.analyzer.find_folders_by_path(
1013
1377
  collection_data["item"], target_path)
1014
1378
  if not folders:
1015
1379
  raise ToolException(
@@ -1019,14 +1383,14 @@ class PostmanApiWrapper(BaseToolApiWrapper):
1019
1383
  # Add to same location as source
1020
1384
  source_folder_path = "/".join(source_path.split("/")[:-1])
1021
1385
  if source_folder_path:
1022
- folders = self._find_folders_by_path(
1386
+ folders = self.analyzer.find_folders_by_path(
1023
1387
  collection_data["item"], source_folder_path)
1024
1388
  folders[0]["item"].append(request_copy)
1025
1389
  else:
1026
1390
  collection_data["item"].append(request_copy)
1027
1391
 
1028
1392
  # Update collection
1029
- response = self._make_request('PUT', f'/collections/{collection_id}',
1393
+ response = self._make_request('PUT', f'/collections/{self.collection_id}',
1030
1394
  json={"collection": collection_data})
1031
1395
  return json.dumps({"message": f"Request duplicated as '{new_name}'"}, indent=2)
1032
1396
  except Exception as e:
@@ -1035,16 +1399,16 @@ class PostmanApiWrapper(BaseToolApiWrapper):
1035
1399
  raise ToolException(
1036
1400
  f"Unable to duplicate request '{source_path}': {str(e)}")
1037
1401
 
1038
- def move_request(self, collection_id: str, source_path: str, target_path: str = None, **kwargs) -> str:
1402
+ def move_request(self, source_path: str, target_path: str = None, **kwargs) -> str:
1039
1403
  """Move an API request to a different folder."""
1040
1404
  try:
1041
1405
  # Get current collection
1042
1406
  collection = self._make_request(
1043
- 'GET', f'/collections/{collection_id}')
1407
+ 'GET', f'/collections/{self.collection_id}')
1044
1408
  collection_data = collection["collection"]
1045
1409
 
1046
1410
  # Find source request
1047
- source_request = self._find_request_by_path(
1411
+ source_request = self.analyzer.find_request_by_path(
1048
1412
  collection_data["item"], source_path)
1049
1413
  if not source_request:
1050
1414
  raise ToolException(
@@ -1053,11 +1417,11 @@ class PostmanApiWrapper(BaseToolApiWrapper):
1053
1417
  request_data = json.loads(json.dumps(source_request)) # Deep copy
1054
1418
 
1055
1419
  # Remove from source location
1056
- self._remove_request_by_path(collection_data["item"], source_path)
1420
+ self.analyzer.remove_request_by_path(collection_data["item"], source_path)
1057
1421
 
1058
1422
  # Add to target location
1059
1423
  if target_path:
1060
- folders = self._find_folders_by_path(
1424
+ folders = self.analyzer.find_folders_by_path(
1061
1425
  collection_data["item"], target_path)
1062
1426
  if not folders:
1063
1427
  raise ToolException(
@@ -1067,7 +1431,7 @@ class PostmanApiWrapper(BaseToolApiWrapper):
1067
1431
  collection_data["item"].append(request_data)
1068
1432
 
1069
1433
  # Update collection
1070
- response = self._make_request('PUT', f'/collections/{collection_id}',
1434
+ response = self._make_request('PUT', f'/collections/{self.collection_id}',
1071
1435
  json={"collection": collection_data})
1072
1436
  return json.dumps({"message": f"Request moved from '{source_path}' to '{target_path or 'root'}'"}, indent=2)
1073
1437
  except Exception as e:
@@ -1075,958 +1439,46 @@ class PostmanApiWrapper(BaseToolApiWrapper):
1075
1439
  logger.error(f"Exception when moving request: {stacktrace}")
1076
1440
  raise ToolException(
1077
1441
  f"Unable to move request '{source_path}': {str(e)}")
1078
-
1442
+
1079
1443
  # =================================================================
1080
1444
  # HELPER METHODS
1081
1445
  # =================================================================
1082
1446
 
1083
- def _find_folders_by_path(self, items: List[Dict], path: str) -> List[Dict]:
1084
- """Find folders by path (supports nested paths like 'API/Users')."""
1085
- path_parts = [part.strip() for part in path.split('/') if part.strip()]
1086
- if not path_parts:
1087
- return items
1088
-
1089
- results = []
1090
-
1091
- def find_in_items(current_items: List[Dict], current_path: List[str], depth: int = 0):
1092
- if depth >= len(current_path):
1093
- results.extend(current_items)
1094
- return
1095
-
1096
- target_name = current_path[depth]
1097
- for item in current_items:
1098
- if (item.get('name', '').lower() == target_name.lower() or
1099
- target_name.lower() in item.get('name', '').lower()) and item.get('item'):
1100
- if depth == len(current_path) - 1:
1101
- # This is the target folder
1102
- results.append(item)
1103
- else:
1104
- # Continue searching in subfolders
1105
- find_in_items(item['item'], current_path, depth + 1)
1106
-
1107
- find_in_items(items, path_parts)
1108
- return results
1109
-
1110
- def _extract_requests_from_items(self, items: List[Dict], include_details: bool = False) -> List[Dict]:
1111
- """Extract requests from items recursively."""
1112
- requests = []
1113
1447
 
1114
- for item in items:
1115
- if item.get('request'):
1116
- # This is a request
1117
- request_data = {
1118
- "name": item.get('name'),
1119
- "method": item['request'].get('method'),
1120
- "url": item['request'].get('url')
1121
- }
1122
1448
 
1123
- if include_details:
1124
- request_data.update({
1125
- "description": item.get('description'),
1126
- "headers": item['request'].get('header', []),
1127
- "body": item['request'].get('body'),
1128
- "auth": item['request'].get('auth'),
1129
- "tests": [e for e in item.get('event', []) if e.get('listen') == 'test'],
1130
- "pre_request_scripts": [e for e in item.get('event', []) if e.get('listen') == 'prerequest']
1131
- })
1132
-
1133
- requests.append(request_data)
1134
- elif item.get('item'):
1135
- # This is a folder, recurse
1136
- requests.extend(self._extract_requests_from_items(
1137
- item['item'], include_details))
1449
+ def _make_request(self, method: str, endpoint: str, **kwargs) -> Dict[str, Any]:
1450
+ """Make HTTP request to Postman API."""
1451
+ url = f"{self.base_url.rstrip('/')}{endpoint}"
1138
1452
 
1139
- return requests
1453
+ try:
1454
+ logger.info(f"Making {method.upper()} request to {url}")
1455
+ response = self.session.request(method, url, timeout=self.timeout, **kwargs)
1456
+ response.raise_for_status()
1140
1457
 
1141
- def _search_requests_in_items(self, items: List[Dict], query: str, search_in: str, method: str = None) -> List[Dict]:
1142
- """Search for requests in items recursively."""
1143
- results = []
1144
- query_lower = query.lower()
1458
+ if response.content:
1459
+ return response.json()
1460
+ return {}
1461
+
1462
+ except requests.exceptions.RequestException as e:
1463
+ logger.error(f"Request failed: {e}")
1464
+ raise ToolException(f"Postman API request failed: {str(e)}")
1465
+ except json.JSONDecodeError as e:
1466
+ logger.error(f"Failed to decode JSON response: {e}")
1467
+ raise ToolException(
1468
+ f"Invalid JSON response from Postman API: {str(e)}")
1145
1469
 
1146
- for item in items:
1147
- if item.get('request'):
1148
- # This is a request
1149
- request = item['request']
1150
- matches = False
1151
-
1152
- # Check method filter first
1153
- if method and request.get('method', '').upper() != method.upper():
1154
- continue
1155
-
1156
- # Check search criteria
1157
- if search_in == 'all' or search_in == 'name':
1158
- if query_lower in item.get('name', '').lower():
1159
- matches = True
1160
-
1161
- if search_in == 'all' or search_in == 'url':
1162
- url = request.get('url', '')
1163
- if isinstance(url, dict):
1164
- url = url.get('raw', '')
1165
- if query_lower in url.lower():
1166
- matches = True
1167
-
1168
- if search_in == 'all' or search_in == 'description':
1169
- description = item.get(
1170
- 'description', '') or request.get('description', '')
1171
- if query_lower in description.lower():
1172
- matches = True
1173
-
1174
- if matches:
1175
- results.append({
1176
- "name": item.get('name'),
1177
- "method": request.get('method'),
1178
- "url": request.get('url'),
1179
- "description": item.get('description') or request.get('description'),
1180
- "path": self._get_item_path(items, item)
1181
- })
1182
-
1183
- elif item.get('item'):
1184
- # This is a folder, recurse
1185
- results.extend(self._search_requests_in_items(
1186
- item['item'], query, search_in, method))
1187
-
1188
- return results
1189
-
1190
- def _get_item_path(self, root_items: List[Dict], target_item: Dict, current_path: str = "") -> str:
1191
- """Get the path of an item within the collection structure."""
1192
- for item in root_items:
1193
- item_path = f"{current_path}/{item['name']}" if current_path else item['name']
1194
-
1195
- if item == target_item:
1196
- return item_path
1197
-
1198
- if item.get('item'):
1199
- result = self._get_item_path(
1200
- item['item'], target_item, item_path)
1201
- if result:
1202
- return result
1203
-
1204
- return ""
1205
-
1206
- def _find_request_by_path(self, items: List[Dict], request_path: str) -> Optional[Dict]:
1207
- """Find a request by its path."""
1208
- path_parts = [part.strip()
1209
- for part in request_path.split('/') if part.strip()]
1210
- if not path_parts:
1211
- return None
1212
-
1213
- current_items = items
1214
-
1215
- # Navigate through folders to the request
1216
- for i, part in enumerate(path_parts):
1217
- found = False
1218
- for item in current_items:
1219
- if item.get('name', '').lower() == part.lower():
1220
- if i == len(path_parts) - 1:
1221
- # This should be the request
1222
- if item.get('request'):
1223
- return item
1224
- else:
1225
- return None
1226
- else:
1227
- # This should be a folder
1228
- if item.get('item'):
1229
- current_items = item['item']
1230
- found = True
1231
- break
1232
- else:
1233
- return None
1234
-
1235
- if not found:
1236
- return None
1237
-
1238
- return None
1239
-
1240
- def _remove_folder_by_path(self, items: List[Dict], folder_path: str) -> bool:
1241
- """Remove a folder by its path."""
1242
- path_parts = [part.strip()
1243
- for part in folder_path.split('/') if part.strip()]
1244
- if not path_parts:
1245
- return False
1246
-
1247
- if len(path_parts) == 1:
1248
- # Remove from current level
1249
- for i, item in enumerate(items):
1250
- if item.get('name', '').lower() == path_parts[0].lower() and item.get('item') is not None:
1251
- del items[i]
1252
- return True
1253
- return False
1254
- else:
1255
- # Navigate to parent folder
1256
- parent_path = '/'.join(path_parts[:-1])
1257
- parent_folders = self._find_folders_by_path(items, parent_path)
1258
- if parent_folders:
1259
- return self._remove_folder_by_path(parent_folders[0]['item'], path_parts[-1])
1260
- return False
1261
-
1262
- def _remove_request_by_path(self, items: List[Dict], request_path: str) -> bool:
1263
- """Remove a request by its path."""
1264
- path_parts = [part.strip()
1265
- for part in request_path.split('/') if part.strip()]
1266
- if not path_parts:
1267
- return False
1268
-
1269
- if len(path_parts) == 1:
1270
- # Remove from current level
1271
- for i, item in enumerate(items):
1272
- if item.get('name', '').lower() == path_parts[0].lower() and item.get('request'):
1273
- del items[i]
1274
- return True
1275
- return False
1276
- else:
1277
- # Navigate to parent folder
1278
- parent_path = '/'.join(path_parts[:-1])
1279
- parent_folders = self._find_folders_by_path(items, parent_path)
1280
- if parent_folders:
1281
- return self._remove_request_by_path(parent_folders[0]['item'], path_parts[-1])
1282
- return False
1283
-
1284
- def _remove_item_ids(self, items: List[Dict]):
1285
- """Remove IDs from items recursively for duplication."""
1286
- for item in items:
1287
- if 'id' in item:
1288
- del item['id']
1289
- if item.get('item'):
1290
- self._remove_item_ids(item['item'])
1291
1470
 
1292
- # =================================================================
1293
- # ANALYSIS HELPER METHODS
1294
- # =================================================================
1295
1471
 
1296
- def _perform_collection_analysis(self, collection: Dict) -> Dict:
1297
- """Perform comprehensive analysis of a collection."""
1298
- collection_data = collection['collection']
1299
- folders = self._analyze_folders(collection_data.get('item', []))
1300
- total_requests = self._count_requests(collection_data.get('item', []))
1301
- issues = self._identify_collection_issues(collection_data)
1302
- score = self._calculate_quality_score(collection_data, folders, issues)
1303
- recommendations = self._generate_recommendations(issues)
1304
-
1305
- return {
1306
- "collection_id": collection_data['info'].get('_postman_id', ''),
1307
- "collection_name": collection_data['info'].get('name', ''),
1308
- "total_requests": total_requests,
1309
- "folders": folders,
1310
- "issues": issues,
1311
- "recommendations": recommendations,
1312
- "score": score,
1313
- "overall_security_score": self._calculate_overall_security_score(folders),
1314
- "overall_performance_score": self._calculate_overall_performance_score(folders),
1315
- "overall_documentation_score": self._calculate_overall_documentation_score(folders)
1316
- }
1317
-
1318
- def _analyze_folders(self, items: List[Dict], base_path: str = "") -> List[Dict]:
1319
- """Analyze all folders in a collection."""
1320
- folders = []
1321
1472
 
1322
- for item in items:
1323
- if item.get('item') is not None: # This is a folder
1324
- folder_path = f"{base_path}/{item['name']}" if base_path else item['name']
1325
- analysis = self._perform_folder_analysis(item, folder_path)
1326
- folders.append(analysis)
1327
-
1328
- # Recursively analyze subfolders
1329
- subfolders = self._analyze_folders(item['item'], folder_path)
1330
- folders.extend(subfolders)
1331
-
1332
- return folders
1333
-
1334
- def _perform_folder_analysis(self, folder: Dict, path: str) -> Dict:
1335
- """Perform analysis of a specific folder."""
1336
- requests = self._analyze_requests(folder.get('item', []))
1337
- request_count = self._count_requests(folder.get('item', []))
1338
- issues = self._identify_folder_issues(folder, requests)
1339
-
1340
- return {
1341
- "name": folder['name'],
1342
- "path": path,
1343
- "request_count": request_count,
1344
- "requests": requests,
1345
- "issues": issues,
1346
- "has_consistent_naming": self._check_consistent_naming(folder.get('item', [])),
1347
- "has_proper_structure": bool(folder.get('description') and folder.get('item')),
1348
- "auth_consistency": self._check_auth_consistency(requests),
1349
- "avg_documentation_quality": self._calculate_avg_documentation_quality(requests),
1350
- "avg_security_score": self._calculate_avg_security_score(requests),
1351
- "avg_performance_score": self._calculate_avg_performance_score(requests)
1352
- }
1353
-
1354
- def _analyze_requests(self, items: List[Dict]) -> List[Dict]:
1355
- """Analyze requests within a folder."""
1356
- requests = []
1473
+
1474
+ # =================================================================
1475
+ # HELPER METHODS
1476
+ # =================================================================
1357
1477
 
1358
1478
  for item in items:
1359
1479
  if item.get('request'): # This is a request
1360
- analysis = self._perform_request_analysis(item)
1480
+ analysis = self.analyzer.perform_request_analysis(item)
1361
1481
  requests.append(analysis)
1362
1482
 
1363
1483
  return requests
1364
1484
 
1365
- def _perform_request_analysis(self, item: Dict) -> Dict:
1366
- """Perform comprehensive analysis of a specific request."""
1367
- request = item['request']
1368
- issues = []
1369
-
1370
- # Basic checks
1371
- has_auth = bool(request.get('auth')
1372
- or self._has_auth_in_headers(request))
1373
- has_description = bool(item.get('description')
1374
- or request.get('description'))
1375
- has_tests = bool([e for e in item.get('event', [])
1376
- if e.get('listen') == 'test'])
1377
- has_examples = bool(item.get('response', []))
1378
-
1379
- # Enhanced analysis
1380
- url = request.get('url', '')
1381
- if isinstance(url, dict):
1382
- url = url.get('raw', '')
1383
-
1384
- has_hardcoded_url = self._detect_hardcoded_url(url)
1385
- has_hardcoded_data = self._detect_hardcoded_data(request)
1386
- has_proper_headers = self._validate_headers(request)
1387
- has_variables = self._detect_variable_usage(request)
1388
- has_error_handling = self._detect_error_handling(item)
1389
- follows_naming_convention = self._validate_naming_convention(
1390
- item['name'])
1391
- has_security_issues = self._detect_security_issues(request)
1392
- has_performance_issues = self._detect_performance_issues(request)
1393
-
1394
- # Calculate scores
1395
- security_score = self._calculate_security_score(
1396
- request, has_auth, has_security_issues)
1397
- performance_score = self._calculate_performance_score(
1398
- request, has_performance_issues)
1399
-
1400
- # Generate issues
1401
- self._generate_request_issues(issues, item, {
1402
- 'has_description': has_description,
1403
- 'has_auth': has_auth,
1404
- 'has_tests': has_tests,
1405
- 'has_hardcoded_url': has_hardcoded_url,
1406
- 'has_hardcoded_data': has_hardcoded_data,
1407
- 'has_proper_headers': has_proper_headers,
1408
- 'has_security_issues': has_security_issues,
1409
- 'follows_naming_convention': follows_naming_convention
1410
- })
1411
-
1412
- return {
1413
- "name": item['name'],
1414
- "method": request.get('method'),
1415
- "url": url,
1416
- "has_auth": has_auth,
1417
- "has_description": has_description,
1418
- "has_tests": has_tests,
1419
- "has_examples": has_examples,
1420
- "issues": issues,
1421
- "has_hardcoded_url": has_hardcoded_url,
1422
- "has_hardcoded_data": has_hardcoded_data,
1423
- "has_proper_headers": has_proper_headers,
1424
- "has_variables": has_variables,
1425
- "has_error_handling": has_error_handling,
1426
- "follows_naming_convention": follows_naming_convention,
1427
- "has_security_issues": has_security_issues,
1428
- "has_performance_issues": has_performance_issues,
1429
- "auth_type": request.get('auth', {}).get('type'),
1430
- "response_examples": len(item.get('response', [])),
1431
- "test_coverage": self._assess_test_coverage(item),
1432
- "documentation_quality": self._assess_documentation_quality(item),
1433
- "security_score": security_score,
1434
- "performance_score": performance_score
1435
- }
1436
-
1437
- def _count_requests(self, items: List[Dict]) -> int:
1438
- """Count total requests in items."""
1439
- count = 0
1440
- for item in items:
1441
- if item.get('request'):
1442
- count += 1
1443
- elif item.get('item'):
1444
- count += self._count_requests(item['item'])
1445
- return count
1446
-
1447
- def _has_auth_in_headers(self, request: Dict) -> bool:
1448
- """Check if request has authentication in headers."""
1449
- headers = request.get('header', [])
1450
- auth_headers = ['authorization', 'x-api-key', 'x-auth-token']
1451
- return any(h.get('key', '').lower() in auth_headers for h in headers)
1452
-
1453
- def _detect_hardcoded_url(self, url: str) -> bool:
1454
- """Detect hardcoded URLs that should use variables."""
1455
- hardcoded_patterns = [
1456
- r'^https?://\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}', # IP addresses
1457
- r'^https?://localhost', # localhost
1458
- # Direct domains
1459
- r'^https?://[a-zA-Z0-9.-]+\.(com|org|net|io|dev)',
1460
- r'api\.example\.com', # Example domains
1461
- r'staging\.|dev\.|test\.' # Environment-specific
1462
- ]
1463
- return any(re.search(pattern, url) for pattern in hardcoded_patterns) and '{{' not in url
1464
-
1465
- def _detect_hardcoded_data(self, request: Dict) -> bool:
1466
- """Detect hardcoded data in request body and headers."""
1467
- # Check headers
1468
- headers = request.get('header', [])
1469
- has_hardcoded_headers = any(
1470
- ('token' in h.get('key', '').lower() or
1471
- 'key' in h.get('key', '').lower() or
1472
- 'secret' in h.get('key', '').lower()) and
1473
- '{{' not in h.get('value', '')
1474
- for h in headers
1475
- )
1476
-
1477
- # Check body
1478
- has_hardcoded_body = False
1479
- body = request.get('body', {})
1480
- if body.get('raw'):
1481
- try:
1482
- body_data = json.loads(body['raw'])
1483
- has_hardcoded_body = self._contains_hardcoded_values(body_data)
1484
- except json.JSONDecodeError:
1485
- # If not JSON, check for common patterns
1486
- has_hardcoded_body = re.search(
1487
- r'("api_key"|"token"|"password"):\s*"[^{]', body['raw']) is not None
1488
-
1489
- return has_hardcoded_headers or has_hardcoded_body
1490
-
1491
- def _contains_hardcoded_values(self, obj: Any) -> bool:
1492
- """Check if object contains hardcoded values that should be variables."""
1493
- if not isinstance(obj, dict):
1494
- return False
1495
-
1496
- for key, value in obj.items():
1497
- if isinstance(value, str):
1498
- # Check for sensitive keys
1499
- if key.lower() in ['token', 'key', 'secret', 'password', 'api_key', 'client_id', 'client_secret']:
1500
- if '{{' not in value:
1501
- return True
1502
- # Check for email patterns, URLs
1503
- if re.search(r'@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}', value) or value.startswith('http'):
1504
- if '{{' not in value:
1505
- return True
1506
- elif isinstance(value, dict):
1507
- if self._contains_hardcoded_values(value):
1508
- return True
1509
-
1510
- return False
1511
-
1512
- def _validate_headers(self, request: Dict) -> bool:
1513
- """Validate request headers."""
1514
- headers = request.get('header', [])
1515
- header_names = [h.get('key', '').lower() for h in headers]
1516
- method = request.get('method', '').upper()
1517
-
1518
- # Check for essential headers
1519
- if method in ['POST', 'PUT', 'PATCH'] and request.get('body'):
1520
- if 'content-type' not in header_names:
1521
- return False
1522
-
1523
- if method in ['GET', 'POST', 'PUT', 'PATCH']:
1524
- if 'accept' not in header_names:
1525
- return False
1526
-
1527
- return True
1528
-
1529
- def _detect_variable_usage(self, request: Dict) -> bool:
1530
- """Detect variable usage in request."""
1531
- url = request.get('url', '')
1532
- if isinstance(url, dict):
1533
- url = url.get('raw', '')
1534
-
1535
- has_url_variables = '{{' in url
1536
- has_header_variables = any('{{' in h.get('value', '')
1537
- for h in request.get('header', []))
1538
-
1539
- has_body_variables = False
1540
- body = request.get('body', {})
1541
- if body.get('raw'):
1542
- has_body_variables = '{{' in body['raw']
1543
-
1544
- return has_url_variables or has_header_variables or has_body_variables
1545
-
1546
- def _detect_error_handling(self, item: Dict) -> bool:
1547
- """Detect error handling in tests."""
1548
- test_scripts = [e for e in item.get(
1549
- 'event', []) if e.get('listen') == 'test']
1550
-
1551
- for script in test_scripts:
1552
- script_code = '\n'.join(script.get('script', {}).get('exec', []))
1553
- if ('4' in script_code or '5' in script_code or
1554
- 'error' in script_code.lower() or 'fail' in script_code.lower()):
1555
- return True
1556
-
1557
- return False
1558
-
1559
- def _validate_naming_convention(self, name: str) -> bool:
1560
- """Validate naming convention."""
1561
- has_consistent_case = re.match(
1562
- r'^[a-zA-Z][a-zA-Z0-9\s\-_]*$', name) is not None
1563
- has_descriptive_name = len(
1564
- name) > 3 and 'test' not in name.lower() and 'temp' not in name.lower()
1565
- return has_consistent_case and has_descriptive_name
1566
-
1567
- def _detect_security_issues(self, request: Dict) -> bool:
1568
- """Detect security issues."""
1569
- url = request.get('url', '')
1570
- if isinstance(url, dict):
1571
- url = url.get('raw', '')
1572
-
1573
- # Check for exposed credentials in URL
1574
- if re.search(r'[?&](token|key|password|secret)=([^&\s]+)', url):
1575
- return True
1576
-
1577
- # Check for weak authentication
1578
- auth = request.get('auth', {})
1579
- if auth.get('type') == 'basic' and not url.startswith('https'):
1580
- return True
1581
-
1582
- # Check headers for exposed credentials
1583
- headers = request.get('header', [])
1584
- return any('secret' in h.get('key', '').lower() or 'password' in h.get('key', '').lower()
1585
- for h in headers)
1586
-
1587
- def _detect_performance_issues(self, request: Dict) -> bool:
1588
- """Detect performance issues."""
1589
- # Large request body
1590
- body = request.get('body', {})
1591
- if body.get('raw') and len(body['raw']) > 10000:
1592
- return True
1593
-
1594
- # Too many headers
1595
- if len(request.get('header', [])) > 20:
1596
- return True
1597
-
1598
- # Too many query parameters
1599
- url = request.get('url', '')
1600
- if isinstance(url, dict):
1601
- url = url.get('raw', '')
1602
-
1603
- query_params = url.split('?')[1] if '?' in url else ''
1604
- if query_params and len(query_params.split('&')) > 15:
1605
- return True
1606
-
1607
- return False
1608
-
1609
- def _calculate_security_score(self, request: Dict, has_auth: bool, has_security_issues: bool) -> int:
1610
- """Calculate security score."""
1611
- score = 100
1612
- method = request.get('method', '').upper()
1613
-
1614
- if not has_auth and method in ['POST', 'PUT', 'PATCH', 'DELETE']:
1615
- score -= 40
1616
-
1617
- if has_security_issues:
1618
- score -= 30
1619
-
1620
- url = request.get('url', '')
1621
- if isinstance(url, dict):
1622
- url = url.get('raw', '')
1623
-
1624
- if url.startswith('http://'):
1625
- score -= 20
1626
-
1627
- auth = request.get('auth', {})
1628
- if auth.get('type') == 'basic':
1629
- score -= 10
1630
-
1631
- return max(0, score)
1632
-
1633
- def _calculate_performance_score(self, request: Dict, has_performance_issues: bool) -> int:
1634
- """Calculate performance score."""
1635
- score = 100
1636
-
1637
- if has_performance_issues:
1638
- score -= 50
1639
-
1640
- headers = request.get('header', [])
1641
- header_names = [h.get('key', '').lower() for h in headers]
1642
-
1643
- if 'cache-control' not in header_names:
1644
- score -= 10
1645
-
1646
- if 'accept-encoding' not in header_names:
1647
- score -= 10
1648
-
1649
- return max(0, score)
1650
-
1651
- def _assess_test_coverage(self, item: Dict) -> str:
1652
- """Assess test coverage."""
1653
- test_scripts = [e for e in item.get(
1654
- 'event', []) if e.get('listen') == 'test']
1655
-
1656
- if not test_scripts:
1657
- return 'none'
1658
-
1659
- all_test_code = '\n'.join([
1660
- '\n'.join(script.get('script', {}).get('exec', []))
1661
- for script in test_scripts
1662
- ])
1663
-
1664
- checks = [
1665
- 'pm.response.code' in all_test_code or 'status' in all_test_code,
1666
- 'responseTime' in all_test_code,
1667
- 'pm.response.json' in all_test_code or 'body' in all_test_code,
1668
- '4' in all_test_code or '5' in all_test_code
1669
- ]
1670
-
1671
- check_count = sum(checks)
1672
-
1673
- if check_count >= 3:
1674
- return 'comprehensive'
1675
- elif check_count >= 1:
1676
- return 'basic'
1677
-
1678
- return 'none'
1679
-
1680
- def _assess_documentation_quality(self, item: Dict) -> str:
1681
- """Assess documentation quality."""
1682
- description = item.get('description', '') or item.get(
1683
- 'request', {}).get('description', '')
1684
-
1685
- if not description:
1686
- return 'none'
1687
-
1688
- description_lower = description.lower()
1689
- quality_factors = [
1690
- 'parameter' in description_lower,
1691
- 'response' in description_lower,
1692
- 'example' in description_lower,
1693
- 'auth' in description_lower,
1694
- 'error' in description_lower
1695
- ]
1696
-
1697
- factor_count = sum(quality_factors)
1698
-
1699
- if factor_count >= 4:
1700
- return 'excellent'
1701
- elif factor_count >= 2:
1702
- return 'good'
1703
- elif factor_count >= 1 or len(description) > 50:
1704
- return 'minimal'
1705
-
1706
- return 'none'
1707
-
1708
- def _check_consistent_naming(self, items: List[Dict]) -> bool:
1709
- """Check if items have consistent naming."""
1710
- if len(items) <= 1:
1711
- return True
1712
-
1713
- naming_patterns = []
1714
- for item in items:
1715
- name = item.get('name', '').lower()
1716
- if re.match(r'^[a-z][a-z0-9]*(_[a-z0-9]+)*$', name):
1717
- naming_patterns.append('snake_case')
1718
- elif re.match(r'^[a-z][a-zA-Z0-9]*$', name):
1719
- naming_patterns.append('camelCase')
1720
- elif re.match(r'^[a-z][a-z0-9]*(-[a-z0-9]+)*$', name):
1721
- naming_patterns.append('kebab-case')
1722
- else:
1723
- naming_patterns.append('mixed')
1724
-
1725
- unique_patterns = set(naming_patterns)
1726
- return len(unique_patterns) == 1 and 'mixed' not in unique_patterns
1727
-
1728
- def _check_auth_consistency(self, requests: List[Dict]) -> str:
1729
- """Check authentication consistency across requests."""
1730
- if not requests:
1731
- return 'none'
1732
-
1733
- auth_types = set(req.get('auth_type') or 'none' for req in requests)
1734
-
1735
- if len(auth_types) == 1:
1736
- return 'none' if 'none' in auth_types else 'consistent'
1737
-
1738
- return 'mixed'
1739
-
1740
- def _calculate_avg_documentation_quality(self, requests: List[Dict]) -> int:
1741
- """Calculate average documentation quality score."""
1742
- if not requests:
1743
- return 0
1744
-
1745
- quality_scores = {
1746
- 'excellent': 100,
1747
- 'good': 75,
1748
- 'minimal': 50,
1749
- 'none': 0
1750
- }
1751
-
1752
- scores = [quality_scores.get(
1753
- req.get('documentation_quality', 'none'), 0) for req in requests]
1754
- return round(sum(scores) / len(scores))
1755
-
1756
- def _calculate_avg_security_score(self, requests: List[Dict]) -> int:
1757
- """Calculate average security score."""
1758
- if not requests:
1759
- return 0
1760
-
1761
- scores = [req.get('security_score', 0) for req in requests]
1762
- return round(sum(scores) / len(scores))
1763
-
1764
- def _calculate_avg_performance_score(self, requests: List[Dict]) -> int:
1765
- """Calculate average performance score."""
1766
- if not requests:
1767
- return 0
1768
-
1769
- scores = [req.get('performance_score', 0) for req in requests]
1770
- return round(sum(scores) / len(scores))
1771
-
1772
- def _identify_collection_issues(self, collection_data: Dict) -> List[Dict]:
1773
- """Identify collection-level issues."""
1774
- issues = []
1775
-
1776
- if not collection_data.get('info', {}).get('description'):
1777
- issues.append({
1778
- 'type': 'warning',
1779
- 'severity': 'medium',
1780
- 'message': 'Collection lacks description',
1781
- 'location': 'Collection root',
1782
- 'suggestion': 'Add a description explaining the purpose of this collection'
1783
- })
1784
-
1785
- if not collection_data.get('auth'):
1786
- issues.append({
1787
- 'type': 'info',
1788
- 'severity': 'low',
1789
- 'message': 'Collection lacks default authentication',
1790
- 'location': 'Collection root',
1791
- 'suggestion': 'Consider setting up collection-level authentication'
1792
- })
1793
-
1794
- return issues
1795
-
1796
- def _identify_folder_issues(self, folder: Dict, requests: List[Dict]) -> List[Dict]:
1797
- """Identify folder-level issues."""
1798
- issues = []
1799
-
1800
- if not folder.get('description'):
1801
- issues.append({
1802
- 'type': 'warning',
1803
- 'severity': 'low',
1804
- 'message': 'Folder lacks description',
1805
- 'location': folder['name'],
1806
- 'suggestion': 'Add a description explaining the purpose of this folder'
1807
- })
1808
-
1809
- if not requests and (not folder.get('item') or len(folder['item']) == 0):
1810
- issues.append({
1811
- 'type': 'warning',
1812
- 'severity': 'medium',
1813
- 'message': 'Empty folder',
1814
- 'location': folder['name'],
1815
- 'suggestion': 'Consider removing empty folders or adding requests'
1816
- })
1817
-
1818
- return issues
1819
-
1820
- def _generate_request_issues(self, issues: List[Dict], item: Dict, analysis: Dict):
1821
- """Generate request-specific issues."""
1822
- if not analysis['has_description']:
1823
- issues.append({
1824
- 'type': 'warning',
1825
- 'severity': 'medium',
1826
- 'message': 'Request lacks description',
1827
- 'location': item['name'],
1828
- 'suggestion': 'Add a clear description explaining what this request does'
1829
- })
1830
-
1831
- if not analysis['has_auth'] and item['request']['method'] in ['POST', 'PUT', 'PATCH', 'DELETE']:
1832
- issues.append({
1833
- 'type': 'warning',
1834
- 'severity': 'high',
1835
- 'message': 'Sensitive operation without authentication',
1836
- 'location': item['name'],
1837
- 'suggestion': 'Add authentication for this request'
1838
- })
1839
-
1840
- if not analysis['has_tests']:
1841
- issues.append({
1842
- 'type': 'info',
1843
- 'severity': 'high',
1844
- 'message': 'Request lacks test scripts',
1845
- 'location': item['name'],
1846
- 'suggestion': 'Add test scripts to validate response'
1847
- })
1848
-
1849
- if analysis['has_hardcoded_url']:
1850
- issues.append({
1851
- 'type': 'warning',
1852
- 'severity': 'high',
1853
- 'message': 'Request contains hardcoded URL',
1854
- 'location': item['name'],
1855
- 'suggestion': 'Replace hardcoded URLs with environment variables'
1856
- })
1857
-
1858
- if analysis['has_security_issues']:
1859
- issues.append({
1860
- 'type': 'error',
1861
- 'severity': 'high',
1862
- 'message': 'Security vulnerabilities detected',
1863
- 'location': item['name'],
1864
- 'suggestion': 'Address security issues such as exposed credentials'
1865
- })
1866
-
1867
- def _calculate_quality_score(self, collection_data: Dict, folders: List[Dict], issues: List[Dict]) -> int:
1868
- """Calculate quality score (0-100)."""
1869
- score = 100
1870
-
1871
- # Deduct points for issues
1872
- for issue in issues:
1873
- severity = issue.get('severity', 'low')
1874
- if severity == 'high':
1875
- score -= 10
1876
- elif severity == 'medium':
1877
- score -= 5
1878
- elif severity == 'low':
1879
- score -= 2
1880
-
1881
- # Deduct points for folder and request issues
1882
- for folder in folders:
1883
- for issue in folder.get('issues', []):
1884
- severity = issue.get('severity', 'low')
1885
- if severity == 'high':
1886
- score -= 5
1887
- elif severity == 'medium':
1888
- score -= 3
1889
- elif severity == 'low':
1890
- score -= 1
1891
-
1892
- for request in folder.get('requests', []):
1893
- for issue in request.get('issues', []):
1894
- severity = issue.get('severity', 'low')
1895
- if severity == 'high':
1896
- score -= 3
1897
- elif severity == 'medium':
1898
- score -= 2
1899
- elif severity == 'low':
1900
- score -= 1
1901
-
1902
- return max(0, min(100, score))
1903
-
1904
- def _generate_recommendations(self, issues: List[Dict]) -> List[str]:
1905
- """Generate recommendations based on issues."""
1906
- recommendations = []
1907
- suggestion_counts = {}
1908
-
1909
- # Count similar suggestions
1910
- for issue in issues:
1911
- suggestion = issue.get('suggestion', '')
1912
- if suggestion:
1913
- suggestion_counts[suggestion] = suggestion_counts.get(
1914
- suggestion, 0) + 1
1915
-
1916
- # Generate recommendations from most common suggestions
1917
- sorted_suggestions = sorted(
1918
- suggestion_counts.items(), key=lambda x: x[1], reverse=True)[:10]
1919
-
1920
- for suggestion, count in sorted_suggestions:
1921
- if count > 1:
1922
- recommendations.append(f"{suggestion} ({count} instances)")
1923
- else:
1924
- recommendations.append(suggestion)
1925
-
1926
- return recommendations
1927
-
1928
- def _calculate_overall_security_score(self, folders: List[Dict]) -> int:
1929
- """Calculate overall security score."""
1930
- if not folders:
1931
- return 0
1932
-
1933
- scores = []
1934
- for folder in folders:
1935
- avg_score = folder.get('avg_security_score', 0)
1936
- if avg_score > 0:
1937
- scores.append(avg_score)
1938
-
1939
- return round(sum(scores) / len(scores)) if scores else 0
1940
-
1941
- def _calculate_overall_performance_score(self, folders: List[Dict]) -> int:
1942
- """Calculate overall performance score."""
1943
- if not folders:
1944
- return 0
1945
-
1946
- scores = []
1947
- for folder in folders:
1948
- avg_score = folder.get('avg_performance_score', 0)
1949
- if avg_score > 0:
1950
- scores.append(avg_score)
1951
-
1952
- return round(sum(scores) / len(scores)) if scores else 0
1953
-
1954
- def _calculate_overall_documentation_score(self, folders: List[Dict]) -> int:
1955
- """Calculate overall documentation score."""
1956
- if not folders:
1957
- return 0
1958
-
1959
- scores = []
1960
- for folder in folders:
1961
- avg_score = folder.get('avg_documentation_quality', 0)
1962
- if avg_score > 0:
1963
- scores.append(avg_score)
1964
-
1965
- return round(sum(scores) / len(scores)) if scores else 0
1966
-
1967
- def _generate_improvements(self, analysis: Dict) -> List[Dict]:
1968
- """Generate improvement suggestions with enhanced analysis."""
1969
- improvements = []
1970
-
1971
- # Collection-level improvements
1972
- if analysis['score'] < 80:
1973
- improvements.append({
1974
- 'id': 'collection-quality',
1975
- 'title': 'Improve Overall Collection Quality',
1976
- 'description': f"Collection quality score is {analysis['score']}/100. Focus on addressing high-priority issues.",
1977
- 'priority': 'high',
1978
- 'category': 'quality',
1979
- 'impact': 'high'
1980
- })
1981
-
1982
- if analysis['overall_security_score'] < 70:
1983
- improvements.append({
1984
- 'id': 'security-enhancement',
1985
- 'title': 'Enhance Security Practices',
1986
- 'description': f"Security score is {analysis['overall_security_score']}/100. Review authentication and data handling.",
1987
- 'priority': 'high',
1988
- 'category': 'security',
1989
- 'impact': 'high'
1990
- })
1991
-
1992
- if analysis['overall_documentation_score'] < 60:
1993
- improvements.append({
1994
- 'id': 'documentation-improvement',
1995
- 'title': 'Improve Documentation',
1996
- 'description': f"Documentation score is {analysis['overall_documentation_score']}/100. Add descriptions and examples.",
1997
- 'priority': 'medium',
1998
- 'category': 'documentation',
1999
- 'impact': 'medium'
2000
- })
2001
-
2002
- # Add specific improvements based on common issues
2003
- issue_counts = {}
2004
- for folder in analysis.get('folders', []):
2005
- for request in folder.get('requests', []):
2006
- for issue in request.get('issues', []):
2007
- issue_type = issue.get('message', '')
2008
- issue_counts[issue_type] = issue_counts.get(
2009
- issue_type, 0) + 1
2010
-
2011
- # Generate improvements for most common issues
2012
- if issue_counts.get('Request lacks test scripts', 0) > 3:
2013
- improvements.append({
2014
- 'id': 'add-test-scripts',
2015
- 'title': 'Add Test Scripts to Requests',
2016
- 'description': f"Found {issue_counts['Request lacks test scripts']} requests without test scripts.",
2017
- 'priority': 'medium',
2018
- 'category': 'testing',
2019
- 'impact': 'medium'
2020
- })
2021
-
2022
- if issue_counts.get('Request contains hardcoded URL', 0) > 2:
2023
- improvements.append({
2024
- 'id': 'use-environment-variables',
2025
- 'title': 'Use Environment Variables',
2026
- 'description': f"Found {issue_counts['Request contains hardcoded URL']} requests with hardcoded URLs.",
2027
- 'priority': 'high',
2028
- 'category': 'maintainability',
2029
- 'impact': 'high'
2030
- })
2031
-
2032
- return improvements