Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions Lib/test/test_clinic.py
Original file line number Diff line number Diff line change
Expand Up @@ -2586,6 +2586,25 @@ class T "" ""
"""
self.expect_failure(block, err, lineno=1)

def test_can_convert_module_getattr(self):
function = self.parse_function("""
module m
__getattr__
name: object
/
""")
self.assertEqual(function.kind, FunctionKind.METHOD_GETATTR)

def test_can_convert_class_getattr(self):
function = self.parse_function("""
module m
class m.T "PyObject *" ""
m.T.__getattr__
name: object
/
""", signatures_in_block=3, function_index=2)
self.assertEqual(function.kind, FunctionKind.METHOD_GETATTR)

def test_cannot_specify_pydefault_without_default(self):
err = "You can't specify py_default without specifying a default value!"
block = """
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Argument clinic now supports :meth:`module.__getattr__`.
39 changes: 38 additions & 1 deletion Modules/clinic/zlibmodule.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 14 additions & 9 deletions Modules/zlibmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -2015,15 +2015,20 @@ zlib_crc32_combine_impl(PyObject *module, unsigned int crc1,
return crc32_combine(crc1, crc2, len);
}

/*[clinic input]
zlib.__getattr__

name: str
/

Module __getattr__
[clinic start generated code]*/

static PyObject *
zlib_getattr(PyObject *self, PyObject *args)
zlib___getattr___impl(PyObject *module, const char *name)
/*[clinic end generated code: output=5ec48f62426a4b95 input=59a69e9dcff70564]*/
{
PyObject *name;
if (!PyArg_UnpackTuple(args, "__getattr__", 1, 1, &name)) {
return NULL;
}

if (PyUnicode_Check(name) && PyUnicode_EqualToUTF8(name, "__version__")) {
if (strcmp(name, "__version__") == 0) {
if (PyErr_WarnEx(PyExc_DeprecationWarning,
"'__version__' is deprecated and slated for removal in Python 3.20",
1) < 0) {
Expand All @@ -2032,7 +2037,7 @@ zlib_getattr(PyObject *self, PyObject *args)
return PyUnicode_FromString("1.0");
}

PyErr_Format(PyExc_AttributeError, "module 'zlib' has no attribute %R", name);
PyErr_Format(PyExc_AttributeError, "module 'zlib' has no attribute %s", name);
return NULL;
}

Expand All @@ -2046,7 +2051,7 @@ static PyMethodDef zlib_methods[] =
ZLIB_CRC32_COMBINE_METHODDEF
ZLIB_DECOMPRESS_METHODDEF
ZLIB_DECOMPRESSOBJ_METHODDEF
{"__getattr__", zlib_getattr, METH_VARARGS, "Module __getattr__"},
ZLIB___GETATTR___METHODDEF
{NULL, NULL}
};

Expand Down
4 changes: 2 additions & 2 deletions Tools/clinic/libclinic/converters.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from libclinic import fail, Null, unspecified, unknown
from libclinic.function import (
Function, Parameter,
CALLABLE, STATIC_METHOD, CLASS_METHOD, METHOD_INIT, METHOD_NEW,
CALLABLE, STATIC_METHOD, CLASS_METHOD, METHOD_INIT, METHOD_NEW, METHOD_GETATTR,
GETTER, SETTER)
from libclinic.codegen import CRenderData, TemplateDict
from libclinic.converter import (
Expand Down Expand Up @@ -1090,7 +1090,7 @@ def correct_name_for_self(
f: Function,
parser: bool = False
) -> tuple[str, str]:
if f.kind in {CALLABLE, METHOD_INIT, GETTER, SETTER}:
if f.kind in {CALLABLE, METHOD_INIT, METHOD_GETATTR, GETTER, SETTER}:
if f.cls:
return "PyObject *", "self"
return "PyObject *", "module"
Expand Down
7 changes: 4 additions & 3 deletions Tools/clinic/libclinic/dsl_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from libclinic.function import (
Module, Class, Function, Parameter,
FunctionKind,
CALLABLE, STATIC_METHOD, CLASS_METHOD, METHOD_INIT, METHOD_NEW,
CALLABLE, STATIC_METHOD, CLASS_METHOD, METHOD_INIT, METHOD_NEW, METHOD_GETATTR,
GETTER, SETTER)
from libclinic.converter import (
converters, legacy_converters)
Expand Down Expand Up @@ -45,7 +45,6 @@
__float__
__floordiv__
__ge__
__getattr__
__getattribute__
__getitem__
__gt__
Expand Down Expand Up @@ -598,6 +597,8 @@ def normalize_function_kind(self, fullname: str) -> None:
self.kind = METHOD_NEW
elif name == '__init__':
self.kind = METHOD_INIT
elif name == '__getattr__':
self.kind = METHOD_GETATTR

def resolve_return_converter(
self, full_name: str, forced_converter: str
Expand Down Expand Up @@ -1533,7 +1534,7 @@ def format_docstring(self) -> str:
assert self.function is not None
f = self.function
# For the following special cases, it does not make sense to render a docstring.
if f.kind in {METHOD_INIT, METHOD_NEW, GETTER, SETTER} and not f.docstring:
if f.kind in {METHOD_INIT, METHOD_NEW, METHOD_GETATTR, GETTER, SETTER} and not f.docstring:
return f.docstring

# Enforce the summary line!
Expand Down
4 changes: 3 additions & 1 deletion Tools/clinic/libclinic/function.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ class FunctionKind(enum.Enum):
CLASS_METHOD = enum.auto()
METHOD_INIT = enum.auto()
METHOD_NEW = enum.auto()
METHOD_GETATTR = enum.auto()
GETTER = enum.auto()
SETTER = enum.auto()

Expand All @@ -74,6 +75,7 @@ def __repr__(self) -> str:
CLASS_METHOD: Final = FunctionKind.CLASS_METHOD
METHOD_INIT: Final = FunctionKind.METHOD_INIT
METHOD_NEW: Final = FunctionKind.METHOD_NEW
METHOD_GETATTR: Final = FunctionKind.METHOD_GETATTR
GETTER: Final = FunctionKind.GETTER
SETTER: Final = FunctionKind.SETTER

Expand Down Expand Up @@ -161,7 +163,7 @@ def methoddef_flags(self) -> str | None:
case FunctionKind.STATIC_METHOD:
flags.append('METH_STATIC')
case _ as kind:
acceptable_kinds = {FunctionKind.CALLABLE, FunctionKind.GETTER, FunctionKind.SETTER}
acceptable_kinds = {FunctionKind.CALLABLE, FunctionKind.GETTER, FunctionKind.SETTER, FunctionKind.METHOD_GETATTR}
assert kind in acceptable_kinds, f"unknown kind: {kind!r}"
if self.coexist:
flags.append('METH_COEXIST')
Expand Down
Loading