skylos 1.0.10__py3-none-any.whl → 2.5.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. skylos/__init__.py +9 -3
  2. skylos/analyzer.py +674 -168
  3. skylos/cfg_visitor.py +60 -0
  4. skylos/cli.py +719 -235
  5. skylos/codemods.py +277 -0
  6. skylos/config.py +50 -0
  7. skylos/constants.py +78 -0
  8. skylos/gatekeeper.py +147 -0
  9. skylos/linter.py +18 -0
  10. skylos/rules/base.py +20 -0
  11. skylos/rules/danger/calls.py +119 -0
  12. skylos/rules/danger/danger.py +157 -0
  13. skylos/rules/danger/danger_cmd/cmd_flow.py +75 -0
  14. skylos/rules/danger/danger_fs/__init__.py +0 -0
  15. skylos/rules/danger/danger_fs/path_flow.py +79 -0
  16. skylos/rules/danger/danger_net/__init__.py +0 -0
  17. skylos/rules/danger/danger_net/ssrf_flow.py +80 -0
  18. skylos/rules/danger/danger_sql/__init__.py +0 -0
  19. skylos/rules/danger/danger_sql/sql_flow.py +245 -0
  20. skylos/rules/danger/danger_sql/sql_raw_flow.py +96 -0
  21. skylos/rules/danger/danger_web/__init__.py +0 -0
  22. skylos/rules/danger/danger_web/xss_flow.py +170 -0
  23. skylos/rules/danger/taint.py +110 -0
  24. skylos/rules/quality/__init__.py +0 -0
  25. skylos/rules/quality/complexity.py +95 -0
  26. skylos/rules/quality/logic.py +96 -0
  27. skylos/rules/quality/nesting.py +101 -0
  28. skylos/rules/quality/structure.py +99 -0
  29. skylos/rules/secrets.py +325 -0
  30. skylos/server.py +554 -0
  31. skylos/visitor.py +502 -90
  32. skylos/visitors/__init__.py +0 -0
  33. skylos/visitors/framework_aware.py +437 -0
  34. skylos/visitors/test_aware.py +74 -0
  35. skylos-2.5.2.dist-info/METADATA +21 -0
  36. skylos-2.5.2.dist-info/RECORD +42 -0
  37. {skylos-1.0.10.dist-info → skylos-2.5.2.dist-info}/WHEEL +1 -1
  38. {skylos-1.0.10.dist-info → skylos-2.5.2.dist-info}/top_level.txt +0 -1
  39. skylos-1.0.10.dist-info/METADATA +0 -8
  40. skylos-1.0.10.dist-info/RECORD +0 -21
  41. test/compare_tools.py +0 -604
  42. test/diagnostics.py +0 -364
  43. test/sample_repo/app.py +0 -13
  44. test/sample_repo/sample_repo/commands.py +0 -81
  45. test/sample_repo/sample_repo/models.py +0 -122
  46. test/sample_repo/sample_repo/routes.py +0 -89
  47. test/sample_repo/sample_repo/utils.py +0 -36
  48. test/test_skylos.py +0 -456
  49. test/test_visitor.py +0 -220
  50. {test → skylos/rules}/__init__.py +0 -0
  51. {test/sample_repo → skylos/rules/danger}/__init__.py +0 -0
  52. {test/sample_repo/sample_repo → skylos/rules/danger/danger_cmd}/__init__.py +0 -0
  53. {skylos-1.0.10.dist-info → skylos-2.5.2.dist-info}/entry_points.txt +0 -0
test/diagnostics.py DELETED
@@ -1,364 +0,0 @@
1
- #!/usr/bin/env python3
2
- import sys
3
- import json
4
- import subprocess
5
- import re
6
- from pathlib import Path
7
-
8
- def load_ground_truth(path):
9
- """Load the ground truth data from a ground_truth.json file with the Skylos test structure"""
10
- try:
11
- with open(path, 'r') as f:
12
- data = json.load(f)
13
-
14
- dead_items = []
15
- if "files" in data and "code.py" in data["files"]:
16
- if "dead_items" in data["files"]["code.py"]:
17
- return data["files"]["code.py"]["dead_items"]
18
-
19
- return []
20
- except Exception as e:
21
- print(f"Error loading ground truth from {path}: {e}")
22
- return []
23
-
24
- def run_skylos_on_file(code_file, skylos_path):
25
- try:
26
- cmd = [sys.executable, skylos_path, str(code_file)]
27
- result = subprocess.run(cmd, capture_output=True, text=True)
28
-
29
- if result.returncode != 0:
30
- print(f"Error running Skylos subprocess: {result.stderr}")
31
- return {}
32
-
33
- json_start = result.stdout.find('{')
34
- if json_start > -1:
35
- json_str = result.stdout[json_start:]
36
- try:
37
- return json.loads(json_str)
38
- except json.JSONDecodeError:
39
- print(f"Error parsing Skylos JSON output")
40
- return {}
41
- else:
42
- print(f"Could not find JSON in output")
43
- return {}
44
- except Exception as e:
45
- print(f"Error running Skylos: {e}")
46
- return {}
47
-
48
- def run_vulture_on_file(code_file, confidence=0):
49
- try:
50
- cmd = ["vulture", str(code_file), f"--min-confidence={confidence}"]
51
- result = subprocess.run(cmd, capture_output=True, text=True)
52
-
53
- items = []
54
- pattern = r"([^:]+):(\d+): (\w+) '([^']+)' is never used"
55
-
56
- for line in result.stdout.splitlines():
57
- match = re.search(pattern, line)
58
- if match:
59
- filename, lineno, item_type, name = match.groups()
60
-
61
- if item_type == "unused variable":
62
- continue
63
- elif item_type == "unused import":
64
- normalized_type = "import"
65
- elif item_type == "unused class":
66
- normalized_type = "class"
67
- elif item_type == "unused function":
68
- if "." in name:
69
- normalized_type = "method"
70
- else:
71
- normalized_type = "function"
72
- elif item_type == "unused method":
73
- normalized_type = "method"
74
- elif item_type == "unused property":
75
- normalized_type = "method"
76
- else:
77
- normalized_type = item_type.replace("unused ", "")
78
-
79
- if normalized_type == "method" and "." in name:
80
- items.append({
81
- "name": name,
82
- "type": normalized_type,
83
- "file": filename,
84
- "line": int(lineno)
85
- })
86
- else:
87
- items.append({
88
- "name": name,
89
- "type": normalized_type,
90
- "file": filename,
91
- "line": int(lineno)
92
- })
93
-
94
- return items
95
- except Exception as e:
96
- print(f"Error running Vulture on {code_file}: {e}")
97
- return []
98
-
99
- def normalize_item(item):
100
- if isinstance(item, str):
101
- return item
102
-
103
- name = item.get("name", "")
104
-
105
- if item.get("type") == "method" and "." in name:
106
- parts = name.split(".")
107
- if len(parts) > 2:
108
- return ".".join(parts[-2:])
109
- return name
110
-
111
- # for other types, just get the last part
112
- if "." in name:
113
- return name.split(".")[-1]
114
-
115
- return name
116
-
117
- def get_skylos_results_as_list(skylos_results):
118
- """Convert Skylos JSON results to a flat list of items"""
119
- result = []
120
-
121
- for item in skylos_results.get("unused_functions", []):
122
- # try to determine if its a method or function based on name
123
- if "." in item["name"]:
124
- item_type = "method"
125
- else:
126
- item_type = "function"
127
-
128
- result.append({
129
- "name": item["name"],
130
- "type": item_type,
131
- "file": item.get("file", ""),
132
- "line": item.get("line", 0)
133
- })
134
-
135
- for item in skylos_results.get("unused_imports", []):
136
- result.append({
137
- "name": item["name"],
138
- "type": "import",
139
- "file": item.get("file", ""),
140
- "line": item.get("line", 0)
141
- })
142
-
143
- for item in skylos_results.get("unused_classes", []):
144
- result.append({
145
- "name": item["name"],
146
- "type": "class",
147
- "file": item.get("file", ""),
148
- "line": item.get("line", 0)
149
- })
150
-
151
- return result
152
-
153
- def find_test_cases(test_dir):
154
- """Find all test cases in the directory structure"""
155
- test_cases = []
156
-
157
- for path in Path(test_dir).rglob("ground_truth.json"):
158
- code_file = path.parent / "code.py"
159
- if code_file.exists():
160
- test_cases.append({
161
- "name": path.parent.name,
162
- "code_file": code_file,
163
- "ground_truth_file": path
164
- })
165
-
166
- return test_cases
167
-
168
- def compare_results(skylos_results, vulture_results, ground_truth, test_case):
169
- """Compare tool results with ground truth for a single test case"""
170
- gt_normalized = set()
171
- for item in ground_truth:
172
- if isinstance(item, dict):
173
- gt_normalized.add((normalize_item(item), item.get("type")))
174
-
175
- skylos_flat = get_skylos_results_as_list(skylos_results)
176
- skylos_normalized = set()
177
- for item in skylos_flat:
178
- skylos_normalized.add((normalize_item(item), item.get("type")))
179
-
180
- vulture_normalized = set()
181
- for item in vulture_results:
182
- vulture_normalized.add((normalize_item(item), item.get("type")))
183
-
184
- skylos_tp = skylos_normalized.intersection(gt_normalized)
185
- skylos_fp = skylos_normalized - gt_normalized
186
- skylos_fn = gt_normalized - skylos_normalized
187
-
188
- vulture_tp = vulture_normalized.intersection(gt_normalized)
189
- vulture_fp = vulture_normalized - gt_normalized
190
- vulture_fn = gt_normalized - vulture_normalized
191
-
192
- vulture_only_tp = vulture_tp - skylos_tp
193
-
194
- skylos_only_tp = skylos_tp - vulture_tp
195
-
196
- return {
197
- "name": test_case["name"],
198
- "ground_truth_count": len(gt_normalized),
199
- "skylos": {
200
- "tp": skylos_tp,
201
- "fp": skylos_fp,
202
- "fn": skylos_fn,
203
- "tp_count": len(skylos_tp),
204
- "fp_count": len(skylos_fp),
205
- "fn_count": len(skylos_fn),
206
- "precision": len(skylos_tp) / len(skylos_normalized) if skylos_normalized else 0,
207
- "recall": len(skylos_tp) / len(gt_normalized) if gt_normalized else 0,
208
- },
209
- "vulture": {
210
- "tp": vulture_tp,
211
- "fp": vulture_fp,
212
- "fn": vulture_fn,
213
- "tp_count": len(vulture_tp),
214
- "fp_count": len(vulture_fp),
215
- "fn_count": len(vulture_fn),
216
- "precision": len(vulture_tp) / len(vulture_normalized) if vulture_normalized else 0,
217
- "recall": len(vulture_tp) / len(gt_normalized) if gt_normalized else 0,
218
- },
219
- "vulture_only_tp": vulture_only_tp,
220
- "skylos_only_tp": skylos_only_tp
221
- }
222
-
223
- def print_test_result(result):
224
- """Print results for a single test case"""
225
- print(f"\n=== Test Case: {result['name']} ===")
226
- print(f"Ground Truth: {result['ground_truth_count']} items")
227
-
228
- print("\nSkylos Results:")
229
- print(f" True Positives: {result['skylos']['tp_count']}")
230
- print(f" False Positives: {result['skylos']['fp_count']}")
231
- print(f" False Negatives: {result['skylos']['fn_count']}")
232
- print(f" Precision: {result['skylos']['precision']:.4f}")
233
- print(f" Recall: {result['skylos']['recall']:.4f}")
234
-
235
- print("\nVulture Results:")
236
- print(f" True Positives: {result['vulture']['tp_count']}")
237
- print(f" False Positives: {result['vulture']['fp_count']}")
238
- print(f" False Negatives: {result['vulture']['fn_count']}")
239
- print(f" Precision: {result['vulture']['precision']:.4f}")
240
- print(f" Recall: {result['vulture']['recall']:.4f}")
241
-
242
- if result['skylos']['fp_count'] > 0:
243
- print("\nSkylos False Positives (items Skylos flags that should be used):")
244
- for item in result['skylos']['fp']:
245
- print(f" - {item[0]} ({item[1]})")
246
-
247
- if result['skylos']['fn_count'] > 0:
248
- print("\nSkylos False Negatives (items Skylos misses that should be flagged):")
249
- for item in result['skylos']['fn']:
250
- print(f" - {item[0]} ({item[1]})")
251
-
252
- if result['vulture_only_tp']:
253
- print("\nVulture-only True Positives (What Vulture catches correctly but Skylos misses):")
254
- for item in result['vulture_only_tp']:
255
- print(f" - {item[0]} ({item[1]})")
256
-
257
- if result['skylos_only_tp']:
258
- print("\nSkylos-only True Positives (What Skylos catches correctly but Vulture misses):")
259
- for item in result['skylos_only_tp']:
260
- print(f" - {item[0]} ({item[1]})")
261
-
262
- def main():
263
- if len(sys.argv) < 3:
264
- print("Usage: python analyze_test_cases.py <skylos_path> <test_cases_dir>")
265
- return
266
-
267
- skylos_path = sys.argv[1]
268
- test_dir = sys.argv[2]
269
-
270
- print(f"Finding test cases in {test_dir}...")
271
- test_cases = find_test_cases(test_dir)
272
- print(f"Found {len(test_cases)} test cases.")
273
-
274
- total_results = {
275
- "test_cases": len(test_cases),
276
- "ground_truth_total": 0,
277
- "skylos": {
278
- "tp_total": 0,
279
- "fp_total": 0,
280
- "fn_total": 0,
281
- "precision_avg": 0,
282
- "recall_avg": 0
283
- },
284
- "vulture": {
285
- "tp_total": 0,
286
- "fp_total": 0,
287
- "fn_total": 0,
288
- "precision_avg": 0,
289
- "recall_avg": 0
290
- },
291
- "problem_cases": []
292
- }
293
-
294
- test_results = []
295
- for test_case in test_cases:
296
- print(f"\nProcessing test case: {test_case['name']}...")
297
-
298
- ground_truth = load_ground_truth(test_case["ground_truth_file"])
299
- total_results["ground_truth_total"] += len(ground_truth)
300
-
301
- skylos_results = run_skylos_on_file(test_case["code_file"], skylos_path)
302
-
303
- vulture_results = run_vulture_on_file(test_case["code_file"])
304
-
305
- result = compare_results(skylos_results, vulture_results, ground_truth, test_case)
306
- test_results.append(result)
307
-
308
- total_results["skylos"]["tp_total"] += result["skylos"]["tp_count"]
309
- total_results["skylos"]["fp_total"] += result["skylos"]["fp_count"]
310
- total_results["skylos"]["fn_total"] += result["skylos"]["fn_count"]
311
- total_results["skylos"]["precision_avg"] += result["skylos"]["precision"]
312
- total_results["skylos"]["recall_avg"] += result["skylos"]["recall"]
313
-
314
- total_results["vulture"]["tp_total"] += result["vulture"]["tp_count"]
315
- total_results["vulture"]["fp_total"] += result["vulture"]["fp_count"]
316
- total_results["vulture"]["fn_total"] += result["vulture"]["fn_count"]
317
- total_results["vulture"]["precision_avg"] += result["vulture"]["precision"]
318
- total_results["vulture"]["recall_avg"] += result["vulture"]["recall"]
319
-
320
- if result["skylos"]["fp_count"] > 0 or result["skylos"]["fn_count"] > 0:
321
- total_results["problem_cases"].append({
322
- "name": test_case["name"],
323
- "fp": result["skylos"]["fp_count"],
324
- "fn": result["skylos"]["fn_count"]
325
- })
326
-
327
- print_test_result(result)
328
-
329
- if test_cases:
330
- total_results["skylos"]["precision_avg"] /= len(test_cases)
331
- total_results["skylos"]["recall_avg"] /= len(test_cases)
332
- total_results["vulture"]["precision_avg"] /= len(test_cases)
333
- total_results["vulture"]["recall_avg"] /= len(test_cases)
334
-
335
- print("\n=== OVERALL SUMMARY ===")
336
- print(f"Total Test Cases: {total_results['test_cases']}")
337
- print(f"Total Ground Truth Items: {total_results['ground_truth_total']}")
338
-
339
- print("\nSkylos Overall Results:")
340
- print(f" Total True Positives: {total_results['skylos']['tp_total']}")
341
- print(f" Total False Positives: {total_results['skylos']['fp_total']}")
342
- print(f" Total False Negatives: {total_results['skylos']['fn_total']}")
343
- print(f" Average Precision: {total_results['skylos']['precision_avg']:.4f}")
344
- print(f" Average Recall: {total_results['skylos']['recall_avg']:.4f}")
345
-
346
- print("\nVulture Overall Results:")
347
- print(f" Total True Positives: {total_results['vulture']['tp_total']}")
348
- print(f" Total False Positives: {total_results['vulture']['fp_total']}")
349
- print(f" Total False Negatives: {total_results['vulture']['fn_total']}")
350
- print(f" Average Precision: {total_results['vulture']['precision_avg']:.4f}")
351
- print(f" Average Recall: {total_results['vulture']['recall_avg']:.4f}")
352
-
353
- if total_results["problem_cases"]:
354
- print("\nProblem Test Cases (with Skylos FP or FN):")
355
- sorted_problems = sorted(total_results["problem_cases"],
356
- key=lambda x: x["fp"] + x["fn"],
357
- reverse=True)
358
- for case in sorted_problems:
359
- print(f" - {case['name']} (FP: {case['fp']}, FN: {case['fn']})")
360
-
361
- print("\nRecommendation: Fix these specific test cases to improve Skylos performance.")
362
-
363
- if __name__ == "__main__":
364
- main()
test/sample_repo/app.py DELETED
@@ -1,13 +0,0 @@
1
- import os
2
- import sys
3
- import json # Unused import
4
- import logging
5
- from .sample_repo.routes import create_app
6
-
7
- def main():
8
- """Run the application"""
9
- app = create_app()
10
- app.run()
11
-
12
- if __name__ == "__main__":
13
- main()
@@ -1,81 +0,0 @@
1
- import os
2
- import sys
3
- import argparse
4
- import csv # Unused import
5
- from functools import wraps
6
-
7
- from sample_repo import User, Product
8
- from sample_repo import get_current_time
9
-
10
- def register_commands():
11
- """Register CLI commands - used function."""
12
- parser = argparse.ArgumentParser(description='Sample CLI')
13
- subparsers = parser.add_subparsers(dest='command')
14
-
15
- user_parser = subparsers.add_parser('user', help='User commands')
16
- user_parser.add_argument('action', choices=['list', 'create'])
17
-
18
- product_parser = subparsers.add_parser('product', help='Product commands')
19
- product_parser.add_argument('action', choices=['list'])
20
-
21
- return parser
22
-
23
- def handle_user_command(args):
24
- """Handle user command - used function."""
25
- if args.action == 'list':
26
- users = [
27
- User('john', 'john@example.com'),
28
- User('alice', 'alice@example.com')
29
- ]
30
- for user in users:
31
- print(f"User: {user.username} ({user.email})")
32
- elif args.action == 'create':
33
- user = User('new_user', 'new@example.com')
34
- print(f"Created user: {user.username} ({user.email})")
35
-
36
- def handle_product_command(args):
37
- """Handle product command - used function."""
38
- if args.action == 'list':
39
- products = [
40
- Product('Laptop', 999.99),
41
- Product('Phone', 499.99)
42
- ]
43
- for product in products:
44
- print(f"Product: {product.name} - ${product.price:.2f}")
45
-
46
- def export_data(data, filename):
47
- """Export data to CSV - unused function."""
48
- with open(filename, 'w', newline='') as f:
49
- writer = csv.writer(f)
50
- writer.writerow(['id', 'name', 'value'])
51
- for item in data:
52
- writer.writerow([item['id'], item['name'], item['value']])
53
-
54
- def import_data(filename):
55
- """Import data from CSV - unused function."""
56
- data = []
57
- with open(filename, 'r', newline='') as f:
58
- reader = csv.reader(f)
59
- next(reader)
60
- for row in reader:
61
- data.append({
62
- 'id': row[0],
63
- 'name': row[1],
64
- 'value': row[2]
65
- })
66
- return data
67
-
68
- class CommandDecorator:
69
- """Decorator for CLI commands - unused class."""
70
-
71
- def __init__(self, name, help_text):
72
- self.name = name
73
- self.help_text = help_text
74
-
75
- def __call__(self, func):
76
- """Call method - unused method."""
77
- @wraps(func)
78
- def wrapper(*args, **kwargs):
79
- print(f"Executing command: {self.name}")
80
- return func(*args, **kwargs)
81
- return wrapper
@@ -1,122 +0,0 @@
1
- import datetime
2
- import uuid
3
- import hashlib # Unused import
4
- from typing import List, Dict, Optional
5
-
6
- from .utils import get_current_time
7
-
8
- class BaseModel:
9
- """Base model class with common functionality."""
10
-
11
- def __init__(self, id=None):
12
- self.id = id or str(uuid.uuid4())
13
- self.created_at = get_current_time()
14
-
15
- def to_dict(self):
16
- """Convert model to dictionary - used method."""
17
- return {
18
- 'id': self.id,
19
- 'created_at': self.created_at
20
- }
21
-
22
- def to_json(self):
23
- """Convert model to JSON - unused method."""
24
- import json
25
- return json.dumps(self.to_dict())
26
-
27
- @classmethod
28
- def from_dict(cls, data):
29
- """Create instance from dictionary - used method."""
30
- instance = cls(id=data.get('id'))
31
- if 'created_at' in data:
32
- instance.created_at = data['created_at']
33
- return instance
34
-
35
- class User(BaseModel):
36
- """User model - used class."""
37
-
38
- def __init__(self, username, email, id=None):
39
- super().__init__(id)
40
- self.username = username
41
- self.email = email
42
- self.active = True
43
-
44
- def to_dict(self):
45
- """Override to_dict to include user fields - used method."""
46
- data = super().to_dict()
47
- data.update({
48
- 'username': self.username,
49
- 'email': self.email,
50
- 'active': self.active
51
- })
52
- return data
53
-
54
- def deactivate(self):
55
- """Deactivate user - used method."""
56
- self.active = False
57
-
58
- def send_welcome_email(self):
59
- """Send welcome email - unused method."""
60
- pass
61
-
62
- class Product(BaseModel):
63
- """Product model - used class."""
64
-
65
- def __init__(self, name, price, id=None):
66
- super().__init__(id)
67
- self.name = name
68
- self.price = price
69
-
70
- def to_dict(self):
71
- """Override to_dict for product - used method."""
72
- data = super().to_dict()
73
- data.update({
74
- 'name': self.name,
75
- 'price': self.price
76
- })
77
- return data
78
-
79
- def apply_discount(self, percentage):
80
- """Apply discount to product - unused method."""
81
- self.price = self.price * (1 - percentage / 100)
82
-
83
- class Order(BaseModel):
84
- """Order model - unused class."""
85
-
86
- def __init__(self, user_id, products, id=None):
87
- super().__init__(id)
88
- self.user_id = user_id
89
- self.products = products
90
- self.status = 'pending'
91
-
92
- def to_dict(self):
93
- """Order to dict - unused method."""
94
- data = super().to_dict()
95
- data.update({
96
- 'user_id': self.user_id,
97
- 'products': self.products,
98
- 'status': self.status
99
- })
100
- return data
101
-
102
- def complete(self):
103
- """Mark order as complete - unused method."""
104
- self.status = 'completed'
105
-
106
- class Cart:
107
- """Shopping cart - unused class."""
108
-
109
- def __init__(self, user_id):
110
- self.user_id = user_id
111
- self.items = []
112
-
113
- def add_item(self, product_id, quantity=1):
114
- """Add item to cart - unused method."""
115
- self.items.append({
116
- 'product_id': product_id,
117
- 'quantity': quantity
118
- })
119
-
120
- def clear(self):
121
- """Clear cart - unused method."""
122
- self.items = []
@@ -1,89 +0,0 @@
1
- import os
2
- import json
3
- import logging
4
- from functools import wraps
5
-
6
- from sample_repo import User, Product
7
- from sample_repo import format_currency
8
-
9
- def require_auth(f):
10
- """Decorator to require authentication - used decorator."""
11
- @wraps(f)
12
- def decorated(*args, **kwargs):
13
- return f(*args, **kwargs)
14
- return decorated
15
-
16
- def rate_limit(limit):
17
- """Decorator for rate limiting - unused decorator."""
18
- def decorator(f):
19
- @wraps(f)
20
- def decorated(*args, **kwargs):
21
- return f(*args, **kwargs)
22
- return decorated
23
- return decorator
24
-
25
- class App:
26
- """Simple application class - used class."""
27
-
28
- def __init__(self):
29
- self.routes = {}
30
- self.middlewares = []
31
- self._setup_routes()
32
-
33
- def _setup_routes(self):
34
- """Set up API routes - used method."""
35
- self.routes = {
36
- '/users': self.get_users,
37
- '/users/create': self.create_user,
38
- '/products': self.get_products
39
- }
40
-
41
- def add_middleware(self, middleware):
42
- """Add middleware - unused method."""
43
- self.middlewares.append(middleware)
44
-
45
- @require_auth
46
- def get_users(self):
47
- """Get users endpoint - used method."""
48
- users = [
49
- User('john', 'john@example.com'),
50
- User('alice', 'alice@example.com')
51
- ]
52
- return [user.to_dict() for user in users]
53
-
54
- def create_user(self):
55
- """Create user endpoint - used method."""
56
- user = User('new_user', 'new@example.com')
57
- return user.to_dict()
58
-
59
- def get_products(self):
60
- """Get products endpoint - used method."""
61
- products = [
62
- Product('Laptop', 999.99),
63
- Product('Phone', 499.99)
64
- ]
65
- return [
66
- {**product.to_dict(), 'formatted_price': format_currency(product.price)}
67
- for product in products
68
- ]
69
-
70
- @rate_limit(100)
71
- def get_analytics(self):
72
- """Get analytics endpoint - unused method."""
73
- return {'visits': 1000, 'unique_users': 500}
74
-
75
- def run(self):
76
- """Run the application - used method."""
77
- print("API server running...")
78
-
79
- def create_app():
80
- """Create application - used function."""
81
- return App()
82
-
83
- def parse_config(config_path):
84
- """Parse configuration file - unused function."""
85
- if not os.path.exists(config_path):
86
- return {}
87
-
88
- with open(config_path, 'r') as f:
89
- return json.load(f)