cnotebook 2.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.
- cnotebook/__init__.py +400 -0
- cnotebook/align.py +454 -0
- cnotebook/context.py +523 -0
- cnotebook/grid/__init__.py +55 -0
- cnotebook/grid/grid.py +1649 -0
- cnotebook/helpers.py +201 -0
- cnotebook/ipython_ext.py +56 -0
- cnotebook/marimo_ext.py +272 -0
- cnotebook/pandas_ext.py +1156 -0
- cnotebook/polars_ext.py +1235 -0
- cnotebook/render.py +200 -0
- cnotebook-2.1.0.dist-info/METADATA +336 -0
- cnotebook-2.1.0.dist-info/RECORD +16 -0
- cnotebook-2.1.0.dist-info/WHEEL +5 -0
- cnotebook-2.1.0.dist-info/licenses/LICENSE +21 -0
- cnotebook-2.1.0.dist-info/top_level.txt +1 -0
cnotebook/helpers.py
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import re
|
|
3
|
+
from typing import Callable, Literal, Sequence
|
|
4
|
+
from openeye import oechem, oedepict
|
|
5
|
+
|
|
6
|
+
log = logging.getLogger("cnotebook")
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
# Type alias for highlight style
|
|
10
|
+
HighlightStyle = int | Literal["overlay_default", "overlay_ball_and_stick"]
|
|
11
|
+
|
|
12
|
+
# Type alias for highlight colors
|
|
13
|
+
HighlightColors = oechem.OEColor | oechem.OEColorIter
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def escape_html(val):
|
|
17
|
+
"""
|
|
18
|
+
Perform the same HTML escaping done by Pandas for displaying in Notebooks
|
|
19
|
+
:param val: Value to escape
|
|
20
|
+
:return: Escaped value (if val was a string)
|
|
21
|
+
"""
|
|
22
|
+
if isinstance(val, str):
|
|
23
|
+
return val.replace("&", r"&").replace("<", r"<").replace(">", r">")
|
|
24
|
+
return val
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def escape_brackets(val):
|
|
28
|
+
"""
|
|
29
|
+
Escapes only HTML brackets
|
|
30
|
+
:param val: Value to escape
|
|
31
|
+
:return: Escaped value (if string)
|
|
32
|
+
"""
|
|
33
|
+
if isinstance(val, str):
|
|
34
|
+
return val.replace("<", r"<").replace(">", r">")
|
|
35
|
+
return val
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# Remove conformer identifier from compound ID
|
|
39
|
+
CONFORMER_ID_REGEX = re.compile(r'(.*?)_\d+$')
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def remove_omega_conformer_id(val):
|
|
43
|
+
"""
|
|
44
|
+
Remove the conformer ID from a compound identifier
|
|
45
|
+
:param val: Value
|
|
46
|
+
:return: Processed value
|
|
47
|
+
"""
|
|
48
|
+
if isinstance(val, str):
|
|
49
|
+
m = re.search(CONFORMER_ID_REGEX, val)
|
|
50
|
+
if m is not None:
|
|
51
|
+
return m.group(1)
|
|
52
|
+
return val
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def create_structure_highlighter(
|
|
56
|
+
query: str | oechem.OESubSearch | oechem.OEMCSSearch | oechem.OEQMol,
|
|
57
|
+
color: HighlightColors | None = None,
|
|
58
|
+
style: HighlightStyle = "overlay_default"
|
|
59
|
+
) -> Callable[[oedepict.OE2DMolDisplay], None]:
|
|
60
|
+
"""
|
|
61
|
+
Closure that creates a callback to highlight SMARTS patterns or MCSS results in a molecule.
|
|
62
|
+
|
|
63
|
+
:param query: SMARTS pattern, oechem.OESubSearch, or oechem.OEMCSSearch object.
|
|
64
|
+
:param color: Highlight color(s). Can be a single oechem.OEColor or an oechem.OEColorIter
|
|
65
|
+
(e.g., oechem.OEGetLightColors()). Defaults to oechem.OEGetLightColors().
|
|
66
|
+
:param style: Highlight style. Can be an int (OEHighlightStyle constant) or a string
|
|
67
|
+
("overlay_default", "overlay_ball_and_stick"). Defaults to "overlay_default".
|
|
68
|
+
:returns: Callback function that highlights structures.
|
|
69
|
+
"""
|
|
70
|
+
# Default color
|
|
71
|
+
if color is None:
|
|
72
|
+
color = oechem.OEGetLightColors()
|
|
73
|
+
|
|
74
|
+
# Create the substructure search object
|
|
75
|
+
if isinstance(query, (str, oechem.OEQMol)):
|
|
76
|
+
ss = oechem.OESubSearch(query)
|
|
77
|
+
elif isinstance(query, (oechem.OESubSearch, oechem.OEMCSSearch)):
|
|
78
|
+
ss = query
|
|
79
|
+
else:
|
|
80
|
+
raise TypeError(f'Cannot create structure highlighter with object pattern of type {type(query).__name__}')
|
|
81
|
+
|
|
82
|
+
# Determine highlighting approach based on style
|
|
83
|
+
use_overlay = isinstance(style, str) and style in ("overlay_default", "overlay_ball_and_stick")
|
|
84
|
+
|
|
85
|
+
# Check if color is compatible with overlay
|
|
86
|
+
if use_overlay and isinstance(color, oechem.OEColor):
|
|
87
|
+
log.warning(
|
|
88
|
+
"Overlay coloring is not compatible with a single oechem.OEColor. Falling back to standard highlighting")
|
|
89
|
+
use_overlay = False
|
|
90
|
+
style = oedepict.OEHighlightStyle_BallAndStick
|
|
91
|
+
|
|
92
|
+
if use_overlay:
|
|
93
|
+
# Overlay highlighting with color iterator
|
|
94
|
+
highlight = oedepict.OEHighlightOverlayByBallAndStick(color)
|
|
95
|
+
|
|
96
|
+
def _overlay_highlighter(disp: oedepict.OE2DMolDisplay):
|
|
97
|
+
oedepict.OEAddHighlightOverlay(disp, highlight, ss.Match(disp.GetMolecule(), True))
|
|
98
|
+
|
|
99
|
+
return _overlay_highlighter
|
|
100
|
+
|
|
101
|
+
else:
|
|
102
|
+
# Traditional highlighting with OEHighlightStyle int
|
|
103
|
+
# For traditional highlighting, we need a single color
|
|
104
|
+
if isinstance(color, oechem.OEColor):
|
|
105
|
+
highlight_color = color
|
|
106
|
+
else:
|
|
107
|
+
# Get first color from iterator for traditional highlighting
|
|
108
|
+
highlight_color = oechem.OELightBlue
|
|
109
|
+
for c in color:
|
|
110
|
+
highlight_color = c
|
|
111
|
+
break
|
|
112
|
+
|
|
113
|
+
def _structure_highlighter(disp: oedepict.OE2DMolDisplay):
|
|
114
|
+
for match in ss.Match(disp.GetMolecule(), True):
|
|
115
|
+
oedepict.OEAddHighlighting(disp, highlight_color, style, match)
|
|
116
|
+
|
|
117
|
+
return _structure_highlighter
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def highlight_smarts(
|
|
121
|
+
mol: oechem.OEMolBase,
|
|
122
|
+
smarts: str | Sequence[str],
|
|
123
|
+
color: oechem.OEColor | Sequence[oechem.OEColor] = oechem.OEColor(oechem.OELightBlue),
|
|
124
|
+
style: int | Sequence[int] = oedepict.OEHighlightStyle_Stick,
|
|
125
|
+
opts: oedepict.OE2DMolDisplayOptions | None = None,
|
|
126
|
+
) -> oedepict.OE2DMolDisplay:
|
|
127
|
+
"""Highlight SMARTS patterns in a molecule and return a display object.
|
|
128
|
+
|
|
129
|
+
:param mol: OpenEye molecule to highlight.
|
|
130
|
+
:param smarts: SMARTS pattern or sequence of SMARTS patterns to highlight.
|
|
131
|
+
:param color: Highlight color or sequence of colors. If a single color, it is
|
|
132
|
+
applied to all patterns. If a sequence, must match length of smarts.
|
|
133
|
+
:param style: Highlight style or sequence of styles. If a single style, it is
|
|
134
|
+
applied to all patterns. If a sequence, must match length of smarts.
|
|
135
|
+
:param opts: Display options. If None, default options are used.
|
|
136
|
+
:returns: OE2DMolDisplay object with highlighted substructures.
|
|
137
|
+
:raises ValueError: If color/style sequence length doesn't match smarts length.
|
|
138
|
+
|
|
139
|
+
Example::
|
|
140
|
+
|
|
141
|
+
from openeye import oechem
|
|
142
|
+
import cnotebook
|
|
143
|
+
|
|
144
|
+
mol = oechem.OEGraphMol()
|
|
145
|
+
oechem.OESmilesToMol(mol, "c1ccc2c(c1)nc(n2)N")
|
|
146
|
+
|
|
147
|
+
# Single color/style for all patterns
|
|
148
|
+
disp = cnotebook.highlight_smarts(mol, ["ncn", "c1ccccc1"])
|
|
149
|
+
|
|
150
|
+
# Different colors for each pattern
|
|
151
|
+
disp = cnotebook.highlight_smarts(
|
|
152
|
+
mol,
|
|
153
|
+
["ncn", "c1ccccc1"],
|
|
154
|
+
color=[oechem.OEColor(oechem.OELightBlue), oechem.OEColor(oechem.OEPink)]
|
|
155
|
+
)
|
|
156
|
+
"""
|
|
157
|
+
# Prepare molecule for depiction
|
|
158
|
+
oedepict.OEPrepareDepiction(mol)
|
|
159
|
+
|
|
160
|
+
# Create display options if not provided
|
|
161
|
+
if opts is None:
|
|
162
|
+
opts = oedepict.OE2DMolDisplayOptions()
|
|
163
|
+
|
|
164
|
+
# Create display object
|
|
165
|
+
disp = oedepict.OE2DMolDisplay(mol, opts)
|
|
166
|
+
|
|
167
|
+
# Normalize smarts to a list
|
|
168
|
+
if isinstance(smarts, str):
|
|
169
|
+
smarts = [smarts]
|
|
170
|
+
|
|
171
|
+
n_patterns = len(smarts)
|
|
172
|
+
|
|
173
|
+
# Normalize colors to a list
|
|
174
|
+
if isinstance(color, oechem.OEColor):
|
|
175
|
+
colors = [color] * n_patterns
|
|
176
|
+
else:
|
|
177
|
+
colors = list(color)
|
|
178
|
+
if len(colors) != n_patterns:
|
|
179
|
+
raise ValueError(
|
|
180
|
+
f"Length of color sequence ({len(colors)}) must match "
|
|
181
|
+
f"length of smarts sequence ({n_patterns})"
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
# Normalize styles to a list
|
|
185
|
+
if isinstance(style, int):
|
|
186
|
+
styles = [style] * n_patterns
|
|
187
|
+
else:
|
|
188
|
+
styles = list(style)
|
|
189
|
+
if len(styles) != n_patterns:
|
|
190
|
+
raise ValueError(
|
|
191
|
+
f"Length of style sequence ({len(styles)}) must match "
|
|
192
|
+
f"length of smarts sequence ({n_patterns})"
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
# Highlight all patterns with corresponding color and style
|
|
196
|
+
for pattern, c, s in zip(smarts, colors, styles):
|
|
197
|
+
subs = oechem.OESubSearch(pattern)
|
|
198
|
+
for match in subs.Match(mol, True):
|
|
199
|
+
oedepict.OEAddHighlighting(disp, c, s, match)
|
|
200
|
+
|
|
201
|
+
return disp
|
cnotebook/ipython_ext.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from openeye import oechem, oedepict
|
|
3
|
+
from .render import (
|
|
4
|
+
oemol_to_html,
|
|
5
|
+
oedisp_to_html,
|
|
6
|
+
oeimage_to_html,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
# Only register iPython formatters if that is present
|
|
10
|
+
try:
|
|
11
|
+
# noinspection PyProtectedMember,PyPackageRequirements
|
|
12
|
+
from IPython import get_ipython
|
|
13
|
+
ipython_present = True
|
|
14
|
+
except ModuleNotFoundError:
|
|
15
|
+
ipython_present = False
|
|
16
|
+
|
|
17
|
+
log = logging.getLogger("cnotebook")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
########################################################################################################################
|
|
21
|
+
# Register iPython formatters
|
|
22
|
+
########################################################################################################################
|
|
23
|
+
|
|
24
|
+
if ipython_present:
|
|
25
|
+
|
|
26
|
+
def register_ipython_formatters():
|
|
27
|
+
"""
|
|
28
|
+
Register formatters for OpenEye types here that can be rendered. Calls to this function are idempotent.
|
|
29
|
+
"""
|
|
30
|
+
ipython_instance = get_ipython()
|
|
31
|
+
|
|
32
|
+
if ipython_instance is not None:
|
|
33
|
+
html_formatter = ipython_instance.display_formatter.formatters['text/html']
|
|
34
|
+
|
|
35
|
+
try:
|
|
36
|
+
_ = html_formatter.lookup(oechem.OEMolBase)
|
|
37
|
+
except KeyError:
|
|
38
|
+
html_formatter.for_type(oechem.OEMolBase, oemol_to_html)
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
_ = html_formatter.lookup(oedepict.OE2DMolDisplay)
|
|
42
|
+
except KeyError:
|
|
43
|
+
html_formatter.for_type(oedepict.OE2DMolDisplay, oedisp_to_html)
|
|
44
|
+
|
|
45
|
+
try:
|
|
46
|
+
_ = html_formatter.lookup(oedepict.OEImage)
|
|
47
|
+
except KeyError:
|
|
48
|
+
html_formatter.for_type(oedepict.OEImage, oeimage_to_html)
|
|
49
|
+
else:
|
|
50
|
+
log.debug("[cnotebook] iPython installed but not in use - cannot register iPython extension")
|
|
51
|
+
|
|
52
|
+
else:
|
|
53
|
+
|
|
54
|
+
# iPython is not present, so we do not register formatters for OpenEye objects
|
|
55
|
+
def register_ipython_formatters():
|
|
56
|
+
pass
|
cnotebook/marimo_ext.py
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Marimo integration for CNotebook.
|
|
3
|
+
|
|
4
|
+
This module provides MIME handlers for OpenEye objects and patches Marimo's
|
|
5
|
+
internal table implementation to support molecule rendering with callbacks
|
|
6
|
+
(highlighting, alignment, etc.) in Marimo's built-in DataFrame table component.
|
|
7
|
+
"""
|
|
8
|
+
import logging
|
|
9
|
+
import pandas as pd
|
|
10
|
+
from openeye import oechem, oedepict
|
|
11
|
+
|
|
12
|
+
# Import oepandas for dtype checking
|
|
13
|
+
try:
|
|
14
|
+
# noinspection PyUnusedImports
|
|
15
|
+
import oepandas as oepd
|
|
16
|
+
oepandas_available = True
|
|
17
|
+
except ImportError:
|
|
18
|
+
oepandas_available = False
|
|
19
|
+
|
|
20
|
+
# Import oepolars for dtype checking
|
|
21
|
+
try:
|
|
22
|
+
# noinspection PyUnusedImports
|
|
23
|
+
import polars as pl
|
|
24
|
+
# noinspection PyUnusedImports
|
|
25
|
+
import oepolars as oeplr
|
|
26
|
+
oepolars_available = True
|
|
27
|
+
except ImportError:
|
|
28
|
+
oepolars_available = False
|
|
29
|
+
|
|
30
|
+
from .context import cnotebook_context, get_series_context
|
|
31
|
+
from .render import (
|
|
32
|
+
oemol_to_html,
|
|
33
|
+
oedisp_to_html,
|
|
34
|
+
oeimage_to_html,
|
|
35
|
+
oemol_to_disp,
|
|
36
|
+
render_empty_molecule,
|
|
37
|
+
render_invalid_molecule
|
|
38
|
+
)
|
|
39
|
+
from .pandas_ext import render_dataframe
|
|
40
|
+
|
|
41
|
+
log = logging.getLogger("cnotebook")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
########################################################################################################################
|
|
45
|
+
# MIME handlers for individual OpenEye objects
|
|
46
|
+
########################################################################################################################
|
|
47
|
+
|
|
48
|
+
def _display_mol(self: oechem.OEMolBase):
|
|
49
|
+
ctx = cnotebook_context.get().copy()
|
|
50
|
+
# Allow user's image_format preference (SVG or PNG)
|
|
51
|
+
return "text/html", oemol_to_html(self, ctx=ctx)
|
|
52
|
+
|
|
53
|
+
oechem.OEMolBase._mime_ = _display_mol
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _display_display(self: oedepict.OE2DMolDisplay):
|
|
57
|
+
ctx = cnotebook_context.get().copy()
|
|
58
|
+
# Allow user's image_format preference (SVG or PNG)
|
|
59
|
+
return "text/html", oedisp_to_html(self, ctx=ctx)
|
|
60
|
+
|
|
61
|
+
oedepict.OE2DMolDisplay._mime_ = _display_display
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _display_image(self: oedepict.OEImage):
|
|
65
|
+
ctx = cnotebook_context.get().copy()
|
|
66
|
+
# Allow user's image_format preference (SVG or PNG)
|
|
67
|
+
return "text/html", oeimage_to_html(self, ctx=ctx)
|
|
68
|
+
|
|
69
|
+
oedepict.OEImage._mime_ = _display_image
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
########################################################################################################################
|
|
73
|
+
# Formatter factories for mo.ui.table format_mapping
|
|
74
|
+
########################################################################################################################
|
|
75
|
+
|
|
76
|
+
def _create_molecule_formatter(ctx):
|
|
77
|
+
"""
|
|
78
|
+
Create a formatter closure that renders molecules with callbacks applied.
|
|
79
|
+
|
|
80
|
+
:param ctx: CNotebookContext with callbacks (e.g., highlighting)
|
|
81
|
+
:return: Formatter function for use in mo.ui.table format_mapping
|
|
82
|
+
"""
|
|
83
|
+
def formatter(mol):
|
|
84
|
+
if mol is None:
|
|
85
|
+
return ""
|
|
86
|
+
|
|
87
|
+
if not isinstance(mol, oechem.OEMolBase):
|
|
88
|
+
return str(mol)
|
|
89
|
+
|
|
90
|
+
# Handle invalid molecules
|
|
91
|
+
if not mol.IsValid():
|
|
92
|
+
return render_invalid_molecule(ctx=ctx)
|
|
93
|
+
|
|
94
|
+
# Handle empty molecules
|
|
95
|
+
if mol.NumAtoms() == 0:
|
|
96
|
+
return render_empty_molecule(ctx=ctx)
|
|
97
|
+
|
|
98
|
+
# Create display object
|
|
99
|
+
disp = oemol_to_disp(mol, ctx=ctx)
|
|
100
|
+
|
|
101
|
+
# Apply callbacks (highlighting, etc.)
|
|
102
|
+
if ctx.callbacks:
|
|
103
|
+
for callback in ctx.callbacks:
|
|
104
|
+
callback(disp)
|
|
105
|
+
|
|
106
|
+
# Return display object
|
|
107
|
+
return disp
|
|
108
|
+
|
|
109
|
+
return formatter
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _create_display_formatter(ctx):
|
|
113
|
+
"""
|
|
114
|
+
Create a formatter closure that renders OE2DMolDisplay objects.
|
|
115
|
+
|
|
116
|
+
:param ctx: CNotebookContext for rendering options
|
|
117
|
+
:return: Formatter function for use in mo.ui.table format_mapping
|
|
118
|
+
"""
|
|
119
|
+
def formatter(disp):
|
|
120
|
+
if disp is None:
|
|
121
|
+
return ""
|
|
122
|
+
|
|
123
|
+
if not isinstance(disp, oedepict.OE2DMolDisplay):
|
|
124
|
+
return str(disp)
|
|
125
|
+
|
|
126
|
+
if not disp.IsValid():
|
|
127
|
+
return str(disp)
|
|
128
|
+
|
|
129
|
+
# Copy the display to avoid modifying the original
|
|
130
|
+
disp_copy = oedepict.OE2DMolDisplay(disp)
|
|
131
|
+
|
|
132
|
+
# Apply callbacks if any
|
|
133
|
+
if ctx.callbacks:
|
|
134
|
+
for callback in ctx.callbacks:
|
|
135
|
+
callback(disp_copy)
|
|
136
|
+
|
|
137
|
+
return disp_copy
|
|
138
|
+
|
|
139
|
+
return formatter
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
########################################################################################################################
|
|
143
|
+
# Marimo DataFrame formatter registration
|
|
144
|
+
#
|
|
145
|
+
# This registers a custom formatter with Marimo's OPINIONATED_FORMATTERS registry
|
|
146
|
+
# to handle DataFrames containing molecule columns. The formatter:
|
|
147
|
+
# - Detects MoleculeDtype and DisplayDtype columns
|
|
148
|
+
# - Creates format_mapping entries that apply callbacks (highlighting, alignment, etc.)
|
|
149
|
+
# - Returns OE2DMolDisplay objects which Marimo renders via their _mime_() method
|
|
150
|
+
########################################################################################################################
|
|
151
|
+
|
|
152
|
+
try:
|
|
153
|
+
import marimo as mo
|
|
154
|
+
# noinspection PyProtectedMember,PyUnusedImports
|
|
155
|
+
from marimo._output.formatting import OPINIONATED_FORMATTERS
|
|
156
|
+
# noinspection PyProtectedMember,PyUnusedImports
|
|
157
|
+
from marimo._plugins.ui._impl.table import table
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
# 1. Define the custom formatting logic
|
|
161
|
+
def marimo_pandas_formatter(df: pd.DataFrame):
|
|
162
|
+
"""
|
|
163
|
+
Monkey patch the Marimo DataFrame formatter
|
|
164
|
+
"""
|
|
165
|
+
format_mapping = {}
|
|
166
|
+
|
|
167
|
+
# Check for MoleculeDtype / DisplayDtype (OEPandas specific)
|
|
168
|
+
if oepandas_available:
|
|
169
|
+
for col in df.columns:
|
|
170
|
+
dtype = df[col].dtype
|
|
171
|
+
|
|
172
|
+
if isinstance(dtype, oepd.MoleculeDtype):
|
|
173
|
+
arr = df[col].array
|
|
174
|
+
ctx = get_series_context(arr.metadata).copy()
|
|
175
|
+
format_mapping[col] = _create_molecule_formatter(ctx)
|
|
176
|
+
|
|
177
|
+
elif isinstance(dtype, oepd.DisplayDtype):
|
|
178
|
+
arr = df[col].array
|
|
179
|
+
ctx = get_series_context(arr.metadata).copy()
|
|
180
|
+
format_mapping[col] = _create_display_formatter(ctx)
|
|
181
|
+
|
|
182
|
+
# Return a Marimo table with our custom mapping
|
|
183
|
+
# noinspection PyProtectedMember,PyTypeChecker
|
|
184
|
+
return table(df, selection=None, format_mapping=format_mapping, pagination=True)._mime_()
|
|
185
|
+
|
|
186
|
+
# 2. Inject into the Registry
|
|
187
|
+
def install_marimo_pandas_formatter():
|
|
188
|
+
# Check if we've already installed it to avoid duplicates
|
|
189
|
+
for typ, func in OPINIONATED_FORMATTERS.formatters.items():
|
|
190
|
+
if typ is pd.DataFrame and func.__name__ == "marimo_pandas_formatter":
|
|
191
|
+
return # Already installed
|
|
192
|
+
|
|
193
|
+
OPINIONATED_FORMATTERS.formatters[pd.DataFrame] = marimo_pandas_formatter
|
|
194
|
+
|
|
195
|
+
# Do the installation
|
|
196
|
+
install_marimo_pandas_formatter()
|
|
197
|
+
|
|
198
|
+
def marimo_polars_formatter(df: pl.DataFrame):
|
|
199
|
+
"""
|
|
200
|
+
Marimo DataFrame formatter for Polars DataFrames with molecule columns.
|
|
201
|
+
"""
|
|
202
|
+
format_mapping = {}
|
|
203
|
+
|
|
204
|
+
# Check for MoleculeType / DisplayType (OEPolars specific)
|
|
205
|
+
if oepolars_available:
|
|
206
|
+
for col in df.columns:
|
|
207
|
+
dtype = df.schema[col]
|
|
208
|
+
|
|
209
|
+
if isinstance(dtype, oeplr.MoleculeType):
|
|
210
|
+
series = df.get_column(col)
|
|
211
|
+
metadata = series.chem.metadata if hasattr(series, 'chem') else {}
|
|
212
|
+
ctx = get_series_context(metadata).copy()
|
|
213
|
+
format_mapping[col] = _create_molecule_formatter(ctx)
|
|
214
|
+
|
|
215
|
+
elif isinstance(dtype, oeplr.DisplayType):
|
|
216
|
+
series = df.get_column(col)
|
|
217
|
+
metadata = series.chem.metadata if hasattr(series, 'chem') else {}
|
|
218
|
+
ctx = get_series_context(metadata).copy()
|
|
219
|
+
format_mapping[col] = _create_display_formatter(ctx)
|
|
220
|
+
|
|
221
|
+
# Return a Marimo table with our custom mapping
|
|
222
|
+
# noinspection PyProtectedMember,PyTypeChecker
|
|
223
|
+
return table(df, selection=None, format_mapping=format_mapping, pagination=True)._mime_()
|
|
224
|
+
|
|
225
|
+
def install_marimo_polars_formatter():
|
|
226
|
+
"""Install the Polars DataFrame formatter if polars is available."""
|
|
227
|
+
if not oepolars_available:
|
|
228
|
+
return
|
|
229
|
+
|
|
230
|
+
# Check if we've already installed it to avoid duplicates
|
|
231
|
+
for typ, func in OPINIONATED_FORMATTERS.formatters.items():
|
|
232
|
+
if typ is pl.DataFrame and func.__name__ == "marimo_polars_formatter":
|
|
233
|
+
return # Already installed
|
|
234
|
+
|
|
235
|
+
OPINIONATED_FORMATTERS.formatters[pl.DataFrame] = marimo_polars_formatter
|
|
236
|
+
|
|
237
|
+
if oepolars_available:
|
|
238
|
+
install_marimo_polars_formatter()
|
|
239
|
+
|
|
240
|
+
except (ImportError, AttributeError) as ex:
|
|
241
|
+
# Marimo not installed or API changed - skip formatter registration
|
|
242
|
+
log.debug(f'Marimo formatter registration skipped: {ex}')
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
########################################################################################################################
|
|
246
|
+
# Fallback DataFrame MIME handler for non-Marimo contexts
|
|
247
|
+
########################################################################################################################
|
|
248
|
+
|
|
249
|
+
def _display_dataframe(self: pd.DataFrame):
|
|
250
|
+
"""
|
|
251
|
+
Fallback MIME hook for Pandas DataFrames in non-Marimo contexts.
|
|
252
|
+
|
|
253
|
+
In Marimo, the internal table patch handles DataFrame display.
|
|
254
|
+
This is used for static exports or other tools that check _mime_.
|
|
255
|
+
"""
|
|
256
|
+
return "text/html", render_dataframe(df=self, formatters=None, col_space=None)
|
|
257
|
+
|
|
258
|
+
pd.DataFrame._mime_ = _display_dataframe
|
|
259
|
+
|
|
260
|
+
if oepolars_available:
|
|
261
|
+
from .polars_ext import render_polars_dataframe
|
|
262
|
+
|
|
263
|
+
def _display_polars_dataframe(self: pl.DataFrame):
|
|
264
|
+
"""
|
|
265
|
+
Fallback MIME hook for Polars DataFrames in non-Marimo contexts.
|
|
266
|
+
|
|
267
|
+
In Marimo, the internal table patch handles DataFrame display.
|
|
268
|
+
This is used for static exports or other tools that check _mime_.
|
|
269
|
+
"""
|
|
270
|
+
return "text/html", render_polars_dataframe(df=self, formatters=None, col_space=None)
|
|
271
|
+
|
|
272
|
+
pl.DataFrame._mime_ = _display_polars_dataframe
|