mito-ai 0.1.38__py3-none-any.whl → 0.1.39__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.

Potentially problematic release.


This version of mito-ai might be problematic. Click here for more details.

Files changed (47) hide show
  1. mito_ai/__init__.py +8 -0
  2. mito_ai/_version.py +1 -1
  3. mito_ai/app_builder/handlers.py +16 -11
  4. mito_ai/completions/handlers.py +1 -1
  5. mito_ai/completions/prompt_builders/agent_system_message.py +18 -45
  6. mito_ai/completions/prompt_builders/chat_name_prompt.py +6 -6
  7. mito_ai/openai_client.py +1 -1
  8. mito_ai/streamlit_conversion/agent_utils.py +116 -0
  9. mito_ai/streamlit_conversion/prompts/prompt_constants.py +59 -0
  10. mito_ai/streamlit_conversion/prompts/prompt_utils.py +10 -0
  11. mito_ai/streamlit_conversion/prompts/streamlit_app_creation_prompt.py +45 -0
  12. mito_ai/streamlit_conversion/prompts/streamlit_error_correction_prompt.py +28 -0
  13. mito_ai/streamlit_conversion/prompts/streamlit_finish_todo_prompt.py +44 -0
  14. mito_ai/streamlit_conversion/streamlit_agent_handler.py +72 -42
  15. mito_ai/streamlit_conversion/streamlit_system_prompt.py +19 -17
  16. mito_ai/streamlit_conversion/streamlit_utils.py +43 -5
  17. mito_ai/streamlit_conversion/validate_streamlit_app.py +116 -0
  18. mito_ai/streamlit_preview/handlers.py +7 -4
  19. mito_ai/tests/streamlit_conversion/test_streamlit_agent_handler.py +153 -66
  20. mito_ai/tests/streamlit_conversion/test_validate_streamlit_app.py +119 -0
  21. mito_ai/tests/utils/test_anthropic_utils.py +2 -2
  22. mito_ai/utils/anthropic_utils.py +4 -4
  23. mito_ai/utils/open_ai_utils.py +0 -4
  24. {mito_ai-0.1.38.data → mito_ai-0.1.39.data}/data/share/jupyter/labextensions/mito_ai/build_log.json +1 -1
  25. {mito_ai-0.1.38.data → mito_ai-0.1.39.data}/data/share/jupyter/labextensions/mito_ai/package.json +2 -2
  26. {mito_ai-0.1.38.data → mito_ai-0.1.39.data}/data/share/jupyter/labextensions/mito_ai/schemas/mito_ai/package.json.orig +1 -1
  27. mito_ai-0.1.38.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.5d1d7c234e2dc7c9d97b.js → mito_ai-0.1.39.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.16b532b655cd2906e04a.js +262 -65
  28. mito_ai-0.1.39.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.16b532b655cd2906e04a.js.map +1 -0
  29. mito_ai-0.1.38.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.bcce4ea34631acf6dbbe.js → mito_ai-0.1.39.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.606207904e6aaa42b1bf.js +3 -3
  30. mito_ai-0.1.38.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.bcce4ea34631acf6dbbe.js.map → mito_ai-0.1.39.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.606207904e6aaa42b1bf.js.map +1 -1
  31. {mito_ai-0.1.38.dist-info → mito_ai-0.1.39.dist-info}/METADATA +4 -1
  32. {mito_ai-0.1.38.dist-info → mito_ai-0.1.39.dist-info}/RECORD +44 -38
  33. mito_ai/streamlit_conversion/validate_and_run_streamlit_code.py +0 -208
  34. mito_ai/tests/streamlit_conversion/test_validate_and_run_streamlit_code.py +0 -418
  35. mito_ai-0.1.38.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.5d1d7c234e2dc7c9d97b.js.map +0 -1
  36. {mito_ai-0.1.38.data → mito_ai-0.1.39.data}/data/etc/jupyter/jupyter_server_config.d/mito_ai.json +0 -0
  37. {mito_ai-0.1.38.data → mito_ai-0.1.39.data}/data/share/jupyter/labextensions/mito_ai/schemas/mito_ai/toolbar-buttons.json +0 -0
  38. {mito_ai-0.1.38.data → mito_ai-0.1.39.data}/data/share/jupyter/labextensions/mito_ai/static/style.js +0 -0
  39. {mito_ai-0.1.38.data → mito_ai-0.1.39.data}/data/share/jupyter/labextensions/mito_ai/static/style_index_js.5876024bb17dbd6a3ee6.js +0 -0
  40. {mito_ai-0.1.38.data → mito_ai-0.1.39.data}/data/share/jupyter/labextensions/mito_ai/static/style_index_js.5876024bb17dbd6a3ee6.js.map +0 -0
  41. {mito_ai-0.1.38.data → mito_ai-0.1.39.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_semver_index_js.9795f79265ddb416864b.js +0 -0
  42. {mito_ai-0.1.38.data → mito_ai-0.1.39.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_semver_index_js.9795f79265ddb416864b.js.map +0 -0
  43. {mito_ai-0.1.38.data → mito_ai-0.1.39.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_vscode-diff_dist_index_js.ea55f1f9346638aafbcf.js +0 -0
  44. {mito_ai-0.1.38.data → mito_ai-0.1.39.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_vscode-diff_dist_index_js.ea55f1f9346638aafbcf.js.map +0 -0
  45. {mito_ai-0.1.38.dist-info → mito_ai-0.1.39.dist-info}/WHEEL +0 -0
  46. {mito_ai-0.1.38.dist-info → mito_ai-0.1.39.dist-info}/entry_points.txt +0 -0
  47. {mito_ai-0.1.38.dist-info → mito_ai-0.1.39.dist-info}/licenses/LICENSE +0 -0
@@ -1,418 +0,0 @@
1
- # Copyright (c) Saga Inc.
2
- # Distributed under the terms of the GNU Affero General Public License v3.0 License.
3
-
4
- import pytest
5
- import tempfile
6
- import os
7
- import subprocess
8
- import time
9
- import ast
10
- import importlib.util
11
- from unittest.mock import patch, MagicMock, mock_open
12
- from mito_ai.streamlit_conversion.validate_and_run_streamlit_code import (
13
- StreamlitValidator,
14
- streamlit_code_validator
15
- )
16
-
17
-
18
- class TestStreamlitValidator:
19
- """Test cases for StreamlitValidator class"""
20
-
21
- def test_validate_syntax_valid_code(self):
22
- """Test syntax validation with valid Python code"""
23
- validator = StreamlitValidator()
24
- code = "import streamlit\nst.title('Hello World')"
25
-
26
- is_valid, message = validator.validate_syntax(code)
27
-
28
- assert is_valid is True
29
- assert "Syntax is valid" in message
30
-
31
- def test_validate_syntax_invalid_code(self):
32
- """Test syntax validation with invalid Python code"""
33
- validator = StreamlitValidator()
34
- code = "import streamlit\nst.title('Hello World' # Missing closing parenthesis"
35
-
36
- is_valid, message = validator.validate_syntax(code)
37
-
38
- assert is_valid is False
39
- assert "Syntax error" in message
40
-
41
- def test_validate_syntax_empty_code(self):
42
- """Test syntax validation with empty code"""
43
- validator = StreamlitValidator()
44
- code = ""
45
-
46
- is_valid, message = validator.validate_syntax(code)
47
-
48
- assert is_valid is True
49
- assert "Syntax is valid" in message
50
-
51
- def test_create_temp_app(self):
52
- """Test creating temporary app file"""
53
- validator = StreamlitValidator()
54
- code = "import streamlit\nst.title('Test')"
55
-
56
- app_path = validator.create_temp_app(code)
57
-
58
- assert validator.temp_dir is not None
59
- assert os.path.exists(validator.temp_dir)
60
- assert app_path.endswith("app.py")
61
- assert os.path.exists(app_path)
62
-
63
- # Check file content
64
- with open(app_path, 'r') as f:
65
- content = f.read()
66
- assert content == code
67
-
68
- # Cleanup
69
- validator.cleanup()
70
-
71
- def test_create_temp_app_empty_code(self):
72
- """Test creating temporary app file with empty code"""
73
- validator = StreamlitValidator()
74
- code = ""
75
-
76
- app_path = validator.create_temp_app(code)
77
-
78
- assert os.path.exists(app_path)
79
-
80
- with open(app_path, 'r') as f:
81
- content = f.read()
82
- assert content == ""
83
-
84
- # Cleanup
85
- validator.cleanup()
86
-
87
- @patch('subprocess.Popen')
88
- def test_start_streamlit_app_success(self, mock_popen):
89
- """Test successful Streamlit app startup"""
90
- validator = StreamlitValidator(port=8502)
91
- app_path = "/tmp/test/app.py"
92
-
93
- # Mock successful subprocess
94
- mock_process = MagicMock()
95
- mock_popen.return_value = mock_process
96
-
97
- success, message = validator.start_streamlit_app(app_path)
98
-
99
- assert success is True
100
- assert "Streamlit app started" in message
101
- assert validator.process == mock_process
102
-
103
- # Verify subprocess was called with correct arguments
104
- mock_popen.assert_called_once()
105
- call_args = mock_popen.call_args[0][0]
106
- assert "streamlit" in call_args
107
- assert "run" in call_args
108
- assert app_path in call_args
109
- assert "8502" in call_args
110
-
111
- @patch('subprocess.Popen')
112
- def test_start_streamlit_app_failure(self, mock_popen):
113
- """Test Streamlit app startup failure"""
114
- validator = StreamlitValidator()
115
- app_path = "/tmp/test/app.py"
116
-
117
- # Mock subprocess failure
118
- mock_popen.side_effect = Exception("Failed to start process")
119
-
120
- success, message = validator.start_streamlit_app(app_path)
121
-
122
- assert success is False
123
- assert "Failed to start Streamlit" in message
124
- assert validator.process is None
125
-
126
- @patch('requests.get')
127
- def test_wait_for_app_success(self, mock_get):
128
- """Test waiting for app to be ready successfully"""
129
- validator = StreamlitValidator(port=8502, timeout=5)
130
-
131
- # Mock successful HTTP response
132
- mock_response = MagicMock()
133
- mock_response.status_code = 200
134
- mock_get.return_value = mock_response
135
-
136
- success, message = validator.wait_for_app()
137
-
138
- assert success is True
139
- assert "App is running successfully" in message
140
- mock_get.assert_called_with("http://localhost:8502", timeout=5)
141
-
142
- @patch('requests.get')
143
- def test_wait_for_app_http_error(self, mock_get):
144
- """Test waiting for app with HTTP error"""
145
- validator = StreamlitValidator(port=8501, timeout=5)
146
-
147
- # Mock HTTP error response
148
- mock_response = MagicMock()
149
- mock_response.status_code = 500
150
- mock_get.return_value = mock_response
151
-
152
- success, message = validator.wait_for_app()
153
-
154
- assert success is False
155
- assert "App failed to start within timeout" in message
156
-
157
- @patch('subprocess.Popen')
158
- def test_check_for_errors_process_running(self, mock_popen):
159
- """Test error checking when process is running"""
160
- validator = StreamlitValidator()
161
-
162
- # Mock running process
163
- mock_process = MagicMock()
164
- mock_process.poll.return_value = None # Process is running
165
- validator.process = mock_process
166
-
167
- success, message = validator.check_for_errors()
168
-
169
- assert success is True
170
- assert "App is running without errors" in message
171
-
172
- @patch('subprocess.Popen')
173
- def test_check_for_errors_process_crashed(self, mock_popen):
174
- """Test error checking when process has crashed"""
175
- validator = StreamlitValidator()
176
-
177
- # Mock crashed process
178
- mock_process = MagicMock()
179
- mock_process.poll.return_value = 1 # Process has exited
180
- mock_process.communicate.return_value = ("stdout", "stderr error message")
181
- validator.process = mock_process
182
-
183
- success, message = validator.check_for_errors()
184
-
185
- assert success is False
186
- assert "App crashed" in message
187
- assert "stderr error message" in message
188
-
189
- @patch('subprocess.Popen')
190
- def test_check_for_errors_process_crashed_with_warnings(self, mock_popen):
191
- """Test error checking when process crashed but only has warnings"""
192
- validator = StreamlitValidator()
193
-
194
- # Mock crashed process with only warnings
195
- mock_process = MagicMock()
196
- mock_process.poll.return_value = 1
197
- mock_process.communicate.return_value = ("stdout", "missing ScriptRunContext warning")
198
- validator.process = mock_process
199
-
200
- success, message = validator.check_for_errors()
201
-
202
- assert success is True
203
- assert "App is running without errors" in message
204
-
205
- def test_check_for_errors_no_process(self):
206
- """Test error checking when no process exists"""
207
- validator = StreamlitValidator()
208
-
209
- success, message = validator.check_for_errors()
210
-
211
- assert success is False
212
- assert "No process found" in message
213
-
214
- @patch('subprocess.Popen')
215
- def test_cleanup_with_process(self, mock_popen):
216
- """Test cleanup with running process"""
217
- validator = StreamlitValidator()
218
-
219
- # Mock process
220
- mock_process = MagicMock()
221
- validator.process = mock_process
222
- validator.temp_dir = "/tmp/test_dir"
223
-
224
- # Mock directory exists
225
- with patch('os.path.exists', return_value=True):
226
- with patch('shutil.rmtree') as mock_rmtree:
227
- validator.cleanup()
228
-
229
- mock_process.terminate.assert_called_once()
230
- mock_process.wait.assert_called_once()
231
- mock_rmtree.assert_called_once_with("/tmp/test_dir")
232
-
233
- assert validator.process is None
234
- assert validator.temp_dir is None # type: ignore[unreachable]
235
-
236
- def test_cleanup_without_process(self):
237
- """Test cleanup without process"""
238
- validator = StreamlitValidator()
239
-
240
- # Should not raise any exceptions
241
- validator.cleanup()
242
-
243
- assert validator.process is None
244
- assert validator.temp_dir is None
245
-
246
- @patch('subprocess.Popen')
247
- @patch('requests.get')
248
- def test_validate_app_success(self, mock_get, mock_popen):
249
- """Test complete validation pipeline success"""
250
- validator = StreamlitValidator(port=8501, timeout=5)
251
- code = "import streamlit\nst.title('Hello World')"
252
-
253
- # Mock successful subprocess
254
- mock_process = MagicMock()
255
- mock_process.poll.return_value = None
256
- mock_popen.return_value = mock_process
257
-
258
- # Mock successful HTTP response
259
- mock_response = MagicMock()
260
- mock_response.status_code = 200
261
- mock_get.return_value = mock_response
262
-
263
- results = validator.validate_app(code)
264
-
265
- assert results['syntax_valid'] is True
266
- assert results['app_starts'] is True
267
- assert results['app_responsive'] is True
268
- assert len(results['errors']) == 0
269
-
270
- def test_validate_app_syntax_error(self):
271
- """Test validation pipeline with syntax error"""
272
- validator = StreamlitValidator()
273
- code = "import streamlit\nst.title('Hello World' # Missing parenthesis"
274
-
275
- results = validator.validate_app(code)
276
-
277
- assert results['syntax_valid'] is False
278
- assert results['app_starts'] is False
279
- assert results['app_responsive'] is False
280
- assert len(results['errors']) == 1
281
- assert "Syntax error" in results['errors'][0]
282
-
283
- @patch('subprocess.Popen')
284
- def test_validate_app_startup_failure(self, mock_popen):
285
- """Test validation pipeline with startup failure"""
286
- validator = StreamlitValidator()
287
- code = "import streamlit\nst.title('Hello World')"
288
-
289
- # Mock startup failure
290
- mock_popen.side_effect = Exception("Failed to start")
291
-
292
- results = validator.validate_app(code)
293
-
294
- assert results['syntax_valid'] is True
295
- assert results['app_starts'] is False
296
- assert results['app_responsive'] is False
297
- assert len(results['errors']) == 1
298
- assert "Failed to start Streamlit" in results['errors'][0]
299
-
300
- @patch('subprocess.Popen')
301
- @patch('requests.get')
302
- def test_validate_app_runtime_error(self, mock_get, mock_popen):
303
- """Test validation pipeline with runtime error"""
304
- validator = StreamlitValidator(port=8501, timeout=5)
305
- code = "import streamlit\nst.title('Hello World')"
306
-
307
- # Mock successful startup
308
- mock_process = MagicMock()
309
- mock_process.poll.return_value = 1 # Process crashed
310
- mock_process.communicate.return_value = ("stdout", "Runtime error occurred")
311
- mock_popen.return_value = mock_process
312
-
313
- # Mock successful HTTP response
314
- mock_response = MagicMock()
315
- mock_response.status_code = 200
316
- mock_get.return_value = mock_response
317
-
318
- results = validator.validate_app(code)
319
-
320
- assert results['syntax_valid'] is True
321
- assert results['app_starts'] is True
322
- assert results['app_responsive'] is True
323
- assert len(results['errors']) == 1
324
- assert "App crashed" in results['errors'][0]
325
-
326
- def test_validate_app_exception_handling(self):
327
- """Test validation pipeline exception handling"""
328
- validator = StreamlitValidator()
329
- code = "import streamlit\nst.title('Hello World')"
330
-
331
- # Mock an exception during validation
332
- with patch.object(validator, 'validate_syntax', side_effect=Exception("Unexpected error")):
333
- results = validator.validate_app(code)
334
-
335
- assert results['syntax_valid'] is False
336
- assert results['app_starts'] is False
337
- assert results['app_responsive'] is False
338
- assert len(results['errors']) == 1
339
- assert "Validation error" in results['errors'][0]
340
-
341
-
342
- class TestStreamlitCodeValidator:
343
- """Test cases for streamlit_code_validator function"""
344
-
345
- def test_streamlit_code_validator_success(self):
346
- """Test successful code validation"""
347
- code = "import streamlit\nst.title('Hello World')"
348
-
349
- with patch('mito_ai.streamlit_conversion.validate_and_run_streamlit_code.StreamlitValidator') as mock_validator_class:
350
- mock_validator = MagicMock()
351
- mock_validator_class.return_value = mock_validator
352
-
353
- mock_validator.validate_app.return_value = {
354
- 'syntax_valid': True,
355
- 'app_starts': True,
356
- 'app_responsive': True,
357
- 'errors': []
358
- }
359
-
360
- has_error, message = streamlit_code_validator(code)
361
-
362
- assert has_error is False
363
- assert "Errors found" not in message
364
- mock_validator.validate_app.assert_called_once_with(code)
365
-
366
- def test_streamlit_code_validator_error_in_code(self):
367
- """Test code validation when code contains 'error'"""
368
- code = "error in the code"
369
-
370
- has_error, message = streamlit_code_validator(code)
371
-
372
- assert has_error is True
373
- assert "Errors found" in message
374
-
375
- def test_streamlit_code_validator_empty_code(self):
376
- """Test code validation with empty code"""
377
- code = ""
378
-
379
- with patch('mito_ai.streamlit_conversion.validate_and_run_streamlit_code.StreamlitValidator') as mock_validator_class:
380
- mock_validator = MagicMock()
381
- mock_validator_class.return_value = mock_validator
382
-
383
- mock_validator.validate_app.return_value = {
384
- 'syntax_valid': True,
385
- 'app_starts': True,
386
- 'app_responsive': True,
387
- 'errors': []
388
- }
389
-
390
- has_error, message = streamlit_code_validator(code)
391
-
392
- assert has_error is False
393
- assert "Errors found" not in message
394
-
395
- def test_streamlit_code_validator_multiple_errors(self):
396
- """Test code validation with multiple errors"""
397
- code = "import streamlit\nst.title('Hello World')"
398
-
399
- with patch('mito_ai.streamlit_conversion.validate_and_run_streamlit_code.StreamlitValidator') as mock_validator_class:
400
- mock_validator = MagicMock()
401
- mock_validator_class.return_value = mock_validator
402
-
403
- mock_validator.validate_app.return_value = {
404
- 'syntax_valid': False,
405
- 'app_starts': False,
406
- 'app_responsive': False,
407
- 'errors': [
408
- 'Syntax error: invalid syntax',
409
- 'App failed to start'
410
- ]
411
- }
412
-
413
- has_error, message = streamlit_code_validator(code)
414
-
415
- assert has_error is True
416
- assert "Errors found" in message
417
- assert "Syntax error" in message
418
- assert "App failed to start" in message