Skip to content

Commit 7767466

Browse files
Argument Clinic: document converters for getters and setters
Use _ssl._SSLContext.check_hostname as the main example.
1 parent 708aa7c commit 7767466

1 file changed

Lines changed: 65 additions & 44 deletions

File tree

development-tools/clinic/howto.rst

Lines changed: 65 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1397,65 +1397,108 @@ that facilitate :py:class:`property`-like access for a class.
13971397
You can use the ``@getter`` and ``@setter`` directives to generate
13981398
"impl" functions using Argument Clinic.
13991399

1400-
This example --- taken from :cpy-file:`Modules/_io/textio.c` ---
1400+
This example --- taken from :cpy-file:`Modules/_ssl.c` ---
14011401
shows the use of ``@getter`` and ``@setter`` in combination with
14021402
the :ref:`@critical_section <clinic-howto-critical-sections>` directive
14031403
(which achieves thread safety without causing deadlocks between threads)::
14041404

14051405
/*[clinic input]
14061406
@critical_section
14071407
@getter
1408-
_io.TextIOWrapper._CHUNK_SIZE
1408+
_ssl._SSLContext.check_hostname -> bool
14091409
[clinic start generated code]*/
14101410

14111411
/*[clinic input]
14121412
@critical_section
14131413
@setter
1414-
_io.TextIOWrapper._CHUNK_SIZE
1414+
_ssl._SSLContext.check_hostname
1415+
value: bool
14151416
[clinic start generated code]*/
14161417

14171418
The generated glue code looks like this:
14181419

14191420
.. code-block:: c
14201421
14211422
static PyObject *
1422-
_io_TextIOWrapper__CHUNK_SIZE_get(PyObject *self, void *Py_UNUSED(context))
1423+
_ssl__SSLContext_check_hostname_get(PyObject *self, void *Py_UNUSED(context))
14231424
{
14241425
PyObject *return_value = NULL;
1426+
int _return_value;
14251427
14261428
Py_BEGIN_CRITICAL_SECTION(self);
1427-
return_value = _io_TextIOWrapper__CHUNK_SIZE_get_impl((textio *)self);
1429+
_return_value = _ssl__SSLContext_check_hostname_get_impl((PySSLContext *)self);
14281430
Py_END_CRITICAL_SECTION();
1431+
if ((_return_value == -1) && PyErr_Occurred()) {
1432+
goto exit;
1433+
}
1434+
return_value = PyBool_FromLong((long)_return_value);
14291435
1436+
exit:
14301437
return return_value;
14311438
}
14321439
14331440
static int
1434-
_io_TextIOWrapper__CHUNK_SIZE_set(PyObject *self, PyObject *value, void *Py_UNUSED(context))
1441+
_ssl__SSLContext_check_hostname_set(PyObject *self, PyObject *arg, void *Py_UNUSED(context))
14351442
{
1436-
int return_value;
1443+
int return_value = -1;
1444+
int value;
14371445
1438-
if (value == NULL) {
1446+
if (arg == NULL) {
14391447
PyErr_Format(PyExc_AttributeError,
1440-
"attribute '_CHUNK_SIZE' of '%.100s' objects cannot be deleted",
1448+
"attribute 'check_hostname' of '%.100s' objects cannot be deleted",
14411449
Py_TYPE(self)->tp_name);
14421450
return -1;
14431451
}
1452+
value = PyObject_IsTrue(arg);
1453+
if (value < 0) {
1454+
goto exit;
1455+
}
14441456
Py_BEGIN_CRITICAL_SECTION(self);
1445-
return_value = _io_TextIOWrapper__CHUNK_SIZE_set_impl((textio *)self, value);
1457+
return_value = _ssl__SSLContext_check_hostname_set_impl((PySSLContext *)self, value);
14461458
Py_END_CRITICAL_SECTION();
14471459
1460+
exit:
14481461
return return_value;
14491462
}
14501463
14511464
.. note::
14521465

14531466
Getters and setters must be declared as separate functions.
1454-
The *value* parameter for a "setter" is added implicitly by Argument Clinic.
14551467
It is possible to create a docstring for the property by adding it to
14561468
the ``@getter``.
1457-
The accessors of the same attribute must share the C basename;
1458-
declaring the same accessor twice is an error.
1469+
The accessors of the same attribute are identified by the Python name of
1470+
that attribute, so they can use different C basenames.
1471+
Declaring the same accessor twice is only allowed if each of them is
1472+
compiled under its own preprocessor condition.
1473+
1474+
And then the implementation will work the same as a Python method which is
1475+
decorated by :py:class:`property`:
1476+
1477+
.. code-block:: pycon
1478+
1479+
>>> import ssl
1480+
>>> ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
1481+
>>> ctx.check_hostname
1482+
True
1483+
>>> ctx.check_hostname = False
1484+
>>> ctx.check_hostname
1485+
False
1486+
>>> del ctx.check_hostname
1487+
Traceback (most recent call last):
1488+
...
1489+
AttributeError: attribute 'check_hostname' of 'SSLContext' objects cannot be deleted
1490+
1491+
A "getter" can define a :ref:`return converter
1492+
<clinic-howto-return-converters>`, as shown above, so that the "impl"
1493+
function returns a C value instead of an object.
1494+
1495+
The new value of the attribute is passed to the "impl" function as the only
1496+
argument.
1497+
As shown above, it can be declared as a parameter named *value*, with
1498+
a converter, and is then converted like an argument of a function.
1499+
If it is not declared, ``value: object`` is added implicitly.
1500+
Note that an error for a value of a wrong type names the attribute instead of
1501+
an argument of a function.
14591502

14601503
The setter slot of :c:type:`PyGetSetDef` is used both for setting and for
14611504
deleting the attribute: the setter is called with ``NULL`` as the value to
@@ -1473,42 +1516,20 @@ handling this case, as in this example taken from
14731516
@critical_section
14741517
@setter
14751518
@deleter
1476-
function.__annotations__
1519+
function.__type_params__
1520+
value: object(subclass_of='&PyTuple_Type') = NULL
14771521
[clinic start generated code]*/
14781522

1479-
.. code-block:: c
1480-
1481-
static int
1482-
function___annotations___set_impl(PyFunctionObject *self, PyObject *value)
1483-
{
1484-
if (value == Py_None)
1485-
value = NULL;
1486-
/* Legal to del f.func_annotations.
1487-
* Can only set func_annotations to NULL (through C api)
1488-
* or a dict. */
1489-
if (value != NULL && !PyDict_Check(value)) {
1490-
PyErr_SetString(PyExc_TypeError,
1491-
"__annotations__ must be set to a dict object");
1492-
return -1;
1493-
}
1494-
...
1495-
}
1496-
1497-
And then the implementation will work the same as a Python method which is
1498-
decorated by :py:class:`property`:
1499-
1500-
.. code-block:: pycon
1501-
1502-
>>> import sys, _io
1503-
>>> a = _io.TextIOWrapper(sys.stdout)
1504-
>>> a._CHUNK_SIZE
1505-
8192
1506-
>>> a._CHUNK_SIZE = 30
1507-
>>> a._CHUNK_SIZE
1508-
30
1523+
The value is only converted if the attribute is not deleted, so it must have
1524+
a default value, which the "impl" function receives for the deletion.
1525+
The implicitly declared value gets the default ``NULL``.
15091526

15101527
.. versionadded:: 3.13
15111528

1529+
.. versionchanged:: 3.16
1530+
Added support for converters and for several implementations of the same
1531+
accessor in different preprocessor conditional blocks.
1532+
15121533

15131534
.. _clinic-howto-deprecate-positional:
15141535
.. _clinic-howto-deprecate-keyword:

0 commit comments

Comments
 (0)