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.
- mito_ai/__init__.py +8 -0
- mito_ai/_version.py +1 -1
- mito_ai/app_builder/handlers.py +16 -11
- mito_ai/completions/handlers.py +1 -1
- mito_ai/completions/prompt_builders/agent_system_message.py +18 -45
- mito_ai/completions/prompt_builders/chat_name_prompt.py +6 -6
- mito_ai/openai_client.py +1 -1
- mito_ai/streamlit_conversion/agent_utils.py +116 -0
- mito_ai/streamlit_conversion/prompts/prompt_constants.py +59 -0
- mito_ai/streamlit_conversion/prompts/prompt_utils.py +10 -0
- mito_ai/streamlit_conversion/prompts/streamlit_app_creation_prompt.py +45 -0
- mito_ai/streamlit_conversion/prompts/streamlit_error_correction_prompt.py +28 -0
- mito_ai/streamlit_conversion/prompts/streamlit_finish_todo_prompt.py +44 -0
- mito_ai/streamlit_conversion/streamlit_agent_handler.py +72 -42
- mito_ai/streamlit_conversion/streamlit_system_prompt.py +19 -17
- mito_ai/streamlit_conversion/streamlit_utils.py +43 -5
- mito_ai/streamlit_conversion/validate_streamlit_app.py +116 -0
- mito_ai/streamlit_preview/handlers.py +7 -4
- mito_ai/tests/streamlit_conversion/test_streamlit_agent_handler.py +153 -66
- mito_ai/tests/streamlit_conversion/test_validate_streamlit_app.py +119 -0
- mito_ai/tests/utils/test_anthropic_utils.py +2 -2
- mito_ai/utils/anthropic_utils.py +4 -4
- mito_ai/utils/open_ai_utils.py +0 -4
- {mito_ai-0.1.38.data → mito_ai-0.1.39.data}/data/share/jupyter/labextensions/mito_ai/build_log.json +1 -1
- {mito_ai-0.1.38.data → mito_ai-0.1.39.data}/data/share/jupyter/labextensions/mito_ai/package.json +2 -2
- {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
- 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
- mito_ai-0.1.39.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.16b532b655cd2906e04a.js.map +1 -0
- 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
- 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
- {mito_ai-0.1.38.dist-info → mito_ai-0.1.39.dist-info}/METADATA +4 -1
- {mito_ai-0.1.38.dist-info → mito_ai-0.1.39.dist-info}/RECORD +44 -38
- mito_ai/streamlit_conversion/validate_and_run_streamlit_code.py +0 -208
- mito_ai/tests/streamlit_conversion/test_validate_and_run_streamlit_code.py +0 -418
- mito_ai-0.1.38.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.5d1d7c234e2dc7c9d97b.js.map +0 -1
- {mito_ai-0.1.38.data → mito_ai-0.1.39.data}/data/etc/jupyter/jupyter_server_config.d/mito_ai.json +0 -0
- {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
- {mito_ai-0.1.38.data → mito_ai-0.1.39.data}/data/share/jupyter/labextensions/mito_ai/static/style.js +0 -0
- {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
- {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
- {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
- {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
- {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
- {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
- {mito_ai-0.1.38.dist-info → mito_ai-0.1.39.dist-info}/WHEEL +0 -0
- {mito_ai-0.1.38.dist-info → mito_ai-0.1.39.dist-info}/entry_points.txt +0 -0
- {mito_ai-0.1.38.dist-info → mito_ai-0.1.39.dist-info}/licenses/LICENSE +0 -0
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
# Distributed under the terms of the GNU Affero General Public License v3.0 License.
|
|
3
3
|
|
|
4
4
|
import pytest
|
|
5
|
-
from unittest.mock import patch, AsyncMock
|
|
5
|
+
from unittest.mock import patch, AsyncMock, MagicMock
|
|
6
6
|
from mito_ai.streamlit_conversion.streamlit_agent_handler import (
|
|
7
7
|
StreamlitCodeGeneration,
|
|
8
8
|
streamlit_handler
|
|
9
9
|
)
|
|
10
|
-
from
|
|
10
|
+
from mito_ai.streamlit_conversion.streamlit_utils import clean_directory_check
|
|
11
11
|
|
|
12
12
|
# Add this line to enable async support
|
|
13
13
|
pytest_plugins = ('pytest_asyncio',)
|
|
@@ -16,20 +16,24 @@ pytest_plugins = ('pytest_asyncio',)
|
|
|
16
16
|
class TestStreamlitCodeGeneration:
|
|
17
17
|
"""Test cases for StreamlitCodeGeneration class"""
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
@pytest.mark.asyncio
|
|
20
|
+
@patch('mito_ai.streamlit_conversion.streamlit_agent_handler.stream_anthropic_completion_from_mito_server')
|
|
21
|
+
async def test_init(self, mock_stream):
|
|
20
22
|
"""Test StreamlitCodeGeneration initialization"""
|
|
23
|
+
# Mock the async generator
|
|
24
|
+
async def mock_async_gen():
|
|
25
|
+
yield "Here's your code:\n```python\nimport streamlit\nst.title('Test')\n```"
|
|
26
|
+
|
|
27
|
+
mock_stream.return_value = mock_async_gen()
|
|
28
|
+
|
|
21
29
|
notebook_data: dict = {"cells": [{"cell_type": "code", "source": ["import pandas"]}]}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
assert
|
|
29
|
-
assert len(content_list) > 0
|
|
30
|
-
content_item = cast(dict, content_list[0])
|
|
31
|
-
assert content_item["type"] == "text"
|
|
32
|
-
assert "jupyter notebook content" in content_item["text"]
|
|
30
|
+
streamlit_code_handler = StreamlitCodeGeneration()
|
|
31
|
+
|
|
32
|
+
streamlit_code = await streamlit_code_handler.generate_streamlit_code(notebook_data)
|
|
33
|
+
|
|
34
|
+
assert streamlit_code is not None
|
|
35
|
+
assert len(streamlit_code) > 0
|
|
36
|
+
assert "import streamlit" in streamlit_code
|
|
33
37
|
|
|
34
38
|
@pytest.mark.asyncio
|
|
35
39
|
@patch('mito_ai.streamlit_conversion.streamlit_agent_handler.stream_anthropic_completion_from_mito_server')
|
|
@@ -48,9 +52,9 @@ class TestStreamlitCodeGeneration:
|
|
|
48
52
|
mock_stream.return_value = mock_async_gen()
|
|
49
53
|
|
|
50
54
|
notebook_data: dict = {"cells": []}
|
|
51
|
-
|
|
55
|
+
streamlit_code_handler = StreamlitCodeGeneration()
|
|
52
56
|
|
|
53
|
-
result = await
|
|
57
|
+
result = await streamlit_code_handler.generate_streamlit_code(notebook_data)
|
|
54
58
|
|
|
55
59
|
assert result == expected_result
|
|
56
60
|
mock_stream.assert_called_once()
|
|
@@ -63,28 +67,10 @@ class TestStreamlitCodeGeneration:
|
|
|
63
67
|
mock_stream.side_effect = Exception("API Error")
|
|
64
68
|
|
|
65
69
|
notebook_data: dict = {"cells": []}
|
|
66
|
-
|
|
70
|
+
streamlit_code_handler = StreamlitCodeGeneration()
|
|
67
71
|
|
|
68
72
|
with pytest.raises(Exception, match="API Error"):
|
|
69
|
-
await
|
|
70
|
-
|
|
71
|
-
def test_add_agent_response_to_context(self):
|
|
72
|
-
"""Test adding agent response to message history"""
|
|
73
|
-
notebook_data: dict = {"cells": []}
|
|
74
|
-
generator = StreamlitCodeGeneration(notebook_data)
|
|
75
|
-
|
|
76
|
-
initial_count = len(generator.messages)
|
|
77
|
-
generator.add_agent_response_to_context("Test response")
|
|
78
|
-
|
|
79
|
-
assert len(generator.messages) == initial_count + 1
|
|
80
|
-
assert generator.messages[-1]["role"] == "assistant"
|
|
81
|
-
# Access content properly as a list and cast to expected type
|
|
82
|
-
content_list = cast(list, generator.messages[-1]["content"])
|
|
83
|
-
assert isinstance(content_list, list)
|
|
84
|
-
assert len(content_list) > 0
|
|
85
|
-
content_item = cast(dict, content_list[0])
|
|
86
|
-
assert content_item["type"] == "text"
|
|
87
|
-
assert content_item["text"] == "Test response"
|
|
73
|
+
await streamlit_code_handler.generate_streamlit_code(notebook_data)
|
|
88
74
|
|
|
89
75
|
@pytest.mark.asyncio
|
|
90
76
|
@patch('mito_ai.streamlit_conversion.streamlit_agent_handler.stream_anthropic_completion_from_mito_server')
|
|
@@ -99,22 +85,26 @@ class TestStreamlitCodeGeneration:
|
|
|
99
85
|
mock_stream.return_value = mock_async_gen()
|
|
100
86
|
|
|
101
87
|
notebook_data: dict = {"cells": []}
|
|
102
|
-
|
|
88
|
+
streamlit_code_handler = StreamlitCodeGeneration()
|
|
103
89
|
|
|
104
|
-
result = await
|
|
90
|
+
result = await streamlit_code_handler.generate_streamlit_code(notebook_data)
|
|
105
91
|
|
|
106
92
|
expected_code = "import streamlit\nst.title('Hello')\n"
|
|
107
93
|
assert result == expected_code
|
|
108
|
-
|
|
109
|
-
# Check that response was added to context
|
|
110
|
-
assert len(generator.messages) == 2
|
|
111
|
-
assert generator.messages[-1]["role"] == "assistant"
|
|
112
94
|
|
|
113
95
|
@pytest.mark.asyncio
|
|
114
96
|
@patch('mito_ai.streamlit_conversion.streamlit_agent_handler.stream_anthropic_completion_from_mito_server')
|
|
115
97
|
async def test_correct_error_in_generation_success(self, mock_stream):
|
|
116
98
|
"""Test successful error correction"""
|
|
117
|
-
mock_response = "
|
|
99
|
+
mock_response = """```unified_diff
|
|
100
|
+
--- a/app.py
|
|
101
|
+
+++ b/app.py
|
|
102
|
+
@@ -1,1 +1,1 @@
|
|
103
|
+
-import streamlit
|
|
104
|
+
-st.title('Test')
|
|
105
|
+
+import streamlit
|
|
106
|
+
+st.title('Fixed')
|
|
107
|
+
```"""
|
|
118
108
|
async def mock_async_gen():
|
|
119
109
|
for item in [mock_response]:
|
|
120
110
|
yield item
|
|
@@ -122,29 +112,23 @@ class TestStreamlitCodeGeneration:
|
|
|
122
112
|
mock_stream.return_value = mock_async_gen()
|
|
123
113
|
|
|
124
114
|
notebook_data: dict = {"cells": []}
|
|
125
|
-
|
|
115
|
+
streamlit_code_handler = StreamlitCodeGeneration()
|
|
126
116
|
|
|
127
|
-
result = await
|
|
117
|
+
result = await streamlit_code_handler.correct_error_in_generation("ImportError: No module named 'pandas'", "import streamlit\nst.title('Test')")
|
|
128
118
|
|
|
129
119
|
expected_code = "import streamlit\nst.title('Fixed')\n"
|
|
130
120
|
assert result == expected_code
|
|
131
121
|
|
|
132
|
-
# Check that error message and response were added to context
|
|
133
|
-
assert len(generator.messages) == 3
|
|
134
|
-
assert generator.messages[-2]["role"] == "user"
|
|
135
|
-
assert generator.messages[-1]["role"] == "assistant"
|
|
136
|
-
|
|
137
122
|
@pytest.mark.asyncio
|
|
138
123
|
@patch('mito_ai.streamlit_conversion.streamlit_agent_handler.stream_anthropic_completion_from_mito_server')
|
|
139
124
|
async def test_correct_error_in_generation_exception(self, mock_stream):
|
|
140
125
|
"""Test exception handling in error correction"""
|
|
141
126
|
mock_stream.side_effect = Exception("API Error")
|
|
142
127
|
|
|
143
|
-
|
|
144
|
-
generator = StreamlitCodeGeneration(notebook_data)
|
|
128
|
+
streamlit_code_handler = StreamlitCodeGeneration()
|
|
145
129
|
|
|
146
130
|
with pytest.raises(Exception, match="API Error"):
|
|
147
|
-
await
|
|
131
|
+
await streamlit_code_handler.correct_error_in_generation("Some error", "import streamlit\nst.title('Test')")
|
|
148
132
|
|
|
149
133
|
|
|
150
134
|
class TestStreamlitHandler:
|
|
@@ -153,9 +137,10 @@ class TestStreamlitHandler:
|
|
|
153
137
|
@pytest.mark.asyncio
|
|
154
138
|
@patch('mito_ai.streamlit_conversion.streamlit_agent_handler.parse_jupyter_notebook_to_extract_required_content')
|
|
155
139
|
@patch('mito_ai.streamlit_conversion.streamlit_agent_handler.StreamlitCodeGeneration')
|
|
156
|
-
@patch('mito_ai.streamlit_conversion.streamlit_agent_handler.
|
|
140
|
+
@patch('mito_ai.streamlit_conversion.streamlit_agent_handler.validate_app')
|
|
157
141
|
@patch('mito_ai.streamlit_conversion.streamlit_agent_handler.create_app_file')
|
|
158
|
-
|
|
142
|
+
@patch('mito_ai.streamlit_conversion.streamlit_agent_handler.clean_directory_check')
|
|
143
|
+
async def test_streamlit_handler_success(self, mock_clean_directory, mock_create_file, mock_validator, mock_generator_class, mock_parse):
|
|
159
144
|
"""Test successful streamlit handler execution"""
|
|
160
145
|
# Mock notebook parsing
|
|
161
146
|
mock_notebook_data: dict = {"cells": [{"cell_type": "code", "source": ["import pandas"]}]}
|
|
@@ -170,7 +155,10 @@ class TestStreamlitHandler:
|
|
|
170
155
|
mock_validator.return_value = (False, "")
|
|
171
156
|
|
|
172
157
|
# Mock file creation
|
|
173
|
-
mock_create_file.return_value = (True, "/path/to/app", "File created successfully")
|
|
158
|
+
mock_create_file.return_value = (True, "/path/to/app.py", "File created successfully")
|
|
159
|
+
|
|
160
|
+
# Mock clean directory check (no-op)
|
|
161
|
+
mock_clean_directory.return_value = None
|
|
174
162
|
|
|
175
163
|
result = await streamlit_handler("/path/to/notebook.ipynb")
|
|
176
164
|
|
|
@@ -179,15 +167,15 @@ class TestStreamlitHandler:
|
|
|
179
167
|
|
|
180
168
|
# Verify calls
|
|
181
169
|
mock_parse.assert_called_once_with("/path/to/notebook.ipynb")
|
|
182
|
-
mock_generator_class.assert_called_once_with(
|
|
170
|
+
mock_generator_class.assert_called_once_with()
|
|
183
171
|
mock_generator.generate_streamlit_code.assert_called_once()
|
|
184
|
-
mock_validator.assert_called_once_with("import streamlit\nst.title('Test')")
|
|
172
|
+
mock_validator.assert_called_once_with("import streamlit\nst.title('Test')", "/path/to/notebook.ipynb")
|
|
185
173
|
mock_create_file.assert_called_once_with("/path/to", "import streamlit\nst.title('Test')")
|
|
186
174
|
|
|
187
175
|
@pytest.mark.asyncio
|
|
188
176
|
@patch('mito_ai.streamlit_conversion.streamlit_agent_handler.parse_jupyter_notebook_to_extract_required_content')
|
|
189
177
|
@patch('mito_ai.streamlit_conversion.streamlit_agent_handler.StreamlitCodeGeneration')
|
|
190
|
-
@patch('mito_ai.streamlit_conversion.streamlit_agent_handler.
|
|
178
|
+
@patch('mito_ai.streamlit_conversion.streamlit_agent_handler.validate_app')
|
|
191
179
|
async def test_streamlit_handler_max_retries_exceeded(self, mock_validator, mock_generator_class, mock_parse):
|
|
192
180
|
"""Test streamlit handler when max retries are exceeded"""
|
|
193
181
|
# Mock notebook parsing
|
|
@@ -200,10 +188,10 @@ class TestStreamlitHandler:
|
|
|
200
188
|
mock_generator.correct_error_in_generation.return_value = "import streamlit\nst.title('Fixed')"
|
|
201
189
|
mock_generator_class.return_value = mock_generator
|
|
202
190
|
|
|
203
|
-
# Mock validation (always errors) -
|
|
204
|
-
mock_validator.return_value = (True, "Persistent error")
|
|
191
|
+
# Mock validation (always errors) - Return list of errors as expected by validate_app
|
|
192
|
+
mock_validator.return_value = (True, ["Persistent error"])
|
|
205
193
|
|
|
206
|
-
result = await streamlit_handler("/
|
|
194
|
+
result = await streamlit_handler("/notebook.ipynb")
|
|
207
195
|
|
|
208
196
|
# Verify the result indicates failure
|
|
209
197
|
assert result[0] is False
|
|
@@ -215,9 +203,10 @@ class TestStreamlitHandler:
|
|
|
215
203
|
@pytest.mark.asyncio
|
|
216
204
|
@patch('mito_ai.streamlit_conversion.streamlit_agent_handler.parse_jupyter_notebook_to_extract_required_content')
|
|
217
205
|
@patch('mito_ai.streamlit_conversion.streamlit_agent_handler.StreamlitCodeGeneration')
|
|
218
|
-
@patch('mito_ai.streamlit_conversion.streamlit_agent_handler.
|
|
206
|
+
@patch('mito_ai.streamlit_conversion.streamlit_agent_handler.validate_app')
|
|
219
207
|
@patch('mito_ai.streamlit_conversion.streamlit_agent_handler.create_app_file')
|
|
220
|
-
|
|
208
|
+
@patch('mito_ai.streamlit_conversion.streamlit_agent_handler.clean_directory_check')
|
|
209
|
+
async def test_streamlit_handler_file_creation_failure(self, mock_clean_directory, mock_create_file, mock_validator, mock_generator_class, mock_parse):
|
|
221
210
|
"""Test streamlit handler when file creation fails"""
|
|
222
211
|
# Mock notebook parsing
|
|
223
212
|
mock_notebook_data: dict = {"cells": []}
|
|
@@ -234,6 +223,9 @@ class TestStreamlitHandler:
|
|
|
234
223
|
# Mock file creation failure
|
|
235
224
|
mock_create_file.return_value = (False, None, "Permission denied")
|
|
236
225
|
|
|
226
|
+
# Mock clean directory check (no-op)
|
|
227
|
+
mock_clean_directory.return_value = None
|
|
228
|
+
|
|
237
229
|
result = await streamlit_handler("/path/to/notebook.ipynb")
|
|
238
230
|
|
|
239
231
|
assert result[0] is False
|
|
@@ -241,8 +233,12 @@ class TestStreamlitHandler:
|
|
|
241
233
|
|
|
242
234
|
@pytest.mark.asyncio
|
|
243
235
|
@patch('mito_ai.streamlit_conversion.streamlit_agent_handler.parse_jupyter_notebook_to_extract_required_content')
|
|
244
|
-
|
|
236
|
+
@patch('mito_ai.streamlit_conversion.streamlit_agent_handler.clean_directory_check')
|
|
237
|
+
async def test_streamlit_handler_parse_notebook_exception(self, mock_clean_directory, mock_parse):
|
|
245
238
|
"""Test streamlit handler when notebook parsing fails"""
|
|
239
|
+
# Mock clean directory check (no-op)
|
|
240
|
+
mock_clean_directory.return_value = None
|
|
241
|
+
|
|
246
242
|
mock_parse.side_effect = FileNotFoundError("Notebook not found")
|
|
247
243
|
|
|
248
244
|
with pytest.raises(FileNotFoundError, match="Notebook not found"):
|
|
@@ -251,7 +247,8 @@ class TestStreamlitHandler:
|
|
|
251
247
|
@pytest.mark.asyncio
|
|
252
248
|
@patch('mito_ai.streamlit_conversion.streamlit_agent_handler.parse_jupyter_notebook_to_extract_required_content')
|
|
253
249
|
@patch('mito_ai.streamlit_conversion.streamlit_agent_handler.StreamlitCodeGeneration')
|
|
254
|
-
|
|
250
|
+
@patch('mito_ai.streamlit_conversion.streamlit_agent_handler.clean_directory_check')
|
|
251
|
+
async def test_streamlit_handler_generation_exception(self, mock_clean_directory, mock_generator_class, mock_parse):
|
|
255
252
|
"""Test streamlit handler when code generation fails"""
|
|
256
253
|
# Mock notebook parsing
|
|
257
254
|
mock_notebook_data: dict = {"cells": []}
|
|
@@ -262,5 +259,95 @@ class TestStreamlitHandler:
|
|
|
262
259
|
mock_generator.generate_streamlit_code.side_effect = Exception("Generation failed")
|
|
263
260
|
mock_generator_class.return_value = mock_generator
|
|
264
261
|
|
|
262
|
+
# Mock clean directory check (no-op)
|
|
263
|
+
mock_clean_directory.return_value = None
|
|
264
|
+
|
|
265
265
|
with pytest.raises(Exception, match="Generation failed"):
|
|
266
266
|
await streamlit_handler("/path/to/notebook.ipynb")
|
|
267
|
+
|
|
268
|
+
@pytest.mark.asyncio
|
|
269
|
+
@patch('mito_ai.streamlit_conversion.streamlit_agent_handler.parse_jupyter_notebook_to_extract_required_content')
|
|
270
|
+
@patch('mito_ai.streamlit_conversion.streamlit_agent_handler.StreamlitCodeGeneration')
|
|
271
|
+
@patch('mito_ai.streamlit_conversion.streamlit_agent_handler.validate_app')
|
|
272
|
+
@patch('mito_ai.streamlit_conversion.streamlit_agent_handler.create_app_file')
|
|
273
|
+
@patch('mito_ai.streamlit_conversion.streamlit_agent_handler.clean_directory_check')
|
|
274
|
+
async def test_streamlit_handler_too_many_files_in_directory(self, mock_clean_directory, mock_create_file, mock_validator, mock_generator_class, mock_parse):
|
|
275
|
+
"""Test streamlit handler when there are too many files in the directory"""
|
|
276
|
+
# Mock clean directory check to raise ValueError (simulating >10 files)
|
|
277
|
+
mock_clean_directory.side_effect = ValueError("Too many files in directory: 10 allowed but 15 present. Create a new directory and retry")
|
|
278
|
+
|
|
279
|
+
# The function should raise the ValueError before any other processing
|
|
280
|
+
with pytest.raises(ValueError, match="Too many files in directory: 10 allowed but 15 present. Create a new directory and retry"):
|
|
281
|
+
await streamlit_handler("/path/to/notebook.ipynb")
|
|
282
|
+
|
|
283
|
+
# Verify that clean_directory_check was called
|
|
284
|
+
mock_clean_directory.assert_called_once_with("/path/to/notebook.ipynb")
|
|
285
|
+
|
|
286
|
+
# Verify that no other functions were called since the error occurred early
|
|
287
|
+
mock_parse.assert_not_called()
|
|
288
|
+
mock_generator_class.assert_not_called()
|
|
289
|
+
mock_validator.assert_not_called()
|
|
290
|
+
mock_create_file.assert_not_called()
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
class TestCleanDirectoryCheck:
|
|
294
|
+
"""Test cases for clean_directory_check function"""
|
|
295
|
+
|
|
296
|
+
@patch('mito_ai.streamlit_conversion.streamlit_utils.Path')
|
|
297
|
+
def test_clean_directory_check_under_limit(self, mock_path):
|
|
298
|
+
"""Test clean_directory_check when directory has 10 or fewer files"""
|
|
299
|
+
# Mock the Path class and its methods
|
|
300
|
+
mock_path_instance = mock_path.return_value
|
|
301
|
+
mock_path_instance.resolve.return_value = mock_path_instance
|
|
302
|
+
mock_path_instance.parent = mock_path_instance
|
|
303
|
+
|
|
304
|
+
# Mock directory existence check
|
|
305
|
+
mock_path_instance.exists.return_value = True
|
|
306
|
+
|
|
307
|
+
# Mock directory contents with 8 files
|
|
308
|
+
mock_files = []
|
|
309
|
+
for i in range(8):
|
|
310
|
+
mock_file = MagicMock()
|
|
311
|
+
mock_file.is_file.return_value = True
|
|
312
|
+
mock_files.append(mock_file)
|
|
313
|
+
|
|
314
|
+
mock_path_instance.iterdir.return_value = mock_files
|
|
315
|
+
|
|
316
|
+
# Should not raise any exception
|
|
317
|
+
clean_directory_check('/path/to/notebook.ipynb')
|
|
318
|
+
|
|
319
|
+
# Verify calls
|
|
320
|
+
mock_path.assert_called_once_with('/path/to/notebook.ipynb')
|
|
321
|
+
mock_path_instance.resolve.assert_called_once()
|
|
322
|
+
mock_path_instance.exists.assert_called_once()
|
|
323
|
+
mock_path_instance.iterdir.assert_called_once()
|
|
324
|
+
|
|
325
|
+
@patch('mito_ai.streamlit_conversion.streamlit_utils.Path')
|
|
326
|
+
def test_clean_directory_check_over_limit(self, mock_path):
|
|
327
|
+
"""Test clean_directory_check when directory has more than 10 files"""
|
|
328
|
+
# Mock the Path class and its methods
|
|
329
|
+
mock_path_instance = mock_path.return_value
|
|
330
|
+
mock_path_instance.resolve.return_value = mock_path_instance
|
|
331
|
+
mock_path_instance.parent = mock_path_instance
|
|
332
|
+
|
|
333
|
+
# Mock directory existence check
|
|
334
|
+
mock_path_instance.exists.return_value = True
|
|
335
|
+
|
|
336
|
+
# Mock directory contents with 15 files
|
|
337
|
+
mock_files = []
|
|
338
|
+
for i in range(15):
|
|
339
|
+
mock_file = MagicMock()
|
|
340
|
+
mock_file.is_file.return_value = True
|
|
341
|
+
mock_files.append(mock_file)
|
|
342
|
+
|
|
343
|
+
mock_path_instance.iterdir.return_value = mock_files
|
|
344
|
+
|
|
345
|
+
# Should raise ValueError
|
|
346
|
+
with pytest.raises(ValueError, match="Too many files in directory: 10 allowed but 15 present. Create a new directory and retry"):
|
|
347
|
+
clean_directory_check('/path/to/notebook.ipynb')
|
|
348
|
+
|
|
349
|
+
# Verify calls
|
|
350
|
+
mock_path.assert_called_once_with('/path/to/notebook.ipynb')
|
|
351
|
+
mock_path_instance.resolve.assert_called_once()
|
|
352
|
+
mock_path_instance.exists.assert_called_once()
|
|
353
|
+
mock_path_instance.iterdir.assert_called_once()
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# Copyright (c) Saga Inc.
|
|
2
|
+
# Distributed under the terms of the GNU Affero General Public License v3.0 License.
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
import tempfile
|
|
6
|
+
from unittest.mock import patch, MagicMock
|
|
7
|
+
from mito_ai.streamlit_conversion.validate_streamlit_app import (
|
|
8
|
+
StreamlitValidator,
|
|
9
|
+
validate_app
|
|
10
|
+
)
|
|
11
|
+
import pytest
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class TestStreamlitValidator:
|
|
15
|
+
"""Test cases for StreamlitValidator class"""
|
|
16
|
+
|
|
17
|
+
@pytest.mark.parametrize("code,expected_error,test_description", [
|
|
18
|
+
# Valid Python code should return no error
|
|
19
|
+
(
|
|
20
|
+
"import streamlit\nst.title('Hello World')",
|
|
21
|
+
None,
|
|
22
|
+
"valid Python code"
|
|
23
|
+
),
|
|
24
|
+
# Invalid Python syntax should be caught
|
|
25
|
+
(
|
|
26
|
+
"import streamlit\nst.title('Hello World'",
|
|
27
|
+
"SyntaxError",
|
|
28
|
+
"invalid Python code"
|
|
29
|
+
),
|
|
30
|
+
# Empty streamlit app is valid
|
|
31
|
+
(
|
|
32
|
+
"",
|
|
33
|
+
None,
|
|
34
|
+
"empty code"
|
|
35
|
+
),
|
|
36
|
+
])
|
|
37
|
+
def test_validate_syntax(self, code, expected_error, test_description):
|
|
38
|
+
"""Test syntax validation with various code inputs"""
|
|
39
|
+
validator = StreamlitValidator()
|
|
40
|
+
|
|
41
|
+
error = validator.get_syntax_error(code)
|
|
42
|
+
|
|
43
|
+
if expected_error is None:
|
|
44
|
+
assert error is None, f"Expected no error for {test_description}"
|
|
45
|
+
else:
|
|
46
|
+
assert error is not None, f"Expected error for {test_description}"
|
|
47
|
+
assert expected_error in error, f"Expected '{expected_error}' in error for {test_description}"
|
|
48
|
+
|
|
49
|
+
@pytest.mark.parametrize("app_code,expected_error", [
|
|
50
|
+
("x = 5", None),
|
|
51
|
+
("1/0", "division by zero"),
|
|
52
|
+
("", None)
|
|
53
|
+
])
|
|
54
|
+
def test_get_runtime_errors(self, app_code, expected_error):
|
|
55
|
+
"""Test getting runtime errors"""
|
|
56
|
+
validator = StreamlitValidator()
|
|
57
|
+
|
|
58
|
+
errors = validator.get_runtime_errors(app_code, '/app.py')
|
|
59
|
+
|
|
60
|
+
if expected_error is None:
|
|
61
|
+
assert errors is None
|
|
62
|
+
else:
|
|
63
|
+
errors_str = str(errors)
|
|
64
|
+
assert expected_error in errors_str
|
|
65
|
+
|
|
66
|
+
def test_get_runtime_errors_with_relative_path(self):
|
|
67
|
+
"""Test getting runtime errors"""
|
|
68
|
+
|
|
69
|
+
app_code ="""
|
|
70
|
+
import streamlit as st
|
|
71
|
+
import pandas as pd
|
|
72
|
+
|
|
73
|
+
df=pd.read_csv('data.csv')
|
|
74
|
+
"""
|
|
75
|
+
# Create a temporary csv file in the directory temp/data.csv
|
|
76
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
77
|
+
directory = 'app_directory'
|
|
78
|
+
csv_path = os.path.join(temp_dir, directory, "data.csv")
|
|
79
|
+
|
|
80
|
+
os.makedirs(os.path.join(temp_dir, directory), exist_ok=True)
|
|
81
|
+
app_path = os.path.join(temp_dir, directory, "app.py")
|
|
82
|
+
|
|
83
|
+
# Create the file if it doesn't exist
|
|
84
|
+
with open(csv_path, "w") as f:
|
|
85
|
+
f.write("name,age\nJohn,25\nJane,30")
|
|
86
|
+
|
|
87
|
+
validator = StreamlitValidator()
|
|
88
|
+
errors = validator.get_runtime_errors(app_code, app_path)
|
|
89
|
+
assert errors is None
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@patch('subprocess.Popen')
|
|
93
|
+
def test_cleanup_with_process(self, mock_popen):
|
|
94
|
+
"""Test cleanup with running process"""
|
|
95
|
+
validator = StreamlitValidator()
|
|
96
|
+
validator.temp_dir = "/tmp/test_dir"
|
|
97
|
+
|
|
98
|
+
# Mock directory exists
|
|
99
|
+
with patch('os.path.exists', return_value=True):
|
|
100
|
+
with patch('shutil.rmtree') as mock_rmtree:
|
|
101
|
+
validator.cleanup()
|
|
102
|
+
|
|
103
|
+
mock_rmtree.assert_called_once()
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@pytest.mark.parametrize("app_code,expected_has_validation_error,expected_error_message", [
|
|
107
|
+
("x=5", False, ""),
|
|
108
|
+
("1/0", True, "division by zero"),
|
|
109
|
+
("print('Hello World'", True, "SyntaxError"),
|
|
110
|
+
("", False, ""),
|
|
111
|
+
])
|
|
112
|
+
def test_streamlit_code_validator(self, app_code, expected_has_validation_error, expected_error_message):
|
|
113
|
+
|
|
114
|
+
has_validation_error, errors = validate_app(app_code, '/app.py')
|
|
115
|
+
|
|
116
|
+
assert has_validation_error == expected_has_validation_error
|
|
117
|
+
assert expected_error_message in str(errors)
|
|
118
|
+
|
|
119
|
+
|
|
@@ -5,7 +5,7 @@ import pytest
|
|
|
5
5
|
import anthropic
|
|
6
6
|
from typing import List, Dict, Any, Tuple, Union, cast
|
|
7
7
|
from anthropic.types import MessageParam, ToolUnionParam, ToolParam
|
|
8
|
-
from mito_ai.utils.anthropic_utils import _prepare_anthropic_request_data_and_headers
|
|
8
|
+
from mito_ai.utils.anthropic_utils import ANTHROPIC_TIMEOUT, _prepare_anthropic_request_data_and_headers
|
|
9
9
|
from mito_ai.completions.models import MessageType
|
|
10
10
|
from mito_ai.utils.schema import UJ_STATIC_USER_ID, UJ_USER_EMAIL
|
|
11
11
|
from mito_ai.utils.db import get_user_field
|
|
@@ -52,7 +52,7 @@ def test_basic_request_preparation():
|
|
|
52
52
|
)
|
|
53
53
|
|
|
54
54
|
assert headers == {"Content-Type": "application/json"}
|
|
55
|
-
assert data["timeout"] ==
|
|
55
|
+
assert data["timeout"] == ANTHROPIC_TIMEOUT
|
|
56
56
|
assert data["max_retries"] == 1
|
|
57
57
|
assert data["email"] == "test@example.com"
|
|
58
58
|
assert data["user_id"] == "test_user_id"
|
mito_ai/utils/anthropic_utils.py
CHANGED
|
@@ -23,7 +23,7 @@ from mito_ai.constants import MITO_ANTHROPIC_URL
|
|
|
23
23
|
__user_email: Optional[str] = None
|
|
24
24
|
__user_id: Optional[str] = None
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
ANTHROPIC_TIMEOUT = 60
|
|
27
27
|
max_retries = 1
|
|
28
28
|
|
|
29
29
|
FAST_ANTHROPIC_MODEL = "claude-3-5-haiku-latest"
|
|
@@ -63,7 +63,7 @@ def _prepare_anthropic_request_data_and_headers(
|
|
|
63
63
|
inner_data["stream"] = stream
|
|
64
64
|
# Compose the outer data dict
|
|
65
65
|
data = {
|
|
66
|
-
"timeout":
|
|
66
|
+
"timeout": ANTHROPIC_TIMEOUT,
|
|
67
67
|
"max_retries": max_retries,
|
|
68
68
|
"email": __user_email,
|
|
69
69
|
"user_id": __user_id,
|
|
@@ -90,7 +90,7 @@ async def get_anthropic_completion_from_mito_server(
|
|
|
90
90
|
MITO_ANTHROPIC_URL,
|
|
91
91
|
headers,
|
|
92
92
|
data,
|
|
93
|
-
|
|
93
|
+
ANTHROPIC_TIMEOUT,
|
|
94
94
|
max_retries,
|
|
95
95
|
message_type,
|
|
96
96
|
provider_name="Claude"
|
|
@@ -118,7 +118,7 @@ async def stream_anthropic_completion_from_mito_server(
|
|
|
118
118
|
url=MITO_ANTHROPIC_URL,
|
|
119
119
|
headers=headers,
|
|
120
120
|
data=data,
|
|
121
|
-
timeout=
|
|
121
|
+
timeout=ANTHROPIC_TIMEOUT,
|
|
122
122
|
max_retries=max_retries,
|
|
123
123
|
message_type=message_type,
|
|
124
124
|
reply_fn=actual_reply_fn,
|
mito_ai/utils/open_ai_utils.py
CHANGED
|
@@ -196,9 +196,5 @@ def get_open_ai_completion_function_params(
|
|
|
196
196
|
"strict": True
|
|
197
197
|
}
|
|
198
198
|
}
|
|
199
|
-
|
|
200
|
-
# o3-mini will error if we try setting the temperature
|
|
201
|
-
if not model.startswith("o3"):
|
|
202
|
-
completion_function_params["temperature"] = 0.0
|
|
203
199
|
|
|
204
200
|
return completion_function_params
|
{mito_ai-0.1.38.data → mito_ai-0.1.39.data}/data/share/jupyter/labextensions/mito_ai/package.json
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mito_ai",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.39",
|
|
4
4
|
"description": "AI chat for JupyterLab",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"jupyter",
|
|
@@ -138,7 +138,7 @@
|
|
|
138
138
|
"outputDir": "mito_ai/labextension",
|
|
139
139
|
"schemaDir": "schema",
|
|
140
140
|
"_build": {
|
|
141
|
-
"load": "static/remoteEntry.
|
|
141
|
+
"load": "static/remoteEntry.606207904e6aaa42b1bf.js",
|
|
142
142
|
"extension": "./extension",
|
|
143
143
|
"style": "./style"
|
|
144
144
|
}
|