mitosheet 0.2.54__py2.py3-none-any.whl → 0.2.56__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.
Files changed (26) hide show
  1. mitosheet/dataframe_display_formatters.py +33 -66
  2. mitosheet/formatter/__init__.py +10 -0
  3. mitosheet/formatter/viewer.py +148 -0
  4. mitosheet/labextension/package.json +23 -7
  5. mitosheet/labextension/static/339.967abbd46339aee7868c.js +1 -0
  6. mitosheet/labextension/static/418.0ec6f919714352f77210.js +1 -0
  7. mitosheet/labextension/static/remoteEntry.25a5fbf3d0f508ee1860.js +1 -0
  8. mitosheet/saved_analyses/save_utils.py +1 -1
  9. mitosheet/tests/test_formatter.py +240 -0
  10. {mitosheet-0.2.54.data → mitosheet-0.2.56.data}/data/share/jupyter/labextensions/mitosheet/package.json +23 -7
  11. mitosheet-0.2.56.data/data/share/jupyter/labextensions/mitosheet/static/339.967abbd46339aee7868c.js +1 -0
  12. mitosheet-0.2.56.data/data/share/jupyter/labextensions/mitosheet/static/418.0ec6f919714352f77210.js +1 -0
  13. mitosheet-0.2.56.data/data/share/jupyter/labextensions/mitosheet/static/remoteEntry.25a5fbf3d0f508ee1860.js +1 -0
  14. {mitosheet-0.2.54.dist-info → mitosheet-0.2.56.dist-info}/METADATA +5 -2
  15. {mitosheet-0.2.54.dist-info → mitosheet-0.2.56.dist-info}/RECORD +22 -17
  16. mitosheet/labextension/static/339.5f826c48e1170f2309de.js +0 -1
  17. mitosheet/labextension/static/remoteEntry.ad0d7beec7bf313e216f.js +0 -1
  18. mitosheet-0.2.54.data/data/share/jupyter/labextensions/mitosheet/static/339.5f826c48e1170f2309de.js +0 -1
  19. mitosheet-0.2.54.data/data/share/jupyter/labextensions/mitosheet/static/remoteEntry.ad0d7beec7bf313e216f.js +0 -1
  20. {mitosheet-0.2.54.data → mitosheet-0.2.56.data}/data/share/jupyter/labextensions/mitosheet/static/405.4dea6fd8e64e4dc9015a.js +0 -0
  21. {mitosheet-0.2.54.data → mitosheet-0.2.56.data}/data/share/jupyter/labextensions/mitosheet/static/509.9cd86a57778b85719494.js +0 -0
  22. {mitosheet-0.2.54.data → mitosheet-0.2.56.data}/data/share/jupyter/labextensions/mitosheet/static/style.js +0 -0
  23. {mitosheet-0.2.54.data → mitosheet-0.2.56.data}/data/share/jupyter/labextensions/mitosheet/static/third-party-licenses.json +0 -0
  24. {mitosheet-0.2.54.dist-info → mitosheet-0.2.56.dist-info}/LICENSE.txt +0 -0
  25. {mitosheet-0.2.54.dist-info → mitosheet-0.2.56.dist-info}/WHEEL +0 -0
  26. {mitosheet-0.2.54.dist-info → mitosheet-0.2.56.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
- This code is responsible for making mitosheet the default dataframe display when Jupyter
6
- is rendering a dataframe to the output cell.
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
- This approach works by overriding the default HTML and plain text formatters for pandas DataFrames in IPython.
9
- When a DataFrame is displayed, the custom formatter functions are called, which in turn call mitosheet.sheet()
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
- This approach still fails the race condition caused when the user clicks "Restart Kernel and run all cells".
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
- Previous approaches:
24
-
25
- Before landing on this approach, we tried to create a custom dataframe mime renderer. Using the mime renderer
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
- In order to create the mitosheet, we need to execute the mitosheet.sheet() function. To do so, we
29
- had to send a new kernel message from the mimerender with the code mitosheet.sheet(df). However, becasue
30
- the kernel message queue might have had additional messages already queued that edited the df, by the time
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
- For a more thorough explanation of attempted solutions, see the comment here: https://github.com/mito-ds/mito/pull/1330#issuecomment-2386428760
35
- """
36
-
37
- def set_dataframe_display_formatters() -> None:
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
- try:
40
- # Since Mito is used in Streamlit which is not a iPython enviornment,
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
- from IPython import get_ipython
44
- import pandas as pd
45
- import mitosheet
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
- ip = get_ipython() # type: ignore
48
- if not ip: # Exit if not in an IPython environment
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
- # Custom HTML formatter for DataFrames using Mitosheet
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
- ip = get_ipython() # type: ignore
74
-
75
- html_formatter = ip.display_formatter.formatters['text/html']
76
- plain_formatter = ip.display_formatter.formatters['text/plain']
43
+ def set_dataframe_display_formatters() -> None:
44
+ """
45
+ Deprecated
77
46
 
78
- # Register the custom formatters
79
- html_formatter.for_type(pd.DataFrame, mitosheet_display_formatter)
80
- plain_formatter.for_type(pd.DataFrame, mitosheet_plain_formatter)
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,10 @@
1
+ # Copyright (c) Saga Inc.
2
+ # Distributed under the terms of the GNU Affero General Public License v3.0 License.
3
+
4
+ from .viewer import (
5
+ register_ipython_formatter,
6
+ )
7
+
8
+ __all__ = [
9
+ "register_ipython_formatter",
10
+ ]
@@ -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.ad0d7beec7bf313e216f.js",
14
- "extension": "./extension"
14
+ "load": "static/remoteEntry.25a5fbf3d0f508ee1860.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/monorepo/issues"
26
+ "url": "https://github.com/mito-ds/mito/issues"
25
27
  },
26
28
  "repository": {
27
- "url": "https://github.com/mito-ds/monorepo",
29
+ "url": "https://github.com/mito-ds/mito",
28
30
  "type": "git"
29
31
  },
30
- "version": "0.2.54",
32
+ "version": "0.2.56",
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://trymito.io",
123
+ "homepage": "https://github.com/mito-ds/mito",
108
124
  "types": "./lib/index.d.ts",
109
125
  "description": "The Mito Spreadsheet",
110
126
  "resolutions": {