django-cfg 1.4.10__py3-none-any.whl → 1.4.13__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.
- django_cfg/apps/agents/management/commands/create_agent.py +1 -1
- django_cfg/apps/agents/management/commands/orchestrator_status.py +3 -3
- django_cfg/apps/newsletter/serializers.py +40 -3
- django_cfg/apps/newsletter/views/campaigns.py +12 -3
- django_cfg/apps/newsletter/views/emails.py +14 -3
- django_cfg/apps/newsletter/views/subscriptions.py +12 -2
- django_cfg/apps/payments/views/api/currencies.py +49 -6
- django_cfg/apps/payments/views/api/webhooks.py +72 -7
- django_cfg/apps/payments/views/overview/serializers.py +34 -1
- django_cfg/apps/payments/views/overview/views.py +2 -1
- django_cfg/apps/payments/views/serializers/payments.py +6 -6
- django_cfg/apps/urls.py +106 -45
- django_cfg/core/base/config_model.py +2 -2
- django_cfg/core/constants.py +1 -1
- django_cfg/core/generation/integration_generators/__init__.py +1 -1
- django_cfg/core/generation/integration_generators/api.py +73 -49
- django_cfg/core/integration/display/startup.py +30 -22
- django_cfg/core/integration/url_integration.py +15 -16
- django_cfg/management/commands/check_endpoints.py +11 -160
- django_cfg/management/commands/check_settings.py +13 -348
- django_cfg/management/commands/clear_constance.py +13 -201
- django_cfg/management/commands/create_token.py +13 -321
- django_cfg/management/commands/generate_clients.py +23 -0
- django_cfg/management/commands/list_urls.py +13 -306
- django_cfg/management/commands/migrate_all.py +13 -126
- django_cfg/management/commands/migrator.py +13 -396
- django_cfg/management/commands/rundramatiq.py +15 -247
- django_cfg/management/commands/rundramatiq_simulator.py +12 -429
- django_cfg/management/commands/runserver_ngrok.py +15 -160
- django_cfg/management/commands/script.py +12 -488
- django_cfg/management/commands/show_config.py +12 -215
- django_cfg/management/commands/show_urls.py +12 -342
- django_cfg/management/commands/superuser.py +15 -295
- django_cfg/management/commands/task_clear.py +14 -217
- django_cfg/management/commands/task_status.py +13 -248
- django_cfg/management/commands/test_email.py +15 -86
- django_cfg/management/commands/test_telegram.py +14 -61
- django_cfg/management/commands/test_twilio.py +15 -105
- django_cfg/management/commands/tree.py +13 -383
- django_cfg/management/commands/validate_openapi.py +10 -0
- django_cfg/middleware/README.md +1 -1
- django_cfg/middleware/user_activity.py +3 -3
- django_cfg/models/__init__.py +2 -2
- django_cfg/models/api/drf/spectacular.py +6 -6
- django_cfg/models/django/__init__.py +2 -2
- django_cfg/models/django/openapi.py +162 -0
- django_cfg/modules/django_admin/management/commands/check_endpoints.py +169 -0
- django_cfg/modules/django_admin/management/commands/check_settings.py +355 -0
- django_cfg/modules/django_admin/management/commands/clear_constance.py +208 -0
- django_cfg/modules/django_admin/management/commands/create_token.py +328 -0
- django_cfg/modules/django_admin/management/commands/list_urls.py +313 -0
- django_cfg/modules/django_admin/management/commands/migrate_all.py +133 -0
- django_cfg/modules/django_admin/management/commands/migrator.py +403 -0
- django_cfg/modules/django_admin/management/commands/script.py +496 -0
- django_cfg/modules/django_admin/management/commands/show_config.py +225 -0
- django_cfg/modules/django_admin/management/commands/show_urls.py +361 -0
- django_cfg/modules/django_admin/management/commands/superuser.py +302 -0
- django_cfg/modules/django_admin/management/commands/tree.py +390 -0
- django_cfg/modules/django_client/__init__.py +20 -0
- django_cfg/modules/django_client/apps.py +35 -0
- django_cfg/modules/django_client/core/__init__.py +56 -0
- django_cfg/modules/django_client/core/archive/__init__.py +11 -0
- django_cfg/modules/django_client/core/archive/manager.py +134 -0
- django_cfg/modules/django_client/core/cli/__init__.py +12 -0
- django_cfg/modules/django_client/core/cli/main.py +235 -0
- django_cfg/modules/django_client/core/config/__init__.py +18 -0
- django_cfg/modules/django_client/core/config/config.py +208 -0
- django_cfg/modules/django_client/core/config/group.py +101 -0
- django_cfg/modules/django_client/core/config/service.py +209 -0
- django_cfg/modules/django_client/core/generator/__init__.py +115 -0
- django_cfg/modules/django_client/core/generator/base.py +838 -0
- django_cfg/modules/django_client/core/generator/python/__init__.py +16 -0
- django_cfg/modules/django_client/core/generator/python/async_client_gen.py +174 -0
- django_cfg/modules/django_client/core/generator/python/files_generator.py +180 -0
- django_cfg/modules/django_client/core/generator/python/generator.py +182 -0
- django_cfg/modules/django_client/core/generator/python/models_generator.py +318 -0
- django_cfg/modules/django_client/core/generator/python/operations_generator.py +278 -0
- django_cfg/modules/django_client/core/generator/python/sync_client_gen.py +102 -0
- django_cfg/modules/django_client/core/generator/python/templates/__init__.py.jinja +9 -0
- django_cfg/modules/django_client/core/generator/python/templates/api_wrapper.py.jinja +153 -0
- django_cfg/modules/django_client/core/generator/python/templates/app_init.py.jinja +6 -0
- django_cfg/modules/django_client/core/generator/python/templates/client/app_client.py.jinja +18 -0
- django_cfg/modules/django_client/core/generator/python/templates/client/flat_client.py.jinja +38 -0
- django_cfg/modules/django_client/core/generator/python/templates/client/main_client.py.jinja +68 -0
- django_cfg/modules/django_client/core/generator/python/templates/client/main_client_file.py.jinja +14 -0
- django_cfg/modules/django_client/core/generator/python/templates/client/operation_method.py.jinja +9 -0
- django_cfg/modules/django_client/core/generator/python/templates/client/sub_client.py.jinja +18 -0
- django_cfg/modules/django_client/core/generator/python/templates/client/sync_main_client.py.jinja +50 -0
- django_cfg/modules/django_client/core/generator/python/templates/client/sync_operation_method.py.jinja +9 -0
- django_cfg/modules/django_client/core/generator/python/templates/client/sync_sub_client.py.jinja +18 -0
- django_cfg/modules/django_client/core/generator/python/templates/client_file.py.jinja +13 -0
- django_cfg/modules/django_client/core/generator/python/templates/main_init.py.jinja +52 -0
- django_cfg/modules/django_client/core/generator/python/templates/models/app_models.py.jinja +17 -0
- django_cfg/modules/django_client/core/generator/python/templates/models/enum_class.py.jinja +17 -0
- django_cfg/modules/django_client/core/generator/python/templates/models/enums.py.jinja +8 -0
- django_cfg/modules/django_client/core/generator/python/templates/models/models.py.jinja +17 -0
- django_cfg/modules/django_client/core/generator/python/templates/models/schema_class.py.jinja +21 -0
- django_cfg/modules/django_client/core/generator/python/templates/pyproject.toml.jinja +55 -0
- django_cfg/modules/django_client/core/generator/python/templates/utils/logger.py.jinja +255 -0
- django_cfg/modules/django_client/core/generator/python/templates/utils/retry.py.jinja +271 -0
- django_cfg/modules/django_client/core/generator/python/templates/utils/schema.py.jinja +12 -0
- django_cfg/modules/django_client/core/generator/typescript/__init__.py +14 -0
- django_cfg/modules/django_client/core/generator/typescript/client_generator.py +165 -0
- django_cfg/modules/django_client/core/generator/typescript/fetchers_generator.py +428 -0
- django_cfg/modules/django_client/core/generator/typescript/files_generator.py +207 -0
- django_cfg/modules/django_client/core/generator/typescript/generator.py +432 -0
- django_cfg/modules/django_client/core/generator/typescript/hooks_generator.py +536 -0
- django_cfg/modules/django_client/core/generator/typescript/models_generator.py +245 -0
- django_cfg/modules/django_client/core/generator/typescript/operations_generator.py +298 -0
- django_cfg/modules/django_client/core/generator/typescript/schemas_generator.py +329 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/api_instance.ts.jinja +131 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/app_index.ts.jinja +2 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/client/app_client.ts.jinja +18 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/client/client.ts.jinja +403 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/client/flat_client.ts.jinja +109 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/client/main_client_file.ts.jinja +10 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/client/operation.ts.jinja +61 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/client/sub_client.ts.jinja +15 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/client_file.ts.jinja +9 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/fetchers.ts.jinja +45 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/index.ts.jinja +30 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/index.ts.jinja +5 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/main_index.ts.jinja +268 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/models/app_models.ts.jinja +8 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/models/enums.ts.jinja +4 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/models/models.ts.jinja +8 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/package.json.jinja +52 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/schemas/index.ts.jinja +21 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/schemas/schema.ts.jinja +24 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/tsconfig.json.jinja +20 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/utils/errors.ts.jinja +116 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/utils/http.ts.jinja +98 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/utils/logger.ts.jinja +259 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/utils/retry.ts.jinja +175 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/utils/schema.ts.jinja +7 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/utils/storage.ts.jinja +158 -0
- django_cfg/modules/django_client/core/groups/__init__.py +13 -0
- django_cfg/modules/django_client/core/groups/detector.py +178 -0
- django_cfg/modules/django_client/core/groups/manager.py +314 -0
- django_cfg/modules/django_client/core/ir/__init__.py +57 -0
- django_cfg/modules/django_client/core/ir/context.py +387 -0
- django_cfg/modules/django_client/core/ir/operation.py +518 -0
- django_cfg/modules/django_client/core/ir/schema.py +353 -0
- django_cfg/modules/django_client/core/parser/__init__.py +74 -0
- django_cfg/modules/django_client/core/parser/base.py +648 -0
- django_cfg/modules/django_client/core/parser/models/__init__.py +74 -0
- django_cfg/modules/django_client/core/parser/models/base.py +212 -0
- django_cfg/modules/django_client/core/parser/models/components.py +160 -0
- django_cfg/modules/django_client/core/parser/models/openapi.py +203 -0
- django_cfg/modules/django_client/core/parser/models/operation.py +207 -0
- django_cfg/modules/django_client/core/parser/models/schema.py +266 -0
- django_cfg/modules/django_client/core/parser/openapi30.py +56 -0
- django_cfg/modules/django_client/core/parser/openapi31.py +64 -0
- django_cfg/modules/django_client/core/validation/__init__.py +22 -0
- django_cfg/modules/django_client/core/validation/checker.py +134 -0
- django_cfg/modules/django_client/core/validation/fixer.py +216 -0
- django_cfg/modules/django_client/core/validation/reporter.py +480 -0
- django_cfg/modules/django_client/core/validation/rules/__init__.py +11 -0
- django_cfg/modules/django_client/core/validation/rules/base.py +96 -0
- django_cfg/modules/django_client/core/validation/rules/type_hints.py +288 -0
- django_cfg/modules/django_client/core/validation/safety.py +266 -0
- django_cfg/modules/django_client/management/__init__.py +3 -0
- django_cfg/modules/django_client/management/commands/__init__.py +3 -0
- django_cfg/modules/django_client/management/commands/generate_client.py +427 -0
- django_cfg/modules/django_client/management/commands/validate_openapi.py +343 -0
- django_cfg/modules/django_client/pytest.ini +30 -0
- django_cfg/modules/django_client/spectacular/__init__.py +10 -0
- django_cfg/modules/django_client/spectacular/async_detection.py +187 -0
- django_cfg/modules/django_client/spectacular/enum_naming.py +192 -0
- django_cfg/modules/django_client/urls.py +72 -0
- django_cfg/{dashboard → modules/django_dashboard}/DEBUG_README.md +2 -2
- django_cfg/{dashboard → modules/django_dashboard}/REFACTORING_SUMMARY.md +1 -1
- django_cfg/modules/django_dashboard/management/__init__.py +0 -0
- django_cfg/modules/django_dashboard/management/commands/__init__.py +0 -0
- django_cfg/{dashboard → modules/django_dashboard}/management/commands/debug_dashboard.py +5 -5
- django_cfg/modules/django_dashboard/sections/documentation.py +391 -0
- django_cfg/modules/django_email/management/__init__.py +0 -0
- django_cfg/modules/django_email/management/commands/__init__.py +0 -0
- django_cfg/modules/django_email/management/commands/test_email.py +93 -0
- django_cfg/modules/django_logging/LOGGING_GUIDE.md +1 -1
- django_cfg/modules/django_logging/django_logger.py +6 -6
- django_cfg/modules/django_ngrok/management/__init__.py +0 -0
- django_cfg/modules/django_ngrok/management/commands/__init__.py +0 -0
- django_cfg/modules/django_ngrok/management/commands/runserver_ngrok.py +167 -0
- django_cfg/modules/django_tasks/management/__init__.py +0 -0
- django_cfg/modules/django_tasks/management/commands/__init__.py +0 -0
- django_cfg/modules/django_tasks/management/commands/rundramatiq.py +254 -0
- django_cfg/modules/django_tasks/management/commands/rundramatiq_simulator.py +437 -0
- django_cfg/modules/django_tasks/management/commands/task_clear.py +226 -0
- django_cfg/modules/django_tasks/management/commands/task_status.py +257 -0
- django_cfg/modules/django_telegram/management/__init__.py +0 -0
- django_cfg/modules/django_telegram/management/commands/__init__.py +0 -0
- django_cfg/modules/django_telegram/management/commands/test_telegram.py +68 -0
- django_cfg/modules/django_twilio/management/__init__.py +0 -0
- django_cfg/modules/django_twilio/management/commands/__init__.py +0 -0
- django_cfg/modules/django_twilio/management/commands/test_twilio.py +112 -0
- django_cfg/modules/django_unfold/callbacks/main.py +21 -10
- django_cfg/modules/django_unfold/callbacks/revolution.py +41 -36
- django_cfg/pyproject.toml +2 -6
- django_cfg/registry/third_party.py +5 -7
- django_cfg/routing/callbacks.py +1 -1
- django_cfg/static/admin/css/prose-unfold.css +666 -0
- django_cfg/templates/admin/index.html +8 -0
- django_cfg/templates/admin/index_new.html +13 -0
- django_cfg/templates/admin/layouts/dashboard_with_tabs.html +15 -3
- django_cfg/templates/admin/sections/documentation_section.html +172 -0
- django_cfg/templates/admin/snippets/tabs/documentation_tab.html +231 -0
- {django_cfg-1.4.10.dist-info → django_cfg-1.4.13.dist-info}/METADATA +2 -2
- {django_cfg-1.4.10.dist-info → django_cfg-1.4.13.dist-info}/RECORD +224 -74
- django_cfg/management/commands/generate.py +0 -107
- /django_cfg/models/django/{revolution.py → revolution_legacy.py} +0 -0
- /django_cfg/{dashboard → modules/django_admin}/management/__init__.py +0 -0
- /django_cfg/{dashboard → modules/django_admin}/management/commands/__init__.py +0 -0
- /django_cfg/{dashboard → modules/django_dashboard}/__init__.py +0 -0
- /django_cfg/{dashboard → modules/django_dashboard}/components.py +0 -0
- /django_cfg/{dashboard → modules/django_dashboard}/debug.py +0 -0
- /django_cfg/{dashboard → modules/django_dashboard}/sections/__init__.py +0 -0
- /django_cfg/{dashboard → modules/django_dashboard}/sections/base.py +0 -0
- /django_cfg/{dashboard → modules/django_dashboard}/sections/commands.py +0 -0
- /django_cfg/{dashboard → modules/django_dashboard}/sections/overview.py +0 -0
- /django_cfg/{dashboard → modules/django_dashboard}/sections/stats.py +0 -0
- /django_cfg/{dashboard → modules/django_dashboard}/sections/system.py +0 -0
- {django_cfg-1.4.10.dist-info → django_cfg-1.4.13.dist-info}/WHEEL +0 -0
- {django_cfg-1.4.10.dist-info → django_cfg-1.4.13.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.4.10.dist-info → django_cfg-1.4.13.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,134 @@
|
|
1
|
+
"""Validation checker orchestrating all rules."""
|
2
|
+
|
3
|
+
from pathlib import Path
|
4
|
+
from typing import List, Optional
|
5
|
+
|
6
|
+
from .rules import Issue, ValidationRule
|
7
|
+
from .rules.type_hints import TypeHintRule
|
8
|
+
|
9
|
+
|
10
|
+
class ValidationChecker:
|
11
|
+
"""
|
12
|
+
Checks code against all validation rules.
|
13
|
+
|
14
|
+
Example:
|
15
|
+
>>> checker = ValidationChecker()
|
16
|
+
>>> issues = checker.check_directory(Path('apps/'))
|
17
|
+
>>> for issue in issues:
|
18
|
+
... print(issue)
|
19
|
+
"""
|
20
|
+
|
21
|
+
def __init__(self, rules: Optional[List[ValidationRule]] = None):
|
22
|
+
"""
|
23
|
+
Initialize checker.
|
24
|
+
|
25
|
+
Args:
|
26
|
+
rules: List of validation rules. If None, uses default rules.
|
27
|
+
"""
|
28
|
+
if rules is None:
|
29
|
+
# Default rules
|
30
|
+
self.rules = [
|
31
|
+
TypeHintRule(),
|
32
|
+
# Future: ResponseSchemaRule(),
|
33
|
+
# Future: EnumConflictRule(),
|
34
|
+
]
|
35
|
+
else:
|
36
|
+
self.rules = rules
|
37
|
+
|
38
|
+
def check_file(self, file_path: Path) -> List[Issue]:
|
39
|
+
"""
|
40
|
+
Check single file against all rules.
|
41
|
+
|
42
|
+
Args:
|
43
|
+
file_path: Path to Python file
|
44
|
+
|
45
|
+
Returns:
|
46
|
+
List of issues found
|
47
|
+
"""
|
48
|
+
all_issues = []
|
49
|
+
|
50
|
+
for rule in self.rules:
|
51
|
+
try:
|
52
|
+
issues = rule.check(file_path)
|
53
|
+
all_issues.extend(issues)
|
54
|
+
except Exception as e:
|
55
|
+
print(f"Error in rule {rule.rule_id} for {file_path}: {e}")
|
56
|
+
|
57
|
+
return all_issues
|
58
|
+
|
59
|
+
def check_directory(
|
60
|
+
self,
|
61
|
+
directory: Path,
|
62
|
+
pattern: str = "*serializers.py",
|
63
|
+
recursive: bool = True
|
64
|
+
) -> List[Issue]:
|
65
|
+
"""
|
66
|
+
Check all matching files in directory.
|
67
|
+
|
68
|
+
Args:
|
69
|
+
directory: Directory to search
|
70
|
+
pattern: File pattern to match (default: *serializers.py)
|
71
|
+
recursive: If True, search recursively
|
72
|
+
|
73
|
+
Returns:
|
74
|
+
List of all issues found
|
75
|
+
"""
|
76
|
+
all_issues = []
|
77
|
+
|
78
|
+
if recursive:
|
79
|
+
files = directory.rglob(pattern)
|
80
|
+
else:
|
81
|
+
files = directory.glob(pattern)
|
82
|
+
|
83
|
+
for file_path in files:
|
84
|
+
if file_path.is_file():
|
85
|
+
issues = self.check_file(file_path)
|
86
|
+
all_issues.extend(issues)
|
87
|
+
|
88
|
+
return all_issues
|
89
|
+
|
90
|
+
def check_files(self, file_paths: List[Path]) -> List[Issue]:
|
91
|
+
"""
|
92
|
+
Check specific list of files.
|
93
|
+
|
94
|
+
Args:
|
95
|
+
file_paths: List of file paths to check
|
96
|
+
|
97
|
+
Returns:
|
98
|
+
List of all issues found
|
99
|
+
"""
|
100
|
+
all_issues = []
|
101
|
+
|
102
|
+
for file_path in file_paths:
|
103
|
+
if file_path.is_file():
|
104
|
+
issues = self.check_file(file_path)
|
105
|
+
all_issues.extend(issues)
|
106
|
+
|
107
|
+
return all_issues
|
108
|
+
|
109
|
+
def get_fixable_issues(self, issues: List[Issue]) -> List[Issue]:
|
110
|
+
"""
|
111
|
+
Filter issues to only auto-fixable ones.
|
112
|
+
|
113
|
+
Args:
|
114
|
+
issues: List of issues
|
115
|
+
|
116
|
+
Returns:
|
117
|
+
List of auto-fixable issues
|
118
|
+
"""
|
119
|
+
fixable = []
|
120
|
+
|
121
|
+
for issue in issues:
|
122
|
+
# Find rule for this issue
|
123
|
+
rule = self._get_rule(issue.rule_id)
|
124
|
+
if rule and rule.can_fix(issue):
|
125
|
+
fixable.append(issue)
|
126
|
+
|
127
|
+
return fixable
|
128
|
+
|
129
|
+
def _get_rule(self, rule_id: str) -> Optional[ValidationRule]:
|
130
|
+
"""Get rule by ID."""
|
131
|
+
for rule in self.rules:
|
132
|
+
if rule.rule_id == rule_id:
|
133
|
+
return rule
|
134
|
+
return None
|
@@ -0,0 +1,216 @@
|
|
1
|
+
"""Safe auto-fixer with rollback support."""
|
2
|
+
|
3
|
+
from pathlib import Path
|
4
|
+
from typing import Dict, List, Any
|
5
|
+
|
6
|
+
from .rules import Issue
|
7
|
+
from .rules.type_hints import TypeHintRule
|
8
|
+
from .safety import SafetyManager
|
9
|
+
|
10
|
+
|
11
|
+
class SafeFixer:
|
12
|
+
"""
|
13
|
+
Safely apply fixes to code with backup/rollback.
|
14
|
+
|
15
|
+
Example:
|
16
|
+
>>> safety = SafetyManager(workspace=Path('.'))
|
17
|
+
>>> fixer = SafeFixer(safety)
|
18
|
+
>>> results = fixer.fix_issues(issues, dry_run=False)
|
19
|
+
>>> print(f"Fixed: {results['fixed']}")
|
20
|
+
"""
|
21
|
+
|
22
|
+
def __init__(self, safety_manager: SafetyManager):
|
23
|
+
"""
|
24
|
+
Initialize fixer.
|
25
|
+
|
26
|
+
Args:
|
27
|
+
safety_manager: SafetyManager instance for backups/rollbacks
|
28
|
+
"""
|
29
|
+
self.safety = safety_manager
|
30
|
+
self.rules = {
|
31
|
+
'type-hint-001': TypeHintRule(),
|
32
|
+
# Future rules here
|
33
|
+
}
|
34
|
+
|
35
|
+
def fix_issues(
|
36
|
+
self,
|
37
|
+
issues: List[Issue],
|
38
|
+
dry_run: bool = True,
|
39
|
+
confirm: bool = True,
|
40
|
+
verbose: bool = False
|
41
|
+
) -> Dict[str, Any]:
|
42
|
+
"""
|
43
|
+
Fix issues with safety guarantees.
|
44
|
+
|
45
|
+
Args:
|
46
|
+
issues: List of issues to fix
|
47
|
+
dry_run: If True, only show what would be fixed
|
48
|
+
confirm: If True, ask for user confirmation
|
49
|
+
verbose: If True, show detailed progress
|
50
|
+
|
51
|
+
Returns:
|
52
|
+
Dict with results: {
|
53
|
+
'fixed': int,
|
54
|
+
'failed': int,
|
55
|
+
'skipped': int,
|
56
|
+
'errors': List[str]
|
57
|
+
}
|
58
|
+
"""
|
59
|
+
results = {
|
60
|
+
'fixed': 0,
|
61
|
+
'failed': 0,
|
62
|
+
'skipped': 0,
|
63
|
+
'errors': []
|
64
|
+
}
|
65
|
+
|
66
|
+
# Filter auto-fixable issues
|
67
|
+
fixable_issues = [i for i in issues if self._can_fix(i)]
|
68
|
+
|
69
|
+
if not fixable_issues:
|
70
|
+
if verbose:
|
71
|
+
print("ℹ️ No auto-fixable issues found")
|
72
|
+
return results
|
73
|
+
|
74
|
+
# Dry run: just show what would be fixed
|
75
|
+
if dry_run:
|
76
|
+
print(f"\n🔍 Dry run: {len(fixable_issues)} issue(s) can be fixed\n")
|
77
|
+
for issue in fixable_issues:
|
78
|
+
print(f" {issue.file.name}:{issue.line}")
|
79
|
+
print(f" {issue.message}")
|
80
|
+
print(f" Fix: {issue.suggestion}\n")
|
81
|
+
results['skipped'] = len(fixable_issues)
|
82
|
+
return results
|
83
|
+
|
84
|
+
# Ask for confirmation
|
85
|
+
if confirm:
|
86
|
+
if not self._confirm_fixes(fixable_issues):
|
87
|
+
results['skipped'] = len(fixable_issues)
|
88
|
+
return results
|
89
|
+
|
90
|
+
# Start transaction
|
91
|
+
transaction_id = self.safety.start_transaction()
|
92
|
+
print(f"\n🔧 Starting fixes (transaction: {transaction_id})\n")
|
93
|
+
|
94
|
+
try:
|
95
|
+
# Group by file
|
96
|
+
by_file = {}
|
97
|
+
for issue in fixable_issues:
|
98
|
+
by_file.setdefault(issue.file, []).append(issue)
|
99
|
+
|
100
|
+
# Fix each file
|
101
|
+
for file_path, file_issues in by_file.items():
|
102
|
+
if verbose:
|
103
|
+
print(f"📝 Fixing {file_path.name} ({len(file_issues)} issue(s))...")
|
104
|
+
|
105
|
+
# Backup
|
106
|
+
self.safety.backup_file(file_path)
|
107
|
+
|
108
|
+
# Group issues by rule_id for batch processing
|
109
|
+
issues_by_rule = {}
|
110
|
+
for issue in file_issues:
|
111
|
+
issues_by_rule.setdefault(issue.rule_id, []).append(issue)
|
112
|
+
|
113
|
+
# Apply fixes by rule
|
114
|
+
file_success = 0
|
115
|
+
for rule_id, rule_issues in issues_by_rule.items():
|
116
|
+
rule = self.rules.get(rule_id)
|
117
|
+
if not rule:
|
118
|
+
results['failed'] += len(rule_issues)
|
119
|
+
continue
|
120
|
+
|
121
|
+
# Use batch fix if available (for better handling of imports)
|
122
|
+
if hasattr(rule, 'fix_batch') and len(rule_issues) > 1:
|
123
|
+
try:
|
124
|
+
if rule.fix_batch(rule_issues):
|
125
|
+
file_success += len(rule_issues)
|
126
|
+
if verbose:
|
127
|
+
for issue in rule_issues:
|
128
|
+
print(f" ✓ {issue.message}")
|
129
|
+
else:
|
130
|
+
results['failed'] += len(rule_issues)
|
131
|
+
except Exception as e:
|
132
|
+
if verbose:
|
133
|
+
print(f" ✗ Batch fix failed: {e}")
|
134
|
+
results['failed'] += len(rule_issues)
|
135
|
+
else:
|
136
|
+
# Fall back to individual fixes
|
137
|
+
for issue in rule_issues:
|
138
|
+
if self._fix_issue(issue, verbose):
|
139
|
+
file_success += 1
|
140
|
+
else:
|
141
|
+
results['failed'] += 1
|
142
|
+
|
143
|
+
# Validate syntax
|
144
|
+
if file_success > 0:
|
145
|
+
if self.safety.validate_syntax(file_path):
|
146
|
+
results['fixed'] += file_success
|
147
|
+
if verbose:
|
148
|
+
print(f" ✅ Fixed {file_success} issue(s)")
|
149
|
+
else:
|
150
|
+
# Rollback this file
|
151
|
+
self.safety.rollback_file(file_path)
|
152
|
+
results['failed'] += file_success
|
153
|
+
results['errors'].append(f"Syntax error after fixing {file_path.name}")
|
154
|
+
print(f" ❌ Syntax error - rolled back {file_path.name}")
|
155
|
+
|
156
|
+
# Commit if successful
|
157
|
+
if results['failed'] == 0:
|
158
|
+
self.safety.commit_transaction()
|
159
|
+
print(f"\n✅ Successfully fixed {results['fixed']} issue(s)")
|
160
|
+
else:
|
161
|
+
self.safety.rollback_transaction()
|
162
|
+
print(f"\n⚠️ Rolled back due to {results['failed']} failure(s)")
|
163
|
+
|
164
|
+
except Exception as e:
|
165
|
+
# Rollback on any error
|
166
|
+
self.safety.rollback_transaction()
|
167
|
+
results['errors'].append(str(e))
|
168
|
+
print(f"\n❌ Error: {e}")
|
169
|
+
print(" Rolled back all changes")
|
170
|
+
|
171
|
+
return results
|
172
|
+
|
173
|
+
def _can_fix(self, issue: Issue) -> bool:
|
174
|
+
"""Check if issue can be auto-fixed."""
|
175
|
+
rule = self.rules.get(issue.rule_id)
|
176
|
+
return rule and rule.can_fix(issue)
|
177
|
+
|
178
|
+
def _fix_issue(self, issue: Issue, verbose: bool = False) -> bool:
|
179
|
+
"""Fix a single issue."""
|
180
|
+
rule = self.rules.get(issue.rule_id)
|
181
|
+
if not rule:
|
182
|
+
return False
|
183
|
+
|
184
|
+
try:
|
185
|
+
success = rule.fix(issue)
|
186
|
+
if verbose and success:
|
187
|
+
print(f" ✓ {issue.message}")
|
188
|
+
return success
|
189
|
+
except Exception as e:
|
190
|
+
if verbose:
|
191
|
+
print(f" ✗ {issue.message}: {e}")
|
192
|
+
return False
|
193
|
+
|
194
|
+
def _confirm_fixes(self, issues: List[Issue]) -> bool:
|
195
|
+
"""Ask user to confirm fixes."""
|
196
|
+
print(f"\n🔧 Ready to fix {len(issues)} issue(s):\n")
|
197
|
+
|
198
|
+
# Group by file
|
199
|
+
by_file = {}
|
200
|
+
for issue in issues:
|
201
|
+
by_file.setdefault(issue.file, []).append(issue)
|
202
|
+
|
203
|
+
# Show summary
|
204
|
+
for file_path, file_issues in list(by_file.items())[:5]:
|
205
|
+
print(f" 📝 {file_path.name}: {len(file_issues)} issue(s)")
|
206
|
+
for issue in file_issues[:2]:
|
207
|
+
print(f" - {issue.message}")
|
208
|
+
if len(file_issues) > 2:
|
209
|
+
print(f" ... and {len(file_issues) - 2} more")
|
210
|
+
|
211
|
+
if len(by_file) > 5:
|
212
|
+
print(f" ... and {len(by_file) - 5} more file(s)")
|
213
|
+
|
214
|
+
print()
|
215
|
+
response = input("Apply these fixes? [y/N]: ")
|
216
|
+
return response.lower() in ['y', 'yes']
|