Skip to content

Commit 478bce9

Browse files
authoredApr 3, 2024··
Unittest IPython extension + Automatic loading (#478)
* Automatically load IPython extension * Test functions in notebook test * Add unittest for Jupyter extension * Change parameter name to `autoload_ipython_extension` * Combine pytest into single command * Refactor documentation of extension autoloading
1 parent f586f24 commit 478bce9

File tree

5 files changed

+171
-5
lines changed

5 files changed

+171
-5
lines changed
 

‎.github/workflows/tests.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ jobs:
7070
- name: Install dependencies
7171
run: |
7272
python -m pip install --upgrade pip
73-
pip install flake8 pytest pytest-cov
73+
pip install flake8 pytest pytest-cov nbval numpy
7474
cp pysrc/juliacall/juliapkg-dev.json pysrc/juliacall/juliapkg.json
7575
pip install -e .
7676
- name: Lint with flake8
@@ -81,7 +81,7 @@ jobs:
8181
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
8282
- name: Run tests
8383
run: |
84-
pytest -s --cov=pysrc
84+
pytest -s --nbval --cov=pysrc ./pytest/
8585
- name: Upload coverage to Codecov
8686
uses: codecov/codecov-action@v2
8787
env:

‎docs/src/compat.md

+6-2
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,9 @@ The `juliacall` IPython extension adds these features to your IPython session:
6767

6868
The extension is experimental and unstable - the API can change at any time.
6969

70-
Enable the extension with `%load_ext juliacall`.
71-
See [the IPython docs](https://ipython.readthedocs.io/en/stable/config/extensions/).
70+
You can explicitly enable the extension with `%load_ext juliacall`, but
71+
it will automatically be loaded if `juliacall` is imported and IPython is detected.
72+
You can disable this behavior with an [environment variable](@ref julia-config).
7273

7374
The `%%julia` cell magic can synchronise variables between Julia and Python by listing them
7475
on the first line:
@@ -88,6 +89,9 @@ In [5]: z
8889
Out[5]: '2^8 = 256'
8990
```
9091

92+
Also see [the IPython docs](https://ipython.readthedocs.io/en/stable/config/extensions/)
93+
for more information on extensions.
94+
9195
## Asynchronous Julia code (including Makie)
9296

9397
Asynchronous Julia code will not normally run while Python is executing, unless it is in a

‎docs/src/juliacall.md

+1
Original file line numberDiff line numberDiff line change
@@ -123,3 +123,4 @@ be configured in two ways:
123123
| `-X juliacall-sysimage=<file>` | `PYTHON_JULIACALL_SYSIMAGE=<file>` | Use the given system image. |
124124
| `-X juliacall-threads=<N\|auto>` | `PYTHON_JULIACALL_THREADS=<N\|auto>` | Launch N threads. |
125125
| `-X juliacall-warn-overwrite=<yes\|no>` | `PYTHON_JULIACALL_WARN_OVERWRITE=<yes\|no>` | Enable or disable method overwrite warnings. |
126+
| `-X juliacall-autoload-ipython-extension=<yes\|no>` | `PYTHON_JULIACALL_AUTOLOAD_IPYTHON_EXTENSION=<yes\|no>` | Enable or disable IPython extension autoloading. |

‎pysrc/juliacall/__init__.py

+23-1
Original file line numberDiff line numberDiff line change
@@ -248,8 +248,30 @@ def jlstr(x):
248248
"PYTHON_JULIACALL_HANDLE_SIGNALS=no."
249249
)
250250

251-
init()
251+
# Next, automatically load the juliacall extension if we are in IPython or Jupyter
252+
CONFIG['autoload_ipython_extension'] = choice('autoload_ipython_extension', ['yes', 'no'])[0]
253+
if CONFIG['autoload_ipython_extension'] in {'yes', None}:
254+
try:
255+
get_ipython = sys.modules['IPython'].get_ipython
256+
257+
if CONFIG['autoload_ipython_extension'] is None:
258+
# Only let the user know if it was not explicitly set
259+
print(
260+
"Detected IPython. Loading juliacall extension. See https://juliapy.github.io/PythonCall.jl/stable/compat/#IPython"
261+
)
262+
263+
load_ipython_extension(get_ipython())
264+
except Exception as e:
265+
if CONFIG['autoload_ipython_extension'] == 'yes':
266+
# Only warn if the user explicitly requested the extension to be loaded
267+
warnings.warn(
268+
"Could not load juliacall extension in Jupyter notebook: " + str(e)
269+
)
270+
pass
271+
252272

253273
def load_ipython_extension(ip):
254274
import juliacall.ipython
255275
juliacall.ipython.load_ipython_extension(ip)
276+
277+
init()

‎pytest/test_nb.ipynb

+139
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "code",
5+
"execution_count": null,
6+
"metadata": {},
7+
"outputs": [],
8+
"source": [
9+
"# NBVAL_IGNORE_OUTPUT\n",
10+
"import numpy as np\n",
11+
"from juliacall import Main as jl"
12+
]
13+
},
14+
{
15+
"cell_type": "code",
16+
"execution_count": 2,
17+
"metadata": {},
18+
"outputs": [
19+
{
20+
"name": "stdout",
21+
"output_type": "stream",
22+
"text": [
23+
"3\n"
24+
]
25+
}
26+
],
27+
"source": [
28+
"%%julia\n",
29+
"\n",
30+
"# Automatically activates Julia magic\n",
31+
"\n",
32+
"x = 1\n",
33+
"println(x + 2)"
34+
]
35+
},
36+
{
37+
"cell_type": "code",
38+
"execution_count": 3,
39+
"metadata": {},
40+
"outputs": [
41+
{
42+
"name": "stdout",
43+
"output_type": "stream",
44+
"text": [
45+
"4\n"
46+
]
47+
}
48+
],
49+
"source": [
50+
"%julia println(x + 3)"
51+
]
52+
},
53+
{
54+
"cell_type": "code",
55+
"execution_count": 4,
56+
"metadata": {},
57+
"outputs": [
58+
{
59+
"data": {
60+
"text/plain": [
61+
"1.0"
62+
]
63+
},
64+
"execution_count": 4,
65+
"metadata": {},
66+
"output_type": "execute_result"
67+
}
68+
],
69+
"source": [
70+
"jl.cos(0)"
71+
]
72+
},
73+
{
74+
"cell_type": "code",
75+
"execution_count": 5,
76+
"metadata": {},
77+
"outputs": [
78+
{
79+
"data": {
80+
"text/plain": [
81+
"f (generic function with 1 method)"
82+
]
83+
},
84+
"execution_count": 5,
85+
"metadata": {},
86+
"output_type": "execute_result"
87+
}
88+
],
89+
"source": [
90+
"%%julia\n",
91+
"\n",
92+
"function f(x::AbstractArray)\n",
93+
" sum(x)\n",
94+
"end"
95+
]
96+
},
97+
{
98+
"cell_type": "code",
99+
"execution_count": 6,
100+
"metadata": {},
101+
"outputs": [
102+
{
103+
"data": {
104+
"text/plain": [
105+
"6"
106+
]
107+
},
108+
"execution_count": 6,
109+
"metadata": {},
110+
"output_type": "execute_result"
111+
}
112+
],
113+
"source": [
114+
"jl.f(np.array([1, 2, 3]))"
115+
]
116+
}
117+
],
118+
"metadata": {
119+
"kernelspec": {
120+
"display_name": "Python 3 (ipykernel)",
121+
"language": "python",
122+
"name": "python3"
123+
},
124+
"language_info": {
125+
"codemirror_mode": {
126+
"name": "ipython",
127+
"version": 3
128+
},
129+
"file_extension": ".py",
130+
"mimetype": "text/x-python",
131+
"name": "python",
132+
"nbconvert_exporter": "python",
133+
"pygments_lexer": "ipython3",
134+
"version": "3.10.10"
135+
}
136+
},
137+
"nbformat": 4,
138+
"nbformat_minor": 2
139+
}

0 commit comments

Comments
 (0)
Please sign in to comment.