Skip to content

Commit

Permalink
sagemathgh-38946: Allow Cython compilation to open a web browser cont…
Browse files Browse the repository at this point in the history
…aining the annotated HTML file

    
As in the title.

I think this is a rather common use case, to investigate whether the
code does get the desired speedup.

Remark: there's `sage.misc.viewer.browser()` which could be used
instead, but then a lot of existing code in SageMath uses
`webbrowser.open()` anyway?

### 📝 Checklist

<!-- Put an `x` in all the boxes that apply. -->

- [x] The title is concise and informative.
- [x] The description explains in detail what this PR is about.
- [x] I have linked a relevant issue or discussion. (not aware of one)
- [x] I have created tests covering the changes.
- [x] I have updated the documentation and checked the documentation
preview.

### Dependencies

Depends on sagemath#38945 .
    
URL: sagemath#38946
Reported by: user202729
Reviewer(s): Kwankyu Lee, user202729
  • Loading branch information
Release Manager committed Jan 2, 2025
2 parents c9dd1e8 + 6116696 commit a796b44
Show file tree
Hide file tree
Showing 2 changed files with 132 additions and 3 deletions.
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 @@ def _standard_libs_libdirs_incdirs_aliases():
sequence_number = {}


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


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 @@ 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
- ``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 @@ 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 +426,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
#
# 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, 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 @@ 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

0 comments on commit a796b44

Please sign in to comment.