variable-explorer 0.1.0__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 (49) hide show
  1. variable_explorer/__init__.py +7 -0
  2. variable_explorer/_version.py +4 -0
  3. variable_explorer/kernel/__init__.py +5 -0
  4. variable_explorer/kernel/comm_handler.py +255 -0
  5. variable_explorer/kernel/data_provider.py +235 -0
  6. variable_explorer/kernel/editor.py +88 -0
  7. variable_explorer/kernel/introspection.py +186 -0
  8. variable_explorer/kernel/serialization.py +73 -0
  9. variable_explorer/kernel/sorter.py +34 -0
  10. variable_explorer/kernel/statistics.py +101 -0
  11. variable_explorer/labextension/build_log.json +726 -0
  12. variable_explorer/labextension/package.json +96 -0
  13. variable_explorer/labextension/schemas/variable-explorer/package.json.orig +91 -0
  14. variable_explorer/labextension/schemas/variable-explorer/plugin.json +75 -0
  15. variable_explorer/labextension/static/lib_index_js.88a2cd3be0f2bf49f0eb.js +1417 -0
  16. variable_explorer/labextension/static/lib_index_js.88a2cd3be0f2bf49f0eb.js.map +1 -0
  17. variable_explorer/labextension/static/remoteEntry.a8ed3dcc7548f0b68f93.js +576 -0
  18. variable_explorer/labextension/static/remoteEntry.a8ed3dcc7548f0b68f93.js.map +1 -0
  19. variable_explorer/labextension/static/style.js +4 -0
  20. variable_explorer/labextension/static/style_index_js-data_font_woff2_charset_utf-8_base64_d09GMgABAAAAABmsAAsAAAAANbQAABlcAAEAAAAAA-5c9677.c69a59632d259bde8f84.js +785 -0
  21. variable_explorer/labextension/static/style_index_js-data_font_woff2_charset_utf-8_base64_d09GMgABAAAAABmsAAsAAAAANbQAABlcAAEAAAAAA-5c9677.c69a59632d259bde8f84.js.map +1 -0
  22. variable_explorer/labextension/static/vendors-node_modules_ag-grid-community_dist_package_main_esm_mjs.c38425b170e91e5db052.js +50347 -0
  23. variable_explorer/labextension/static/vendors-node_modules_ag-grid-community_dist_package_main_esm_mjs.c38425b170e91e5db052.js.map +1 -0
  24. variable_explorer/labextension/static/vendors-node_modules_ag-grid-community_styles_ag-grid_css-node_modules_ag-grid-community_styl-7d25f0.7424d30423d9f1c112f6.js +8124 -0
  25. variable_explorer/labextension/static/vendors-node_modules_ag-grid-community_styles_ag-grid_css-node_modules_ag-grid-community_styl-7d25f0.7424d30423d9f1c112f6.js.map +1 -0
  26. variable_explorer/labextension/static/vendors-node_modules_ag-grid-react_dist_package_index_esm_mjs.ca52d36c364e6562240a.js +2917 -0
  27. variable_explorer/labextension/static/vendors-node_modules_ag-grid-react_dist_package_index_esm_mjs.ca52d36c364e6562240a.js.map +1 -0
  28. variable_explorer-0.1.0.data/data/share/jupyter/labextensions/variable-explorer/build_log.json +726 -0
  29. variable_explorer-0.1.0.data/data/share/jupyter/labextensions/variable-explorer/install.json +5 -0
  30. variable_explorer-0.1.0.data/data/share/jupyter/labextensions/variable-explorer/package.json +96 -0
  31. variable_explorer-0.1.0.data/data/share/jupyter/labextensions/variable-explorer/schemas/variable-explorer/package.json.orig +91 -0
  32. variable_explorer-0.1.0.data/data/share/jupyter/labextensions/variable-explorer/schemas/variable-explorer/plugin.json +75 -0
  33. variable_explorer-0.1.0.data/data/share/jupyter/labextensions/variable-explorer/static/lib_index_js.88a2cd3be0f2bf49f0eb.js +1417 -0
  34. variable_explorer-0.1.0.data/data/share/jupyter/labextensions/variable-explorer/static/lib_index_js.88a2cd3be0f2bf49f0eb.js.map +1 -0
  35. variable_explorer-0.1.0.data/data/share/jupyter/labextensions/variable-explorer/static/remoteEntry.a8ed3dcc7548f0b68f93.js +576 -0
  36. variable_explorer-0.1.0.data/data/share/jupyter/labextensions/variable-explorer/static/remoteEntry.a8ed3dcc7548f0b68f93.js.map +1 -0
  37. variable_explorer-0.1.0.data/data/share/jupyter/labextensions/variable-explorer/static/style.js +4 -0
  38. variable_explorer-0.1.0.data/data/share/jupyter/labextensions/variable-explorer/static/style_index_js-data_font_woff2_charset_utf-8_base64_d09GMgABAAAAABmsAAsAAAAANbQAABlcAAEAAAAAA-5c9677.c69a59632d259bde8f84.js +785 -0
  39. variable_explorer-0.1.0.data/data/share/jupyter/labextensions/variable-explorer/static/style_index_js-data_font_woff2_charset_utf-8_base64_d09GMgABAAAAABmsAAsAAAAANbQAABlcAAEAAAAAA-5c9677.c69a59632d259bde8f84.js.map +1 -0
  40. variable_explorer-0.1.0.data/data/share/jupyter/labextensions/variable-explorer/static/vendors-node_modules_ag-grid-community_dist_package_main_esm_mjs.c38425b170e91e5db052.js +50347 -0
  41. variable_explorer-0.1.0.data/data/share/jupyter/labextensions/variable-explorer/static/vendors-node_modules_ag-grid-community_dist_package_main_esm_mjs.c38425b170e91e5db052.js.map +1 -0
  42. variable_explorer-0.1.0.data/data/share/jupyter/labextensions/variable-explorer/static/vendors-node_modules_ag-grid-community_styles_ag-grid_css-node_modules_ag-grid-community_styl-7d25f0.7424d30423d9f1c112f6.js +8124 -0
  43. variable_explorer-0.1.0.data/data/share/jupyter/labextensions/variable-explorer/static/vendors-node_modules_ag-grid-community_styles_ag-grid_css-node_modules_ag-grid-community_styl-7d25f0.7424d30423d9f1c112f6.js.map +1 -0
  44. variable_explorer-0.1.0.data/data/share/jupyter/labextensions/variable-explorer/static/vendors-node_modules_ag-grid-react_dist_package_index_esm_mjs.ca52d36c364e6562240a.js +2917 -0
  45. variable_explorer-0.1.0.data/data/share/jupyter/labextensions/variable-explorer/static/vendors-node_modules_ag-grid-react_dist_package_index_esm_mjs.ca52d36c364e6562240a.js.map +1 -0
  46. variable_explorer-0.1.0.dist-info/METADATA +80 -0
  47. variable_explorer-0.1.0.dist-info/RECORD +49 -0
  48. variable_explorer-0.1.0.dist-info/WHEEL +4 -0
  49. variable_explorer-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,186 @@
1
+ """Namespace introspection — scan user variables and extract metadata."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sys
6
+ from typing import Any
7
+
8
+ # Types/modules to skip
9
+ _SKIP_TYPES = (type, type(sys), type(lambda: None))
10
+ _SKIP_PREFIXES = ('_', '__')
11
+ _SKIP_NAMES = {
12
+ 'In', 'Out', 'get_ipython', 'exit', 'quit',
13
+ '_dh', '_ih', '_oh', '_sh', '_i', '_ii', '_iii',
14
+ }
15
+
16
+
17
+ def get_variable_list(user_ns: dict) -> list[dict]:
18
+ """Scan the user namespace and return metadata for each variable."""
19
+ variables = []
20
+
21
+ for name, obj in user_ns.items():
22
+ if name in _SKIP_NAMES:
23
+ continue
24
+ if any(name.startswith(p) for p in _SKIP_PREFIXES):
25
+ continue
26
+ if isinstance(obj, _SKIP_TYPES):
27
+ continue
28
+
29
+ try:
30
+ info = _inspect_variable(name, obj)
31
+ if info is not None:
32
+ variables.append(info)
33
+ except Exception:
34
+ pass
35
+
36
+ return variables
37
+
38
+
39
+ def _inspect_variable(name: str, obj: Any) -> dict | None:
40
+ """Extract metadata for a single variable."""
41
+ type_name = type(obj).__name__
42
+ shape = _get_shape(obj)
43
+ memory_bytes = _get_memory(obj)
44
+ short_repr = _get_short_repr(obj)
45
+ tabular_info = _classify_tabular(obj)
46
+
47
+ return {
48
+ 'name': name,
49
+ 'typeName': type_name,
50
+ 'shape': shape,
51
+ 'memoryBytes': memory_bytes,
52
+ 'shortRepr': short_repr,
53
+ 'isTabular': tabular_info['isTabular'],
54
+ 'tabularKind': tabular_info['kind'],
55
+ 'childCount': tabular_info.get('childCount', 0),
56
+ }
57
+
58
+
59
+ def _classify_tabular(obj: Any) -> dict:
60
+ """Classify what kind of tabular display an object supports."""
61
+ try:
62
+ import pandas as pd
63
+ if isinstance(obj, pd.DataFrame):
64
+ return {'isTabular': True, 'kind': 'dataframe'}
65
+ if isinstance(obj, pd.Series):
66
+ return {'isTabular': True, 'kind': 'series'}
67
+ except ImportError:
68
+ pass
69
+
70
+ try:
71
+ import numpy as np
72
+ if isinstance(obj, np.ndarray) and obj.ndim <= 2:
73
+ return {'isTabular': True, 'kind': 'ndarray'}
74
+ except ImportError:
75
+ pass
76
+
77
+ if isinstance(obj, dict):
78
+ values = list(obj.values())
79
+ if not values:
80
+ return {'isTabular': False, 'kind': 'dict_empty'}
81
+
82
+ # Dict of lists → tabular
83
+ if all(isinstance(v, (list, tuple)) for v in values):
84
+ return {'isTabular': True, 'kind': 'dict_of_lists'}
85
+
86
+ # Dict of DataFrames → container
87
+ try:
88
+ import pandas as pd
89
+ if all(isinstance(v, pd.DataFrame) for v in values):
90
+ return {'isTabular': True, 'kind': 'dict_of_dataframes', 'childCount': len(values)}
91
+ except ImportError:
92
+ pass
93
+
94
+ # Dict of dicts → try to convert to DataFrame
95
+ if all(isinstance(v, dict) for v in values):
96
+ return {'isTabular': True, 'kind': 'dict_of_dicts'}
97
+
98
+ # Generic dict with scalar values → single-row tabular
99
+ return {'isTabular': True, 'kind': 'dict_scalar'}
100
+
101
+ if isinstance(obj, (list, tuple)):
102
+ if not obj:
103
+ return {'isTabular': False, 'kind': 'list_empty'}
104
+
105
+ # List of DataFrames → container
106
+ try:
107
+ import pandas as pd
108
+ if all(isinstance(v, pd.DataFrame) for v in obj):
109
+ return {'isTabular': True, 'kind': 'list_of_dataframes', 'childCount': len(obj)}
110
+ except ImportError:
111
+ pass
112
+
113
+ # List of dicts → tabular (common pattern from APIs/JSON)
114
+ if all(isinstance(v, dict) for v in obj):
115
+ return {'isTabular': True, 'kind': 'list_of_dicts'}
116
+
117
+ # List of lists/tuples → tabular
118
+ if all(isinstance(v, (list, tuple)) for v in obj):
119
+ return {'isTabular': True, 'kind': 'list_of_lists'}
120
+
121
+ # Simple list of scalars → single-column tabular
122
+ return {'isTabular': True, 'kind': 'list_scalar'}
123
+
124
+ return {'isTabular': False, 'kind': 'other'}
125
+
126
+
127
+ def _get_shape(obj: Any) -> list[int]:
128
+ """Get the shape of an object."""
129
+ try:
130
+ import pandas as pd
131
+ if isinstance(obj, pd.DataFrame):
132
+ return list(obj.shape)
133
+ if isinstance(obj, pd.Series):
134
+ return [len(obj)]
135
+ except ImportError:
136
+ pass
137
+
138
+ try:
139
+ import numpy as np
140
+ if isinstance(obj, np.ndarray):
141
+ return list(obj.shape)
142
+ except ImportError:
143
+ pass
144
+
145
+ if hasattr(obj, '__len__'):
146
+ try:
147
+ return [len(obj)]
148
+ except Exception:
149
+ pass
150
+
151
+ return []
152
+
153
+
154
+ def _get_memory(obj: Any) -> int:
155
+ """Get memory usage in bytes."""
156
+ try:
157
+ import pandas as pd
158
+ if isinstance(obj, pd.DataFrame):
159
+ return int(obj.memory_usage(deep=True).sum())
160
+ if isinstance(obj, pd.Series):
161
+ return int(obj.memory_usage(deep=True))
162
+ except (ImportError, Exception):
163
+ pass
164
+
165
+ try:
166
+ import numpy as np
167
+ if isinstance(obj, np.ndarray):
168
+ return int(obj.nbytes)
169
+ except (ImportError, Exception):
170
+ pass
171
+
172
+ try:
173
+ return sys.getsizeof(obj)
174
+ except Exception:
175
+ return 0
176
+
177
+
178
+ def _get_short_repr(obj: Any, max_len: int = 200) -> str:
179
+ """Get a short string representation."""
180
+ try:
181
+ r = repr(obj)
182
+ if len(r) > max_len:
183
+ return r[:max_len] + '...'
184
+ return r
185
+ except Exception:
186
+ return f'<{type(obj).__name__}>'
@@ -0,0 +1,73 @@
1
+ """JSON-safe serialization for comm messages."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+
8
+ def safe_serialize(data: Any) -> Any:
9
+ """Recursively convert data to JSON-safe types."""
10
+ if data is None:
11
+ return None
12
+
13
+ if isinstance(data, dict):
14
+ return {str(k): safe_serialize(v) for k, v in data.items()}
15
+
16
+ if isinstance(data, (list, tuple)):
17
+ return [safe_serialize(item) for item in data]
18
+
19
+ if isinstance(data, bool):
20
+ return data
21
+
22
+ if isinstance(data, int):
23
+ return data
24
+
25
+ if isinstance(data, float):
26
+ import math
27
+ if math.isnan(data) or math.isinf(data):
28
+ return None
29
+ return data
30
+
31
+ if isinstance(data, str):
32
+ return data
33
+
34
+ # numpy types
35
+ try:
36
+ import numpy as np
37
+ if isinstance(data, np.integer):
38
+ return int(data)
39
+ if isinstance(data, np.floating):
40
+ v = float(data)
41
+ if np.isnan(v) or np.isinf(v):
42
+ return None
43
+ return v
44
+ if isinstance(data, np.bool_):
45
+ return bool(data)
46
+ if isinstance(data, np.ndarray):
47
+ return data.tolist()
48
+ except ImportError:
49
+ pass
50
+
51
+ # pandas types
52
+ try:
53
+ import pandas as pd
54
+ if isinstance(data, pd.Timestamp):
55
+ return data.isoformat()
56
+ if pd.isna(data):
57
+ return None
58
+ except (ImportError, TypeError, ValueError):
59
+ pass
60
+
61
+ # datetime
62
+ if hasattr(data, 'isoformat'):
63
+ return data.isoformat()
64
+
65
+ # bytes
66
+ if isinstance(data, bytes):
67
+ return data.decode('utf-8', errors='replace')
68
+
69
+ # Fallback
70
+ try:
71
+ return str(data)
72
+ except Exception:
73
+ return '<unserializable>'
@@ -0,0 +1,34 @@
1
+ """Kernel-side DataFrame sorting."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+
8
+ def apply_sort(df: Any, sort_model: list[dict]) -> Any:
9
+ """Apply multi-column sort to a DataFrame.
10
+
11
+ sort_model: [{'colId': 'price', 'sort': 'asc'}, ...]
12
+ Returns a new sorted DataFrame (does not modify the original).
13
+ """
14
+ if not sort_model:
15
+ return df
16
+
17
+ cols = [s['colId'] for s in sort_model]
18
+ ascending = [s['sort'] == 'asc' for s in sort_model]
19
+
20
+ # Validate columns exist
21
+ valid_cols = []
22
+ valid_asc = []
23
+ for col, asc in zip(cols, ascending):
24
+ if col in df.columns:
25
+ valid_cols.append(col)
26
+ valid_asc.append(asc)
27
+
28
+ if not valid_cols:
29
+ return df
30
+
31
+ try:
32
+ return df.sort_values(by=valid_cols, ascending=valid_asc, na_position='last')
33
+ except Exception:
34
+ return df
@@ -0,0 +1,101 @@
1
+ """Column statistics and histogram computation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+
8
+ def compute_column_stats(var_name: str, obj: Any) -> dict:
9
+ """Compute statistics for all columns of a DataFrame-like object."""
10
+ import pandas as pd
11
+ import numpy as np
12
+
13
+ # Convert to DataFrame
14
+ if isinstance(obj, pd.Series):
15
+ df = obj.to_frame()
16
+ elif isinstance(obj, pd.DataFrame):
17
+ df = obj
18
+ else:
19
+ try:
20
+ df = pd.DataFrame(obj)
21
+ except Exception:
22
+ return {
23
+ 'type': 'column_stats',
24
+ 'variable': var_name,
25
+ 'stats': []
26
+ }
27
+
28
+ stats = []
29
+ for col in df.columns:
30
+ series = df[col]
31
+ info: dict[str, Any] = {
32
+ 'name': str(col),
33
+ 'dtype': str(series.dtype),
34
+ 'nullCount': int(series.isna().sum()),
35
+ 'uniqueCount': int(series.nunique()),
36
+ }
37
+
38
+ try:
39
+ info['histogram'] = _compute_histogram(series)
40
+ except Exception:
41
+ info['histogram'] = None
42
+
43
+ stats.append(info)
44
+
45
+ return {
46
+ 'type': 'column_stats',
47
+ 'variable': var_name,
48
+ 'stats': stats,
49
+ }
50
+
51
+
52
+ def _compute_histogram(series) -> dict | None:
53
+ """Compute histogram data for a single column."""
54
+ import pandas as pd
55
+ import numpy as np
56
+
57
+ dtype = series.dtype
58
+
59
+ # Boolean
60
+ if pd.api.types.is_bool_dtype(dtype):
61
+ return {
62
+ 'type': 'boolean',
63
+ 'trueCount': int(series.sum()),
64
+ 'falseCount': int((~series).sum()),
65
+ 'nullCount': int(series.isna().sum()),
66
+ }
67
+
68
+ # Numeric (not bool)
69
+ if pd.api.types.is_numeric_dtype(dtype):
70
+ clean = series.dropna()
71
+ if len(clean) == 0:
72
+ return None
73
+
74
+ # Compute histogram bins
75
+ n_bins = min(20, max(5, len(clean) // 10))
76
+ try:
77
+ counts, edges = np.histogram(clean, bins=n_bins)
78
+ return {
79
+ 'type': 'numeric',
80
+ 'counts': counts.tolist(),
81
+ 'edges': edges.tolist(),
82
+ 'min': float(clean.min()),
83
+ 'max': float(clean.max()),
84
+ 'mean': float(clean.mean()),
85
+ 'std': float(clean.std()),
86
+ }
87
+ except Exception:
88
+ return None
89
+
90
+ # Categorical / object / string
91
+ if pd.api.types.is_categorical_dtype(dtype) or dtype == object or pd.api.types.is_string_dtype(dtype):
92
+ vc = series.value_counts().head(10)
93
+ if len(vc) == 0:
94
+ return None
95
+ return {
96
+ 'type': 'categorical',
97
+ 'labels': [str(label) for label in vc.index.tolist()],
98
+ 'counts': vc.values.tolist(),
99
+ }
100
+
101
+ return None