mitosheet 0.2.54__py2.py3-none-any.whl → 0.2.55__py2.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.
- mitosheet/dataframe_display_formatters.py +33 -66
- mitosheet/formatter/__init__.py +10 -0
- mitosheet/formatter/viewer.py +148 -0
- mitosheet/labextension/package.json +23 -7
- mitosheet/labextension/static/339.967abbd46339aee7868c.js +1 -0
- mitosheet/labextension/static/418.0ec6f919714352f77210.js +1 -0
- mitosheet/labextension/static/remoteEntry.b3dffe9fd1efc1d162d4.js +1 -0
- mitosheet/saved_analyses/save_utils.py +1 -1
- mitosheet/tests/test_formatter.py +240 -0
- {mitosheet-0.2.54.data → mitosheet-0.2.55.data}/data/share/jupyter/labextensions/mitosheet/package.json +23 -7
- mitosheet-0.2.55.data/data/share/jupyter/labextensions/mitosheet/static/339.967abbd46339aee7868c.js +1 -0
- mitosheet-0.2.55.data/data/share/jupyter/labextensions/mitosheet/static/418.0ec6f919714352f77210.js +1 -0
- mitosheet-0.2.55.data/data/share/jupyter/labextensions/mitosheet/static/remoteEntry.b3dffe9fd1efc1d162d4.js +1 -0
- {mitosheet-0.2.54.dist-info → mitosheet-0.2.55.dist-info}/METADATA +5 -2
- {mitosheet-0.2.54.dist-info → mitosheet-0.2.55.dist-info}/RECORD +22 -17
- mitosheet/labextension/static/339.5f826c48e1170f2309de.js +0 -1
- mitosheet/labextension/static/remoteEntry.ad0d7beec7bf313e216f.js +0 -1
- mitosheet-0.2.54.data/data/share/jupyter/labextensions/mitosheet/static/339.5f826c48e1170f2309de.js +0 -1
- mitosheet-0.2.54.data/data/share/jupyter/labextensions/mitosheet/static/remoteEntry.ad0d7beec7bf313e216f.js +0 -1
- {mitosheet-0.2.54.data → mitosheet-0.2.55.data}/data/share/jupyter/labextensions/mitosheet/static/405.4dea6fd8e64e4dc9015a.js +0 -0
- {mitosheet-0.2.54.data → mitosheet-0.2.55.data}/data/share/jupyter/labextensions/mitosheet/static/509.9cd86a57778b85719494.js +0 -0
- {mitosheet-0.2.54.data → mitosheet-0.2.55.data}/data/share/jupyter/labextensions/mitosheet/static/style.js +0 -0
- {mitosheet-0.2.54.data → mitosheet-0.2.55.data}/data/share/jupyter/labextensions/mitosheet/static/third-party-licenses.json +0 -0
- {mitosheet-0.2.54.dist-info → mitosheet-0.2.55.dist-info}/LICENSE.txt +0 -0
- {mitosheet-0.2.54.dist-info → mitosheet-0.2.55.dist-info}/WHEEL +0 -0
- {mitosheet-0.2.54.dist-info → mitosheet-0.2.55.dist-info}/top_level.txt +0 -0
|
@@ -2,82 +2,49 @@
|
|
|
2
2
|
# Distributed under the terms of the GNU Affero General Public License v3.0 License.
|
|
3
3
|
|
|
4
4
|
"""
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
This code is responsible for making mitosheet the default dataframe display when Jupyter
|
|
6
|
+
is rendering a dataframe to the output cell.
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
to render the DataFrame using mitosheet.
|
|
11
|
-
|
|
12
|
-
In order for the custom renderers to work, the mitosheet package must first be imported in the user's kernel.
|
|
13
|
-
To do so, when the Mito extension is activated, we register a kernel status listener that imports the mitosheet package
|
|
14
|
-
whenever the kernel changes.
|
|
8
|
+
The new approach add a new custom mimetype to hook a custom viewer. This allows to keep
|
|
9
|
+
the default view if the user prefers it.
|
|
15
10
|
|
|
16
|
-
|
|
17
|
-
When doing so, the `import mitosheet` does not get executed until after all of the code cells have been executed.
|
|
18
|
-
Which means that the mitosheet package is not imported when rendering any dataframes in the notebook. In
|
|
19
|
-
this case, we just default to the pandas dataframe renderer. That is okay.
|
|
11
|
+
_____
|
|
20
12
|
|
|
21
|
-
|
|
13
|
+
Previous approaches:
|
|
22
14
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
did not work because:
|
|
15
|
+
This approach works by overriding the default HTML and plain text formatters for pandas DataFrames in IPython.
|
|
16
|
+
When a DataFrame is displayed, the custom formatter functions are called, which in turn call mitosheet.sheet()
|
|
17
|
+
to render the DataFrame using mitosheet.
|
|
27
18
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
the mitosheet was rendered, it might have displayed a dataframe that reflected future code cell edits instead of the
|
|
32
|
-
current state of the dataframe at the time the code cell with the hanging df was executed. This is not what we want.
|
|
19
|
+
In order for the custom renderers to work, the mitosheet package must first be imported in the user's kernel.
|
|
20
|
+
To do so, when the Mito extension is activated, we register a kernel status listener that imports the mitosheet package
|
|
21
|
+
whenever the kernel changes.
|
|
33
22
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
23
|
+
This approach still fails the race condition caused when the user clicks "Restart Kernel and run all cells".
|
|
24
|
+
When doing so, the `import mitosheet` does not get executed until after all of the code cells have been executed.
|
|
25
|
+
Which means that the mitosheet package is not imported when rendering any dataframes in the notebook. In
|
|
26
|
+
this case, we just default to the pandas dataframe renderer. That is okay.
|
|
38
27
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
# we just wrap this entire thing in a try, except statement
|
|
28
|
+
Before landing on this approach, we tried to create a custom dataframe mime renderer. Using the mime renderer
|
|
29
|
+
did not work because:
|
|
42
30
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
31
|
+
In order to create the mitosheet, we need to execute the mitosheet.sheet() function. To do so, we
|
|
32
|
+
had to send a new kernel message from the mimerender with the code mitosheet.sheet(df). However, becasue
|
|
33
|
+
the kernel message queue might have had additional messages already queued that edited the df, by the time
|
|
34
|
+
the mitosheet was rendered, it might have displayed a dataframe that reflected future code cell edits instead of the
|
|
35
|
+
current state of the dataframe at the time the code cell with the hanging df was executed. This is not what we want.
|
|
46
36
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
return
|
|
37
|
+
For a more thorough explanation of attempted solutions, see the comment here: https://github.com/mito-ds/mito/pull/1330#issuecomment-2386428760
|
|
38
|
+
"""
|
|
50
39
|
|
|
51
|
-
|
|
52
|
-
def mitosheet_display_formatter(obj, include=None, exclude=None):
|
|
53
|
-
if isinstance(obj, pd.DataFrame):
|
|
54
|
-
# Render the DataFrame using Mitosheet
|
|
55
|
-
return mitosheet.sheet(obj, input_cell_execution_count=ip.execution_count)
|
|
56
|
-
# Returning None tells Jupyter that this formatter (the text/html one) didn’t produce any output for the given object.
|
|
57
|
-
# This causes Jupyter to “fall back” to lower-priority formatters (like text/plain).
|
|
58
|
-
return None
|
|
40
|
+
from .formatter import register_ipython_formatter
|
|
59
41
|
|
|
60
|
-
# Custom plain text formatter to suppress plain text for DataFrames
|
|
61
|
-
def mitosheet_plain_formatter(obj, p, cycle):
|
|
62
|
-
# TODO: I'm not 100% confident that this is correct, but if I don't overwrite the plain text formatter
|
|
63
|
-
# for dataframes, then I end up getting the mitosheet followed by a printed version of the dataframe.
|
|
64
|
-
if isinstance(obj, pd.DataFrame):
|
|
65
|
-
# Returning None here tells Jupyter not to render anything in the text/plain format
|
|
66
|
-
# for DataFrames. In this case, Jupyter has already rendered the text/html output,
|
|
67
|
-
# so returning None for text/plain means “suppress this output,”
|
|
68
|
-
# preventing Jupyter from rendering the plain text fallback.
|
|
69
|
-
return None
|
|
70
|
-
# For other objects, use the default plain text formatter
|
|
71
|
-
return p.text(obj)
|
|
72
42
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
plain_formatter = ip.display_formatter.formatters['text/plain']
|
|
43
|
+
def set_dataframe_display_formatters() -> None:
|
|
44
|
+
"""
|
|
45
|
+
Deprecated
|
|
77
46
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
except:
|
|
83
|
-
pass
|
|
47
|
+
The registration of a custom formatter is now done automatically by the
|
|
48
|
+
mitosheet plugin.
|
|
49
|
+
"""
|
|
50
|
+
register_ipython_formatter()
|
|
@@ -0,0 +1,148 @@
|
|
|
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 Dict, Optional
|
|
5
|
+
|
|
6
|
+
import pandas as pd
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _is_describe_output(df: pd.DataFrame) -> bool:
|
|
10
|
+
"""
|
|
11
|
+
Detect if DataFrame is the result of a describe() call.
|
|
12
|
+
|
|
13
|
+
describe() DataFrames have specific characteristics:
|
|
14
|
+
- Index contains statistical measures (count, mean, std, min, max)
|
|
15
|
+
"""
|
|
16
|
+
if df.empty or len(df.index) < 5:
|
|
17
|
+
return False
|
|
18
|
+
|
|
19
|
+
# Check if index contains typical describe() statistical measures
|
|
20
|
+
expected_stats = {"count", "mean", "std", "min", "max"}
|
|
21
|
+
index_values = set(str(idx).strip() for idx in df.index)
|
|
22
|
+
|
|
23
|
+
# If the expected stats are in the index, likely describe() output
|
|
24
|
+
matches = expected_stats.intersection(index_values)
|
|
25
|
+
return len(matches) == 5
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def register_ipython_formatter() -> None:
|
|
29
|
+
"""
|
|
30
|
+
Register custom formatter with IPython display system.
|
|
31
|
+
"""
|
|
32
|
+
try:
|
|
33
|
+
from IPython.core.getipython import get_ipython
|
|
34
|
+
|
|
35
|
+
ip = get_ipython() # type: ignore
|
|
36
|
+
if ip is None:
|
|
37
|
+
return
|
|
38
|
+
|
|
39
|
+
# Register formatter for pandas DataFrames with application/x.mito+json mimetype
|
|
40
|
+
if getattr(ip, "display_formatter", None) is not None:
|
|
41
|
+
formatter = getattr(ip.display_formatter, "formatters", None)
|
|
42
|
+
if formatter is not None:
|
|
43
|
+
# Try to register with existing mimetype formatter
|
|
44
|
+
if "application/x.mito+json" not in formatter:
|
|
45
|
+
# Create a new formatter for the custom mimetype
|
|
46
|
+
from IPython.core.formatters import BaseFormatter
|
|
47
|
+
|
|
48
|
+
class MitoJSONFormatter(BaseFormatter):
|
|
49
|
+
def __call__(self, obj):
|
|
50
|
+
if isinstance(obj, pd.DataFrame):
|
|
51
|
+
# Skip custom formatter for describe() output
|
|
52
|
+
if _is_describe_output(obj):
|
|
53
|
+
return None
|
|
54
|
+
return format_dataframe_mimetype(obj)
|
|
55
|
+
else:
|
|
56
|
+
return None
|
|
57
|
+
|
|
58
|
+
formatter["application/x.mito+json"] = MitoJSONFormatter()
|
|
59
|
+
|
|
60
|
+
except ImportError:
|
|
61
|
+
# IPython not available
|
|
62
|
+
pass
|
|
63
|
+
except Exception as e:
|
|
64
|
+
# Log error but don't fail
|
|
65
|
+
print(f"Warning: Could not register mito viewer formatter: {e}")
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def format_dataframe_mimetype(obj: pd.DataFrame) -> Optional[Dict]:
|
|
69
|
+
"""
|
|
70
|
+
Format a pandas DataFrame as a mimetype object for JupyterLab rendering.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
obj: pandas DataFrame to display
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
Dictionary with mimetype and data for JupyterLab renderer, or None if should skip formatting
|
|
77
|
+
"""
|
|
78
|
+
# Skip custom formatter for describe() output
|
|
79
|
+
if _is_describe_output(obj):
|
|
80
|
+
return None
|
|
81
|
+
|
|
82
|
+
# Get pandas display options
|
|
83
|
+
max_rows = pd.get_option("display.max_rows")
|
|
84
|
+
|
|
85
|
+
# Prepare data
|
|
86
|
+
total_rows = len(obj)
|
|
87
|
+
has_multi_columns = isinstance(obj.columns, pd.MultiIndex)
|
|
88
|
+
column_levels = obj.columns.nlevels if has_multi_columns else 1
|
|
89
|
+
columns = list(obj.columns)
|
|
90
|
+
|
|
91
|
+
# Get index information - handle MultiIndex
|
|
92
|
+
index_name = obj.index.name if obj.index.name is not None else "index"
|
|
93
|
+
index_dtype = str(obj.index.dtype)
|
|
94
|
+
|
|
95
|
+
# Check if we have a MultiIndex
|
|
96
|
+
has_multi_index = isinstance(obj.index, pd.MultiIndex)
|
|
97
|
+
if has_multi_index:
|
|
98
|
+
index_levels = obj.index.nlevels
|
|
99
|
+
index_names = [
|
|
100
|
+
name if name is not None else f"level_{i}"
|
|
101
|
+
for i, name in enumerate(obj.index.names)
|
|
102
|
+
]
|
|
103
|
+
else:
|
|
104
|
+
index_levels = 1
|
|
105
|
+
index_names = [index_name]
|
|
106
|
+
|
|
107
|
+
# Limit rows if necessary
|
|
108
|
+
if max_rows is not None and total_rows > max_rows:
|
|
109
|
+
display_df = obj.head(max_rows)
|
|
110
|
+
else:
|
|
111
|
+
display_df = obj
|
|
112
|
+
|
|
113
|
+
# Convert DataFrame to JSON-serializable format using pandas.to_json
|
|
114
|
+
try:
|
|
115
|
+
json_data = display_df.reset_index().to_json(orient="values", date_format="iso")
|
|
116
|
+
except Exception:
|
|
117
|
+
return None
|
|
118
|
+
|
|
119
|
+
# Prepare column metadata - include index as first column(s)
|
|
120
|
+
column_metadata = []
|
|
121
|
+
|
|
122
|
+
# Add index column(s) first
|
|
123
|
+
for i, name in enumerate(index_names):
|
|
124
|
+
column_metadata.append(
|
|
125
|
+
{
|
|
126
|
+
"name": [str(name)],
|
|
127
|
+
"dtype": f"level_{i + 1}" if has_multi_index else index_dtype,
|
|
128
|
+
}
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
# Add regular columns
|
|
132
|
+
for col in columns:
|
|
133
|
+
dtype = str(obj[col].dtype)
|
|
134
|
+
column_metadata.append(
|
|
135
|
+
{"name": list(col) if has_multi_columns else [str(col)], "dtype": dtype}
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
# Prepare the data payload
|
|
139
|
+
payload = {
|
|
140
|
+
"columns": column_metadata,
|
|
141
|
+
"data": json_data,
|
|
142
|
+
"totalRows": total_rows,
|
|
143
|
+
"indexLevels": index_levels if has_multi_index else 1,
|
|
144
|
+
"columnLevels": column_levels,
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
# Return mimetype data for JupyterLab
|
|
148
|
+
return payload
|
|
@@ -9,9 +9,11 @@
|
|
|
9
9
|
"jupyterlab": {
|
|
10
10
|
"outputDir": "mitosheet/labextension",
|
|
11
11
|
"extension": "lib/plugin",
|
|
12
|
+
"mimeExtension": "lib/viewer/renderer",
|
|
12
13
|
"_build": {
|
|
13
|
-
"load": "static/remoteEntry.
|
|
14
|
-
"extension": "./extension"
|
|
14
|
+
"load": "static/remoteEntry.b3dffe9fd1efc1d162d4.js",
|
|
15
|
+
"extension": "./extension",
|
|
16
|
+
"mimeExtension": "./mimeExtension"
|
|
15
17
|
}
|
|
16
18
|
},
|
|
17
19
|
"name": "mitosheet",
|
|
@@ -21,16 +23,18 @@
|
|
|
21
23
|
"email": "aaron@sagacollab.com"
|
|
22
24
|
},
|
|
23
25
|
"bugs": {
|
|
24
|
-
"url": "https://github.com/mito-ds/
|
|
26
|
+
"url": "https://github.com/mito-ds/mito/issues"
|
|
25
27
|
},
|
|
26
28
|
"repository": {
|
|
27
|
-
"url": "https://github.com/mito-ds/
|
|
29
|
+
"url": "https://github.com/mito-ds/mito",
|
|
28
30
|
"type": "git"
|
|
29
31
|
},
|
|
30
|
-
"version": "0.2.
|
|
32
|
+
"version": "0.2.55",
|
|
31
33
|
"dependencies": {
|
|
32
34
|
"@jupyterlab/application": "^4.0.0",
|
|
33
35
|
"@jupyterlab/notebook": "^4.2.4",
|
|
36
|
+
"@jupyterlab/rendermime-interfaces": "^3.0.0",
|
|
37
|
+
"@jupyterlab/ui-components": "^4.0.0",
|
|
34
38
|
"@types/fscreen": "^1.0.1",
|
|
35
39
|
"@types/react-dom": "^18.3.0",
|
|
36
40
|
"fscreen": "^1.1.0",
|
|
@@ -70,7 +74,10 @@
|
|
|
70
74
|
"lint": "eslint src/ --ext .ts,.tsx --fix",
|
|
71
75
|
"prepack": "jlpm run build:all",
|
|
72
76
|
"build:docs:update_frontend": "python docs/make_function_docs.py update_frontend",
|
|
73
|
-
"build:docs:generate_markdown": "python docs/make_function_docs.py generate_markdown"
|
|
77
|
+
"build:docs:generate_markdown": "python docs/make_function_docs.py generate_markdown",
|
|
78
|
+
"test": "jest",
|
|
79
|
+
"test:watch": "jest --watch",
|
|
80
|
+
"test:coverage": "jest --coverage"
|
|
74
81
|
},
|
|
75
82
|
"keywords": [
|
|
76
83
|
"jupyter",
|
|
@@ -82,7 +89,12 @@
|
|
|
82
89
|
],
|
|
83
90
|
"devDependencies": {
|
|
84
91
|
"@jupyterlab/builder": "^4.0.0",
|
|
92
|
+
"@testing-library/dom": "^10.4.1",
|
|
93
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
94
|
+
"@testing-library/react": "^16.3.0",
|
|
95
|
+
"@testing-library/user-event": "^14.6.1",
|
|
85
96
|
"@types/expect.js": "^0.3.29",
|
|
97
|
+
"@types/jest": "^30.0.0",
|
|
86
98
|
"@types/node": "^18.19.43",
|
|
87
99
|
"@types/react": "^18.3.3",
|
|
88
100
|
"@typescript-eslint/eslint-plugin": "^4.8.1",
|
|
@@ -96,15 +108,19 @@
|
|
|
96
108
|
"expect.js": "^0.3.1",
|
|
97
109
|
"file-loader": "^6.2.0",
|
|
98
110
|
"fs-extra": "^10.0.0",
|
|
111
|
+
"identity-obj-proxy": "^3.0.0",
|
|
112
|
+
"jest": "^30.2.0",
|
|
113
|
+
"jest-environment-jsdom": "^30.2.0",
|
|
99
114
|
"mkdirp": "^1.0.4",
|
|
100
115
|
"mocha": "^9.1.2",
|
|
101
116
|
"npm-run-all": "^4.1.5",
|
|
102
117
|
"prettier": "^2.1.1",
|
|
103
118
|
"rimraf": "^3.0.2",
|
|
119
|
+
"ts-jest": "^29.4.6",
|
|
104
120
|
"typescript": "^5.5.4"
|
|
105
121
|
},
|
|
106
122
|
"main": "lib/index.js",
|
|
107
|
-
"homepage": "https://
|
|
123
|
+
"homepage": "https://github.com/mito-ds/mito",
|
|
108
124
|
"types": "./lib/index.d.ts",
|
|
109
125
|
"description": "The Mito Spreadsheet",
|
|
110
126
|
"resolutions": {
|