Skip to content

Commit 88adc15

Browse files
authored
Merge pull request #1224 from mito-ds/mito-for-flask-py
Mito for flask py
2 parents a1f0960 + 2461621 commit 88adc15

16 files changed

+175
-34
lines changed

mitosheet/mitosheet/mito_backend.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
from mitosheet.enterprise.mito_config import MitoConfig
2626
from mitosheet.errors import (MitoError, get_recent_traceback,
2727
make_execution_error)
28-
from mitosheet.saved_analyses import write_analysis
28+
from mitosheet.saved_analyses import write_save_analysis_file
2929
from mitosheet.steps_manager import StepsManager
3030
from mitosheet.telemetry.telemetry_utils import (log, log_event_processed,
3131
telemetry_turned_on)
@@ -178,7 +178,7 @@ def handle_edit_event(self, event: Dict[str, Any]) -> None:
178178
self.steps_manager.handle_edit_event(event)
179179

180180
# Also, write the analysis to a file!
181-
write_analysis(self.steps_manager)
181+
write_save_analysis_file(self.steps_manager)
182182

183183
# Tell the front-end to render the new sheet and new code with an empty
184184
# response. NOTE: in the future, we can actually send back some data
@@ -217,7 +217,7 @@ def handle_update_event(self, event: Dict[str, Any]) -> None:
217217
raise make_execution_error(error_modal=False)
218218
raise
219219
# Also, write the analysis to a file!
220-
write_analysis(self.steps_manager)
220+
write_save_analysis_file(self.steps_manager)
221221

222222
# Tell the front-end to render the new sheet and new code with an empty
223223
# response.

mitosheet/mitosheet/mito_flask/__init__.py

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
from mitosheet.mito_flask.v1.flatten_utils import (flatten_mito_backend_to_json, read_backend_state_string_to_mito_backend)
2+
from mitosheet.mito_flask.v1.process_event import process_mito_event
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import json
2+
from mitosheet.mito_backend import MitoBackend
3+
from mitosheet.saved_analyses import get_saved_analysis_string
4+
5+
6+
def flatten_mito_backend_to_json(mito_backend: MitoBackend) -> str:
7+
saved_analysis_string = get_saved_analysis_string(mito_backend.steps_manager)
8+
return json.dumps({
9+
'backend_state': saved_analysis_string,
10+
'shared_state_variables': mito_backend.get_shared_state_variables()
11+
})
12+
13+
def read_backend_state_string_to_mito_backend(backend_state_string: str) -> MitoBackend:
14+
15+
mb = MitoBackend()
16+
steps_manager = mb.steps_manager
17+
analysis = json.loads(backend_state_string)
18+
19+
previous_public_interface_version = steps_manager.public_interface_version
20+
previous_code_options = steps_manager.code_options
21+
22+
try:
23+
24+
# Before we execute the steps, update to the public interface version of the saved analysis
25+
steps_manager.public_interface_version = analysis['public_interface_version']
26+
steps_manager.code_options = analysis['code_options']
27+
28+
steps_manager.execute_steps_data(new_steps_data=analysis['steps_data'])
29+
30+
except:
31+
# If we error, reset the public interface version, and code options
32+
steps_manager.public_interface_version = previous_public_interface_version
33+
steps_manager.code_options = previous_code_options
34+
raise
35+
36+
return mb
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
from typing import Any, Dict, Optional
2+
from mitosheet.mito_backend import MitoBackend
3+
from mitosheet.mito_flask.v1.flatten_utils import (flatten_mito_backend_to_json, read_backend_state_string_to_mito_backend)
4+
5+
def process_mito_event(backend_state: Optional[str], mito_event: Optional[Dict[str, Any]]) -> Any:
6+
7+
if backend_state is None:
8+
mito_backend = MitoBackend()
9+
from flask import jsonify
10+
return jsonify({
11+
"state": flatten_mito_backend_to_json(mito_backend),
12+
"response": None,
13+
})
14+
15+
mito_backend = read_backend_state_string_to_mito_backend(backend_state)
16+
17+
response = None
18+
def mito_send(message):
19+
nonlocal response
20+
response = message
21+
22+
mito_backend.mito_send = mito_send
23+
if mito_event:
24+
mito_backend.receive_message(mito_event)
25+
26+
from flask import jsonify
27+
return jsonify({
28+
"state": flatten_mito_backend_to_json(mito_backend),
29+
"response": response,
30+
})

mitosheet/mitosheet/saved_analyses/__init__.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@
55
# Distributed under the terms of the GPL License.
66

77
from mitosheet.saved_analyses.save_utils import (
8-
read_and_upgrade_analysis, write_analysis,
8+
read_and_upgrade_analysis, write_save_analysis_file,
99
delete_saved_analysis, rename_saved_analysis,
1010
_get_all_analysis_filenames, _delete_analyses,
1111
SAVED_ANALYSIS_FOLDER, read_analysis,
12-
make_steps_json_obj, get_analysis_exists
12+
get_steps_obj_for_saved_analysis, get_analysis_exists,
13+
get_saved_analysis_string
1314
)
1415
from mitosheet.saved_analyses.upgrade import is_prev_version
1516
from mitosheet.saved_analyses.upgrade import upgrade_saved_analysis_to_current_version

mitosheet/mitosheet/saved_analyses/save_utils.py

+17-16
Original file line numberDiff line numberDiff line change
@@ -118,20 +118,24 @@ def rename_saved_analysis(old_analysis_name, new_analysis_name):
118118
else:
119119
raise Exception(f'Invalid rename, with old and new analysis are {old_analysis_name} and {new_analysis_name}')
120120

121-
122-
def write_saved_analysis(analysis_path: str, steps_data: List[Dict[str, Any]], public_interface_version: int, args: List[str], code_options: CodeOptions, version: str=__version__) -> None:
121+
def get_saved_analysis_string(steps_manager: StepsManagerType) -> str:
122+
saved_analysis_string = json.dumps({
123+
'version': __version__,
124+
'steps_data': get_steps_obj_for_saved_analysis(steps_manager.steps_including_skipped),
125+
'public_interface_version': steps_manager.public_interface_version,
126+
'args': steps_manager.original_args_raw_strings,
127+
'code': steps_manager.code(),
128+
'code_options': steps_manager.code_options
129+
}, cls=NpEncoder)
130+
return saved_analysis_string
131+
132+
def _write_saved_analysis_file(analysis_path: str, steps_manager: StepsManagerType) -> None:
133+
saved_analysis_string = get_saved_analysis_string(steps_manager)
123134
with open(analysis_path, 'w+') as f:
124-
saved_analysis = {
125-
'version': version,
126-
'steps_data': steps_data,
127-
'public_interface_version': public_interface_version,
128-
'args': args,
129-
'code_options': code_options
130-
}
131-
f.write(json.dumps(saved_analysis, cls=NpEncoder))
135+
f.write(saved_analysis_string)
132136

133137

134-
def make_steps_json_obj(
138+
def get_steps_obj_for_saved_analysis(
135139
steps: List[Step]
136140
) -> List[Dict[str, Any]]:
137141
"""
@@ -182,7 +186,7 @@ def make_steps_json_obj(
182186

183187
return steps_json_obj
184188

185-
def write_analysis(steps_manager: StepsManagerType, analysis_name: Optional[str]=None) -> None:
189+
def write_save_analysis_file(steps_manager: StepsManagerType, analysis_name: Optional[str]=None) -> None:
186190
"""
187191
Writes the analysis saved in steps_manager to
188192
~/.mito/{analysis_name}. If analysis_name is none, gets the temporary
@@ -203,7 +207,4 @@ def write_analysis(steps_manager: StepsManagerType, analysis_name: Optional[str]
203207
analysis_name = steps_manager.analysis_name
204208

205209
analysis_path = f'{SAVED_ANALYSIS_FOLDER}/{analysis_name}.json'
206-
steps = make_steps_json_obj(steps_manager.steps_including_skipped)
207-
208-
# Actually write the file
209-
write_saved_analysis(analysis_path, steps, steps_manager.public_interface_version, steps_manager.original_args_raw_strings, steps_manager.code_options)
210+
_write_saved_analysis_file(analysis_path, steps_manager)

mitosheet/mitosheet/tests/decorators.py

+7-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
import sys
1515
from mitosheet.ai.ai_utils import is_open_ai_credentials_available
1616

17-
from mitosheet.utils import is_prev_version, is_snowflake_connector_python_installed, is_snowflake_credentials_available, is_streamlit_installed, is_dash_installed
17+
from mitosheet.utils import is_flask_installed, is_prev_version, is_snowflake_connector_python_installed, is_snowflake_credentials_available, is_streamlit_installed, is_dash_installed
1818

1919
pandas_pre_1_only = pytest.mark.skipif(
2020
not pd.__version__.startswith('0.'),
@@ -65,11 +65,17 @@
6565
not is_snowflake_connector_python_installed() or not is_snowflake_credentials_available(),
6666
reason='requires snowflake_connector_python package and snowflake credentials'
6767
)
68+
6869
requires_streamlit = pytest.mark.skipif(
6970
not is_streamlit_installed(),
7071
reason='requires streamlit to be installed'
7172
)
7273

74+
requires_flask = pytest.mark.skipif(
75+
not is_flask_installed(),
76+
reason='requires flask to be installed'
77+
)
78+
7379
requires_dash = pytest.mark.skipif(
7480
not is_dash_installed(),
7581
reason='requires dash to be installed'

mitosheet/mitosheet/tests/mito_flask/__init__.py

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import json
2+
import os
3+
import pandas as pd
4+
import pytest
5+
6+
from mitosheet.tests.test_utils import create_mito_wrapper
7+
from mitosheet.tests.decorators import requires_flask
8+
from mitosheet.mito_flask.v1.flatten_utils import (flatten_mito_backend_to_json,
9+
read_backend_state_string_to_mito_backend)
10+
11+
TEST_DFS = [
12+
(pd.DataFrame({"a": [1, 2], "b": ['abc', 'def'], "c": [True, False], "d": [1.0, 2.0]})),
13+
]
14+
15+
16+
@pytest.mark.parametrize("df", TEST_DFS)
17+
@requires_flask
18+
def test_convert_to_string(df):
19+
mito = create_mito_wrapper()
20+
21+
df.to_csv("test.csv", index=False)
22+
mito.simple_import(["test.csv"])
23+
24+
mb = mito.mito_backend
25+
s = flatten_mito_backend_to_json(mb)
26+
27+
assert json.loads(s)["shared_state_variables"] == mb.get_shared_state_variables()
28+
29+
backend_state = json.loads(s)["backend_state"]
30+
new_mb = read_backend_state_string_to_mito_backend(backend_state)
31+
assert new_mb.steps_manager.dfs[0].equals(df)
32+
33+
os.remove("test.csv")
34+
35+
@requires_flask
36+
def test_process_edit_convert_to_string_then_convert_back_still_processes():
37+
test_wrapper = create_mito_wrapper()
38+
df = pd.DataFrame({'A': [1, 2, 3], 'B': [1, 2, 3]})
39+
df.to_csv("test.csv", index=False)
40+
test_wrapper.simple_import(["test.csv"])
41+
test_wrapper.add_column(0, 'C')
42+
43+
s = flatten_mito_backend_to_json(test_wrapper.mito_backend)
44+
new_mb = read_backend_state_string_to_mito_backend(json.loads(s)["backend_state"])
45+
46+
assert new_mb.steps_manager.dfs[0].equals(pd.DataFrame({'A': [1, 2, 3], 'B': [1, 2, 3], 'C': [0, 0, 0]}))
47+
48+
new_test_wrapper = create_mito_wrapper(mito_backend=new_mb)
49+
new_test_wrapper.add_column(0, 'D')
50+
51+
assert new_mb.steps_manager.dfs[0].equals(pd.DataFrame({'A': [1, 2, 3], 'B': [1, 2, 3], 'C': [0, 0, 0], 'D': [0, 0, 0]}))
52+
53+
os.remove("test.csv")
54+

mitosheet/mitosheet/tests/saved_analyses/test_save_utils.py

+5-5
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
import pandas as pd
1414
import pytest
15-
from mitosheet.saved_analyses import SAVED_ANALYSIS_FOLDER, write_analysis
15+
from mitosheet.saved_analyses import SAVED_ANALYSIS_FOLDER, write_save_analysis_file
1616
from mitosheet.saved_analyses.save_utils import read_and_upgrade_analysis
1717
from mitosheet.step_performers.filter import FC_NUMBER_EXACTLY
1818
from mitosheet.tests.test_utils import (create_mito_wrapper_with_data,
@@ -32,7 +32,7 @@ def test_recover_analysis(b_value, b_formula):
3232
mito.set_formula(b_formula, 0, 'B', add_column=True)
3333
# We first write out the analysis
3434
analysis_name = mito.mito_backend.analysis_name
35-
write_analysis(mito.mito_backend.steps_manager)
35+
write_save_analysis_file(mito.mito_backend.steps_manager)
3636

3737
df = pd.DataFrame(data={'A': [1]})
3838
new_mito = create_mito_wrapper(df)
@@ -51,7 +51,7 @@ def test_persist_analysis_multi_sheet(b_value, b_formula):
5151
mito.set_formula(b_formula, 1, 'B', add_column=True)
5252
# We first write out the analysis
5353
analysis_name = mito.mito_backend.analysis_name
54-
write_analysis(mito.mito_backend.steps_manager)
54+
write_save_analysis_file(mito.mito_backend.steps_manager)
5555

5656
df1 = pd.DataFrame(data={'A': [1]})
5757
df2 = pd.DataFrame(data={'A': [1]})
@@ -73,7 +73,7 @@ def test_persist_rename_column():
7373
mito.rename_column(0, 'A', 'NEW_COLUMN')
7474

7575
analysis_name = mito.mito_backend.analysis_name
76-
write_analysis(mito.mito_backend.steps_manager)
76+
write_save_analysis_file(mito.mito_backend.steps_manager)
7777

7878
df1 = pd.DataFrame(data={'A': [1]})
7979

@@ -89,7 +89,7 @@ def test_persisit_delete_column():
8989
mito.delete_columns(0, 'A')
9090

9191
analysis_name = mito.mito_backend.analysis_name
92-
write_analysis(mito.mito_backend.steps_manager)
92+
write_save_analysis_file(mito.mito_backend.steps_manager)
9393

9494
df1 = pd.DataFrame(data={'A': [1]})
9595

mitosheet/mitosheet/tests/test_user_functions.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import pytest
44
import pandas as pd
55
from mitosheet.enterprise.mito_config import MITO_CONFIG_CUSTOM_SHEET_FUNCTIONS_PATH, MITO_CONFIG_VERSION
6-
from mitosheet.saved_analyses.save_utils import write_analysis
6+
from mitosheet.saved_analyses.save_utils import write_save_analysis_file
77
from mitosheet.tests.test_mito_config import delete_all_mito_config_environment_variables
88

99
from mitosheet.tests.test_utils import create_mito_wrapper
@@ -38,7 +38,7 @@ def ADD1(col):
3838
assert mito.dfs[0].equals(pd.DataFrame({'A': [1, 2, 3], 'B': [2, 3, 4]}))
3939

4040
analysis_name = mito.mito_backend.analysis_name
41-
write_analysis(mito.mito_backend.steps_manager)
41+
write_save_analysis_file(mito.mito_backend.steps_manager)
4242

4343
new_mito = create_mito_wrapper(df)
4444
new_mito.replay_analysis(analysis_name)
@@ -55,7 +55,7 @@ def ADD1(col):
5555
assert mito.dfs[0].equals(pd.DataFrame({'A': [1, 2, 3], 'B': [2, 3, 4]}))
5656

5757
analysis_name = mito.mito_backend.analysis_name
58-
write_analysis(mito.mito_backend.steps_manager)
58+
write_save_analysis_file(mito.mito_backend.steps_manager)
5959

6060
new_mito = create_mito_wrapper(df, sheet_functions=[ADD1])
6161
new_mito.replay_analysis(analysis_name)

mitosheet/mitosheet/tests/test_utils.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -1691,12 +1691,14 @@ def create_mito_wrapper(
16911691
sheet_functions: Optional[List[Callable]]=None,
16921692
importers: Optional[List[Callable]]=None,
16931693
editors: Optional[List[Callable]]=None,
1694+
mito_backend: Optional[MitoBackend]=None
16941695
) -> MitoWidgetTestWrapper:
16951696
"""
16961697
Creates a MitoWidgetTestWrapper with a mito instance with the given
16971698
data frames.
16981699
"""
1699-
mito_backend = get_mito_backend(*args, user_defined_functions=sheet_functions, user_defined_importers=importers, user_defined_editors=editors)
1700+
if mito_backend is None:
1701+
mito_backend = get_mito_backend(*args, user_defined_functions=sheet_functions, user_defined_importers=importers, user_defined_editors=editors)
17001702
test_wrapper = MitoWidgetTestWrapper(mito_backend)
17011703

17021704
if arg_names is not None:

mitosheet/mitosheet/updates/save_analysis.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
Saves the analysis with the passed name.
88
"""
99

10-
from mitosheet.saved_analyses import write_analysis
10+
from mitosheet.saved_analyses import write_save_analysis_file
1111

1212
SAVE_ANALYSIS_UPDATE_EVENT = 'save_analysis_update'
1313
SAVE_ANALYSIS_UPDATE_PARAMS = ['analysis_name']
@@ -19,7 +19,7 @@ def execute_save_analysis_update(
1919
"""
2020
Saves the analysis with the passed name
2121
"""
22-
write_analysis(steps_manager, analysis_name)
22+
write_save_analysis_file(steps_manager, analysis_name)
2323

2424

2525
SAVE_ANALYSIS_UPDATE = {

mitosheet/mitosheet/utils.py

+8
Original file line numberDiff line numberDiff line change
@@ -528,6 +528,14 @@ def is_streamlit_installed() -> bool:
528528
return True
529529
except ImportError:
530530
return False
531+
return False
532+
533+
def is_flask_installed() -> bool:
534+
try:
535+
import flask
536+
return True
537+
except ImportError:
538+
return False
531539

532540
def is_dash_installed() -> bool:
533541
try:

mitosheet/setup.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,8 @@ def get_data_files_from_data_files_spec(
145145
# snowflake-connect-python requires at least Python 3.7
146146
'snowflake-connector-python[pandas]; python_version>="3.7"',
147147
'streamlit>=1.24',
148-
'dash>=2.9'
148+
'dash>=2.9',
149+
"flask"
149150
]
150151
},
151152
zip_safe = False,

0 commit comments

Comments
 (0)