Skip to content

Commit

Permalink
Unittest IPython extension + Automatic loading (#478)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
MilesCranmer committed Apr 3, 2024
1 parent f586f24 commit 478bce9
Show file tree
Hide file tree
Showing 5 changed files with 171 additions and 5 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install flake8 pytest pytest-cov
pip install flake8 pytest pytest-cov nbval numpy
cp pysrc/juliacall/juliapkg-dev.json pysrc/juliacall/juliapkg.json
pip install -e .
- name: Lint with flake8
Expand All @@ -81,7 +81,7 @@ jobs:
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
- name: Run tests
run: |
pytest -s --cov=pysrc
pytest -s --nbval --cov=pysrc ./pytest/
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v2
env:
Expand Down
8 changes: 6 additions & 2 deletions docs/src/compat.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,9 @@ The `juliacall` IPython extension adds these features to your IPython session:

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

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

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

Also see [the IPython docs](https://ipython.readthedocs.io/en/stable/config/extensions/)
for more information on extensions.

## Asynchronous Julia code (including Makie)

Asynchronous Julia code will not normally run while Python is executing, unless it is in a
Expand Down
1 change: 1 addition & 0 deletions docs/src/juliacall.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,3 +123,4 @@ be configured in two ways:
| `-X juliacall-sysimage=<file>` | `PYTHON_JULIACALL_SYSIMAGE=<file>` | Use the given system image. |
| `-X juliacall-threads=<N\|auto>` | `PYTHON_JULIACALL_THREADS=<N\|auto>` | Launch N threads. |
| `-X juliacall-warn-overwrite=<yes\|no>` | `PYTHON_JULIACALL_WARN_OVERWRITE=<yes\|no>` | Enable or disable method overwrite warnings. |
| `-X juliacall-autoload-ipython-extension=<yes\|no>` | `PYTHON_JULIACALL_AUTOLOAD_IPYTHON_EXTENSION=<yes\|no>` | Enable or disable IPython extension autoloading. |
24 changes: 23 additions & 1 deletion pysrc/juliacall/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,8 +248,30 @@ def jlstr(x):
"PYTHON_JULIACALL_HANDLE_SIGNALS=no."
)

init()
# Next, automatically load the juliacall extension if we are in IPython or Jupyter
CONFIG['autoload_ipython_extension'] = choice('autoload_ipython_extension', ['yes', 'no'])[0]
if CONFIG['autoload_ipython_extension'] in {'yes', None}:
try:
get_ipython = sys.modules['IPython'].get_ipython

if CONFIG['autoload_ipython_extension'] is None:
# Only let the user know if it was not explicitly set
print(
"Detected IPython. Loading juliacall extension. See https://juliapy.github.io/PythonCall.jl/stable/compat/#IPython"
)

load_ipython_extension(get_ipython())
except Exception as e:
if CONFIG['autoload_ipython_extension'] == 'yes':
# Only warn if the user explicitly requested the extension to be loaded
warnings.warn(
"Could not load juliacall extension in Jupyter notebook: " + str(e)
)
pass


def load_ipython_extension(ip):
import juliacall.ipython
juliacall.ipython.load_ipython_extension(ip)

init()
139 changes: 139 additions & 0 deletions pytest/test_nb.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# NBVAL_IGNORE_OUTPUT\n",
"import numpy as np\n",
"from juliacall import Main as jl"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"3\n"
]
}
],
"source": [
"%%julia\n",
"\n",
"# Automatically activates Julia magic\n",
"\n",
"x = 1\n",
"println(x + 2)"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"4\n"
]
}
],
"source": [
"%julia println(x + 3)"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"1.0"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"jl.cos(0)"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"f (generic function with 1 method)"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"%%julia\n",
"\n",
"function f(x::AbstractArray)\n",
" sum(x)\n",
"end"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"6"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"jl.f(np.array([1, 2, 3]))"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"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.10.10"
}
},
"nbformat": 4,
"nbformat_minor": 2
}

0 comments on commit 478bce9

Please sign in to comment.