Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unittest IPython extension + Automatic loading #478

Merged
merged 6 commits into from
Apr 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you put this near the top of init? Just so if it's set incorrectly the error gets raised early.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't we want it come after Julia is initialized? Otherwise we would see the printout for installing Julia, and the extension autoload message would be lost above it.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant literally just the CONFIG['autoload_ipython_extension'] = ... line.

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(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should maybe be a warning, so can be disabled using Python's warnings mechanisms?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm ambivalent about this.

Copy link
Contributor Author

@MilesCranmer MilesCranmer Mar 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried this. If it is a warning, it feels like you need to set the environment variable (one way or the other), and ends up being a pain. When I have this set up I just want it to run automatically and not worry about it. A single-line print seems nice though.

I also tried logger.info but it's also a pain because you need to reconfigure logging defaults to have it print. I guess I think this is really what print is made for.

The user can always set that env variable to turn off printing. The printing only happens when it's unset.

"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
}
Loading