cnotebook 1.2.0__py3-none-any.whl → 2.1.1__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 +365 -67
- cnotebook/align.py +231 -167
- cnotebook/context.py +50 -18
- cnotebook/grid/__init__.py +56 -0
- cnotebook/grid/grid.py +1655 -0
- cnotebook/helpers.py +147 -15
- cnotebook/ipython_ext.py +0 -3
- cnotebook/marimo_ext.py +67 -0
- cnotebook/pandas_ext.py +760 -514
- cnotebook/polars_ext.py +1237 -0
- cnotebook/render.py +0 -195
- cnotebook-2.1.1.dist-info/METADATA +338 -0
- cnotebook-2.1.1.dist-info/RECORD +16 -0
- {cnotebook-1.2.0.dist-info → cnotebook-2.1.1.dist-info}/WHEEL +1 -1
- cnotebook-1.2.0.dist-info/METADATA +0 -280
- cnotebook-1.2.0.dist-info/RECORD +0 -13
- {cnotebook-1.2.0.dist-info → cnotebook-2.1.1.dist-info}/licenses/LICENSE +0 -0
- {cnotebook-1.2.0.dist-info → cnotebook-2.1.1.dist-info}/top_level.txt +0 -0
cnotebook/helpers.py
CHANGED
|
@@ -1,7 +1,17 @@
|
|
|
1
|
+
import logging
|
|
1
2
|
import re
|
|
2
|
-
from typing import Callable
|
|
3
|
+
from typing import Callable, Literal, Sequence
|
|
3
4
|
from openeye import oechem, oedepict
|
|
4
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
|
+
|
|
5
15
|
|
|
6
16
|
def escape_html(val):
|
|
7
17
|
"""
|
|
@@ -44,26 +54,148 @@ def remove_omega_conformer_id(val):
|
|
|
44
54
|
|
|
45
55
|
def create_structure_highlighter(
|
|
46
56
|
query: str | oechem.OESubSearch | oechem.OEMCSSearch | oechem.OEQMol,
|
|
47
|
-
color:
|
|
48
|
-
style:
|
|
57
|
+
color: HighlightColors | None = None,
|
|
58
|
+
style: HighlightStyle = "overlay_default"
|
|
49
59
|
) -> Callable[[oedepict.OE2DMolDisplay], None]:
|
|
50
60
|
"""
|
|
51
|
-
Closure that creates a callback to highlight SMARTS patterns or MCSS results in a molecule
|
|
52
|
-
|
|
53
|
-
:param
|
|
54
|
-
:param
|
|
55
|
-
|
|
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.
|
|
56
69
|
"""
|
|
70
|
+
# Default color
|
|
71
|
+
if color is None:
|
|
72
|
+
color = oechem.OEGetLightColors()
|
|
57
73
|
|
|
74
|
+
# Create the substructure search object
|
|
58
75
|
if isinstance(query, (str, oechem.OEQMol)):
|
|
59
76
|
ss = oechem.OESubSearch(query)
|
|
60
|
-
|
|
61
|
-
|
|
77
|
+
elif isinstance(query, (oechem.OESubSearch, oechem.OEMCSSearch)):
|
|
78
|
+
ss = query
|
|
79
|
+
else:
|
|
62
80
|
raise TypeError(f'Cannot create structure highlighter with object pattern of type {type(query).__name__}')
|
|
63
81
|
|
|
64
|
-
#
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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)
|
|
68
200
|
|
|
69
|
-
return
|
|
201
|
+
return disp
|
cnotebook/ipython_ext.py
CHANGED
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from openeye import oechem, oedepict
|
|
3
3
|
from .render import (
|
|
4
|
-
CNotebookContext,
|
|
5
|
-
pass_cnotebook_context,
|
|
6
4
|
oemol_to_html,
|
|
7
5
|
oedisp_to_html,
|
|
8
6
|
oeimage_to_html,
|
|
9
|
-
render_molecule_grid # Re-export for backward compatibility
|
|
10
7
|
)
|
|
11
8
|
|
|
12
9
|
# Only register iPython formatters if that is present
|
cnotebook/marimo_ext.py
CHANGED
|
@@ -11,11 +11,22 @@ from openeye import oechem, oedepict
|
|
|
11
11
|
|
|
12
12
|
# Import oepandas for dtype checking
|
|
13
13
|
try:
|
|
14
|
+
# noinspection PyUnusedImports
|
|
14
15
|
import oepandas as oepd
|
|
15
16
|
oepandas_available = True
|
|
16
17
|
except ImportError:
|
|
17
18
|
oepandas_available = False
|
|
18
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
|
+
|
|
19
30
|
from .context import cnotebook_context, get_series_context
|
|
20
31
|
from .render import (
|
|
21
32
|
oemol_to_html,
|
|
@@ -184,6 +195,48 @@ try:
|
|
|
184
195
|
# Do the installation
|
|
185
196
|
install_marimo_pandas_formatter()
|
|
186
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
|
+
|
|
187
240
|
except (ImportError, AttributeError) as ex:
|
|
188
241
|
# Marimo not installed or API changed - skip formatter registration
|
|
189
242
|
log.debug(f'Marimo formatter registration skipped: {ex}')
|
|
@@ -203,3 +256,17 @@ def _display_dataframe(self: pd.DataFrame):
|
|
|
203
256
|
return "text/html", render_dataframe(df=self, formatters=None, col_space=None)
|
|
204
257
|
|
|
205
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
|