Skip to content

Commit

Permalink
Trac #27971: py3 failures in sage/misc/sageinspect.py and sagedoc.py
Browse files Browse the repository at this point in the history
The Python 3 doctest failures in `sage/misc/sageinspect.py` and
`sage/misc/sagedoc.py` all arise because of the second line here (from
`sageinspect.py`):
{{{
        spec = sage_getargspec(obj)
        s = str(inspect.formatargspec(*spec))
}}}
The problem is that `inspect.formatargspec` is deprecated in Python 3.5,
so it prints warning messages. From Sage's point of view, this is
annoying, because there is no direct replacement (as far as I can tell)
for `inspect.formatargspec`. The
[https://docs.python.org/3/library/inspect.html#inspect.formatargspec
documentation] says to use `signature` and `Signature`, but we have
already obtained the signature information using `sage_getargspec` and
just want to format it. Also, `inspect.signature` doesn't seem to work
on Cython functions — see cython
[cython/cython#1864 issue 1864], or for a
Sage-specific example:
{{{
sage: import inspect
sage: from sage.misc.sageinspect import sage_getargspec
sage: from sage.rings.integer import int_to_Z
sage: sage_getargspec(int_to_Z)
ArgSpec(args=['self', 'x'], varargs='args', keywords='kwds',
defaults=None)
}}}
At this point, with Python 2:
{{{
sage: inspect.getargspec(long_to_Z)
...
TypeError: <type 'sage.rings.integer.long_to_Z'> is not a Python
function
}}}
With Python 3:
{{{
sage: inspect.signature(int_to_Z)
...
ValueError: no signature found for builtin type <class
'sage.rings.integer.int_to_Z'>
}}}

Fixing this may also fix #27578.

URL: https://trac.sagemath.org/27971
Reported by: jhpalmieri
Ticket author(s): John Palmieri
Reviewer(s): Vincent Klein
  • Loading branch information
Release Manager committed Jun 27, 2019
2 parents 0f616e7 + bbd5b28 commit 38008db
Show file tree
Hide file tree
Showing 2 changed files with 129 additions and 3 deletions.
117 changes: 116 additions & 1 deletion src/sage/misc/sageinspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -1658,6 +1658,121 @@ def foo(x, a='\')"', b={not (2+1==3):'bar'}): return
defaults = None
return inspect.ArgSpec(args, varargs, varkw, defaults)

def formatannotation(annotation, base_module=None):
"""
This is taken from Python 3.7's inspect.py; the only change is to
add documentation.
INPUT:
- ``annotation`` -- annotation for a function
- ``base_module`` (optional, default ``None``)
This is only relevant with Python 3, so the doctests are marked
accordingly.
EXAMPLES::
sage: from sage.misc.sageinspect import formatannotation
sage: import inspect
sage: def foo(a, *, b:int, **kwargs): # py3
....: pass
....:
sage: s = inspect.signature(foo) # py3
sage: a = s.parameters['a'].annotation # py3
sage: a # py3
<class 'inspect._empty'>
sage: formatannotation(a) # py3
'inspect._empty'
sage: b = s.parameters['b'].annotation # py3
sage: b # py3
<class 'int'>
sage: formatannotation(b) # py3
'int'
"""
if getattr(annotation, '__module__', None) == 'typing':
return repr(annotation).replace('typing.', '')
if isinstance(annotation, type):
if annotation.__module__ in ('builtins', base_module):
return annotation.__qualname__
return annotation.__module__+'.'+annotation.__qualname__
return repr(annotation)

def sage_formatargspec(args, varargs=None, varkw=None, defaults=None,
kwonlyargs=(), kwonlydefaults={}, annotations={},
formatarg=str,
formatvarargs=lambda name: '*' + name,
formatvarkw=lambda name: '**' + name,
formatvalue=lambda value: '=' + repr(value),
formatreturns=lambda text: ' -> ' + text,
formatannotation=formatannotation):
"""
Format an argument spec from the values returned by getfullargspec.
The first seven arguments are (args, varargs, varkw, defaults,
kwonlyargs, kwonlydefaults, annotations). The other five arguments
are the corresponding optional formatting functions that are called to
turn names and values into strings. The last argument is an optional
function to format the sequence of arguments.
This is taken from Python 3.7's inspect.py, where it is
deprecated. The only change, aside from documentation (this
paragraph and the next, plus doctests), is to remove the
deprecation warning.
Sage uses this function to format arguments, as obtained by
:func:`sage_getargspec`. Since :func:`sage_getargspec` works for
Cython functions while Python's inspect module does not, it makes
sense to keep this function for formatting instances of
``inspect.ArgSpec``.
EXAMPLES::
sage: from sage.misc.sageinspect import sage_formatargspec
sage: from inspect import formatargspec # deprecated in Python 3
sage: args = ['a', 'b', 'c']
sage: defaults = [3]
sage: sage_formatargspec(args, defaults=defaults)
'(a, b, c=3)'
sage: formatargspec(args, defaults=defaults) == sage_formatargspec(args, defaults=defaults) # py2
True
sage: formatargspec(args, defaults=defaults) == sage_formatargspec(args, defaults=defaults) # py3
doctest:...: DeprecationWarning: `formatargspec` is deprecated since Python 3.5. Use `signature` and the `Signature` object directly
True
"""
def formatargandannotation(arg):
result = formatarg(arg)
if arg in annotations:
result += ': ' + formatannotation(annotations[arg])
return result
specs = []
if defaults:
firstdefault = len(args) - len(defaults)
for i, arg in enumerate(args):
spec = formatargandannotation(arg)
if defaults and i >= firstdefault:
spec = spec + formatvalue(defaults[i - firstdefault])
specs.append(spec)
if varargs is not None:
specs.append(formatvarargs(formatargandannotation(varargs)))
else:
if kwonlyargs:
specs.append('*')
if kwonlyargs:
for kwonlyarg in kwonlyargs:
spec = formatargandannotation(kwonlyarg)
if kwonlydefaults and kwonlyarg in kwonlydefaults:
spec += formatvalue(kwonlydefaults[kwonlyarg])
specs.append(spec)
if varkw is not None:
specs.append(formatvarkw(formatargandannotation(varkw)))
result = '(' + ', '.join(specs) + ')'
if 'return' in annotations:
result += formatreturns(formatannotation(annotations['return']))
return result


def sage_getdef(obj, obj_name=''):
r"""
Expand Down Expand Up @@ -1693,7 +1808,7 @@ def sage_getdef(obj, obj_name=''):
"""
try:
spec = sage_getargspec(obj)
s = str(inspect.formatargspec(*spec))
s = str(sage_formatargspec(*spec))
s = s.strip('(').strip(')').strip()
if s[:4] == 'self':
s = s[4:]
Expand Down
15 changes: 13 additions & 2 deletions src/sage_setup/docbuild/ext/sage_autodoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
import sphinx
from sphinx.errors import ExtensionError
from sphinx.ext.autodoc.importer import mock, import_object, get_object_members
from sphinx.ext.autodoc.inspector import format_annotation, formatargspec
from sphinx.ext.autodoc.inspector import format_annotation
from sphinx.locale import _, __
from sphinx.pycode import ModuleAnalyzer
from sphinx.errors import ExtensionError, PycodeError
Expand All @@ -51,7 +51,8 @@
from sphinx.util.inspect import getargspec

from sage.misc.sageinspect import (sage_getdoc_original,
sage_getargspec, isclassinstance)
sage_getargspec, isclassinstance,
sage_formatargspec)
from sage.misc.lazy_import import LazyImport

# This is used to filter objects of classes that inherit from
Expand Down Expand Up @@ -123,6 +124,16 @@ def bool_option(arg):
return True


def formatargspec(function, args, varargs=None, varkw=None, defaults=None,
kwonlyargs=(), kwonlydefaults={}, annotations={}):
"""
Sphinx's version of formatargspec is deprecated, so use Sage's instead.
"""
return sage_formatargspec(args, varargs=varargs, varkw=varkw, defaults=defaults,
kwonlyargs=kwonlyargs, kwonlydefaults=kwonlydefaults,
annotations=annotations)


class AutodocReporter(object):
"""
A reporter replacement that assigns the correct source name
Expand Down

0 comments on commit 38008db

Please sign in to comment.