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/render.py CHANGED
@@ -2,9 +2,6 @@ import logging
2
2
  import base64
3
3
  from .context import CNotebookContext, pass_cnotebook_context
4
4
  from openeye import oechem, oedepict
5
- from typing import Literal
6
- from math import floor, ceil
7
- from collections.abc import Iterable, Sequence
8
5
 
9
6
  log = logging.getLogger("cnotebook")
10
7
 
@@ -201,195 +198,3 @@ def oeimage_to_html(image: oedepict.OEImage, *, ctx: CNotebookContext) -> str:
201
198
  )
202
199
 
203
200
 
204
- @pass_cnotebook_context
205
- def render_molecule_grid(
206
- mols: Sequence[oechem.OEMolBase],
207
- nrows: int | None = None,
208
- ncols: int | None = None,
209
- max_width: int = 1280,
210
- max_columns: int = 100,
211
- min_width: int | None = None,
212
- min_height: int | None = None,
213
- align: oechem.OEMolBase | Literal["first"] | None = None,
214
- smarts: str | Iterable[str] | oechem.OESubSearch | Iterable[oechem.OESubSearch] | None = None,
215
- color: oechem.OEColor = oechem.OEColor(oechem.OELightBlue),
216
- style: int = oedepict.OEHighlightStyle_Stick,
217
- scale: float = 1.0,
218
- *,
219
- ctx: CNotebookContext
220
- ) -> oedepict.OEImage:
221
- """
222
- Convenience function to render a molecule grid
223
- :param ctx: Current OpenEye rendering context
224
- :param mols: Iterable of OpenEye molecules
225
- :param nrows: Number of rows to display in the grid
226
- :param ncols: Number of columns to display in the grid
227
- :param max_width: Maximum width of the image
228
- :param max_columns: Maximum number of columns (if ncols is being automatically calculated)
229
- :param min_width: Minimum image width (prevents tiny images)
230
- :param min_height: Minimum image height (prevents tiny images)
231
- :param align: Alignment to the first molecule, or to a reference molecule
232
- :param smarts: SMARTS substructure highlighting
233
- :param color: SMARTS highlighting color
234
- :param style: SMARTS highlighting style
235
- :param scale: Image scale in the 2D grid
236
- :return: Image of the molecule grid
237
- """
238
- from .helpers import create_structure_highlighter
239
-
240
- # Re-scale the images
241
- ctx.display_options.SetScale(ctx.display_options.GetScale() * scale)
242
-
243
- # ---------------------------------------------------------------
244
- # Input validation
245
- # ---------------------------------------------------------------
246
-
247
- # Handle single molecules
248
- if isinstance(mols, oechem.OEMolBase):
249
- mols = [mols]
250
-
251
- # Make a copy of the molecules (so we do not modify them)
252
- # We can use OEGraphMol here because we don't care about conformers
253
- # Filter out None values first
254
- mols = [oechem.OEGraphMol(mol) for mol in mols if mol is not None]
255
-
256
- if len(mols) == 0:
257
- log.warning("No molecules or display objects to render into a grid")
258
- # Return a minimal 1x1 image instead of 0x0 to avoid OpenEye bug
259
- return oedepict.OEImage(1, 1)
260
-
261
- # Get the subset of molecules that will actually be displayed
262
- valid = []
263
-
264
- for idx, mol in enumerate(mols):
265
- if mol is not None:
266
- if isinstance(mol, oechem.OEMolBase):
267
- if mol.IsValid():
268
- valid.append(mol)
269
- else:
270
- log.warning(f'Molecule at index {idx} is not valid')
271
-
272
- else:
273
- log.warning(f'Object at index is not a molecule but type {type(mol).__name__}')
274
-
275
- if len(valid) == 0:
276
- log.warning("No valid molecules or display objects to render into a grid")
277
- # Return a minimal 1x1 image instead of 0x0 to avoid OpenEye bug
278
- return oedepict.OEImage(1, 1)
279
-
280
- # ---------------------------------------------------------------
281
- # For highlighting SMARTS
282
- # ---------------------------------------------------------------
283
-
284
- highlighers = None
285
-
286
- if smarts is not None:
287
- highlighers = []
288
- # Case: Single pattern
289
- if isinstance(smarts, (str, oechem.OESubSearch)):
290
- highlighers.append(
291
- create_structure_highlighter(
292
- smarts,
293
- color=color,
294
- style=style
295
- )
296
- )
297
-
298
- else:
299
- for pattern in smarts:
300
- highlighers.append(
301
- create_structure_highlighter(
302
- pattern,
303
- color=color,
304
- style=style
305
- )
306
- )
307
-
308
- # ---------------------------------------------------------------
309
- # For substructure alignment
310
- # ---------------------------------------------------------------
311
-
312
- align_mcss = None
313
-
314
- if align is not None:
315
-
316
- # If we are doing an alignment
317
- if isinstance(align, bool) and align:
318
- align_ref = mols[0]
319
-
320
- elif isinstance(align, oechem.OEMolBase):
321
- align_ref = align
322
-
323
- else:
324
- raise TypeError(f'Cannot initialize MCSS alignment reference from type {type(align).__name__}')
325
-
326
- # Set up the MCSS
327
- align_mcss = oechem.OEMCSSearch(oechem.OEMCSType_Approximate)
328
- align_mcss.Init(align_ref, oechem.OEExprOpts_DefaultAtoms, oechem.OEExprOpts_DefaultBonds)
329
-
330
- # ---------------------------------------------------------------
331
- # Create the display objects for each molecule
332
- # ---------------------------------------------------------------
333
-
334
- displays = []
335
- max_disp_width = float('-inf')
336
- max_disp_height = float('-inf')
337
-
338
- for mol in valid:
339
- if align_mcss is not None:
340
- oedepict.OEPrepareAlignedDepiction(mol, align_mcss)
341
- else:
342
- oedepict.OEPrepareDepiction(mol)
343
-
344
- # Create the display object
345
- disp = ctx.create_molecule_display(mol, min_width=min_width, min_height=min_height)
346
-
347
- # Highlight SMARTS patterns
348
- if highlighers is not None:
349
- for highlight in highlighers:
350
- highlight(disp)
351
-
352
- displays.append(disp)
353
-
354
- if disp.GetWidth() > max_disp_width:
355
- max_disp_width = disp.GetWidth()
356
-
357
- if disp.GetHeight() > max_disp_height:
358
- max_disp_height = disp.GetHeight()
359
-
360
- # ---------------------------------------------------------------
361
- # Figure out the geometry of the full image
362
- # ---------------------------------------------------------------
363
- # Case: We have one molecule
364
- if len(displays) == 1:
365
- ncols = 1
366
- nrows = 1
367
-
368
- # Case: We are computing based on max_width
369
- elif ncols is None and nrows is None:
370
-
371
- # Number of columns we can fit into max_width
372
- ncols = min(floor(max_width / max_disp_width), max_columns, len(displays))
373
- nrows = ceil(len(displays) / ncols)
374
-
375
- elif nrows is not None:
376
- ncols = ceil(len(displays) / nrows)
377
-
378
- elif ncols is not None:
379
- nrows = ceil(len(displays) / ncols)
380
-
381
- else:
382
- raise RuntimeError("Cannot determine number of rows and columns in molecule grid")
383
-
384
- # Image width and height
385
- width = max_disp_width * ncols
386
- height = max_disp_height * nrows
387
-
388
- image = oedepict.OEImage(width, height)
389
- grid = oedepict.OEImageGrid(image, nrows, ncols)
390
-
391
- # Render the molecules
392
- for disp, cell in zip(displays, grid.GetCells()):
393
- oedepict.OERenderMolecule(cell, disp)
394
-
395
- return image
@@ -0,0 +1,338 @@
1
+ Metadata-Version: 2.4
2
+ Name: cnotebook
3
+ Version: 2.1.1
4
+ Summary: Chemistry visualization in Jupyter Notebooks with the OpenEye Toolkits
5
+ Author-email: Scott Arne Johnson <scott.arne.johnson@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/scott-arne/cnotebook
8
+ Project-URL: Bug Reports, https://github.com/scott-arne/cnotebook/issues
9
+ Project-URL: Source, https://github.com/scott-arne/cnotebook
10
+ Project-URL: Documentation, https://github.com/scott-arne/cnotebook#readme
11
+ Project-URL: Changelog, https://github.com/scott-arne/cnotebook/blob/master/CHANGELOG.md
12
+ Keywords: chemistry,cheminformatics,computational-chemistry,molecular-visualization,jupyter,marimo,openeye,scientific-computing
13
+ Classifier: Development Status :: 5 - Production/Stable
14
+ Classifier: Intended Audience :: Science/Research
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Topic :: Scientific/Engineering :: Chemistry
17
+ Classifier: Topic :: Scientific/Engineering :: Visualization
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3 :: Only
23
+ Classifier: Operating System :: OS Independent
24
+ Classifier: Framework :: Jupyter
25
+ Classifier: Framework :: IPython
26
+ Requires-Python: >=3.11
27
+ Description-Content-Type: text/markdown
28
+ License-File: LICENSE
29
+ Requires-Dist: openeye-toolkits>=2025.2.1
30
+ Requires-Dist: anywidget>=0.9.0
31
+ Requires-Dist: jinja2>=3.0.0
32
+ Provides-Extra: dev
33
+ Requires-Dist: invoke; extra == "dev"
34
+ Requires-Dist: build; extra == "dev"
35
+ Requires-Dist: pytest; extra == "dev"
36
+ Provides-Extra: test
37
+ Requires-Dist: pytest; extra == "test"
38
+ Requires-Dist: pytest-cov; extra == "test"
39
+ Dynamic: license-file
40
+
41
+ # CNotebook
42
+
43
+ [![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/downloads/)
44
+ [![OpenEye Toolkits](https://img.shields.io/badge/OpenEye-2025.2.1+-green.svg)](https://www.eyesopen.com/toolkits)
45
+
46
+ **Author:** Scott Arne Johnson ([scott.arne.johnson@gmail.com](mailto:scott.arne.johnson@gmail.com))
47
+
48
+ **Documentation:** https://cnotebook.readthedocs.io/en/latest/
49
+
50
+ CNotebook provides chemistry visualization for Jupyter Notebooks and Marimo using the OpenEye Toolkits.
51
+ Import the package and your molecular data will automatically render as chemical structures without additional
52
+ configuration.
53
+
54
+ Supports both Pandas and Polars DataFrames with automatic environment detection.
55
+
56
+ **Render molecules in Jupyter and Marimo with style**
57
+ <br>
58
+ <img src="docs/_static/molecule_with_style.png" height="200">
59
+
60
+ **Maintain Jupyter table formatting for Pandas and Polars**
61
+ <br>
62
+ <img src="docs/_static/simple_pandas.png" height="600">
63
+
64
+ **Compatible with native Marimo tables**
65
+ <br>
66
+ <img src="docs/_static/marimo_pandas_polars.png" height="600">
67
+
68
+ **Interactive molecule grids that support data**
69
+ <br>
70
+ <img src="docs/_static/simple_molgrid.png" height="300">
71
+
72
+ ## Table of Contents
73
+
74
+ - [Installation](#installation)
75
+ - [Getting Started](#getting-started)
76
+ - [Features](#features)
77
+ - [MolGrid Interactive Visualization](#molgrid-interactive-visualization)
78
+ - [DataFrame Integration](#dataframe-integration)
79
+ - [Advanced Usage](#advanced-usage)
80
+ - [Example Notebooks](#example-notebooks)
81
+ - [Documentation](#documentation)
82
+ - [Contributing](#contributing)
83
+ - [License](#license)
84
+
85
+ ## Installation
86
+
87
+ ```bash
88
+ pip install cnotebook
89
+ ```
90
+
91
+ **Prerequisites:**
92
+ - [OpenEye Toolkits](http://eyesopen.com): `pip install openeye-toolkits`
93
+ - You must have a valid license (free for academia).
94
+
95
+ **Optional backends:**
96
+ - Pandas support: `pip install pandas oepandas`
97
+ - Polars support: `pip install polars oepolars`
98
+
99
+ Both backends can be installed together, neither are required unless you want to work with DataFrames.
100
+
101
+ ## Getting Started
102
+
103
+ The fastest way to learn CNotebook is through the example notebooks in the `examples/` directory:
104
+
105
+ | Environment | Pandas | Polars | MolGrid |
106
+ |-------------|-----------------------------------------------------------------|-----------------------------------------------------------------|-------------------------------------------------------------------|
107
+ | **Jupyter** | [pandas_jupyter_demo.ipynb](examples/pandas_jupyter_demo.ipynb) | [polars_jupyter_demo.ipynb](examples/polars_jupyter_demo.ipynb) | [molgrid_jupyter_demo.ipynb](examples/molgrid_jupyter_demo.ipynb) |
108
+ | **Marimo** | [pandas_marimo_demo.py](examples/pandas_marimo_demo.py) | [polars_marimo_demo.py](examples/polars_marimo_demo.py) | [molgrid_marimo_demo.py](examples/molgrid_marimo_demo.py) |
109
+
110
+ ### Basic Usage
111
+
112
+ ```python
113
+ import cnotebook
114
+ from openeye import oechem
115
+
116
+ # Create a molecule (supports titles in SMILES)
117
+ mol = oechem.OEGraphMol()
118
+ oechem.OESmilesToMol(mol, "c1cnccc1 Benzene")
119
+
120
+ # Display it - automatically renders as a chemical structure
121
+ mol
122
+ ```
123
+ <img src="docs/_static/benzene.png" />
124
+
125
+ CNotebook registers formatters so OpenEye molecule objects display as chemical structures instead of text representations.
126
+
127
+ ## Features
128
+
129
+ ### Automatic Rendering
130
+ - Zero configuration required
131
+ - Supports Jupyter Notebooks and Marimo
132
+ - Automatic environment and backend detection
133
+
134
+ ### Molecule Support
135
+ - Direct rendering of `oechem.OEMolBase` objects
136
+ - Advanced rendering with `OE2DMolDisplay` options
137
+ - Pandas integration via OEPandas
138
+ - Polars integration via OEPolars
139
+
140
+ ### Visualization Options
141
+ - PNG (default) or SVG output
142
+ - Configurable width, height, and scaling
143
+ - Substructure highlighting with SMARTS patterns
144
+ - Molecular alignment to reference structures
145
+
146
+ ### MolGrid Interactive Visualization
147
+ - Paginated grid display for browsing molecules
148
+ - Text search across molecular properties
149
+ - SMARTS substructure filtering
150
+ - Selection tools with export to SMILES or CSV
151
+ - Information tooltips with molecular data
152
+ - DataFrame integration with automatic field detection
153
+
154
+ ### DataFrame Integration
155
+ - Automatic molecule column detection and rendering
156
+ - Per-row substructure highlighting
157
+ - Molecular alignment within DataFrames
158
+ - Fingerprint similarity visualization
159
+ - Property calculations on molecule columns
160
+
161
+ ## MolGrid Interactive Visualization
162
+
163
+ MolGrid provides an interactive grid for browsing molecular datasets with search and selection capabilities.
164
+
165
+ ### Basic Example
166
+
167
+ ```python
168
+ from cnotebook import MolGrid
169
+ from openeye import oechem
170
+
171
+ # Create molecules
172
+ molecules = []
173
+ for smi in ["CCO", "c1ccccc1", "CC(=O)O"]:
174
+ mol = oechem.OEGraphMol()
175
+ oechem.OESmilesToMol(mol, smi)
176
+ molecules.append(mol)
177
+
178
+ # Display interactive grid
179
+ grid = MolGrid(molecules)
180
+ grid.display()
181
+ ```
182
+ <img src="docs/_static/simple_molgrid.png" height="300">
183
+
184
+ ### Search and Filter
185
+
186
+ MolGrid provides two search modes:
187
+ - **Properties mode:** Search by molecular titles and configurable text fields
188
+ - **SMARTS mode:** Filter by substructure patterns with match highlighting
189
+
190
+ ### Selection
191
+
192
+ - Click molecules or checkboxes to select
193
+ - Use the menu for Select All, Clear, and Invert operations
194
+ - Export selections to SMILES or CSV files
195
+
196
+ ### Information Tooltips
197
+
198
+ - Hover over the information button to view molecular data
199
+ - Click to pin tooltips for comparing multiple molecules
200
+ - Configure displayed fields with the `data` parameter
201
+
202
+ ### DataFrame Integration
203
+
204
+ ```python
205
+ import pandas as pd
206
+ from cnotebook import MolGrid
207
+ from openeye import oechem, oemolprop
208
+
209
+ # Create DataFrame
210
+ df = pd.DataFrame(
211
+ {"Molecule": ["CCO", "c1ccccc1", "CC(=O)O"]}
212
+ ).chem.as_molecule("Molecule")
213
+
214
+ # Calculate some properties
215
+ df["MW"] = df.Molecule.apply(oechem.OECalculateMolecularWeight)
216
+ df["PSA"] = df.Molecule.apply(oemolprop.OEGet2dPSA)
217
+ df["HBA"] = df.Molecule.apply(oemolprop.OEGetHBondAcceptorCount)
218
+ df["HBD"] = df.Molecule.apply(oemolprop.OEGetHBondDonorCount)
219
+
220
+ # Display the grid (using the 'Molecule' column for structures)
221
+ grid = df.chem.molgrid("Molecule")
222
+ grid.display()
223
+ ```
224
+
225
+ This will display the same grid as above, but with molecule data if you click the "i".
226
+
227
+ ### Retrieving Selections
228
+
229
+ ```python
230
+ # Get selected molecules
231
+ selected_mols = grid.get_selection()
232
+
233
+ # Get selected indices
234
+ indices = grid.get_selection_indices()
235
+ ```
236
+
237
+ ## DataFrame Integration
238
+
239
+ ### Pandas DataFrames
240
+
241
+ ```python
242
+ import cnotebook
243
+ import oepandas as oepd
244
+
245
+ # Read the example unaligned molecules
246
+ df = oepd.read_sdf("examples/assets/rotations.sdf", no_title=True)
247
+
248
+ # Rename the "Molecule" column to "Original" so that we can
249
+ # see the original unaligned molecules
250
+ df = df.rename(columns={"Molecule": "Original"})
251
+
252
+ # Create a new molecule column called "Aligned" so that we can
253
+ # see the aligned molecules
254
+ df["Aligned"] = df.Original.chem.copy_molecules()
255
+
256
+ # Add substructure highlighting
257
+ df["Original"].chem.highlight("c1ccccc1")
258
+ df["Aligned"].chem.highlight("c1ccccc1")
259
+
260
+ # Align molecules to a reference
261
+ df["Aligned"].chem.align_depictions("first")
262
+
263
+ # Display the DataFrame
264
+ df
265
+ ```
266
+
267
+ <img src="docs/_static/pandas_highlight_and_align_dataframe.png" height="400">
268
+
269
+ ### Polars DataFrames
270
+
271
+ Same example as above using Polars instead of Pandas. The main difference is that some methods are called
272
+ from the DataFrame instead of the Series.
273
+
274
+ ```python
275
+ import cnotebook
276
+ import oepolars as oepl
277
+
278
+ # Read the example unaligned molecules
279
+ df = oepl.read_sdf("examples/assets/rotations.sdf", no_title=True)
280
+
281
+ # Rename the "Molecule" column to "Original" so that we can
282
+ # see the original unaligned molecules
283
+ df = df.rename({"Molecule": "Original"})
284
+
285
+ # # Create a new molecule column called "Aligned" so that we can
286
+ # # see the aligned molecules
287
+ df = df.chem.copy_molecules("Original", "Aligned")
288
+
289
+ # # Add substructure highlighting
290
+ df.chem.highlight("Original", "c1ccccc1")
291
+ df.chem.highlight("Aligned", "c1ccccc1")
292
+
293
+ # # Align molecules to a reference
294
+ df["Aligned"].chem.align_depictions("first")
295
+
296
+ # Display the DataFrame
297
+ df
298
+ ```
299
+
300
+ This will display the exact same table as above.
301
+
302
+ ## Example Notebooks
303
+
304
+ The `examples/` directory contains comprehensive tutorials for learning CNotebook:
305
+
306
+ ### Jupyter Notebooks
307
+
308
+ - **[pandas_jupyter_demo.ipynb](examples/pandas_jupyter_demo.ipynb)** - Complete Pandas integration tutorial covering molecule rendering, highlighting, alignment, and fingerprint similarity
309
+ - **[polars_jupyter_demo.ipynb](examples/polars_jupyter_demo.ipynb)** - Complete Polars integration tutorial with the same features adapted for Polars patterns
310
+ - **[molgrid_jupyter_demo.ipynb](examples/molgrid_jupyter_demo.ipynb)** - Interactive molecule grid tutorial with search, selection, and export features
311
+ - **[pandas_jupyter_svgs.ipynb](examples/pandas_jupyter_svgs.ipynb)** - SVG vs PNG rendering comparison and quality considerations
312
+
313
+ ### Marimo Applications
314
+
315
+ - **[pandas_marimo_demo.py](examples/pandas_marimo_demo.py)** - Pandas tutorial in reactive Marimo environment
316
+ - **[polars_marimo_demo.py](examples/polars_marimo_demo.py)** - Polars tutorial in reactive Marimo environment
317
+ - **[molgrid_marimo_demo.py](examples/molgrid_marimo_demo.py)** - MolGrid tutorial with reactive selection feedback
318
+
319
+ **Recommended starting point:** Begin with the MolGrid demo for your preferred environment, then explore the Pandas or Polars tutorials for DataFrame integration.
320
+
321
+ ## Contributing
322
+
323
+ Contributions are welcome. Please ensure your code:
324
+ - Follows existing code style and conventions
325
+ - Includes appropriate tests
326
+ - Works with both Jupyter and Marimo environments
327
+ - Maintains compatibility with OpenEye Toolkits
328
+ - Works with both Pandas and Polars when applicable
329
+
330
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for detailed guidelines.
331
+
332
+ ## License
333
+
334
+ This project is licensed under the MIT License. See [LICENSE](LICENSE) for details.
335
+
336
+ ## Support
337
+
338
+ For bug reports, feature requests, or questions, please open an issue on GitHub or contact [scott.arne.johnson@gmail.com](mailto:scott.arne.johnson@gmail.com).
@@ -0,0 +1,16 @@
1
+ cnotebook/__init__.py,sha256=MBF3mPnDEI3ytXH8BwA9TqongkaYMVflLbc0lTt5rLI,13746
2
+ cnotebook/align.py,sha256=wDJy79PvRG0O_XHCAXkGv2uqp6nL65g-NO-oQSnOkpI,17926
3
+ cnotebook/context.py,sha256=HKTx0Bxz0LfleaWjBco_AoSGR2iPrVY-0OsJ_ro6O0o,19229
4
+ cnotebook/helpers.py,sha256=TCcNfykhSi77FrTQC2_2W8G9lxP0n3S7V8tEiGcE0hg,7121
5
+ cnotebook/ipython_ext.py,sha256=Z6IhHg_YP6-becgruEDiE-tVkSB0SZTUU6R2MItaVa4,1874
6
+ cnotebook/marimo_ext.py,sha256=F2WBiM0F3vLcL9vAcZesB7XgIWULd7QiWe78dQy1h3s,9790
7
+ cnotebook/pandas_ext.py,sha256=XrmL5CTQ6a_J3ruV-AK_7ZupcsUU-Y0fZ8Ml_koeOTY,43442
8
+ cnotebook/polars_ext.py,sha256=YC1Sezwk4C9Qoz4AHxsvt6D_4nwHn17wLOL5tieEGgw,47864
9
+ cnotebook/render.py,sha256=jn47U5I0t0H5R_iydhBsz_vVdtBVXyQYwrMb_XG8zy0,6143
10
+ cnotebook/grid/__init__.py,sha256=Orgb6l9C7oJD32-uBCC7gDrjUtNzJf0DLXao7FcL6GQ,1859
11
+ cnotebook/grid/grid.py,sha256=VbcoZCNT1whfcMYWjK9UQ2o9Jr5D_y2xYS54QhfwASo,51500
12
+ cnotebook-2.1.1.dist-info/licenses/LICENSE,sha256=HbIgeZz-pWGC7BEnYFCQ-jfD1m_BfiosF9qjgWw64GU,1080
13
+ cnotebook-2.1.1.dist-info/METADATA,sha256=emROyOs1eYOp7auzcB6sIrKy6bLKTGj8Eah0i72OCu0,11924
14
+ cnotebook-2.1.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
15
+ cnotebook-2.1.1.dist-info/top_level.txt,sha256=jzkieTjQwdNKfMwnoElvDDtNPkeLMjbvWbsbkSsboo8,10
16
+ cnotebook-2.1.1.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.9.0)
2
+ Generator: setuptools (80.10.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5