forked frompython/cpython
- Notifications
You must be signed in to change notification settings - Fork0
Implement PyInterface prototype#44
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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Draft
zooba wants to merge4 commits intomainChoose a base branch fromslots
base:main
Could not load branches
Branch not found:{{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline, and old review comments may become outdated.
Uh oh!
There was an error while loading.Please reload this page.
Draft
Changes fromall commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Jump to file
Failed to load files.
Loading
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
There are no files selected for viewing
1 change: 1 addition & 0 deletionsInclude/Python.h
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.Learn more about bidirectional Unicode characters
2 changes: 2 additions & 0 deletionsInclude/cpython/object.h
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.Learn more about bidirectional Unicode characters
5 changes: 5 additions & 0 deletionsInclude/object.h
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.Learn more about bidirectional Unicode characters
202 changes: 202 additions & 0 deletionsInclude/pyinterface.h
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,202 @@ | ||
| #ifndef Py_PYINTERFACE_H | ||
| #define Py_PYINTERFACE_H | ||
| #ifdef __cplusplus | ||
| extern "C" { | ||
| #endif | ||
| /* | ||
| The PyInterface_Base struct is the generic type for actual interface data | ||
| implementations. The intent is for callers to preallocate the specific struct | ||
| and have the PyObject_GetInterface() function fill it in. | ||
| An example of direct use of the interface API (definitions and example without | ||
| macro helpers below): | ||
| Py_INTERFACE_VAR(PyInterface_GetAttrWChar, attr_data); | ||
| if (PyObject_GetInterface(obj, &attr_data) == 0) { | ||
| result = Py_INTERFACE_CALL(attr_data, getattr, L"attribute_name"); | ||
| PyInterface_Release(&attr_data); | ||
| } else { | ||
| char attr_name[128]; | ||
| wchar_to_char(attr_name, L"attribute_name"); // hypothetical converter | ||
| result = PyObject_GetAttr(obj, attr_name); | ||
| } | ||
| Here are the key points that add value: | ||
| * the PyInterface_GetAttrWChar_Name constant is embedded in the calling | ||
| module, making the value available when running against earlier releases of | ||
| Python (unlike a function export, which would cause a load failure). | ||
| * if the name is not available, the PyObject_GetInterface call can fail safely. | ||
| Thus, new APIs can be added in later releases, and newer compiled modules can | ||
| be fully binary compatible with older releases. | ||
| * "names" are arbitrary ints (for fast comparison/switch statements). Core | ||
| values have high 32-bits zero - others should use a unique value as their top | ||
| 32 bits. The intent is that they are always referred to as a constant, hence, | ||
| "names" rather than "index" or similar. | ||
| * The attr_data value contains all the information required to eventually | ||
| produce the result. It may provide fields for direct access as well as | ||
| function pointers that can calculate/copy/return results as needed. | ||
| * The interface is resolved by the object's type, but does not have to provide | ||
| identical result for every instance. It has independent lifetime from object, | ||
| (though this will often just be a new strong reference to the object). | ||
| * Static/header libraries can be used to wrap up the faster APIs, so that | ||
| extensions can adopt them simply by compiling with the latest release. | ||
| An example is shown at the end of this file. | ||
| Without the Py_INTERFACE_* helper macros, it would look like the below. This is | ||
| primarily for the benefit of non-C developers trying to use the API without | ||
| macros. | ||
| PyInterface_GetAttrWChar attr_data = { | ||
| .size = sizeof(PyInterface_GetAttrWChar), | ||
| .name = PyInterface_GetAttrWChar_Name | ||
| }; | ||
| if (PyObject_GetInterface(obj, &attr_data) == 0) { | ||
| result = (*attr_data.getattr)(&attr_data, L"attribute_name"); | ||
| PyInterface_Release(&attr_data); | ||
| } ... | ||
| */ | ||
| #define Py_INTERFACE_VAR(t, n) t n = { sizeof(t), t ## _Name } | ||
| #define Py_INTERFACE_CALL(data, attr, ...) ((data).attr)(&(data), __VA_ARGS__) | ||
| typedef struct PyInterface_Base { | ||
| Py_ssize_t size; | ||
| uint64_t name; | ||
| /* intf is 'PyInterface_Base' but will be passed the full struct */ | ||
| int (*release)(struct PyInterface_Base *intf); | ||
| /* Possibly: void *func_table; ??? | ||
| Pro: would allow function tables to be static, so less copying (though | ||
| could do this on a case-by-case basis anyway) | ||
| Con: additional indirection and more lossy typing */ | ||
| } PyInterface_Base; | ||
| typedef int (*Py_getinterfacefunc)(PyObject *o, PyInterface_Base *intf); | ||
| /* Functions to get interfaces are defined on PyObject and ... TODO */ | ||
| PyAPI_FUNC(int) PyInterface_Release(void *intf); | ||
| /* Some generic/example interface definitions. | ||
| The first (PyInterface_GetAttrWChar) shows a hypothetical new API that may be added | ||
| in a later release. Rather than modifying the ABI (making newer extensions | ||
| incompatible with earlier releases), it is added as a interface. Runtimes | ||
| without the interface will return a runtime error, and the caller uses a | ||
| fallback (the example above). | ||
| */ | ||
| #define PyInterface_GetAttrWChar_Name 1 | ||
| typedef struct PyInterface_GetAttrWChar { | ||
| PyInterface_Base base; | ||
| PyObject *(*getattr)(struct PyInterface_GetAttrWChar *, const wchar_t *attr); | ||
| int (*hasattr)(struct PyInterface_GetAttrWChar *, const wchar_t *attr); | ||
| // Strong reference to original object, but for private use only. | ||
| PyObject *_obj; | ||
| } PyInterface_GetAttrWChar; | ||
| /* This example (PyInterface_AsUTF8) shows an optionally-optimised API, where in some | ||
| cases the result of the API is readily available, and can be returned to | ||
| pre-allocated memory (in this case, the interface data in the stack of the caller). | ||
| The caller can then either use a fast path to access it directly, or the more | ||
| generic function (pointer) in the struct that will *always* produce a result, | ||
| but may take more computation if the fast result was not present. | ||
| Py_INTERFACE_VAR(PyInterface_AsUTF8, intf); | ||
| if (PyObject_GetInterface(o, &intf) == 0) { | ||
| // This check is optional, as the function calls in the interface should | ||
| // always succeed. However, developers who want to avoid an additional | ||
| // function call/allocation can do the check themselves. | ||
| // This check is defined per-interface struct, so users reference the | ||
| // docs for the specific interfaces they're using to find the check. | ||
| if (intf->s[0]) { | ||
| // use intf->s as the result. | ||
| call_next_api(intf->s); | ||
| } else { | ||
| char *s = Py_INTERFACE_CALL(intf, stralloc); | ||
| call_next_api(s); | ||
| PyMem_Free(s); | ||
| } | ||
| PyInterface_Release(&intf); | ||
| } else { ... } | ||
| */ | ||
| #define PyInterface_AsUTF8_Name 2 | ||
| typedef struct PyInterface_AsUTF8 { | ||
| PyInterface_Base base; | ||
| // Copy of contents if it was readily available. s[0] will be non-zero if set | ||
| char s[16]; | ||
| // Copy the characters into an existing string | ||
| size_t (*strcpy)(struct PyInterface_AsUTF8 *, char *dest, size_t dest_size); | ||
| // Allocate new buffer with PyMem_Malloc (use PyMem_Free to release) | ||
| char * (*stralloc)(struct PyInterface_AsUTF8 *); | ||
| // Optional strong reference to object, if unable to use char array | ||
| PyObject *_obj; | ||
| } PyInterface_AsUTF8; | ||
| /* Example implementation of PyInterface_AsUTF8.strcpy: | ||
| size_t _PyUnicode_AsUTF8Interface_strcpy(PyInterface_AsUTF8 *intf, char *dest, size_t dest_size) | ||
| { | ||
| if (intf->_obj) { | ||
| // copy/convert data from _obj... | ||
| _internal_copy((PyUnicodeObject *)intf->_obj, dest, dest_size); | ||
| return chars_copied; | ||
| } else { | ||
| return strcpy_s(dest, dest_size, intf->_reserved); | ||
| } | ||
| } | ||
| */ | ||
| /* API wrappers. | ||
| These are inline functions (or in a static import library) to embed all the | ||
| implementation into the external module. We can change the implementation over | ||
| releases, and by using interfaces to add optional handling, the behaviour | ||
| remains compatible with earlier releases, and the sources remain compatible. | ||
| Note that additions will usually be at the top of the function, assuming that | ||
| newer interfaces are preferred over older ones. | ||
| This may be defined in its own header or statically linked library, provided the | ||
| implementation ends up in the external module, not in the Python library. | ||
| static inline PyObject *PyObject_GetAttrWChar(PyObject *o, wchar_t *attr) | ||
| { | ||
| PyObject *result = NULL; | ||
| // Added in version N+1 | ||
| Py_INTERFACE_VAR(PyInterface_GetAttrWChar, intf); | ||
| if (PyObject_GetInterface(o, &intf) == 0) { | ||
| result = Py_INTERFACE_CALL(intf, getattr, attr); | ||
| if (PyInterface_Release(&intf) < 0) { | ||
| Py_DecRef(result); | ||
| return NULL; | ||
| } | ||
| return result; | ||
| } | ||
| PyErr_Clear(); | ||
| // Original implementation in version N | ||
| PyObject *attro = PyUnicode_FromWideChar(attr, -1); | ||
| if (attro) { | ||
| result = PyObject_GetAttr(o, attro); | ||
| Py_DecRef(attro); | ||
| } | ||
| return result; | ||
| } | ||
| */ | ||
| #ifdef __cplusplus | ||
| } | ||
| #endif | ||
| #endif |
4 changes: 4 additions & 0 deletionsInclude/typeslots.h
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.Learn more about bidirectional Unicode characters
14 changes: 14 additions & 0 deletionsLib/test/test_capi/test_interfaces.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| import unittest | ||
| from test.support import import_helper | ||
| _testcapi = import_helper.import_module('_testcapi') | ||
| class InterfacesTest(unittest.TestCase): | ||
| def test_interface_getattrwchar(self): | ||
| fn = _testcapi.interface_getattrwchar(_testcapi, "interface_getattrwchar") | ||
| self.assertIs(fn, _testcapi.interface_getattrwchar) | ||
| with self.assertRaises(AttributeError): | ||
| _testcapi.interface_getattrwchar(_testcapi, 'ϼўТλФЙ') |
42 changes: 42 additions & 0 deletionsModules/_testcapi/interfaces.c
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| #define PYTESTCAPI_NEED_INTERNAL_API | ||
| #include "parts.h" | ||
| #include "util.h" | ||
| static PyObject * | ||
| interface_getattrwchar(PyObject *Py_UNUSED(module), PyObject *args) | ||
| { | ||
| PyObject *obj = NULL; | ||
| PyObject *attro = NULL; | ||
| if (!PyArg_ParseTuple(args, "OO", &obj, &attro)) { | ||
| return NULL; | ||
| } | ||
| wchar_t *attr = PyUnicode_AsWideCharString(attro, NULL); | ||
| if (!attr) { | ||
| return NULL; | ||
| } | ||
| Py_INTERFACE_VAR(PyInterface_GetAttrWChar, attr_data); | ||
| if (PyObject_GetInterface(obj, &attr_data) < 0) { | ||
| PyMem_Free(attr); | ||
| return NULL; | ||
| } | ||
| PyObject *result = Py_INTERFACE_CALL(attr_data, getattr, attr); | ||
| PyInterface_Release(&attr_data); | ||
| PyMem_Free(attr); | ||
| return result; | ||
| } | ||
| static PyMethodDef test_methods[] = { | ||
| {"interface_getattrwchar", interface_getattrwchar, METH_VARARGS}, | ||
| {NULL}, | ||
| }; | ||
| int | ||
| _PyTestCapi_Init_Interfaces(PyObject *m) | ||
| { | ||
| return PyModule_AddFunctions(m, test_methods); | ||
| } |
1 change: 1 addition & 0 deletionsModules/_testcapi/parts.h
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.Learn more about bidirectional Unicode characters
3 changes: 3 additions & 0 deletionsModules/_testcapimodule.c
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.Learn more about bidirectional Unicode characters
Oops, something went wrong.
Uh oh!
There was an error while loading.Please reload this page.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.