Skip to content

Commit

Permalink
Add an Excel macro for opening a Jupyter notebook
Browse files Browse the repository at this point in the history
The new "OpenJupyterNotebook" macro can be called from VBA using
Application.Run and takes a single optional argument.

If passed a path of a notebook file that notebook will be opened. If
passed a directory then Jupyter will be started in that directory.

Also addded a new 'disable_ribbon' option to the config so the macro can
be used without adding anything to the ribbon.

Fixes #6
  • Loading branch information
tonyroberts committed Jan 7, 2021
1 parent 56dac1e commit 7c7b4bc
Show file tree
Hide file tree
Showing 6 changed files with 321 additions and 7 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,14 @@ To configure add the following to your pyxll.cfg file (default values shown):
notebook_dir = Documents
timeout = 30
qt = PySide2
disable_ribbon = 0

If *use_workbook_dir* is set and the current workbook is saved then Jupyter will open in the same folder
as the current workbook.

If *disable_ribbon* is set then the ribbon button to start Jupyter will not be shown, however Jupyter
may still be opened using the "OpenJupyterNotebook" macro.

### Qt

The pyxll-jupyter package uses the Qt [QWebEngineView](https://doc.qt.io/qt-5/qwebengineview.html) widget, and by
Expand Down
222 changes: 222 additions & 0 deletions examples/example.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": 44,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>A</th>\n",
" <th>B</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>-5.0</td>\n",
" <td>25.0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>-4.0</td>\n",
" <td>16.0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>-3.0</td>\n",
" <td>9.0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>-2.0</td>\n",
" <td>4.0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>-1.0</td>\n",
" <td>1.0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>5</th>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>6</th>\n",
" <td>1.0</td>\n",
" <td>1.0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>7</th>\n",
" <td>2.0</td>\n",
" <td>4.0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>8</th>\n",
" <td>3.0</td>\n",
" <td>9.0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>9</th>\n",
" <td>4.0</td>\n",
" <td>16.0</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" A B\n",
"0 -5.0 25.0\n",
"1 -4.0 16.0\n",
"2 -3.0 9.0\n",
"3 -2.0 4.0\n",
"4 -1.0 1.0\n",
"5 0.0 0.0\n",
"6 1.0 1.0\n",
"7 2.0 4.0\n",
"8 3.0 9.0\n",
"9 4.0 16.0"
]
},
"execution_count": 44,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Use %xl_get to fetch data as a pandas DataFrame\n",
"df = %xl_get --cell B31\n",
"df"
]
},
{
"cell_type": "code",
"execution_count": 45,
"metadata": {},
"outputs": [],
"source": [
"# Use %xl_set to set data back in Excel\n",
"df[\"C\"] = df[\"A\"].apply(lambda x: x ** 3)\n",
"%xl_set df --cell F31"
]
},
{
"cell_type": "code",
"execution_count": 29,
"metadata": {},
"outputs": [],
"source": [
"# Set the index of the DataFrame from column A\n",
"df = df.set_index(\"A\")"
]
},
{
"cell_type": "code",
"execution_count": 46,
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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\n",
"text/plain": [
"<Figure size 250x200 with 1 Axes>"
]
},
"metadata": {
"needs_background": "light"
},
"output_type": "display_data"
}
],
"source": [
"# Use %xl_plot to plot data in Excel\n",
"ax = df.plot()\n",
"%xl_plot ax --cell J29 --width 250 --height 200 --name \"example plot\""
]
},
{
"cell_type": "code",
"execution_count": 47,
"metadata": {},
"outputs": [],
"source": [
"# Calling in to Excel can be done using pyxll.xl_app\n",
"from pyxll import xl_app\n",
"xl = xl_app()"
]
},
{
"cell_type": "code",
"execution_count": 48,
"metadata": {
"scrolled": true
},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 48,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# For example, change the selection to A1:K20\n",
"r = xl.Range(\"A1:K20\")\n",
"r.Select()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.0"
}
},
"nbformat": 4,
"nbformat_minor": 4
}
Binary file added examples/example.xlsm
Binary file not shown.
84 changes: 79 additions & 5 deletions pyxll_jupyter/pyxll.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"""
from .widget import JupyterQtWidget
from .qtimports import QApplication, QMessageBox
from pyxll import get_config, xl_app
from pyxll import xlcAlert, get_config, xl_app, xl_macro
import ctypes.wintypes
import pkg_resources
import logging
Expand Down Expand Up @@ -40,7 +40,7 @@ def _get_notebook_path(cfg):
_log.error("Unexpected value for JUPYTER.use_workbook_dir.")

if use_workbook_dir:
xl = xl_app()
xl = xl_app(com_package="win32com")
wb = xl.ActiveWorkbook
if wb is not None and wb.FullName and os.path.exists(wb.FullName):
return os.path.dirname(wb.FullName)
Expand Down Expand Up @@ -73,20 +73,42 @@ def _get_jupyter_timeout(cfg):
return max(timeout, 1.0)


def open_jupyter_notebook(*args):
def open_jupyter_notebook(*args, initial_path=None, notebook_path=None):
"""Ribbon action function for opening the Jupyter notebook
browser control.
:param initial_path: Path to open Jupyter in.
:param notebook_path: Path of Jupyter notebook to open.
"""
from pyxll import create_ctp

if initial_path is not None and notebook_path is not None:
raise RuntimeError("'initial_path' and 'notebook_path' cannot both be set.")

if notebook_path is not None:
if not os.path.exists(notebook_path):
raise RuntimeError("Notebook path '%s' not found." % notebook_path)
if not os.path.isfile(notebook_path):
raise RuntimeError("Notebook path '%s' is not a file." % notebook_path)
notebook_path = os.path.abspath(notebook_path)

# Create the Qt Application
app = _get_qt_app()

# The create the widget and add it as an Excel CTP
cfg = get_config()
path = _get_notebook_path(cfg)
timeout = _get_jupyter_timeout(cfg)
widget = JupyterQtWidget(initial_path=path, timeout=timeout)

if notebook_path is None and initial_path is None:
initial_path = _get_notebook_path(cfg)
if initial_path and not os.path.exists(initial_path):
raise RuntimeError("Directory '%s' does not exist.")
if initial_path and not os.path.isdir(initial_path):
raise RuntimeError("Path '%s' is not a directory.")

widget = JupyterQtWidget(initial_path=initial_path,
timeout=timeout,
notebook_path=notebook_path)

create_ctp(widget, width=800)

Expand Down Expand Up @@ -148,6 +170,46 @@ def set_selection_in_ipython(*args):
_log.error("Error setting selection in Excel", exc_info=True)


@xl_macro
def OpenJupyterNotebook(path=None):
"""
Open a Jupyter notebook in a new task pane.
:param path: Path to Jupyter notebook file or directory.
:return: True on success
"""
try:
if path is not None:
if not os.path.isabs(path):
# Try and get the absolute path relative to the active workbook
xl = xl_app(com_package="win32com")
wb = xl.ActiveWorkbook
if wb is not None and wb.FullName and os.path.exists(wb.FullName):
abs_path = os.path.join(os.path.dirname(wb.FullName), path)
if os.path.exists(abs_path):
path = abs_path
if not os.path.exists(path):
raise RuntimeError(f"Path '{path}' not found.")

initial_path = None
notebook_path = None
if path is not None:
if os.path.isdir(path):
initial_path = path
elif os.path.isfile(path):
notebook_path = path
else:
raise RuntimeError(f"Something wrong with {path}")

open_jupyter_notebook(initial_path=initial_path,
notebook_path=notebook_path)

return True
except Exception as e:
xlcAlert(f"Error opening Jupyter notebook: {e}")
raise


def modules():
"""Entry point for getting the pyxll modules.
Returns a list of module names."""
Expand All @@ -160,6 +222,18 @@ def ribbon():
"""Entry point for getting the pyxll ribbon file.
Returns a list of (filename, data) tuples.
"""
cfg = get_config()

disable_ribbon = False
if cfg.has_option("JUPYTER", "disable_ribbon"):
try:
disable_ribbon = bool(int(cfg.get("JUPYTER", "disable_ribbon")))
except (ValueError, TypeError):
_log.error("Unexpected value for JUPYTER.disable_ribbon.")

if disable_ribbon:
return []

ribbon = pkg_resources.resource_string(__name__, "resources/ribbon.xml")
return [
(None, ribbon)
Expand Down
Loading

0 comments on commit 7c7b4bc

Please sign in to comment.