-
-
Notifications
You must be signed in to change notification settings - Fork 280
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
Get argument infos from method descriptors #1368
Changes from all commits
7d0f650
d8f82ad
58f7b71
6adc1df
f356627
8a6cead
7a29b45
846c13c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,7 +12,7 @@ | |
import sys | ||
import types | ||
import warnings | ||
from typing import Iterable, List, Optional | ||
from typing import Iterable, Callable, List, Optional, Tuple | ||
|
||
from astroid import bases, nodes | ||
from astroid.manager import AstroidManager | ||
|
@@ -179,13 +179,29 @@ def object_build_class(node, member, localname): | |
return _base_class_object_build(node, member, basenames, localname=localname) | ||
|
||
|
||
def object_build_function(node, member, localname): | ||
"""create astroid for a living function object""" | ||
signature = inspect.signature(member) | ||
def _get_args_info_from_callable( | ||
member: Callable, | ||
) -> Tuple[List[str], List[str], List[str], List[str]]: | ||
""" | ||
Returns args, posonlyargs, defaults, kwonlyargs. | ||
|
||
:note: currently ignores the return annotation. | ||
""" | ||
args = [] | ||
defaults = [] | ||
posonlyargs = [] | ||
kwonlyargs = [] | ||
|
||
try: | ||
signature = inspect.signature(member) | ||
except ValueError: | ||
# function either has an invalid text signature or no signature | ||
# at all. We distinguish between the two by looking at the | ||
# __text_signature__ attribute | ||
if getattr(member, "__text_signature__", None) is not None: | ||
f"Cannot parse __text_signature__ signature of {member}" | ||
DanielNoord marked this conversation as resolved.
Show resolved
Hide resolved
|
||
signature = inspect.Signature() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a change in behaviour, at least for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes you are right, this was part of the stuff that I needed to change. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a change in behaviour, at least for |
||
|
||
for param_name, param in signature.parameters.items(): | ||
if param.kind == inspect.Parameter.POSITIONAL_ONLY: | ||
posonlyargs.append(param_name) | ||
|
@@ -199,13 +215,23 @@ def object_build_function(node, member, localname): | |
kwonlyargs.append(param_name) | ||
if param.default is not inspect._empty: | ||
defaults.append(param.default) | ||
|
||
return args, posonlyargs, defaults, kwonlyargs | ||
|
||
|
||
def object_build_function(node, member, localname): | ||
"""create astroid for a living function object""" | ||
args, posonlyargs, defaults, kwonlyargs = _get_args_info_from_callable(member) | ||
|
||
func = build_function( | ||
getattr(member, "__name__", None) or localname, | ||
args, | ||
posonlyargs, | ||
defaults, | ||
member.__doc__, | ||
name=getattr(member, "__name__", None) or localname, | ||
args=args, | ||
posonlyargs=posonlyargs, | ||
defaults=defaults, | ||
doc=member.__doc__, | ||
kwonlyargs=kwonlyargs, | ||
) | ||
|
||
node.add_local_node(func, localname) | ||
|
||
|
||
|
@@ -216,13 +242,18 @@ def object_build_datadescriptor(node, member, name): | |
|
||
def object_build_methoddescriptor(node, member, localname): | ||
"""create astroid for a living method descriptor object""" | ||
# FIXME get arguments ? | ||
# get arguments infos | ||
args, posonlyargs, defaults, kwonlyargs = _get_args_info_from_callable(member) | ||
|
||
func = build_function( | ||
getattr(member, "__name__", None) or localname, doc=member.__doc__ | ||
name=getattr(member, "__name__", None) or localname, | ||
args=args, | ||
posonlyargs=posonlyargs, | ||
defaults=defaults, | ||
doc=member.__doc__, | ||
kwonlyargs=kwonlyargs, | ||
) | ||
# set node's arguments to None to notice that we have no information, not | ||
# and empty argument list | ||
Comment on lines
-223
to
-224
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To still be inline with this, I should set There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes! Let's do that! |
||
func.args.args = None | ||
|
||
node.add_local_node(func, localname) | ||
_add_dunder_class(func, member) | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
/* Example of Python c module with an invalid __text_signature__ */ | ||
|
||
#include "Python.h" | ||
|
||
static PyObject* base_valid(PyObject *self, PyObject* args) | ||
{ | ||
printf("Hello World\n"); | ||
return Py_None; | ||
} | ||
|
||
static PyObject* base_invalid(PyObject *self, PyObject* args) | ||
{ | ||
printf("Hello World\n"); | ||
return Py_None; | ||
} | ||
|
||
static PyMethodDef base_methods[] = { | ||
{"valid_text_signature", base_valid, METH_VARARGS, "valid_text_signature($self, a='r', b=-3.14)\n" | ||
"--\n" | ||
"\n" | ||
"Function demonstrating a valid __text_signature__ from C code."}, | ||
|
||
{"invalid_text_signature", base_invalid, METH_VARARGS, "invalid_text_signature(!invalid) -> NotSupported\n" | ||
"--\n" | ||
"\n" | ||
"Function demonstrating an invalid __text_signature__ from C code."}, | ||
|
||
{NULL, NULL, 0, NULL} /* sentinel */ | ||
}; | ||
|
||
static PyModuleDef base_definition = { | ||
PyModuleDef_HEAD_INIT, | ||
"base", | ||
"A Python module demonstrating valid and invalid __text_signature__ from C code.", | ||
-1, | ||
base_methods | ||
}; | ||
|
||
PyObject* PyInit_base(void) { | ||
Py_Initialize(); | ||
return PyModule_Create(&base_definition); | ||
} |
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,8 @@ | ||||||||
from setuptools import setup, Extension | ||||||||
|
||||||||
cmodule = Extension("mymod.base", sources=["mymod/base.c"],) | ||||||||
setup( | ||||||||
name="mymod", | ||||||||
ext_modules=[cmodule], | ||||||||
packages=['mymod'], | ||||||||
) | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,16 +6,22 @@ | |
|
||
import collections | ||
import importlib | ||
import importlib.abc | ||
import importlib.machinery | ||
import importlib.util | ||
import os | ||
import pathlib | ||
import py_compile | ||
import socket | ||
import subprocess | ||
import sys | ||
import tempfile | ||
import textwrap | ||
import unittest | ||
import unittest.mock | ||
import types | ||
from pathlib import Path | ||
from typing import Iterator | ||
|
||
import unittest.mock | ||
import pytest | ||
|
||
from astroid import Instance, builder, nodes, test_utils, util | ||
|
@@ -948,6 +954,54 @@ def test_parse_module_with_invalid_type_comments_does_not_crash(): | |
assert isinstance(node, nodes.Module) | ||
|
||
|
||
def test_c_module_text_signature(): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this test cannot pass on pypy, since they do not support CPython extensions natively. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should be able to tell pytest to skip it right? Although I must confess I don't know if this is actually true. Did you find any documentation about this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Yes we can
Yes I've read that from pypy's documentation, but I can't find the link now. But here's what the pypy wiki says: "There is a compatibility layer for CPython C API extensions called CPyExt, but it is incomplete and experimental.". So I'm positive that, by default, pypy has no support for c-modules built for cpython. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's skip then! |
||
def _find_dot_so_files(path: Path) -> Iterator[Path]: | ||
for entry in path.iterdir(): | ||
if entry.suffix in importlib.machinery.EXTENSION_SUFFIXES: | ||
yield entry | ||
|
||
def _import_module(path: Path, module_full_name: str) -> types.ModuleType: | ||
spec = importlib.util.spec_from_file_location(module_full_name, path) | ||
if spec is None: | ||
raise RuntimeError( | ||
f"Cannot find spec for module {module_full_name} at {path}" | ||
) | ||
py_mod = importlib.util.module_from_spec(spec) | ||
loader = spec.loader | ||
assert isinstance(loader, importlib.abc.Loader), loader | ||
loader.exec_module(py_mod) | ||
return py_mod | ||
Comment on lines
+958
to
+973
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is probably a utility in astroid to do this... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I assume it's OK to include these private helpers, it's only going to be used in the tests. And I didn't see anything the does such imports in astroid, but maybe I did not look at the right place. |
||
|
||
c_module_invalid_text_signature = ( | ||
Path(resources.RESOURCE_PATH) / "c_module_invalid_text_signature" | ||
) | ||
package_path = c_module_invalid_text_signature / "mymod" | ||
|
||
# build extension | ||
try: | ||
cwd = os.getcwd() | ||
code, outstr = subprocess.getstatusoutput( | ||
f"cd {c_module_invalid_text_signature} && python3 setup.py build_ext --inplace" | ||
) | ||
os.chdir(cwd) | ||
|
||
assert code == 0, outstr | ||
|
||
for p in _find_dot_so_files(package_path): | ||
py_mod = _import_module(p, "mymod.base") | ||
break | ||
|
||
mod = builder.AstroidBuilder().module_build(py_mod, "mymod.base") | ||
as_python_string = mod.as_string() | ||
|
||
assert "def invalid_text_signature():" in as_python_string | ||
assert "def valid_text_signature(a='r', b=-3.14):" in as_python_string | ||
|
||
finally: | ||
# cleanup | ||
subprocess.getoutput(f"rm -f {package_path}/*.so") | ||
|
||
|
||
class HermeticInterpreterTest(unittest.TestCase): | ||
"""Modeled on https://github.com/PyCQA/astroid/pull/1207#issuecomment-951455588""" | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we know more about this
Callable
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, it can be any python callable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, so I finally got some time to review this after having spent some time in the module myself (see #1589).
I don't think this typing is correct as normally all
members
are eithertype
or narrowed down types from thetypes
packages.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you more explicit and maybe suggest another typing for the function ?