Skip to content
New issue

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

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

Already on GitHub? # to your account

Allow Cython compilation to open a web browser containing the annotated HTML file #38946

Merged
merged 12 commits into from
Jan 4, 2025
54 changes: 52 additions & 2 deletions src/sage/misc/cython.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
import re
import sys
import shutil
import webbrowser
from pathlib import Path

from sage.env import (SAGE_LOCAL, cython_aliases,
sage_include_directories)
Expand Down Expand Up @@ -78,9 +80,16 @@
sequence_number = {}


def _webbrowser_open_file(path):
"""
Open a html file in a web browser.
"""
webbrowser.open(Path(path).as_uri())

Check warning on line 87 in src/sage/misc/cython.py

View check run for this annotation

Codecov / codecov/patch

src/sage/misc/cython.py#L87

Added line #L87 was not covered by tests


def cython(filename, verbose=0, compile_message=False,
use_cache=False, create_local_c_file=False, annotate=True, sage_namespace=True,
create_local_so_file=False):
use_cache=False, create_local_c_file=False, annotate=True, view_annotate=False,
view_annotate_callback=_webbrowser_open_file, sage_namespace=True, create_local_so_file=False):
r"""
Compile a Cython file. This converts a Cython file to a C (or C++ file),
and then compiles that. The .c file and the .so file are
Expand Down Expand Up @@ -110,6 +119,14 @@
in the temporary directory, but if ``create_local_c_file`` is also True,
then save a copy of the .html file in the current directory.

- ``view_annotate`` -- boolean (default: ``False``); if ``True``, open the
annotated html file in a web browser

- ``view_annotate_callback`` -- function; a function that takes a string
being the path to the html file. This can be overridden to change
what to do with the annotated html file. Have no effect unless
``view_annotate`` is ``True``.

- ``sage_namespace`` -- boolean (default: ``True``); if ``True``, import
``sage.all``

Expand Down Expand Up @@ -226,6 +243,34 @@
....: from sage.misc.cachefunc cimport cache_key
....: ''')

Test ``view_annotate``::

sage: cython('''
....: def f(int n):
....: return n*n
....: ''', view_annotate=True) # optional -- webbrowser

::

sage: cython('''
....: def f(int n):
....: return n*n
....: ''', view_annotate=True, annotate=False)
Traceback (most recent call last):
...
ValueError: cannot view annotated file without creating it

::

sage: collected_paths = []
sage: cython('''
....: def f(int n):
....: return n*n
....: ''', view_annotate=True, view_annotate_callback=collected_paths.append)
sage: collected_paths
['...']
sage: len(collected_paths)
1
"""
if not filename.endswith('pyx'):
print("Warning: file (={}) should have extension .pyx".format(filename), file=sys.stderr)
Expand Down Expand Up @@ -381,6 +426,11 @@
shutil.copy(os.path.join(target_dir, name + ".html"),
os.curdir)

if view_annotate:
if not annotate:
raise ValueError("cannot view annotated file without creating it")
view_annotate_callback(os.path.join(target_dir, name + ".html"))

# This emulates running "setup.py build" with the correct options
#
# setuptools plugins considered harmful:
Expand Down
81 changes: 80 additions & 1 deletion src/sage/repl/ipython_extension.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,23 @@
"""

from IPython.core.magic import Magics, magics_class, line_magic, cell_magic
from IPython.core.display import HTML
from IPython.core.getipython import get_ipython

from sage.repl.load import load_wrap
from sage.env import SAGE_IMPORTALL, SAGE_STARTUP_FILE
from sage.misc.lazy_import import LazyImport
from sage.misc.misc import run_once


def _running_in_notebook():
try:
from ipykernel.zmqshell import ZMQInteractiveShell
except ImportError:
return False
return isinstance(get_ipython(), ZMQInteractiveShell)

Check warning on line 82 in src/sage/repl/ipython_extension.py

View check run for this annotation

Codecov / codecov/patch

src/sage/repl/ipython_extension.py#L78-L82

Added lines #L78 - L82 were not covered by tests


@magics_class
class SageMagics(Magics):

Expand Down Expand Up @@ -348,6 +358,12 @@
This is syntactic sugar on the
:func:`~sage.misc.cython.cython_compile` function.

Note that there is also the ``%%cython`` cell magic provided by Cython,
which can be loaded with ``%load_ext cython``, see
`Cython documentation <https://cython.readthedocs.io/en/latest/src/userguide/source_files_and_compilation.html#compiling-with-a-jupyter-notebook>`_
for more details.
The semantic is slightly different from the version provided by Sage.

INPUT:

- ``line`` -- parsed as keyword arguments. The allowed arguments are:
Expand All @@ -357,12 +373,20 @@
- ``--use-cache``
- ``--create-local-c-file``
- ``--annotate``
- ``--view-annotate``
- ``--sage-namespace``
- ``--create-local-so-file``
- ``--no-compile-message``, ``--no-use-cache``, etc.

See :func:`~sage.misc.cython.cython` for details.

If ``--view-annotate`` is given, the annotation is either displayed
inline in the Sage notebook or opened in a new web browser, depending
on whether the Sage notebook is used.

You can override the selection by specifying
``--view-annotate=webbrowser`` or ``--view-annotate=displayhtml``.

- ``cell`` -- string; the Cython source code to process

OUTPUT: none; the Cython code is compiled and loaded
Expand Down Expand Up @@ -403,6 +427,45 @@
....: ''')
UsageError: unrecognized arguments: --help

Test ``--view-annotate`` invalid arguments::

sage: # needs sage.misc.cython
sage: shell.run_cell('''
....: %%cython --view-annotate=xx
....: print(1)
....: ''')
UsageError: argument --view-annotate: invalid choice: 'xx' (choose from 'none', 'auto', 'webbrowser', 'displayhtml')

Test ``--view-annotate=displayhtml`` (note that in a notebook environment
an inline HTML frame will be displayed)::

sage: # needs sage.misc.cython
sage: shell.run_cell('''
....: %%cython --view-annotate=displayhtml
....: print(1)
....: ''')
1
<IPython.core.display.HTML object>

Test ``--view-annotate=webbrowser``::

sage: # needs sage.misc.cython webbrowser
sage: shell.run_cell('''
....: %%cython --view-annotate
....: print(1)
....: ''')
1
sage: shell.run_cell('''
....: %%cython --view-annotate=auto
....: print(1)
....: ''') # --view-annotate=auto is undocumented feature, equivalent to --view-annotate
1
sage: shell.run_cell('''
....: %%cython --view-annotate=webbrowser
....: print(1)
....: ''')
1

Test invalid quotes::

sage: # needs sage.misc.cython
Expand Down Expand Up @@ -434,10 +497,26 @@
parser.add_argument("--use-cache", action=argparse.BooleanOptionalAction)
parser.add_argument("--create-local-c-file", action=argparse.BooleanOptionalAction)
parser.add_argument("--annotate", action=argparse.BooleanOptionalAction)
parser.add_argument("--view-annotate", choices=["none", "auto", "webbrowser", "displayhtml"],
nargs="?", const="auto", default="none")
parser.add_argument("--sage-namespace", action=argparse.BooleanOptionalAction)
parser.add_argument("--create-local-so-file", action=argparse.BooleanOptionalAction)
args = parser.parse_args(shlex.split(line))
return cython_compile(cell, **{k: v for k, v in args.__dict__.items() if v is not None})
view_annotate = args.view_annotate
del args.view_annotate
if view_annotate == "auto":
if _running_in_notebook():
view_annotate = "displayhtml"

Check warning on line 509 in src/sage/repl/ipython_extension.py

View check run for this annotation

Codecov / codecov/patch

src/sage/repl/ipython_extension.py#L508-L509

Added lines #L508 - L509 were not covered by tests
else:
view_annotate = "webbrowser"

Check warning on line 511 in src/sage/repl/ipython_extension.py

View check run for this annotation

Codecov / codecov/patch

src/sage/repl/ipython_extension.py#L511

Added line #L511 was not covered by tests
args_dict = {k: v for k, v in args.__dict__.items() if v is not None}
if view_annotate != "none":
args_dict["view_annotate"] = True
if view_annotate == "displayhtml":
path_to_annotate_html_container = []
cython_compile(cell, **args_dict, view_annotate_callback=path_to_annotate_html_container.append)
return HTML(filename=path_to_annotate_html_container[0])
return cython_compile(cell, **args_dict)

@cell_magic
def fortran(self, line, cell):
Expand Down
Loading