diff --git a/Doc/c-api/concrete.rst b/Doc/c-api/concrete.rst index 84224dcca523b9..6bd4f030c55426 100644 --- a/Doc/c-api/concrete.rst +++ b/Doc/c-api/concrete.rst @@ -83,6 +83,7 @@ Container Objects dict.rst set.rst + simplenamespace.rst .. _otherobjects: diff --git a/Doc/c-api/simplenamespace.rst b/Doc/c-api/simplenamespace.rst new file mode 100644 index 00000000000000..c2665899c89201 --- /dev/null +++ b/Doc/c-api/simplenamespace.rst @@ -0,0 +1,15 @@ +.. highlight:: c + +SimpleNamespace Objects +----------------------- + +.. c:function:: PyObject* PySimpleNamespace_New(PyObject *attrs) + + Create a new simple namespace object. + + *attrs* must be a mapping object with non-empty string keys. + It can be ``NULL``. + + Return ``NULL`` and raise an exception on failure. + + .. versionadded:: 3.11 diff --git a/Doc/whatsnew/3.11.rst b/Doc/whatsnew/3.11.rst index 734cf1572fcba4..41fec1e3cd5543 100644 --- a/Doc/whatsnew/3.11.rst +++ b/Doc/whatsnew/3.11.rst @@ -476,6 +476,10 @@ New Features * Add a new :c:func:`PyType_GetQualName` function to get type's qualified name. (Contributed by Hai Shi in :issue:`42035`.) +* Add a new :c:func:`PySimpleNamespace_New` function to create a simple + namespace: C API of the :class:`types.SimpleNamespace` type. + (Contributed by Victor Stinner in :issue:`45482`.) + Porting to Python 3.11 ---------------------- diff --git a/Include/Python.h b/Include/Python.h index 89f60fe5c9f945..17886359b77e2f 100644 --- a/Include/Python.h +++ b/Include/Python.h @@ -76,7 +76,7 @@ #include "warnings.h" #include "weakrefobject.h" #include "structseq.h" -#include "namespaceobject.h" +#include "cpython/namespaceobject.h" #include "cpython/picklebufobject.h" #include "cpython/pytime.h" #include "codecs.h" diff --git a/Include/cpython/namespaceobject.h b/Include/cpython/namespaceobject.h new file mode 100644 index 00000000000000..e8ffbe53b60248 --- /dev/null +++ b/Include/cpython/namespaceobject.h @@ -0,0 +1,18 @@ +// Simple namespace object interface + +#ifndef Py_LIMITED_API +#ifndef Py_NAMESPACEOBJECT_H +#define Py_NAMESPACEOBJECT_H +#ifdef __cplusplus +extern "C" { +#endif + +PyAPI_DATA(PyTypeObject) _PySimpleNamespace_Type; + +PyAPI_FUNC(PyObject *) PySimpleNamespace_New(PyObject *attrs); + +#ifdef __cplusplus +} +#endif +#endif // !Py_NAMESPACEOBJECT_H +#endif // !Py_LIMITED_API diff --git a/Include/namespaceobject.h b/Include/namespaceobject.h deleted file mode 100644 index 0c8d95c0f137c6..00000000000000 --- a/Include/namespaceobject.h +++ /dev/null @@ -1,19 +0,0 @@ - -/* simple namespace object interface */ - -#ifndef NAMESPACEOBJECT_H -#define NAMESPACEOBJECT_H -#ifdef __cplusplus -extern "C" { -#endif - -#ifndef Py_LIMITED_API -PyAPI_DATA(PyTypeObject) _PyNamespace_Type; - -PyAPI_FUNC(PyObject *) _PyNamespace_New(PyObject *kwds); -#endif /* !Py_LIMITED_API */ - -#ifdef __cplusplus -} -#endif -#endif /* !NAMESPACEOBJECT_H */ diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py index b1218abc8af5ec..313a368da86a36 100644 --- a/Lib/test/test_types.py +++ b/Lib/test/test_types.py @@ -1,18 +1,20 @@ # Python test set -- part 6, built-in types -from test.support import run_with_locale, cpython_only import collections.abc -from collections import namedtuple import copy import gc import inspect -import pickle import locale +import pickle import sys import types +import typing import unittest.mock import weakref -import typing + +from collections import namedtuple +from test.support import run_with_locale, cpython_only +from test.support import import_helper T = typing.TypeVar("T") @@ -1733,6 +1735,45 @@ class FakeSimpleNamespace(str): with self.assertRaises(TypeError): types.SimpleNamespace() >= FakeSimpleNamespace() + def test_capi(self): + _testcapi = import_helper.import_module('_testcapi') + PySimpleNamespace_New = _testcapi.PySimpleNamespace_New + + def list_attributes(obj): + return [name for name in dir(obj) if not name.startswith('_')] + + # two attributes + value1 = 'value' + value2 = 2 + ns = PySimpleNamespace_New({'attr1': value1, 'attr2': value2}) + self.assertIsInstance(ns, types.SimpleNamespace) + self.assertEqual(list_attributes(ns), ['attr1', 'attr2']) + self.assertIs(ns.attr1, value1) + self.assertIs(ns.attr2, value2) + + # it's possible to modify a namespace + ns.attr1 = 1 + self.assertEqual(ns.attr1, 1) + ns.attr3 = 3 + self.assertEqual(list_attributes(ns), ['attr1', 'attr2', 'attr3']) + + # empty namespace + ns = PySimpleNamespace_New({}) + self.assertIsInstance(ns, types.SimpleNamespace) + self.assertEqual(list_attributes(ns), []) + + # invalid arguments + for invalid_arg in ( + 123, + 'string', + [('key', 'value')], + # non-string key + {123: 'value'}, + ): + with (self.subTest(invalid_arg=invalid_arg), + self.assertRaises(TypeError)): + PySimpleNamespace_New(invalid_arg) + class CoroutineTests(unittest.TestCase): def test_wrong_args(self): diff --git a/Makefile.pre.in b/Makefile.pre.in index 32bbab068f7027..403e60bc827e57 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -1157,7 +1157,6 @@ PYTHON_HEADERS= \ $(srcdir)/Include/methodobject.h \ $(srcdir)/Include/modsupport.h \ $(srcdir)/Include/moduleobject.h \ - $(srcdir)/Include/namespaceobject.h \ $(srcdir)/Include/object.h \ $(srcdir)/Include/objimpl.h \ $(srcdir)/Include/opcode.h \ @@ -1215,6 +1214,7 @@ PYTHON_HEADERS= \ $(srcdir)/Include/cpython/interpreteridobject.h \ $(srcdir)/Include/cpython/listobject.h \ $(srcdir)/Include/cpython/methodobject.h \ + $(srcdir)/Include/cpython/namespaceobject.h \ $(srcdir)/Include/cpython/object.h \ $(srcdir)/Include/cpython/objimpl.h \ $(srcdir)/Include/cpython/odictobject.h \ diff --git a/Misc/NEWS.d/next/C API/2021-10-15-10-01-31.bpo-45482.69r5QI.rst b/Misc/NEWS.d/next/C API/2021-10-15-10-01-31.bpo-45482.69r5QI.rst new file mode 100644 index 00000000000000..c4e70cfe0e5a67 --- /dev/null +++ b/Misc/NEWS.d/next/C API/2021-10-15-10-01-31.bpo-45482.69r5QI.rst @@ -0,0 +1,3 @@ +Add a new :c:func:`PySimpleNamespace_New` function to create a simple +namespace: C API of the :class:`types.SimpleNamespace` type. Patch by Victor +Stinner. diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 7cbd2dc3b6a51f..a24c6ca3354a0a 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -1167,7 +1167,7 @@ test_get_type_qualname(PyObject *self, PyObject *Py_UNUSED(ignored)) assert(strcmp(PyUnicode_AsUTF8(tp_qualname), "int") == 0); Py_DECREF(tp_qualname); - tp_qualname = PyType_GetQualName(&_PyNamespace_Type); + tp_qualname = PyType_GetQualName(&_PySimpleNamespace_Type); assert(strcmp(PyUnicode_AsUTF8(tp_qualname), "SimpleNamespace") == 0); Py_DECREF(tp_qualname); @@ -5674,6 +5674,18 @@ type_get_version(PyObject *self, PyObject *type) } +static PyObject * +test_simplenamespace(PyObject *self, PyObject *args) +{ + PyObject *items = NULL; + if (!PyArg_ParseTuple(args, "|O", &items)) { + return NULL; + } + + return PySimpleNamespace_New(items); +} + + static PyObject *test_buildvalue_issue38913(PyObject *, PyObject *); static PyObject *getargs_s_hash_int(PyObject *, PyObject *, PyObject*); @@ -5957,6 +5969,7 @@ static PyMethodDef TestMethods[] = { {"fatal_error", test_fatal_error, METH_VARARGS, PyDoc_STR("fatal_error(message, release_gil=False): call Py_FatalError(message)")}, {"type_get_version", type_get_version, METH_O, PyDoc_STR("type->tp_version_tag")}, + {"PySimpleNamespace_New", test_simplenamespace, METH_VARARGS, NULL}, {NULL, NULL} /* sentinel */ }; diff --git a/Modules/_testmultiphase.c b/Modules/_testmultiphase.c index e0ed77d265cdcc..9bc9f320e42551 100644 --- a/Modules/_testmultiphase.c +++ b/Modules/_testmultiphase.c @@ -459,7 +459,7 @@ createfunc_nonmodule(PyObject *spec, PyModuleDef *def) PyDict_SetItemString(dct, "three", three); Py_DECREF(three); - ns = _PyNamespace_New(dct); + ns = PySimpleNamespace_New(dct); Py_DECREF(dct); return ns; } diff --git a/Modules/timemodule.c b/Modules/timemodule.c index ca8d62d5cc9380..ac495176438929 100644 --- a/Modules/timemodule.c +++ b/Modules/timemodule.c @@ -1653,7 +1653,7 @@ time_get_clock_info(PyObject *self, PyObject *args) } Py_CLEAR(obj); - ns = _PyNamespace_New(dict); + ns = PySimpleNamespace_New(dict); Py_DECREF(dict); return ns; diff --git a/Objects/namespaceobject.c b/Objects/namespaceobject.c index fa37ed250d30a4..c8e5e72ab7c0ab 100644 --- a/Objects/namespaceobject.c +++ b/Objects/namespaceobject.c @@ -72,8 +72,12 @@ namespace_repr(PyObject *ns) PyObject *separator, *pairsrepr, *repr = NULL; const char * name; - name = Py_IS_TYPE(ns, &_PyNamespace_Type) ? "namespace" - : Py_TYPE(ns)->tp_name; + if (Py_IS_TYPE(ns, &_PySimpleNamespace_Type)) { + name = "namespace"; + } + else { + name = Py_TYPE(ns)->tp_name; + } i = Py_ReprEnter(ns); if (i != 0) { @@ -163,8 +167,8 @@ namespace_clear(_PyNamespaceObject *ns) static PyObject * namespace_richcompare(PyObject *self, PyObject *other, int op) { - if (PyObject_TypeCheck(self, &_PyNamespace_Type) && - PyObject_TypeCheck(other, &_PyNamespace_Type)) + if (PyObject_TypeCheck(self, &_PySimpleNamespace_Type) && + PyObject_TypeCheck(other, &_PySimpleNamespace_Type)) return PyObject_RichCompare(((_PyNamespaceObject *)self)->ns_dict, ((_PyNamespaceObject *)other)->ns_dict, op); Py_RETURN_NOTIMPLEMENTED; @@ -199,7 +203,7 @@ PyDoc_STRVAR(namespace_doc, \n\ SimpleNamespace(**kwargs)"); -PyTypeObject _PyNamespace_Type = { +PyTypeObject _PySimpleNamespace_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) "types.SimpleNamespace", /* tp_name */ sizeof(_PyNamespaceObject), /* tp_basicsize */ @@ -244,18 +248,32 @@ PyTypeObject _PyNamespace_Type = { PyObject * -_PyNamespace_New(PyObject *kwds) +PySimpleNamespace_New(PyObject *attrs) { - PyObject *ns = namespace_new(&_PyNamespace_Type, NULL, NULL); - if (ns == NULL) + if (attrs != NULL && !PyDict_Check(attrs)) { + PyErr_Format(PyExc_TypeError, "expected dict, got %s", + Py_TYPE(attrs)->tp_name); return NULL; + } - if (kwds == NULL) - return ns; - if (PyDict_Update(((_PyNamespaceObject *)ns)->ns_dict, kwds) != 0) { - Py_DECREF(ns); + PyObject *ns = namespace_new(&_PySimpleNamespace_Type, NULL, NULL); + if (ns == NULL) { return NULL; } - return (PyObject *)ns; + if (attrs == NULL) { + return ns; + } + + if (!PyArg_ValidateKeywordArguments(attrs)) { + goto error; + } + if (PyDict_Update(((_PyNamespaceObject *)ns)->ns_dict, attrs) < 0) { + goto error; + } + return ns; + +error: + Py_DECREF(ns); + return NULL; } diff --git a/Objects/object.c b/Objects/object.c index 589dd6647318da..6268b77419ac9a 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -1947,7 +1947,7 @@ _PyTypes_Init(void) INIT_TYPE(_PyInterpreterID_Type); INIT_TYPE(_PyManagedBuffer_Type); INIT_TYPE(_PyMethodWrapper_Type); - INIT_TYPE(_PyNamespace_Type); + INIT_TYPE(_PySimpleNamespace_Type); INIT_TYPE(_PyNone_Type); INIT_TYPE(_PyNotImplemented_Type); INIT_TYPE(_PyWeakref_CallableProxyType); diff --git a/PCbuild/pythoncore.vcxproj b/PCbuild/pythoncore.vcxproj index 877064e877debe..ede9eb1118be8e 100644 --- a/PCbuild/pythoncore.vcxproj +++ b/PCbuild/pythoncore.vcxproj @@ -140,6 +140,7 @@ + @@ -231,7 +232,6 @@ - diff --git a/PCbuild/pythoncore.vcxproj.filters b/PCbuild/pythoncore.vcxproj.filters index b8841c90cc1b96..0812c02b203db1 100644 --- a/PCbuild/pythoncore.vcxproj.filters +++ b/PCbuild/pythoncore.vcxproj.filters @@ -342,9 +342,6 @@ Include - - Include - Include @@ -408,12 +405,15 @@ Include\cpython - - Include\cpython + + Include Include\cpython + + Include\cpython + Include diff --git a/Python/import.c b/Python/import.c index f2160928c4fa7c..45fc9071e0c6df 100644 --- a/Python/import.c +++ b/Python/import.c @@ -2479,7 +2479,7 @@ _PyImport_BootstrapImp(PyThreadState *tstate) if (attrs == NULL) { goto error; } - PyObject *spec = _PyNamespace_New(attrs); + PyObject *spec = PySimpleNamespace_New(attrs); Py_DECREF(attrs); if (spec == NULL) { goto error; diff --git a/Python/sysmodule.c b/Python/sysmodule.c index ae3cbf1954e09e..5eccf3386587ac 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -26,7 +26,7 @@ Data members: #include "pycore_pylifecycle.h" // _PyErr_WriteUnraisableDefaultHook() #include "pycore_pymem.h" // _PyMem_SetDefaultAllocator() #include "pycore_pystate.h" // _PyThreadState_GET() -#include "pycore_structseq.h" // PyStructSequence_InitType() +#include "pycore_structseq.h" // _PyStructSequence_InitType() #include "pycore_tuple.h" // _PyTuple_FromArray() #include "code.h" @@ -2717,7 +2717,7 @@ make_impl_info(PyObject *version_info) /* dict ready */ - ns = _PyNamespace_New(impl_info); + ns = PySimpleNamespace_New(impl_info); Py_DECREF(impl_info); return ns;