kailash 0.9.15__py3-none-any.whl → 0.9.16__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 (30) hide show
  1. kailash/middleware/database/base_models.py +7 -1
  2. kailash/migration/__init__.py +30 -0
  3. kailash/migration/cli.py +340 -0
  4. kailash/migration/compatibility_checker.py +662 -0
  5. kailash/migration/configuration_validator.py +837 -0
  6. kailash/migration/documentation_generator.py +1828 -0
  7. kailash/migration/examples/__init__.py +5 -0
  8. kailash/migration/examples/complete_migration_example.py +692 -0
  9. kailash/migration/migration_assistant.py +715 -0
  10. kailash/migration/performance_comparator.py +760 -0
  11. kailash/migration/regression_detector.py +1141 -0
  12. kailash/migration/tests/__init__.py +6 -0
  13. kailash/migration/tests/test_compatibility_checker.py +403 -0
  14. kailash/migration/tests/test_integration.py +463 -0
  15. kailash/migration/tests/test_migration_assistant.py +397 -0
  16. kailash/migration/tests/test_performance_comparator.py +433 -0
  17. kailash/nodes/data/async_sql.py +1507 -6
  18. kailash/runtime/local.py +1255 -8
  19. kailash/runtime/monitoring/__init__.py +1 -0
  20. kailash/runtime/monitoring/runtime_monitor.py +780 -0
  21. kailash/runtime/resource_manager.py +3033 -0
  22. kailash/sdk_exceptions.py +21 -0
  23. kailash/workflow/cyclic_runner.py +18 -2
  24. {kailash-0.9.15.dist-info → kailash-0.9.16.dist-info}/METADATA +1 -1
  25. {kailash-0.9.15.dist-info → kailash-0.9.16.dist-info}/RECORD +30 -12
  26. {kailash-0.9.15.dist-info → kailash-0.9.16.dist-info}/WHEEL +0 -0
  27. {kailash-0.9.15.dist-info → kailash-0.9.16.dist-info}/entry_points.txt +0 -0
  28. {kailash-0.9.15.dist-info → kailash-0.9.16.dist-info}/licenses/LICENSE +0 -0
  29. {kailash-0.9.15.dist-info → kailash-0.9.16.dist-info}/licenses/NOTICE +0 -0
  30. {kailash-0.9.15.dist-info → kailash-0.9.16.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,397 @@
1
+ """Tests for the MigrationAssistant class."""
2
+
3
+ import tempfile
4
+ import textwrap
5
+ from pathlib import Path
6
+
7
+ import pytest
8
+
9
+ from kailash.migration.compatibility_checker import CompatibilityChecker
10
+ from kailash.migration.migration_assistant import (
11
+ MigrationAssistant,
12
+ MigrationPlan,
13
+ MigrationResult,
14
+ MigrationStep,
15
+ )
16
+
17
+
18
+ @pytest.fixture
19
+ def assistant():
20
+ """Create a MigrationAssistant instance for testing."""
21
+ return MigrationAssistant(dry_run=True, create_backups=False)
22
+
23
+
24
+ @pytest.fixture
25
+ def temp_project_dir():
26
+ """Create a temporary project directory with test files."""
27
+ with tempfile.TemporaryDirectory() as temp_dir:
28
+ temp_path = Path(temp_dir)
29
+
30
+ # Create test files with migration issues
31
+ (temp_path / "legacy_runtime.py").write_text(
32
+ textwrap.dedent(
33
+ """
34
+ from kailash.runtime.local import LocalRuntime
35
+
36
+ # Legacy configuration that needs migration
37
+ runtime = LocalRuntime(
38
+ enable_parallel=True,
39
+ thread_pool_size=8,
40
+ debug_mode=True,
41
+ memory_limit=2048
42
+ )
43
+
44
+ # Legacy method usage
45
+ workflow = build_workflow()
46
+ runtime.execute_sync(workflow)
47
+ results = runtime.get_results()
48
+ """
49
+ ).strip()
50
+ )
51
+
52
+ (temp_path / "config_file.py").write_text(
53
+ textwrap.dedent(
54
+ """
55
+ # Configuration dictionary usage
56
+ RUNTIME_CONFIG = {
57
+ 'timeout': 300,
58
+ 'retry_count': 3,
59
+ 'log_level': 'INFO'
60
+ }
61
+
62
+ from kailash.runtime.local import LocalRuntime
63
+ runtime = LocalRuntime(**RUNTIME_CONFIG)
64
+ """
65
+ ).strip()
66
+ )
67
+
68
+ (temp_path / "modern_usage.py").write_text(
69
+ textwrap.dedent(
70
+ """
71
+ from kailash.runtime.local import LocalRuntime
72
+
73
+ # Already modern - should not need changes
74
+ runtime = LocalRuntime(
75
+ debug=True,
76
+ max_concurrency=10,
77
+ enable_monitoring=True
78
+ )
79
+
80
+ results, run_id = runtime.execute(workflow)
81
+ """
82
+ ).strip()
83
+ )
84
+
85
+ yield temp_path
86
+
87
+
88
+ class TestMigrationAssistant:
89
+ """Test cases for MigrationAssistant."""
90
+
91
+ def test_initialization(self, assistant):
92
+ """Test MigrationAssistant initialization."""
93
+ assert assistant is not None
94
+ assert assistant.dry_run is True
95
+ assert assistant.create_backups is False
96
+ assert isinstance(assistant.compatibility_checker, CompatibilityChecker)
97
+ assert isinstance(assistant.parameter_mappings, dict)
98
+ assert isinstance(assistant.value_transformations, dict)
99
+ assert isinstance(assistant.method_migrations, dict)
100
+
101
+ def test_create_migration_plan(self, assistant, temp_project_dir):
102
+ """Test migration plan creation."""
103
+ plan = assistant.create_migration_plan(temp_project_dir)
104
+
105
+ assert isinstance(plan, MigrationPlan)
106
+ assert len(plan.steps) > 0
107
+ assert plan.estimated_duration_minutes > 0
108
+ assert plan.risk_level in ["low", "medium", "high"]
109
+ assert isinstance(plan.prerequisites, list)
110
+ assert isinstance(plan.post_migration_tests, list)
111
+
112
+ def test_migration_step_creation(self, assistant, temp_project_dir):
113
+ """Test creation of individual migration steps."""
114
+ plan = assistant.create_migration_plan(temp_project_dir)
115
+
116
+ # Should have steps for files with issues
117
+ step_files = {step.file_path for step in plan.steps}
118
+ assert any("legacy_runtime.py" in path for path in step_files)
119
+
120
+ # Check step properties
121
+ for step in plan.steps:
122
+ assert isinstance(step, MigrationStep)
123
+ assert step.step_id
124
+ assert step.description
125
+ assert step.file_path
126
+ assert step.original_code
127
+ assert step.migrated_code
128
+ assert isinstance(step.automated, bool)
129
+ assert isinstance(step.validation_required, bool)
130
+
131
+ def test_parameter_transformation_parallel(self, assistant):
132
+ """Test transformation of enable_parallel to max_concurrency."""
133
+ content = "runtime = LocalRuntime(enable_parallel=True)"
134
+ issue = type("Issue", (), {"code_snippet": content})()
135
+
136
+ result = assistant._transform_parallel_to_concurrency(content, issue)
137
+
138
+ assert "max_concurrency=10" in result
139
+ assert "enable_parallel=True" not in result
140
+
141
+ def test_parameter_transformation_thread_pool(self, assistant):
142
+ """Test transformation of thread_pool_size to max_concurrency."""
143
+ content = "runtime = LocalRuntime(thread_pool_size=5)"
144
+ issue = type("Issue", (), {"code_snippet": content})()
145
+
146
+ result = assistant._transform_thread_pool_size(content, issue)
147
+
148
+ assert "max_concurrency=5" in result
149
+ assert "thread_pool_size=5" not in result
150
+
151
+ def test_parameter_transformation_memory_limit(self, assistant):
152
+ """Test transformation of memory_limit to resource_limits."""
153
+ content = "runtime = LocalRuntime(memory_limit=1024)"
154
+ issue = type("Issue", (), {"code_snippet": content})()
155
+
156
+ result = assistant._transform_memory_limit(content, issue)
157
+
158
+ assert 'resource_limits={"memory_mb": 1024}' in result
159
+ assert "memory_limit=1024" not in result
160
+
161
+ def test_method_migration_execute_sync(self, assistant):
162
+ """Test migration of execute_sync to execute."""
163
+ content = "results = runtime.execute_sync(workflow)"
164
+ issue = type("Issue", (), {"code_snippet": content})()
165
+
166
+ result = assistant._migrate_execute_sync(content, issue)
167
+
168
+ assert "runtime.execute(" in result
169
+ assert ".execute_sync(" not in result
170
+
171
+ def test_method_migration_get_results(self, assistant):
172
+ """Test migration of get_results to direct result access."""
173
+ content = "results = runtime.get_results()"
174
+ issue = type("Issue", (), {"code_snippet": content})()
175
+
176
+ result = assistant._migrate_get_results(content, issue)
177
+
178
+ assert "[0]" in result
179
+ assert ".get_results()" not in result
180
+
181
+ def test_execute_migration_dry_run(self, assistant, temp_project_dir):
182
+ """Test migration execution in dry run mode."""
183
+ plan = assistant.create_migration_plan(temp_project_dir)
184
+ result = assistant.execute_migration(plan)
185
+
186
+ assert isinstance(result, MigrationResult)
187
+ assert result.steps_completed > 0
188
+ assert result.steps_failed == 0
189
+ assert result.success is True
190
+
191
+ # In dry run, no actual files should be modified
192
+ original_content = (temp_project_dir / "legacy_runtime.py").read_text()
193
+ assert "enable_parallel=True" in original_content # Should not be changed
194
+
195
+ def test_execute_migration_real(self, temp_project_dir):
196
+ """Test actual migration execution (not dry run)."""
197
+ # Use non-dry-run assistant
198
+ assistant = MigrationAssistant(dry_run=False, create_backups=False)
199
+
200
+ plan = assistant.create_migration_plan(temp_project_dir)
201
+ result = assistant.execute_migration(plan)
202
+
203
+ assert isinstance(result, MigrationResult)
204
+ assert result.success is True
205
+
206
+ # Check that files were actually modified
207
+ modified_content = (temp_project_dir / "legacy_runtime.py").read_text()
208
+ # Should have some migrations applied
209
+ assert modified_content != ""
210
+
211
+ def test_migration_plan_metadata(self, assistant, temp_project_dir):
212
+ """Test migration plan metadata calculation."""
213
+ plan = assistant.create_migration_plan(temp_project_dir)
214
+
215
+ # Should have reasonable duration estimate
216
+ assert plan.estimated_duration_minutes > 0
217
+ assert plan.estimated_duration_minutes < 1000 # Not too crazy
218
+
219
+ # Should assess risk level
220
+ assert plan.risk_level in ["low", "medium", "high"]
221
+
222
+ # Should have prerequisites and tests
223
+ assert len(plan.prerequisites) > 0
224
+ assert len(plan.post_migration_tests) > 0
225
+
226
+ # Prerequisites should be meaningful
227
+ for prereq in plan.prerequisites:
228
+ assert isinstance(prereq, str)
229
+ assert len(prereq) > 5
230
+
231
+ def test_migration_report_generation(self, assistant, temp_project_dir):
232
+ """Test migration report generation."""
233
+ plan = assistant.create_migration_plan(temp_project_dir)
234
+ result = assistant.execute_migration(plan)
235
+
236
+ report = assistant.generate_migration_report(plan, result)
237
+
238
+ assert isinstance(report, str)
239
+ assert len(report) > 0
240
+ assert "Migration Report" in report
241
+ assert "MIGRATION PLAN SUMMARY" in report
242
+ assert "EXECUTION RESULTS" in report
243
+ assert "MIGRATION STEPS" in report
244
+
245
+ def test_migration_validation(self, assistant):
246
+ """Test migration step validation."""
247
+ # Create a valid migration step
248
+ step = MigrationStep(
249
+ step_id="test_1",
250
+ description="Test step",
251
+ file_path="/dev/null", # Won't exist, but that's OK for this test
252
+ original_code="x = 1",
253
+ migrated_code="x = 2",
254
+ )
255
+
256
+ # Should validate syntax without error
257
+ try:
258
+ assistant._validate_migration_step(step)
259
+ except ValueError:
260
+ pytest.fail("Valid Python code should not raise validation error")
261
+
262
+ def test_migration_validation_syntax_error(self, assistant):
263
+ """Test migration step validation with syntax errors."""
264
+ # Create migration step with syntax error
265
+ step = MigrationStep(
266
+ step_id="test_1",
267
+ description="Test step",
268
+ file_path="/dev/null",
269
+ original_code="x = 1",
270
+ migrated_code="x = (", # Syntax error
271
+ )
272
+
273
+ # Should raise validation error
274
+ with pytest.raises(ValueError):
275
+ assistant._validate_migration_step(step)
276
+
277
+ def test_value_transformation_functions(self, assistant):
278
+ """Test all value transformation functions."""
279
+ transformations = assistant.value_transformations
280
+
281
+ for param_name, transform_func in transformations.items():
282
+ # Create mock issue
283
+ issue = type("Issue", (), {"code_snippet": f"{param_name}=test"})()
284
+ content = f"runtime = LocalRuntime({param_name}=10)"
285
+
286
+ # Should not raise exception
287
+ result = transform_func(content, issue)
288
+ assert isinstance(result, str)
289
+ assert len(result) > 0
290
+
291
+ def test_method_migration_functions(self, assistant):
292
+ """Test all method migration functions."""
293
+ migrations = assistant.method_migrations
294
+
295
+ for method_name, migrate_func in migrations.items():
296
+ # Create mock issue
297
+ issue = type("Issue", (), {"code_snippet": f".{method_name}("})()
298
+ content = f"result = runtime.{method_name}(workflow)"
299
+
300
+ # Should not raise exception
301
+ result = migrate_func(content, issue)
302
+ assert isinstance(result, str)
303
+ assert len(result) > 0
304
+
305
+ def test_rollback_functionality(self, assistant):
306
+ """Test rollback functionality."""
307
+ # Create a mock result with backup path
308
+ with tempfile.TemporaryDirectory() as backup_dir:
309
+ result = MigrationResult(
310
+ success=True,
311
+ steps_completed=5,
312
+ steps_failed=0,
313
+ backup_path=backup_dir,
314
+ rollback_available=True,
315
+ )
316
+
317
+ # Test rollback (won't actually work without proper backup structure)
318
+ rollback_success = assistant.rollback_migration(result)
319
+ # Should return False since backup structure is empty
320
+ assert rollback_success is False
321
+
322
+ def test_include_exclude_patterns(self, assistant, temp_project_dir):
323
+ """Test migration plan creation with include/exclude patterns."""
324
+ # Test with include patterns
325
+ plan_py_only = assistant.create_migration_plan(
326
+ temp_project_dir, include_patterns=["*.py"]
327
+ )
328
+
329
+ # Test with exclude patterns
330
+ plan_exclude_modern = assistant.create_migration_plan(
331
+ temp_project_dir, exclude_patterns=["modern_usage.py"]
332
+ )
333
+
334
+ # Both should create valid plans
335
+ assert isinstance(plan_py_only, MigrationPlan)
336
+ assert isinstance(plan_exclude_modern, MigrationPlan)
337
+
338
+ # Exclude pattern should result in fewer or equal steps
339
+ assert len(plan_exclude_modern.steps) <= len(plan_py_only.steps)
340
+
341
+
342
+ class TestMigrationStep:
343
+ """Test cases for MigrationStep dataclass."""
344
+
345
+ def test_creation(self):
346
+ """Test MigrationStep creation."""
347
+ step = MigrationStep(
348
+ step_id="test_1",
349
+ description="Test migration step",
350
+ file_path="/test/file.py",
351
+ original_code="old_code = True",
352
+ migrated_code="new_code = True",
353
+ )
354
+
355
+ assert step.step_id == "test_1"
356
+ assert step.description == "Test migration step"
357
+ assert step.file_path == "/test/file.py"
358
+ assert step.original_code == "old_code = True"
359
+ assert step.migrated_code == "new_code = True"
360
+ assert step.automated is True
361
+ assert step.validation_required is False
362
+ assert step.rollback_available is True
363
+
364
+
365
+ class TestMigrationPlan:
366
+ """Test cases for MigrationPlan dataclass."""
367
+
368
+ def test_creation(self):
369
+ """Test MigrationPlan creation."""
370
+ plan = MigrationPlan()
371
+
372
+ assert isinstance(plan.steps, list)
373
+ assert plan.estimated_duration_minutes == 0
374
+ assert plan.risk_level == "low"
375
+ assert isinstance(plan.prerequisites, list)
376
+ assert isinstance(plan.post_migration_tests, list)
377
+ assert plan.backup_required is True
378
+
379
+
380
+ class TestMigrationResult:
381
+ """Test cases for MigrationResult dataclass."""
382
+
383
+ def test_creation(self):
384
+ """Test MigrationResult creation."""
385
+ result = MigrationResult(success=True, steps_completed=5, steps_failed=0)
386
+
387
+ assert result.success is True
388
+ assert result.steps_completed == 5
389
+ assert result.steps_failed == 0
390
+ assert isinstance(result.errors, list)
391
+ assert isinstance(result.warnings, list)
392
+ assert result.backup_path is None
393
+ assert result.rollback_available is True
394
+
395
+
396
+ if __name__ == "__main__":
397
+ pytest.main([__file__])