mito-ai 0.1.37__py3-none-any.whl → 0.1.38__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 +9 -1
- mito_ai/_version.py +1 -1
- mito_ai/app_builder/handlers.py +30 -30
- mito_ai/app_builder/models.py +1 -1
- mito_ai/log/handlers.py +10 -3
- mito_ai/log/urls.py +3 -3
- mito_ai/streamlit_conversion/streamlit_agent_handler.py +22 -6
- mito_ai/streamlit_conversion/streamlit_system_prompt.py +11 -0
- mito_ai/streamlit_conversion/streamlit_utils.py +8 -6
- mito_ai/streamlit_conversion/validate_and_run_streamlit_code.py +1 -0
- mito_ai/streamlit_preview/__init__.py +7 -0
- mito_ai/streamlit_preview/handlers.py +161 -0
- mito_ai/streamlit_preview/manager.py +159 -0
- mito_ai/streamlit_preview/urls.py +22 -0
- mito_ai/tests/streamlit_conversion/test_streamlit_agent_handler.py +16 -15
- mito_ai/tests/streamlit_conversion/test_streamlit_utils.py +4 -5
- mito_ai/tests/streamlit_preview/test_streamlit_preview_manager.py +302 -0
- mito_ai/utils/telemetry_utils.py +28 -1
- {mito_ai-0.1.37.data → mito_ai-0.1.38.data}/data/share/jupyter/labextensions/mito_ai/build_log.json +1 -1
- {mito_ai-0.1.37.data → mito_ai-0.1.38.data}/data/share/jupyter/labextensions/mito_ai/package.json +2 -2
- {mito_ai-0.1.37.data → mito_ai-0.1.38.data}/data/share/jupyter/labextensions/mito_ai/schemas/mito_ai/package.json.orig +1 -1
- {mito_ai-0.1.37.data → mito_ai-0.1.38.data}/data/share/jupyter/labextensions/mito_ai/schemas/mito_ai/toolbar-buttons.json +6 -1
- mito_ai-0.1.37.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.831f63b48760c7119b9b.js → mito_ai-0.1.38.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.5d1d7c234e2dc7c9d97b.js +542 -56
- mito_ai-0.1.38.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.5d1d7c234e2dc7c9d97b.js.map +1 -0
- mito_ai-0.1.37.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.93ecc9bc0edba61535cc.js → mito_ai-0.1.38.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.bcce4ea34631acf6dbbe.js +5 -5
- mito_ai-0.1.37.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.93ecc9bc0edba61535cc.js.map → mito_ai-0.1.38.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.bcce4ea34631acf6dbbe.js.map +1 -1
- {mito_ai-0.1.37.dist-info → mito_ai-0.1.38.dist-info}/METADATA +1 -1
- {mito_ai-0.1.37.dist-info → mito_ai-0.1.38.dist-info}/RECORD +39 -34
- mito_ai-0.1.37.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.831f63b48760c7119b9b.js.map +0 -1
- {mito_ai-0.1.37.data → mito_ai-0.1.38.data}/data/etc/jupyter/jupyter_server_config.d/mito_ai.json +0 -0
- {mito_ai-0.1.37.data → mito_ai-0.1.38.data}/data/share/jupyter/labextensions/mito_ai/static/style.js +0 -0
- {mito_ai-0.1.37.data → mito_ai-0.1.38.data}/data/share/jupyter/labextensions/mito_ai/static/style_index_js.5876024bb17dbd6a3ee6.js +0 -0
- {mito_ai-0.1.37.data → mito_ai-0.1.38.data}/data/share/jupyter/labextensions/mito_ai/static/style_index_js.5876024bb17dbd6a3ee6.js.map +0 -0
- {mito_ai-0.1.37.data → mito_ai-0.1.38.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_semver_index_js.9795f79265ddb416864b.js +0 -0
- {mito_ai-0.1.37.data → mito_ai-0.1.38.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_semver_index_js.9795f79265ddb416864b.js.map +0 -0
- {mito_ai-0.1.37.data → mito_ai-0.1.38.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_vscode-diff_dist_index_js.ea55f1f9346638aafbcf.js +0 -0
- {mito_ai-0.1.37.data → mito_ai-0.1.38.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.37.dist-info → mito_ai-0.1.38.dist-info}/WHEEL +0 -0
- {mito_ai-0.1.37.dist-info → mito_ai-0.1.38.dist-info}/entry_points.txt +0 -0
- {mito_ai-0.1.37.dist-info → mito_ai-0.1.38.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Copyright (c) Saga Inc.
|
|
2
|
+
# Distributed under the terms of the GNU Affero General Public License v3.0 License.
|
|
3
|
+
|
|
4
|
+
from typing import Any, List, Tuple
|
|
5
|
+
from jupyter_server.utils import url_path_join
|
|
6
|
+
from mito_ai.streamlit_preview.handlers import StreamlitPreviewHandler
|
|
7
|
+
|
|
8
|
+
def get_streamlit_preview_urls(base_url: str) -> List[Tuple[str, Any, dict]]:
|
|
9
|
+
"""Get all streamlit preview related URL patterns.
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
base_url: The base URL for the Jupyter server
|
|
13
|
+
|
|
14
|
+
Returns:
|
|
15
|
+
List of (url_pattern, handler_class, handler_kwargs) tuples
|
|
16
|
+
"""
|
|
17
|
+
BASE_URL = base_url + "/mito-ai"
|
|
18
|
+
|
|
19
|
+
return [
|
|
20
|
+
(url_path_join(BASE_URL, "streamlit-preview"), StreamlitPreviewHandler, {}),
|
|
21
|
+
(url_path_join(BASE_URL, "streamlit-preview/(.+)"), StreamlitPreviewHandler, {}),
|
|
22
|
+
]
|
|
@@ -170,19 +170,19 @@ class TestStreamlitHandler:
|
|
|
170
170
|
mock_validator.return_value = (False, "")
|
|
171
171
|
|
|
172
172
|
# Mock file creation
|
|
173
|
-
mock_create_file.return_value = (True, "File created successfully")
|
|
173
|
+
mock_create_file.return_value = (True, "/path/to/app", "File created successfully")
|
|
174
174
|
|
|
175
|
-
result = await streamlit_handler("/path/to/notebook.ipynb"
|
|
175
|
+
result = await streamlit_handler("/path/to/notebook.ipynb")
|
|
176
176
|
|
|
177
177
|
assert result[0] is True
|
|
178
|
-
assert "File created successfully" in result[
|
|
178
|
+
assert "File created successfully" in result[2]
|
|
179
179
|
|
|
180
180
|
# Verify calls
|
|
181
181
|
mock_parse.assert_called_once_with("/path/to/notebook.ipynb")
|
|
182
182
|
mock_generator_class.assert_called_once_with(mock_notebook_data)
|
|
183
183
|
mock_generator.generate_streamlit_code.assert_called_once()
|
|
184
184
|
mock_validator.assert_called_once_with("import streamlit\nst.title('Test')")
|
|
185
|
-
mock_create_file.assert_called_once_with("/path/to
|
|
185
|
+
mock_create_file.assert_called_once_with("/path/to", "import streamlit\nst.title('Test')")
|
|
186
186
|
|
|
187
187
|
@pytest.mark.asyncio
|
|
188
188
|
@patch('mito_ai.streamlit_conversion.streamlit_agent_handler.parse_jupyter_notebook_to_extract_required_content')
|
|
@@ -193,20 +193,21 @@ class TestStreamlitHandler:
|
|
|
193
193
|
# Mock notebook parsing
|
|
194
194
|
mock_notebook_data: dict = {"cells": []}
|
|
195
195
|
mock_parse.return_value = mock_notebook_data
|
|
196
|
-
|
|
196
|
+
|
|
197
197
|
# Mock code generation
|
|
198
198
|
mock_generator = AsyncMock()
|
|
199
199
|
mock_generator.generate_streamlit_code.return_value = "import streamlit\nst.title('Test')"
|
|
200
200
|
mock_generator.correct_error_in_generation.return_value = "import streamlit\nst.title('Fixed')"
|
|
201
201
|
mock_generator_class.return_value = mock_generator
|
|
202
|
-
|
|
203
|
-
# Mock validation (always errors)
|
|
202
|
+
|
|
203
|
+
# Mock validation (always errors) - FIX: Return only 2 values
|
|
204
204
|
mock_validator.return_value = (True, "Persistent error")
|
|
205
|
+
|
|
206
|
+
result = await streamlit_handler("/path/to/notebook.ipynb")
|
|
205
207
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
+
# Verify the result indicates failure
|
|
208
209
|
assert result[0] is False
|
|
209
|
-
assert "Error generating streamlit code by agent" in result[
|
|
210
|
+
assert "Error generating streamlit code by agent" in result[2]
|
|
210
211
|
|
|
211
212
|
# Verify that error correction was called 5 times (max retries)
|
|
212
213
|
assert mock_generator.correct_error_in_generation.call_count == 5
|
|
@@ -231,12 +232,12 @@ class TestStreamlitHandler:
|
|
|
231
232
|
mock_validator.return_value = (False, "")
|
|
232
233
|
|
|
233
234
|
# Mock file creation failure
|
|
234
|
-
mock_create_file.return_value = (False, "Permission denied")
|
|
235
|
+
mock_create_file.return_value = (False, None, "Permission denied")
|
|
235
236
|
|
|
236
|
-
result = await streamlit_handler("/path/to/notebook.ipynb"
|
|
237
|
+
result = await streamlit_handler("/path/to/notebook.ipynb")
|
|
237
238
|
|
|
238
239
|
assert result[0] is False
|
|
239
|
-
assert "Permission denied" in result[
|
|
240
|
+
assert "Permission denied" in result[2]
|
|
240
241
|
|
|
241
242
|
@pytest.mark.asyncio
|
|
242
243
|
@patch('mito_ai.streamlit_conversion.streamlit_agent_handler.parse_jupyter_notebook_to_extract_required_content')
|
|
@@ -245,7 +246,7 @@ class TestStreamlitHandler:
|
|
|
245
246
|
mock_parse.side_effect = FileNotFoundError("Notebook not found")
|
|
246
247
|
|
|
247
248
|
with pytest.raises(FileNotFoundError, match="Notebook not found"):
|
|
248
|
-
await streamlit_handler("/path/to/notebook.ipynb"
|
|
249
|
+
await streamlit_handler("/path/to/notebook.ipynb")
|
|
249
250
|
|
|
250
251
|
@pytest.mark.asyncio
|
|
251
252
|
@patch('mito_ai.streamlit_conversion.streamlit_agent_handler.parse_jupyter_notebook_to_extract_required_content')
|
|
@@ -262,4 +263,4 @@ class TestStreamlitHandler:
|
|
|
262
263
|
mock_generator_class.return_value = mock_generator
|
|
263
264
|
|
|
264
265
|
with pytest.raises(Exception, match="Generation failed"):
|
|
265
|
-
await streamlit_handler("/path/to/notebook.ipynb"
|
|
266
|
+
await streamlit_handler("/path/to/notebook.ipynb")
|
|
@@ -13,7 +13,6 @@ from mito_ai.streamlit_conversion.streamlit_utils import (
|
|
|
13
13
|
)
|
|
14
14
|
from typing import Dict, Any
|
|
15
15
|
|
|
16
|
-
|
|
17
16
|
class TestExtractCodeBlocks:
|
|
18
17
|
"""Test cases for extract_code_blocks function"""
|
|
19
18
|
|
|
@@ -52,7 +51,7 @@ class TestCreateAppFile:
|
|
|
52
51
|
file_path = str(tmp_path)
|
|
53
52
|
code = "import streamlit\nst.title('Test')"
|
|
54
53
|
|
|
55
|
-
success, message = create_app_file(file_path, code)
|
|
54
|
+
success, app_path, message = create_app_file(file_path, code)
|
|
56
55
|
|
|
57
56
|
assert success is True
|
|
58
57
|
assert "Successfully created" in message
|
|
@@ -70,7 +69,7 @@ class TestCreateAppFile:
|
|
|
70
69
|
file_path = "/nonexistent/path/that/should/fail"
|
|
71
70
|
code = "import streamlit"
|
|
72
71
|
|
|
73
|
-
success, message = create_app_file(file_path, code)
|
|
72
|
+
success, app_path, message = create_app_file(file_path, code)
|
|
74
73
|
|
|
75
74
|
assert success is False
|
|
76
75
|
assert "Error creating file" in message
|
|
@@ -81,7 +80,7 @@ class TestCreateAppFile:
|
|
|
81
80
|
file_path = "/tmp/test"
|
|
82
81
|
code = "import streamlit"
|
|
83
82
|
|
|
84
|
-
success, message = create_app_file(file_path, code)
|
|
83
|
+
success, app_path, message = create_app_file(file_path, code)
|
|
85
84
|
|
|
86
85
|
assert success is False
|
|
87
86
|
assert "Unexpected error" in message
|
|
@@ -91,7 +90,7 @@ class TestCreateAppFile:
|
|
|
91
90
|
file_path = str(tmp_path)
|
|
92
91
|
code = ""
|
|
93
92
|
|
|
94
|
-
success, message = create_app_file(file_path, code)
|
|
93
|
+
success, app_path, message = create_app_file(file_path, code)
|
|
95
94
|
|
|
96
95
|
assert success is True
|
|
97
96
|
assert "Successfully created" in message
|
|
@@ -0,0 +1,302 @@
|
|
|
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 time
|
|
6
|
+
import tempfile
|
|
7
|
+
import os
|
|
8
|
+
import shutil
|
|
9
|
+
import subprocess
|
|
10
|
+
import threading
|
|
11
|
+
import requests
|
|
12
|
+
from unittest.mock import Mock, patch, MagicMock
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
from mito_ai.streamlit_preview.manager import (
|
|
16
|
+
StreamlitPreviewManager,
|
|
17
|
+
PreviewProcess,
|
|
18
|
+
get_preview_manager
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class TestStreamlitPreviewManager:
|
|
23
|
+
"""Test cases for StreamlitPreviewManager."""
|
|
24
|
+
|
|
25
|
+
@pytest.fixture
|
|
26
|
+
def manager(self):
|
|
27
|
+
"""Create a fresh manager instance for each test."""
|
|
28
|
+
return StreamlitPreviewManager()
|
|
29
|
+
|
|
30
|
+
@pytest.fixture
|
|
31
|
+
def sample_app_code(self):
|
|
32
|
+
"""Sample streamlit app code for testing."""
|
|
33
|
+
return """
|
|
34
|
+
import streamlit as st
|
|
35
|
+
|
|
36
|
+
st.title("Test App")
|
|
37
|
+
st.write("Hello, World!")
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
def test_init(self, manager):
|
|
41
|
+
"""Test manager initialization."""
|
|
42
|
+
assert manager._previews == {}
|
|
43
|
+
assert isinstance(manager._lock, type(threading.Lock()))
|
|
44
|
+
assert manager.log is not None
|
|
45
|
+
|
|
46
|
+
def test_get_free_port(self, manager):
|
|
47
|
+
"""Test getting a free port."""
|
|
48
|
+
port = manager.get_free_port()
|
|
49
|
+
assert isinstance(port, int)
|
|
50
|
+
assert port > 0
|
|
51
|
+
assert port < 65536
|
|
52
|
+
|
|
53
|
+
# Test that we get different ports
|
|
54
|
+
port2 = manager.get_free_port()
|
|
55
|
+
assert port != port2
|
|
56
|
+
|
|
57
|
+
@pytest.mark.parametrize("app_code,preview_id,expected_success", [
|
|
58
|
+
("import streamlit as st\nst.write('Hello')", "test_preview", True),
|
|
59
|
+
("", "empty_preview", True),
|
|
60
|
+
("import streamlit as st\n" * 1000 + "st.write('Large app')", "large_preview", True),
|
|
61
|
+
])
|
|
62
|
+
def test_start_streamlit_preview_success_cases(self, manager, app_code, preview_id, expected_success):
|
|
63
|
+
"""Test successful streamlit preview start with different app codes."""
|
|
64
|
+
with patch('subprocess.Popen') as mock_popen, \
|
|
65
|
+
patch('requests.get') as mock_requests_get, \
|
|
66
|
+
patch('tempfile.mkdtemp') as mock_mkdtemp:
|
|
67
|
+
|
|
68
|
+
# Setup mocks
|
|
69
|
+
app_directory = "/tmp/test_dir"
|
|
70
|
+
mock_mkdtemp.return_value = app_directory
|
|
71
|
+
mock_proc = Mock()
|
|
72
|
+
mock_proc.terminate.return_value = None
|
|
73
|
+
mock_proc.wait.return_value = None
|
|
74
|
+
mock_popen.return_value = mock_proc
|
|
75
|
+
|
|
76
|
+
mock_response = Mock()
|
|
77
|
+
mock_response.status_code = 200
|
|
78
|
+
mock_requests_get.return_value = mock_response
|
|
79
|
+
|
|
80
|
+
# Test
|
|
81
|
+
success, message, port = manager.start_streamlit_preview(app_directory, preview_id)
|
|
82
|
+
|
|
83
|
+
# Assertions
|
|
84
|
+
assert success == expected_success
|
|
85
|
+
if expected_success:
|
|
86
|
+
assert "successfully" in message.lower()
|
|
87
|
+
assert isinstance(port, int)
|
|
88
|
+
assert port > 0
|
|
89
|
+
|
|
90
|
+
# Verify subprocess was called correctly
|
|
91
|
+
mock_popen.assert_called_once()
|
|
92
|
+
call_args = mock_popen.call_args
|
|
93
|
+
assert "streamlit" in call_args[0][0]
|
|
94
|
+
assert "run" in call_args[0][0]
|
|
95
|
+
assert "--server.headless" in call_args[0][0]
|
|
96
|
+
assert "--server.address" in call_args[0][0]
|
|
97
|
+
|
|
98
|
+
# Cleanup
|
|
99
|
+
manager.stop_preview(preview_id)
|
|
100
|
+
|
|
101
|
+
@pytest.mark.parametrize("exception_type,expected_message", [
|
|
102
|
+
(Exception("Temp dir creation failed"), "failed to start preview"),
|
|
103
|
+
(OSError("Permission denied"), "failed to start preview"),
|
|
104
|
+
(ValueError("Invalid argument"), "failed to start preview"),
|
|
105
|
+
])
|
|
106
|
+
def test_start_streamlit_preview_exceptions(self, manager, sample_app_code, exception_type, expected_message):
|
|
107
|
+
"""Test streamlit preview start with different exceptions."""
|
|
108
|
+
with patch('tempfile.mkdtemp', side_effect=exception_type):
|
|
109
|
+
app_directory = "/tmp/test_dir"
|
|
110
|
+
success, message, port = manager.start_streamlit_preview(app_directory, "test_preview")
|
|
111
|
+
|
|
112
|
+
assert success is False
|
|
113
|
+
assert expected_message in message.lower()
|
|
114
|
+
assert port is None
|
|
115
|
+
|
|
116
|
+
@pytest.mark.parametrize("preview_id,expected_result", [
|
|
117
|
+
("existing_preview", True),
|
|
118
|
+
("non_existent", False),
|
|
119
|
+
])
|
|
120
|
+
def test_stop_preview_scenarios(self, manager, sample_app_code, preview_id, expected_result):
|
|
121
|
+
"""Test stopping previews with different scenarios."""
|
|
122
|
+
if expected_result:
|
|
123
|
+
# Start a preview first
|
|
124
|
+
with patch('subprocess.Popen') as mock_popen, \
|
|
125
|
+
patch('requests.get') as mock_requests_get, \
|
|
126
|
+
patch('tempfile.mkdtemp') as mock_mkdtemp, \
|
|
127
|
+
patch('builtins.open', create=True) as mock_open, \
|
|
128
|
+
patch('os.path.exists') as mock_exists:
|
|
129
|
+
|
|
130
|
+
app_directory = "/tmp/test_dir"
|
|
131
|
+
mock_mkdtemp.return_value = app_directory
|
|
132
|
+
mock_proc = Mock()
|
|
133
|
+
mock_proc.terminate.return_value = None
|
|
134
|
+
mock_proc.wait.return_value = None
|
|
135
|
+
mock_popen.return_value = mock_proc
|
|
136
|
+
|
|
137
|
+
mock_response = Mock()
|
|
138
|
+
mock_response.status_code = 200
|
|
139
|
+
mock_requests_get.return_value = mock_response
|
|
140
|
+
|
|
141
|
+
# Mock file operations
|
|
142
|
+
mock_file = Mock()
|
|
143
|
+
mock_open.return_value.__enter__.return_value = mock_file
|
|
144
|
+
mock_exists.return_value = True
|
|
145
|
+
|
|
146
|
+
manager.start_streamlit_preview(app_directory, preview_id)
|
|
147
|
+
|
|
148
|
+
@pytest.mark.parametrize("process_behavior,expected_kill_called", [
|
|
149
|
+
(subprocess.TimeoutExpired("cmd", 5), True),
|
|
150
|
+
(None, False), # Normal termination
|
|
151
|
+
])
|
|
152
|
+
def test_stop_preview_process_behaviors(self, manager, sample_app_code, process_behavior, expected_kill_called):
|
|
153
|
+
"""Test stopping preview with different process behaviors."""
|
|
154
|
+
with patch('subprocess.Popen') as mock_popen, \
|
|
155
|
+
patch('requests.get') as mock_requests_get, \
|
|
156
|
+
patch('tempfile.mkdtemp') as mock_mkdtemp, \
|
|
157
|
+
patch('builtins.open', create=True) as mock_open, \
|
|
158
|
+
patch('os.path.exists') as mock_exists:
|
|
159
|
+
|
|
160
|
+
# Setup mocks for start
|
|
161
|
+
app_directory = "/tmp/test_dir"
|
|
162
|
+
mock_mkdtemp.return_value = app_directory
|
|
163
|
+
|
|
164
|
+
mock_proc = Mock()
|
|
165
|
+
mock_proc.terminate.return_value = None
|
|
166
|
+
mock_proc.wait.return_value = None
|
|
167
|
+
mock_popen.return_value = mock_proc
|
|
168
|
+
|
|
169
|
+
mock_response = Mock()
|
|
170
|
+
mock_response.status_code = 200
|
|
171
|
+
mock_requests_get.return_value = mock_response
|
|
172
|
+
|
|
173
|
+
# Mock file operations
|
|
174
|
+
mock_file = Mock()
|
|
175
|
+
mock_open.return_value.__enter__.return_value = mock_file
|
|
176
|
+
mock_exists.return_value = True
|
|
177
|
+
|
|
178
|
+
# Start a preview
|
|
179
|
+
manager.start_streamlit_preview(app_directory, "test_preview")
|
|
180
|
+
|
|
181
|
+
# Setup process behavior for stop
|
|
182
|
+
if process_behavior:
|
|
183
|
+
# Configure the mock to raise the exception when called with timeout
|
|
184
|
+
def wait_with_timeout(*args, **kwargs):
|
|
185
|
+
if 'timeout' in kwargs:
|
|
186
|
+
raise process_behavior
|
|
187
|
+
return None
|
|
188
|
+
mock_proc.wait.side_effect = wait_with_timeout
|
|
189
|
+
|
|
190
|
+
@pytest.mark.parametrize("preview_id,expected_found", [
|
|
191
|
+
("existing_preview", True),
|
|
192
|
+
("non_existent", False),
|
|
193
|
+
])
|
|
194
|
+
def test_get_preview_scenarios(self, manager, sample_app_code, preview_id, expected_found):
|
|
195
|
+
"""Test getting previews with different scenarios."""
|
|
196
|
+
if expected_found:
|
|
197
|
+
# Start a preview first
|
|
198
|
+
with patch('subprocess.Popen') as mock_popen, \
|
|
199
|
+
patch('requests.get') as mock_requests_get, \
|
|
200
|
+
patch('tempfile.mkdtemp') as mock_mkdtemp, \
|
|
201
|
+
patch('builtins.open', create=True) as mock_open, \
|
|
202
|
+
patch('os.path.exists') as mock_exists:
|
|
203
|
+
|
|
204
|
+
mock_mkdtemp.return_value = "/tmp/test_dir"
|
|
205
|
+
mock_proc = Mock()
|
|
206
|
+
mock_proc.terminate.return_value = None
|
|
207
|
+
mock_proc.wait.return_value = None
|
|
208
|
+
mock_popen.return_value = mock_proc
|
|
209
|
+
|
|
210
|
+
mock_response = Mock()
|
|
211
|
+
mock_response.status_code = 200
|
|
212
|
+
mock_requests_get.return_value = mock_response
|
|
213
|
+
|
|
214
|
+
# Mock file operations
|
|
215
|
+
mock_file = Mock()
|
|
216
|
+
mock_open.return_value.__enter__.return_value = mock_file
|
|
217
|
+
mock_exists.return_value = True
|
|
218
|
+
|
|
219
|
+
manager.start_streamlit_preview("/tmp/test_dir", preview_id)
|
|
220
|
+
|
|
221
|
+
preview = manager.get_preview(preview_id)
|
|
222
|
+
|
|
223
|
+
if expected_found:
|
|
224
|
+
assert preview is not None
|
|
225
|
+
assert isinstance(preview, PreviewProcess)
|
|
226
|
+
assert preview.port > 0
|
|
227
|
+
|
|
228
|
+
# Cleanup
|
|
229
|
+
manager.stop_preview(preview_id)
|
|
230
|
+
else:
|
|
231
|
+
assert preview is None
|
|
232
|
+
|
|
233
|
+
def test_preview_process_dataclass(self):
|
|
234
|
+
"""Test PreviewProcess dataclass."""
|
|
235
|
+
proc = Mock()
|
|
236
|
+
port = 8080
|
|
237
|
+
|
|
238
|
+
preview = PreviewProcess(
|
|
239
|
+
proc=proc,
|
|
240
|
+
port=port
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
assert preview.proc == proc
|
|
244
|
+
assert preview.port == port
|
|
245
|
+
|
|
246
|
+
def test_get_preview_manager_singleton(self):
|
|
247
|
+
"""Test that get_preview_manager returns the same instance."""
|
|
248
|
+
manager1 = get_preview_manager()
|
|
249
|
+
manager2 = get_preview_manager()
|
|
250
|
+
|
|
251
|
+
assert manager1 is manager2
|
|
252
|
+
assert isinstance(manager1, StreamlitPreviewManager)
|
|
253
|
+
|
|
254
|
+
@pytest.mark.parametrize("num_previews", [1, 2, 3])
|
|
255
|
+
def test_concurrent_previews(self, manager, sample_app_code, num_previews):
|
|
256
|
+
"""Test managing multiple concurrent previews."""
|
|
257
|
+
preview_ids = [f"preview_{i}" for i in range(num_previews)]
|
|
258
|
+
ports = []
|
|
259
|
+
|
|
260
|
+
with patch('subprocess.Popen') as mock_popen, \
|
|
261
|
+
patch('requests.get') as mock_requests_get, \
|
|
262
|
+
patch('tempfile.mkdtemp') as mock_mkdtemp, \
|
|
263
|
+
patch('builtins.open', create=True) as mock_open, \
|
|
264
|
+
patch('os.path.exists') as mock_exists:
|
|
265
|
+
|
|
266
|
+
# Setup mocks
|
|
267
|
+
mock_mkdtemp.return_value = "/tmp/test_dir"
|
|
268
|
+
mock_proc = Mock()
|
|
269
|
+
mock_proc.terminate.return_value = None
|
|
270
|
+
mock_proc.wait.return_value = None
|
|
271
|
+
mock_popen.return_value = mock_proc
|
|
272
|
+
|
|
273
|
+
mock_response = Mock()
|
|
274
|
+
mock_response.status_code = 200
|
|
275
|
+
mock_requests_get.return_value = mock_response
|
|
276
|
+
|
|
277
|
+
# Mock file operations
|
|
278
|
+
mock_file = Mock()
|
|
279
|
+
mock_open.return_value.__enter__.return_value = mock_file
|
|
280
|
+
mock_exists.return_value = True
|
|
281
|
+
|
|
282
|
+
# Start multiple previews
|
|
283
|
+
for preview_id in preview_ids:
|
|
284
|
+
success, _, port = manager.start_streamlit_preview("/tmp/test_dir", preview_id)
|
|
285
|
+
assert success is True
|
|
286
|
+
ports.append(port)
|
|
287
|
+
|
|
288
|
+
# Assertions
|
|
289
|
+
assert len(set(ports)) == num_previews # All ports should be different
|
|
290
|
+
|
|
291
|
+
# Check all previews exist
|
|
292
|
+
for preview_id in preview_ids:
|
|
293
|
+
assert manager.get_preview(preview_id) is not None
|
|
294
|
+
|
|
295
|
+
# Stop all previews
|
|
296
|
+
for preview_id in preview_ids:
|
|
297
|
+
assert manager.stop_preview(preview_id)
|
|
298
|
+
|
|
299
|
+
# Verify they're gone
|
|
300
|
+
for preview_id in preview_ids:
|
|
301
|
+
assert manager.get_preview(preview_id) is None
|
|
302
|
+
|
mito_ai/utils/telemetry_utils.py
CHANGED
|
@@ -174,7 +174,6 @@ def log(
|
|
|
174
174
|
If telemetry is not turned off and we are not running tests,
|
|
175
175
|
we log the ai event
|
|
176
176
|
"""
|
|
177
|
-
|
|
178
177
|
final_params: Dict[str, Any] = params or {}
|
|
179
178
|
|
|
180
179
|
# Then, make sure to add the user email
|
|
@@ -355,4 +354,32 @@ def log_db_connection_error(connection_type: str, error_message: str) -> None:
|
|
|
355
354
|
"error_message": error_message,
|
|
356
355
|
}
|
|
357
356
|
)
|
|
357
|
+
|
|
358
|
+
#################################
|
|
359
|
+
# Streamlit Conversion
|
|
360
|
+
#################################
|
|
358
361
|
|
|
362
|
+
def log_streamlit_app_creation_success(key_type: Literal['mito_server_key', 'user_key'], message_type: MessageType) -> None:
|
|
363
|
+
log(
|
|
364
|
+
"mito_ai_streamlit_app_creation_success",
|
|
365
|
+
key_type=key_type
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
def log_streamlit_app_creation_retry(key_type: Literal['mito_server_key', 'user_key'], message_type: MessageType, error: str) -> None:
|
|
369
|
+
log(
|
|
370
|
+
"mito_ai_streamlit_app_creation_retry",
|
|
371
|
+
params={
|
|
372
|
+
"error_message": error,
|
|
373
|
+
},
|
|
374
|
+
key_type=key_type
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
def log_streamlit_app_creation_error(key_type: Literal['mito_server_key', 'user_key'], message_type: MessageType, error: str) -> None:
|
|
378
|
+
log(
|
|
379
|
+
"mito_ai_streamlit_app_creation_error",
|
|
380
|
+
params={
|
|
381
|
+
"error_message": error,
|
|
382
|
+
},
|
|
383
|
+
key_type=key_type
|
|
384
|
+
)
|
|
385
|
+
|
{mito_ai-0.1.37.data → mito_ai-0.1.38.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.38",
|
|
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.bcce4ea34631acf6dbbe.js",
|
|
142
142
|
"extension": "./extension",
|
|
143
143
|
"style": "./style"
|
|
144
144
|
}
|
|
@@ -27,10 +27,15 @@
|
|
|
27
27
|
}
|
|
28
28
|
],
|
|
29
29
|
"Notebook": [
|
|
30
|
+
{
|
|
31
|
+
"name": "preview-app",
|
|
32
|
+
"command": "toolbar-button:preview-as-streamlit",
|
|
33
|
+
"rank": 1000
|
|
34
|
+
},
|
|
30
35
|
{
|
|
31
36
|
"name": "convert-to-streamlit",
|
|
32
37
|
"command": "toolbar-button:convert-to-streamlit",
|
|
33
|
-
"rank":
|
|
38
|
+
"rank": 1001
|
|
34
39
|
}
|
|
35
40
|
]
|
|
36
41
|
}
|