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

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

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

from sage.env import (SAGE_LOCAL, cython_aliases,
sage_include_directories)
Expand Down Expand Up @@ -79,8 +80,8 @@ def _standard_libs_libdirs_incdirs_aliases():


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, 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 +111,14 @@ def cython(filename, verbose=0, compile_message=False,
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 using :func:`webbrowser.open`

- ``view_annotate_callback`` -- function (default: ``webbrowser.open``); 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``.

user202729 marked this conversation as resolved.
Show resolved Hide resolved
- ``sage_namespace`` -- boolean (default: ``True``); if ``True``, import
``sage.all``

Expand Down Expand Up @@ -226,6 +235,34 @@ def cython(filename, verbose=0, compile_message=False,
....: 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 +418,11 @@ def cython(filename, verbose=0, compile_message=False,
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
user202729 marked this conversation as resolved.
Show resolved Hide resolved
#
# 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)


@magics_class
class SageMagics(Magics):

Expand Down Expand Up @@ -348,6 +358,12 @@ def cython(self, line, cell):
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 @@ def cython(self, line, cell):
- ``--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, depends
on whether the Sage notebook is used.

kwankyu marked this conversation as resolved.
Show resolved Hide resolved
You can override the selection by specifying
``--view-annotate=webbrowser`` or ``--view-annotate=displayhtml``.

kwankyu marked this conversation as resolved.
Show resolved Hide resolved
- ``cell`` -- string; the Cython source code to process
user202729 marked this conversation as resolved.
Show resolved Hide resolved

OUTPUT: none; the Cython code is compiled and loaded
Expand Down Expand Up @@ -403,6 +427,45 @@ def cython(self, line, cell):
....: ''')
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 @@ def error(self, message):
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"
else:
view_annotate = "webbrowser"
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