mito-ai 0.1.36__py3-none-any.whl → 0.1.37__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 +6 -4
- mito_ai/_version.py +1 -1
- mito_ai/anthropic_client.py +3 -10
- mito_ai/app_builder/handlers.py +89 -11
- mito_ai/app_builder/models.py +3 -0
- mito_ai/auth/README.md +18 -0
- mito_ai/auth/__init__.py +6 -0
- mito_ai/auth/handlers.py +96 -0
- mito_ai/auth/urls.py +13 -0
- mito_ai/completions/completion_handlers/chat_completion_handler.py +2 -2
- mito_ai/completions/models.py +7 -6
- mito_ai/completions/prompt_builders/agent_execution_prompt.py +8 -3
- mito_ai/completions/prompt_builders/agent_system_message.py +21 -7
- mito_ai/completions/prompt_builders/chat_prompt.py +18 -11
- mito_ai/completions/prompt_builders/utils.py +53 -10
- mito_ai/constants.py +11 -1
- mito_ai/streamlit_conversion/streamlit_agent_handler.py +112 -0
- mito_ai/streamlit_conversion/streamlit_system_prompt.py +42 -0
- mito_ai/streamlit_conversion/streamlit_utils.py +96 -0
- mito_ai/streamlit_conversion/validate_and_run_streamlit_code.py +207 -0
- mito_ai/tests/providers/test_stream_mito_server_utils.py +140 -0
- mito_ai/tests/streamlit_conversion/__init__.py +3 -0
- mito_ai/tests/streamlit_conversion/test_streamlit_agent_handler.py +265 -0
- mito_ai/tests/streamlit_conversion/test_streamlit_utils.py +197 -0
- mito_ai/tests/streamlit_conversion/test_validate_and_run_streamlit_code.py +418 -0
- mito_ai/tests/test_constants.py +18 -3
- mito_ai/utils/anthropic_utils.py +18 -70
- mito_ai/utils/gemini_utils.py +22 -73
- mito_ai/utils/mito_server_utils.py +147 -4
- mito_ai/utils/open_ai_utils.py +18 -107
- {mito_ai-0.1.36.data → mito_ai-0.1.37.data}/data/share/jupyter/labextensions/mito_ai/build_log.json +100 -100
- {mito_ai-0.1.36.data → mito_ai-0.1.37.data}/data/share/jupyter/labextensions/mito_ai/package.json +2 -2
- {mito_ai-0.1.36.data → mito_ai-0.1.37.data}/data/share/jupyter/labextensions/mito_ai/schemas/mito_ai/package.json.orig +1 -1
- mito_ai-0.1.36.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.a20772bc113422d0f505.js → mito_ai-0.1.37.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.831f63b48760c7119b9b.js +1165 -539
- mito_ai-0.1.37.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.831f63b48760c7119b9b.js.map +1 -0
- mito_ai-0.1.36.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.5c9333902dce30642119.js → mito_ai-0.1.37.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.93ecc9bc0edba61535cc.js +18 -14
- mito_ai-0.1.37.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.93ecc9bc0edba61535cc.js.map +1 -0
- mito_ai-0.1.36.data/data/share/jupyter/labextensions/mito_ai/static/style_index_js.76efcc5c3be4056457ee.js → mito_ai-0.1.37.data/data/share/jupyter/labextensions/mito_ai/static/style_index_js.5876024bb17dbd6a3ee6.js +6 -2
- mito_ai-0.1.37.data/data/share/jupyter/labextensions/mito_ai/static/style_index_js.5876024bb17dbd6a3ee6.js.map +1 -0
- {mito_ai-0.1.36.dist-info → mito_ai-0.1.37.dist-info}/METADATA +1 -1
- {mito_ai-0.1.36.dist-info → mito_ai-0.1.37.dist-info}/RECORD +51 -38
- mito_ai-0.1.36.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.a20772bc113422d0f505.js.map +0 -1
- mito_ai-0.1.36.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.5c9333902dce30642119.js.map +0 -1
- mito_ai-0.1.36.data/data/share/jupyter/labextensions/mito_ai/static/style_index_js.76efcc5c3be4056457ee.js.map +0 -1
- {mito_ai-0.1.36.data → mito_ai-0.1.37.data}/data/etc/jupyter/jupyter_server_config.d/mito_ai.json +0 -0
- {mito_ai-0.1.36.data → mito_ai-0.1.37.data}/data/share/jupyter/labextensions/mito_ai/schemas/mito_ai/toolbar-buttons.json +0 -0
- {mito_ai-0.1.36.data → mito_ai-0.1.37.data}/data/share/jupyter/labextensions/mito_ai/static/style.js +0 -0
- {mito_ai-0.1.36.data → mito_ai-0.1.37.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_semver_index_js.9795f79265ddb416864b.js +0 -0
- {mito_ai-0.1.36.data → mito_ai-0.1.37.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_semver_index_js.9795f79265ddb416864b.js.map +0 -0
- {mito_ai-0.1.36.data → mito_ai-0.1.37.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_vscode-diff_dist_index_js.ea55f1f9346638aafbcf.js +0 -0
- {mito_ai-0.1.36.data → mito_ai-0.1.37.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.36.dist-info → mito_ai-0.1.37.dist-info}/WHEEL +0 -0
- {mito_ai-0.1.36.dist-info → mito_ai-0.1.37.dist-info}/entry_points.txt +0 -0
- {mito_ai-0.1.36.dist-info → mito_ai-0.1.37.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,265 @@
|
|
|
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
|
+
from unittest.mock import patch, AsyncMock
|
|
6
|
+
from mito_ai.streamlit_conversion.streamlit_agent_handler import (
|
|
7
|
+
StreamlitCodeGeneration,
|
|
8
|
+
streamlit_handler
|
|
9
|
+
)
|
|
10
|
+
from typing import cast
|
|
11
|
+
|
|
12
|
+
# Add this line to enable async support
|
|
13
|
+
pytest_plugins = ('pytest_asyncio',)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TestStreamlitCodeGeneration:
|
|
17
|
+
"""Test cases for StreamlitCodeGeneration class"""
|
|
18
|
+
|
|
19
|
+
def test_init(self):
|
|
20
|
+
"""Test StreamlitCodeGeneration initialization"""
|
|
21
|
+
notebook_data: dict = {"cells": [{"cell_type": "code", "source": ["import pandas"]}]}
|
|
22
|
+
generator = StreamlitCodeGeneration(notebook_data)
|
|
23
|
+
|
|
24
|
+
assert len(generator.messages) == 1
|
|
25
|
+
assert generator.messages[0]["role"] == "user"
|
|
26
|
+
# Access content properly as a list and cast to expected type
|
|
27
|
+
content_list = cast(list, generator.messages[0]["content"])
|
|
28
|
+
assert isinstance(content_list, list)
|
|
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"]
|
|
33
|
+
|
|
34
|
+
@pytest.mark.asyncio
|
|
35
|
+
@patch('mito_ai.streamlit_conversion.streamlit_agent_handler.stream_anthropic_completion_from_mito_server')
|
|
36
|
+
@pytest.mark.parametrize("mock_items,expected_result", [
|
|
37
|
+
(["Hello", " World", "!"], "Hello World!"),
|
|
38
|
+
([], ""),
|
|
39
|
+
(["Here's your code: import streamlit"], "Here's your code: import streamlit")
|
|
40
|
+
])
|
|
41
|
+
async def test_get_response_from_agent(self, mock_stream, mock_items, expected_result):
|
|
42
|
+
"""Test response from agent with different scenarios"""
|
|
43
|
+
# Mock the async generator
|
|
44
|
+
async def mock_async_gen():
|
|
45
|
+
for item in mock_items:
|
|
46
|
+
yield item
|
|
47
|
+
|
|
48
|
+
mock_stream.return_value = mock_async_gen()
|
|
49
|
+
|
|
50
|
+
notebook_data: dict = {"cells": []}
|
|
51
|
+
generator = StreamlitCodeGeneration(notebook_data)
|
|
52
|
+
|
|
53
|
+
result = await generator.get_response_from_agent(generator.messages)
|
|
54
|
+
|
|
55
|
+
assert result == expected_result
|
|
56
|
+
mock_stream.assert_called_once()
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@pytest.mark.asyncio
|
|
60
|
+
@patch('mito_ai.streamlit_conversion.streamlit_agent_handler.stream_anthropic_completion_from_mito_server')
|
|
61
|
+
async def test_get_response_from_agent_exception(self, mock_stream):
|
|
62
|
+
"""Test exception handling in get_response_from_agent"""
|
|
63
|
+
mock_stream.side_effect = Exception("API Error")
|
|
64
|
+
|
|
65
|
+
notebook_data: dict = {"cells": []}
|
|
66
|
+
generator = StreamlitCodeGeneration(notebook_data)
|
|
67
|
+
|
|
68
|
+
with pytest.raises(Exception, match="API Error"):
|
|
69
|
+
await generator.get_response_from_agent(generator.messages)
|
|
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"
|
|
88
|
+
|
|
89
|
+
@pytest.mark.asyncio
|
|
90
|
+
@patch('mito_ai.streamlit_conversion.streamlit_agent_handler.stream_anthropic_completion_from_mito_server')
|
|
91
|
+
async def test_generate_streamlit_code_success(self, mock_stream):
|
|
92
|
+
"""Test successful streamlit code generation"""
|
|
93
|
+
mock_response = "Here's your code:\n```python\nimport streamlit\nst.title('Hello')\n```"
|
|
94
|
+
|
|
95
|
+
async def mock_async_gen():
|
|
96
|
+
for item in [mock_response]:
|
|
97
|
+
yield item
|
|
98
|
+
|
|
99
|
+
mock_stream.return_value = mock_async_gen()
|
|
100
|
+
|
|
101
|
+
notebook_data: dict = {"cells": []}
|
|
102
|
+
generator = StreamlitCodeGeneration(notebook_data)
|
|
103
|
+
|
|
104
|
+
result = await generator.generate_streamlit_code()
|
|
105
|
+
|
|
106
|
+
expected_code = "import streamlit\nst.title('Hello')\n"
|
|
107
|
+
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
|
+
|
|
113
|
+
@pytest.mark.asyncio
|
|
114
|
+
@patch('mito_ai.streamlit_conversion.streamlit_agent_handler.stream_anthropic_completion_from_mito_server')
|
|
115
|
+
async def test_correct_error_in_generation_success(self, mock_stream):
|
|
116
|
+
"""Test successful error correction"""
|
|
117
|
+
mock_response = "Here's the corrected code:\n```python\nimport streamlit\nst.title('Fixed')\n```"
|
|
118
|
+
async def mock_async_gen():
|
|
119
|
+
for item in [mock_response]:
|
|
120
|
+
yield item
|
|
121
|
+
|
|
122
|
+
mock_stream.return_value = mock_async_gen()
|
|
123
|
+
|
|
124
|
+
notebook_data: dict = {"cells": []}
|
|
125
|
+
generator = StreamlitCodeGeneration(notebook_data)
|
|
126
|
+
|
|
127
|
+
result = await generator.correct_error_in_generation("ImportError: No module named 'pandas'")
|
|
128
|
+
|
|
129
|
+
expected_code = "import streamlit\nst.title('Fixed')\n"
|
|
130
|
+
assert result == expected_code
|
|
131
|
+
|
|
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
|
+
@pytest.mark.asyncio
|
|
138
|
+
@patch('mito_ai.streamlit_conversion.streamlit_agent_handler.stream_anthropic_completion_from_mito_server')
|
|
139
|
+
async def test_correct_error_in_generation_exception(self, mock_stream):
|
|
140
|
+
"""Test exception handling in error correction"""
|
|
141
|
+
mock_stream.side_effect = Exception("API Error")
|
|
142
|
+
|
|
143
|
+
notebook_data: dict = {"cells": []}
|
|
144
|
+
generator = StreamlitCodeGeneration(notebook_data)
|
|
145
|
+
|
|
146
|
+
with pytest.raises(Exception, match="API Error"):
|
|
147
|
+
await generator.correct_error_in_generation("Some error")
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class TestStreamlitHandler:
|
|
151
|
+
"""Test cases for streamlit_handler function"""
|
|
152
|
+
|
|
153
|
+
@pytest.mark.asyncio
|
|
154
|
+
@patch('mito_ai.streamlit_conversion.streamlit_agent_handler.parse_jupyter_notebook_to_extract_required_content')
|
|
155
|
+
@patch('mito_ai.streamlit_conversion.streamlit_agent_handler.StreamlitCodeGeneration')
|
|
156
|
+
@patch('mito_ai.streamlit_conversion.streamlit_agent_handler.streamlit_code_validator')
|
|
157
|
+
@patch('mito_ai.streamlit_conversion.streamlit_agent_handler.create_app_file')
|
|
158
|
+
async def test_streamlit_handler_success(self, mock_create_file, mock_validator, mock_generator_class, mock_parse):
|
|
159
|
+
"""Test successful streamlit handler execution"""
|
|
160
|
+
# Mock notebook parsing
|
|
161
|
+
mock_notebook_data: dict = {"cells": [{"cell_type": "code", "source": ["import pandas"]}]}
|
|
162
|
+
mock_parse.return_value = mock_notebook_data
|
|
163
|
+
|
|
164
|
+
# Mock code generation
|
|
165
|
+
mock_generator = AsyncMock()
|
|
166
|
+
mock_generator.generate_streamlit_code.return_value = "import streamlit\nst.title('Test')"
|
|
167
|
+
mock_generator_class.return_value = mock_generator
|
|
168
|
+
|
|
169
|
+
# Mock validation (no errors)
|
|
170
|
+
mock_validator.return_value = (False, "")
|
|
171
|
+
|
|
172
|
+
# Mock file creation
|
|
173
|
+
mock_create_file.return_value = (True, "File created successfully")
|
|
174
|
+
|
|
175
|
+
result = await streamlit_handler("/path/to/notebook.ipynb", "/path/to/app")
|
|
176
|
+
|
|
177
|
+
assert result[0] is True
|
|
178
|
+
assert "File created successfully" in result[1]
|
|
179
|
+
|
|
180
|
+
# Verify calls
|
|
181
|
+
mock_parse.assert_called_once_with("/path/to/notebook.ipynb")
|
|
182
|
+
mock_generator_class.assert_called_once_with(mock_notebook_data)
|
|
183
|
+
mock_generator.generate_streamlit_code.assert_called_once()
|
|
184
|
+
mock_validator.assert_called_once_with("import streamlit\nst.title('Test')")
|
|
185
|
+
mock_create_file.assert_called_once_with("/path/to/app", "import streamlit\nst.title('Test')")
|
|
186
|
+
|
|
187
|
+
@pytest.mark.asyncio
|
|
188
|
+
@patch('mito_ai.streamlit_conversion.streamlit_agent_handler.parse_jupyter_notebook_to_extract_required_content')
|
|
189
|
+
@patch('mito_ai.streamlit_conversion.streamlit_agent_handler.StreamlitCodeGeneration')
|
|
190
|
+
@patch('mito_ai.streamlit_conversion.streamlit_agent_handler.streamlit_code_validator')
|
|
191
|
+
async def test_streamlit_handler_max_retries_exceeded(self, mock_validator, mock_generator_class, mock_parse):
|
|
192
|
+
"""Test streamlit handler when max retries are exceeded"""
|
|
193
|
+
# Mock notebook parsing
|
|
194
|
+
mock_notebook_data: dict = {"cells": []}
|
|
195
|
+
mock_parse.return_value = mock_notebook_data
|
|
196
|
+
|
|
197
|
+
# Mock code generation
|
|
198
|
+
mock_generator = AsyncMock()
|
|
199
|
+
mock_generator.generate_streamlit_code.return_value = "import streamlit\nst.title('Test')"
|
|
200
|
+
mock_generator.correct_error_in_generation.return_value = "import streamlit\nst.title('Fixed')"
|
|
201
|
+
mock_generator_class.return_value = mock_generator
|
|
202
|
+
|
|
203
|
+
# Mock validation (always errors)
|
|
204
|
+
mock_validator.return_value = (True, "Persistent error")
|
|
205
|
+
|
|
206
|
+
result = await streamlit_handler("/path/to/notebook.ipynb", "/path/to/app")
|
|
207
|
+
|
|
208
|
+
assert result[0] is False
|
|
209
|
+
assert "Error generating streamlit code by agent" in result[1]
|
|
210
|
+
|
|
211
|
+
# Verify that error correction was called 5 times (max retries)
|
|
212
|
+
assert mock_generator.correct_error_in_generation.call_count == 5
|
|
213
|
+
|
|
214
|
+
@pytest.mark.asyncio
|
|
215
|
+
@patch('mito_ai.streamlit_conversion.streamlit_agent_handler.parse_jupyter_notebook_to_extract_required_content')
|
|
216
|
+
@patch('mito_ai.streamlit_conversion.streamlit_agent_handler.StreamlitCodeGeneration')
|
|
217
|
+
@patch('mito_ai.streamlit_conversion.streamlit_agent_handler.streamlit_code_validator')
|
|
218
|
+
@patch('mito_ai.streamlit_conversion.streamlit_agent_handler.create_app_file')
|
|
219
|
+
async def test_streamlit_handler_file_creation_failure(self, mock_create_file, mock_validator, mock_generator_class, mock_parse):
|
|
220
|
+
"""Test streamlit handler when file creation fails"""
|
|
221
|
+
# Mock notebook parsing
|
|
222
|
+
mock_notebook_data: dict = {"cells": []}
|
|
223
|
+
mock_parse.return_value = mock_notebook_data
|
|
224
|
+
|
|
225
|
+
# Mock code generation
|
|
226
|
+
mock_generator = AsyncMock()
|
|
227
|
+
mock_generator.generate_streamlit_code.return_value = "import streamlit\nst.title('Test')"
|
|
228
|
+
mock_generator_class.return_value = mock_generator
|
|
229
|
+
|
|
230
|
+
# Mock validation (no errors)
|
|
231
|
+
mock_validator.return_value = (False, "")
|
|
232
|
+
|
|
233
|
+
# Mock file creation failure
|
|
234
|
+
mock_create_file.return_value = (False, "Permission denied")
|
|
235
|
+
|
|
236
|
+
result = await streamlit_handler("/path/to/notebook.ipynb", "/path/to/app")
|
|
237
|
+
|
|
238
|
+
assert result[0] is False
|
|
239
|
+
assert "Permission denied" in result[1]
|
|
240
|
+
|
|
241
|
+
@pytest.mark.asyncio
|
|
242
|
+
@patch('mito_ai.streamlit_conversion.streamlit_agent_handler.parse_jupyter_notebook_to_extract_required_content')
|
|
243
|
+
async def test_streamlit_handler_parse_notebook_exception(self, mock_parse):
|
|
244
|
+
"""Test streamlit handler when notebook parsing fails"""
|
|
245
|
+
mock_parse.side_effect = FileNotFoundError("Notebook not found")
|
|
246
|
+
|
|
247
|
+
with pytest.raises(FileNotFoundError, match="Notebook not found"):
|
|
248
|
+
await streamlit_handler("/path/to/notebook.ipynb", "/path/to/app")
|
|
249
|
+
|
|
250
|
+
@pytest.mark.asyncio
|
|
251
|
+
@patch('mito_ai.streamlit_conversion.streamlit_agent_handler.parse_jupyter_notebook_to_extract_required_content')
|
|
252
|
+
@patch('mito_ai.streamlit_conversion.streamlit_agent_handler.StreamlitCodeGeneration')
|
|
253
|
+
async def test_streamlit_handler_generation_exception(self, mock_generator_class, mock_parse):
|
|
254
|
+
"""Test streamlit handler when code generation fails"""
|
|
255
|
+
# Mock notebook parsing
|
|
256
|
+
mock_notebook_data: dict = {"cells": []}
|
|
257
|
+
mock_parse.return_value = mock_notebook_data
|
|
258
|
+
|
|
259
|
+
# Mock code generation failure
|
|
260
|
+
mock_generator = AsyncMock()
|
|
261
|
+
mock_generator.generate_streamlit_code.side_effect = Exception("Generation failed")
|
|
262
|
+
mock_generator_class.return_value = mock_generator
|
|
263
|
+
|
|
264
|
+
with pytest.raises(Exception, match="Generation failed"):
|
|
265
|
+
await streamlit_handler("/path/to/notebook.ipynb", "/path/to/app")
|
|
@@ -0,0 +1,197 @@
|
|
|
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 json
|
|
6
|
+
import tempfile
|
|
7
|
+
import os
|
|
8
|
+
from unittest.mock import patch, mock_open
|
|
9
|
+
from mito_ai.streamlit_conversion.streamlit_utils import (
|
|
10
|
+
extract_code_blocks,
|
|
11
|
+
create_app_file,
|
|
12
|
+
parse_jupyter_notebook_to_extract_required_content
|
|
13
|
+
)
|
|
14
|
+
from typing import Dict, Any
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class TestExtractCodeBlocks:
|
|
18
|
+
"""Test cases for extract_code_blocks function"""
|
|
19
|
+
|
|
20
|
+
def test_extract_code_blocks_with_python_blocks(self):
|
|
21
|
+
"""Test extracting code from message with python code blocks"""
|
|
22
|
+
message = "Here's some code:\n```python\nimport streamlit\nst.title('Hello')\n```\nThat's it!"
|
|
23
|
+
result = extract_code_blocks(message)
|
|
24
|
+
expected = "import streamlit\nst.title('Hello')\n"
|
|
25
|
+
assert result == expected
|
|
26
|
+
|
|
27
|
+
def test_extract_code_blocks_without_python_blocks(self):
|
|
28
|
+
"""Test when message doesn't contain python code blocks"""
|
|
29
|
+
message = "This is just regular text without code blocks"
|
|
30
|
+
result = extract_code_blocks(message)
|
|
31
|
+
assert result == message
|
|
32
|
+
|
|
33
|
+
def test_extract_code_blocks_empty_message(self):
|
|
34
|
+
"""Test with empty message"""
|
|
35
|
+
message = ""
|
|
36
|
+
result = extract_code_blocks(message)
|
|
37
|
+
assert result == message
|
|
38
|
+
|
|
39
|
+
def test_extract_code_blocks_multiple_blocks(self):
|
|
40
|
+
"""Test extracting from first python block when multiple exist"""
|
|
41
|
+
message = "```python\ncode1\n```\n```python\ncode2\n```"
|
|
42
|
+
result = extract_code_blocks(message)
|
|
43
|
+
expected = "code1\n\ncode2\n"
|
|
44
|
+
assert result == expected
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class TestCreateAppFile:
|
|
48
|
+
"""Test cases for create_app_file function"""
|
|
49
|
+
|
|
50
|
+
def test_create_app_file_success(self, tmp_path):
|
|
51
|
+
"""Test successful file creation"""
|
|
52
|
+
file_path = str(tmp_path)
|
|
53
|
+
code = "import streamlit\nst.title('Test')"
|
|
54
|
+
|
|
55
|
+
success, message = create_app_file(file_path, code)
|
|
56
|
+
|
|
57
|
+
assert success is True
|
|
58
|
+
assert "Successfully created" in message
|
|
59
|
+
|
|
60
|
+
# Verify file was created with correct content
|
|
61
|
+
app_file_path = os.path.join(file_path, "app.py")
|
|
62
|
+
assert os.path.exists(app_file_path)
|
|
63
|
+
|
|
64
|
+
with open(app_file_path, 'r') as f:
|
|
65
|
+
content = f.read()
|
|
66
|
+
assert content == code
|
|
67
|
+
|
|
68
|
+
def test_create_app_file_io_error(self):
|
|
69
|
+
"""Test file creation with IO error"""
|
|
70
|
+
file_path = "/nonexistent/path/that/should/fail"
|
|
71
|
+
code = "import streamlit"
|
|
72
|
+
|
|
73
|
+
success, message = create_app_file(file_path, code)
|
|
74
|
+
|
|
75
|
+
assert success is False
|
|
76
|
+
assert "Error creating file" in message
|
|
77
|
+
|
|
78
|
+
@patch('builtins.open', side_effect=Exception("Unexpected error"))
|
|
79
|
+
def test_create_app_file_unexpected_error(self, mock_open):
|
|
80
|
+
"""Test file creation with unexpected error"""
|
|
81
|
+
file_path = "/tmp/test"
|
|
82
|
+
code = "import streamlit"
|
|
83
|
+
|
|
84
|
+
success, message = create_app_file(file_path, code)
|
|
85
|
+
|
|
86
|
+
assert success is False
|
|
87
|
+
assert "Unexpected error" in message
|
|
88
|
+
|
|
89
|
+
def test_create_app_file_empty_code(self, tmp_path):
|
|
90
|
+
"""Test creating file with empty code"""
|
|
91
|
+
file_path = str(tmp_path)
|
|
92
|
+
code = ""
|
|
93
|
+
|
|
94
|
+
success, message = create_app_file(file_path, code)
|
|
95
|
+
|
|
96
|
+
assert success is True
|
|
97
|
+
assert "Successfully created" in message
|
|
98
|
+
|
|
99
|
+
app_file_path = os.path.join(file_path, "app.py")
|
|
100
|
+
with open(app_file_path, 'r') as f:
|
|
101
|
+
content = f.read()
|
|
102
|
+
assert content == ""
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class TestParseJupyterNotebookToExtractRequiredContent:
|
|
106
|
+
"""Test cases for parse_jupyter_notebook_to_extract_required_content function"""
|
|
107
|
+
|
|
108
|
+
def test_parse_valid_notebook(self, tmp_path):
|
|
109
|
+
"""Test parsing a valid notebook with cells"""
|
|
110
|
+
notebook_data: Dict[str, Any] = {
|
|
111
|
+
"cells": [
|
|
112
|
+
{
|
|
113
|
+
"cell_type": "code",
|
|
114
|
+
"source": ["import pandas as pd\n", "df = pd.DataFrame()\n"],
|
|
115
|
+
"metadata": {"some": "metadata"},
|
|
116
|
+
"execution_count": 1
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
"cell_type": "markdown",
|
|
120
|
+
"source": ["# Title\n", "Some text\n"],
|
|
121
|
+
"metadata": {"another": "metadata"}
|
|
122
|
+
}
|
|
123
|
+
],
|
|
124
|
+
"metadata": {"notebook_metadata": "value"},
|
|
125
|
+
"nbformat": 4
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
notebook_path = tmp_path / "test.ipynb"
|
|
129
|
+
with open(notebook_path, 'w') as f:
|
|
130
|
+
json.dump(notebook_data, f)
|
|
131
|
+
|
|
132
|
+
result = parse_jupyter_notebook_to_extract_required_content(str(notebook_path))
|
|
133
|
+
|
|
134
|
+
# Check that only cell_type and source are preserved
|
|
135
|
+
assert len(result['cells']) == 2
|
|
136
|
+
assert result['cells'][0]['cell_type'] == 'code'
|
|
137
|
+
assert result['cells'][0]['source'] == ["import pandas as pd\n", "df = pd.DataFrame()\n"]
|
|
138
|
+
assert 'metadata' not in result['cells'][0]
|
|
139
|
+
assert 'execution_count' not in result['cells'][0]
|
|
140
|
+
|
|
141
|
+
assert result['cells'][1]['cell_type'] == 'markdown'
|
|
142
|
+
assert result['cells'][1]['source'] == ["# Title\n", "Some text\n"]
|
|
143
|
+
assert 'metadata' not in result['cells'][1]
|
|
144
|
+
|
|
145
|
+
def test_parse_notebook_file_not_found(self):
|
|
146
|
+
"""Test parsing non-existent notebook file"""
|
|
147
|
+
with pytest.raises(FileNotFoundError, match="Notebook file not found"):
|
|
148
|
+
parse_jupyter_notebook_to_extract_required_content("/nonexistent/path/notebook.ipynb")
|
|
149
|
+
|
|
150
|
+
def test_parse_notebook_with_missing_cell_fields(self, tmp_path):
|
|
151
|
+
"""Test parsing notebook where cells are missing cell_type or source"""
|
|
152
|
+
notebook_data: Dict[str, Any] = {
|
|
153
|
+
"cells": [
|
|
154
|
+
{
|
|
155
|
+
"cell_type": "code"
|
|
156
|
+
# Missing source field
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
"source": ["some text"]
|
|
160
|
+
# Missing cell_type field
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
"cell_type": "markdown",
|
|
164
|
+
"source": ["# Title"]
|
|
165
|
+
}
|
|
166
|
+
]
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
notebook_path = tmp_path / "test.ipynb"
|
|
170
|
+
with open(notebook_path, 'w') as f:
|
|
171
|
+
json.dump(notebook_data, f)
|
|
172
|
+
|
|
173
|
+
result = parse_jupyter_notebook_to_extract_required_content(str(notebook_path))
|
|
174
|
+
|
|
175
|
+
assert len(result['cells']) == 3
|
|
176
|
+
assert result['cells'][0]['cell_type'] == 'code'
|
|
177
|
+
assert result['cells'][0]['source'] == [] # Default empty list
|
|
178
|
+
|
|
179
|
+
assert result['cells'][1]['cell_type'] == '' # Default empty string
|
|
180
|
+
assert result['cells'][1]['source'] == ["some text"]
|
|
181
|
+
|
|
182
|
+
assert result['cells'][2]['cell_type'] == 'markdown'
|
|
183
|
+
assert result['cells'][2]['source'] == ["# Title"]
|
|
184
|
+
|
|
185
|
+
def test_parse_empty_notebook(self, tmp_path):
|
|
186
|
+
"""Test parsing notebook with empty cells list"""
|
|
187
|
+
notebook_data: Dict[str, Any] = {
|
|
188
|
+
"cells": []
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
notebook_path = tmp_path / "test.ipynb"
|
|
192
|
+
with open(notebook_path, 'w') as f:
|
|
193
|
+
json.dump(notebook_data, f)
|
|
194
|
+
|
|
195
|
+
result = parse_jupyter_notebook_to_extract_required_content(str(notebook_path))
|
|
196
|
+
|
|
197
|
+
assert result['cells'] == []
|