From 5c01d99c12876263d8cc700a11cce9ad88994096 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sat, 12 Oct 2013 19:54:30 +0200 Subject: [PATCH] Introduce support for documenting which C API elements are not part of the stable/limited API. --- Doc/c-api/index.rst | 2 +- Doc/c-api/stable.rst | 51 ++++---- Doc/conf.py | 4 +- Doc/tools/sphinxext/c_annotations.py | 117 ++++++++++++++++++ .../pydoctheme/static/pydoctheme.css | 8 ++ 5 files changed, 152 insertions(+), 30 deletions(-) create mode 100644 Doc/tools/sphinxext/c_annotations.py diff --git a/Doc/c-api/index.rst b/Doc/c-api/index.rst index 591136e9d4a..3bfbaf4eedd 100644 --- a/Doc/c-api/index.rst +++ b/Doc/c-api/index.rst @@ -13,6 +13,7 @@ document the API functions in detail. :maxdepth: 2 intro.rst + stable.rst veryhigh.rst refcounting.rst exceptions.rst @@ -22,5 +23,4 @@ document the API functions in detail. init.rst memory.rst objimpl.rst - stable.rst apiabiversion.rst diff --git a/Doc/c-api/stable.rst b/Doc/c-api/stable.rst index a7c9ef91ad3..063f856d227 100644 --- a/Doc/c-api/stable.rst +++ b/Doc/c-api/stable.rst @@ -6,36 +6,33 @@ Stable Application Binary Interface *********************************** -Traditionally, the C API of Python will change with every release. -Most changes will be source-compatible, typically by only adding API, -rather than changing existing API or removing API (although some -interfaces do get removed after being deprecated first). +Traditionally, the C API of Python will change with every release. Most changes +will be source-compatible, typically by only adding API, rather than changing +existing API or removing API (although some interfaces do get removed after +being deprecated first). -Unfortunately, the API compatibility does not extend to binary -compatibility (the ABI). The reason is primarily the evolution of -struct definitions, where addition of a new field, or changing -the type of a field, might not break the API, but can break the ABI. -As a consequence, extension modules need to be recompiled for -every Python release (although an exception is possible on Unix -when none of the affected interfaces are used). In addition, on -Windows, extension modules link with a specific pythonXY.dll and -need to be recompiled to link with a newer one. +Unfortunately, the API compatibility does not extend to binary compatibility +(the ABI). The reason is primarily the evolution of struct definitions, where +addition of a new field, or changing the type of a field, might not break the +API, but can break the ABI. As a consequence, extension modules need to be +recompiled for every Python release (although an exception is possible on Unix +when none of the affected interfaces are used). In addition, on Windows, +extension modules link with a specific pythonXY.dll and need to be recompiled to +link with a newer one. -Since Python 3.2, a subset of the API has been declared to guarantee -a stable ABI. Extension modules wishing to use this API need to define -``Py_LIMITED_API``. A number of interpreter details then become hidden -from the extension module; in return, a module is built that works -on any 3.x version (x>=2) without recompilation. +Since Python 3.2, a subset of the API has been declared to guarantee a stable +ABI. Extension modules wishing to use this API (called "limited API") need to +define ``Py_LIMITED_API``. A number of interpreter details then become hidden +from the extension module; in return, a module is built that works on any 3.x +version (x>=2) without recompilation. In some cases, the stable ABI needs to be extended with new functions. -Extension modules wishing to use these new APIs need to set -``Py_LIMITED_API`` to the ``PY_VERSION_HEX`` value (see -:ref:`apiabiversion`) of the minimum Python version they want to -support (e.g. ``0x03030000`` for Python 3.3). Such modules will work -on all subsequent Python releases, but fail to load (because of +Extension modules wishing to use these new APIs need to set ``Py_LIMITED_API`` +to the ``PY_VERSION_HEX`` value (see :ref:`apiabiversion`) of the minimum Python +version they want to support (e.g. ``0x03030000`` for Python 3.3). Such modules +will work on all subsequent Python releases, but fail to load (because of missing symbols) on the older releases. -As of Python 3.2, the set of functions available to the limited API -is documented in PEP 384. - -.. XXX copy exact list here? Into each functions definition? +As of Python 3.2, the set of functions available to the limited API is +documented in PEP 384. In the C API documentation, API elements that are not +part of the limited API are marked as "Not part of the limited API." diff --git a/Doc/conf.py b/Doc/conf.py index 6b085e04898..5b63cad0289 100644 --- a/Doc/conf.py +++ b/Doc/conf.py @@ -12,8 +12,8 @@ sys.path.append(os.path.abspath('tools/sphinxext')) # General configuration # --------------------- -extensions = ['sphinx.ext.refcounting', 'sphinx.ext.coverage', - 'sphinx.ext.doctest', 'pyspecific'] +extensions = ['sphinx.ext.coverage', 'sphinx.ext.doctest', + 'pyspecific', 'c_annotations'] templates_path = ['tools/sphinxext'] # General substitutions. diff --git a/Doc/tools/sphinxext/c_annotations.py b/Doc/tools/sphinxext/c_annotations.py new file mode 100644 index 00000000000..c93fb7c0fb9 --- /dev/null +++ b/Doc/tools/sphinxext/c_annotations.py @@ -0,0 +1,117 @@ +# -*- coding: utf-8 -*- +""" + c_annotations.py + ~~~~~~~~~~~~~~~~ + + Supports annotations for C API elements: + + * reference count annotations for C API functions. Based on + refcount.py and anno-api.py in the old Python documentation tools. + + * stable API annotations + + Usage: Set the `refcount_file` config value to the path to the reference + count data file. + + :copyright: Copyright 2007-2013 by Georg Brandl. + :license: Python license. +""" + +from os import path +from docutils import nodes +from docutils.parsers.rst import directives + +from sphinx import addnodes +from sphinx.domains.c import CObject + + +class RCEntry: + def __init__(self, name): + self.name = name + self.args = [] + self.result_type = '' + self.result_refs = None + + +class Annotations(dict): + @classmethod + def fromfile(cls, filename): + d = cls() + fp = open(filename, 'r') + try: + for line in fp: + line = line.strip() + if line[:1] in ("", "#"): + # blank lines and comments + continue + parts = line.split(":", 4) + if len(parts) != 5: + raise ValueError("Wrong field count in %r" % line) + function, type, arg, refcount, comment = parts + # Get the entry, creating it if needed: + try: + entry = d[function] + except KeyError: + entry = d[function] = RCEntry(function) + if not refcount or refcount == "null": + refcount = None + else: + refcount = int(refcount) + # Update the entry with the new parameter or the result + # information. + if arg: + entry.args.append((arg, type, refcount)) + else: + entry.result_type = type + entry.result_refs = refcount + finally: + fp.close() + return d + + def add_annotations(self, app, doctree): + for node in doctree.traverse(addnodes.desc_content): + par = node.parent + if par['domain'] != 'c': + continue + if par['notlimited']: + node.insert(0, nodes.emphasis(' Not part of the stable API.', + ' Not part of the stable API.', + classes=['notlimited'])) + if par['objtype'] != 'function': + continue + if not par[0].has_key('names') or not par[0]['names']: + continue + entry = self.get(par[0]['names'][0]) + if not entry: + continue + elif entry.result_type not in ("PyObject*", "PyVarObject*"): + continue + if entry.result_refs is None: + rc = 'Return value: Always NULL.' + elif entry.result_refs: + rc = 'Return value: New reference.' + else: + rc = 'Return value: Borrowed reference.' + node.insert(0, nodes.emphasis(rc, rc, classes=['refcount'])) + + +def init_annotations(app): + refcounts = Annotations.fromfile( + path.join(app.srcdir, app.config.refcount_file)) + app.connect('doctree-read', refcounts.add_annotations) + + +def setup(app): + app.add_config_value('refcount_file', '', True) + app.connect('builder-inited', init_annotations) + + # monkey-patch C object... + CObject.option_spec = { + 'noindex': directives.flag, + 'notlimited': directives.flag, + } + old_handle_signature = CObject.handle_signature + def new_handle_signature(self, sig, signode): + signode.parent['notlimited'] = 'notlimited' in self.options + return old_handle_signature(self, sig, signode) + CObject.handle_signature = new_handle_signature diff --git a/Doc/tools/sphinxext/pydoctheme/static/pydoctheme.css b/Doc/tools/sphinxext/pydoctheme/static/pydoctheme.css index 9942ca631dd..303529b0f9c 100644 --- a/Doc/tools/sphinxext/pydoctheme/static/pydoctheme.css +++ b/Doc/tools/sphinxext/pydoctheme/static/pydoctheme.css @@ -168,3 +168,11 @@ div.footer { div.footer a:hover { color: #0095C4; } + +.refcount { + color: #060; +} + +.notlimited { + color: #922; +}