mysqlshdk/scripting/python_map_wrapper.cc (1,660 lines of code) (raw):

/* * Copyright (c) 2015, 2024, Oracle and/or its affiliates. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2.0, * as published by the Free Software Foundation. * * This program is designed to work with certain software (including * but not limited to OpenSSL) that is licensed under separate terms, * as designated in a particular file or component or in included license * documentation. The authors of MySQL hereby grant you an additional * permission to link the program and your derivative works with the * separately licensed software that they have either included with * the program or referenced in the documentation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See * the GNU General Public License, version 2.0, for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "scripting/python_map_wrapper.h" #include <string> #include <type_traits> #include "mysqlshdk/libs/utils/utils_general.h" namespace shcore { namespace { namespace dict { //////////////////////////////////////////////////////////////////////////////// // dict::Object //////////////////////////////////////////////////////////////////////////////// struct Object { PyDictObject base; Dictionary_t *dict = nullptr; }; //////////////////////////////////////////////////////////////////////////////// // dict::Iterator //////////////////////////////////////////////////////////////////////////////// template <typename It> struct Iterator_base { // clang-format off PyObject_HEAD py::Store o; // clang-format on size_t initial_size = 0; It next; }; using Map_value_type = shcore::Value::Map_type::value_type; using Map_iterator = shcore::Value::Map_type::const_iterator; using Map_reverse_iterator = shcore::Value::Map_type::const_reverse_iterator; using Iterator = Iterator_base<Map_iterator>; using Reverse_iterator = Iterator_base<Map_reverse_iterator>; //////////////////////////////////////////////////////////////////////////////// // dict::View //////////////////////////////////////////////////////////////////////////////// struct View { // clang-format off PyObject_HEAD py::Store o; // clang-format on }; //////////////////////////////////////////////////////////////////////////////// // dict::Type - number protocol //////////////////////////////////////////////////////////////////////////////// PyObject *nb_or(PyObject *self, PyObject *other); PyObject *inplace_or(PyObject *self, PyObject *other); PyNumberMethods as_number = { 0, // binaryfunc nb_add; 0, // binaryfunc nb_subtract; 0, // binaryfunc nb_multiply; 0, // binaryfunc nb_remainder; 0, // binaryfunc nb_divmod; 0, // ternaryfunc nb_power; 0, // unaryfunc nb_negative; 0, // unaryfunc nb_positive; 0, // unaryfunc nb_absolute; 0, // inquiry nb_bool; 0, // unaryfunc nb_invert; 0, // binaryfunc nb_lshift; 0, // binaryfunc nb_rshift; 0, // binaryfunc nb_and; 0, // binaryfunc nb_xor; nb_or, // binaryfunc nb_or; 0, // unaryfunc nb_int; 0, // void *nb_reserved; 0, // unaryfunc nb_float; 0, // binaryfunc nb_inplace_add; 0, // binaryfunc nb_inplace_subtract; 0, // binaryfunc nb_inplace_multiply; 0, // binaryfunc nb_inplace_remainder; 0, // ternaryfunc nb_inplace_power; 0, // binaryfunc nb_inplace_lshift; 0, // binaryfunc nb_inplace_rshift; 0, // binaryfunc nb_inplace_and; 0, // binaryfunc nb_inplace_xor; inplace_or, // binaryfunc nb_inplace_or; 0, // binaryfunc nb_floor_divide; 0, // binaryfunc nb_true_divide; 0, // binaryfunc nb_inplace_floor_divide; 0, // binaryfunc nb_inplace_true_divide; 0, // unaryfunc nb_index; 0, // binaryfunc nb_matrix_multiply; 0, // binaryfunc nb_inplace_matrix_multiply; }; //////////////////////////////////////////////////////////////////////////////// // dict::Type - sequence protocol //////////////////////////////////////////////////////////////////////////////// int contains(dict::Object *self, PyObject *key); PySequenceMethods as_sequence = { 0, // lenfunc sq_length; 0, // binaryfunc sq_concat; 0, // ssizeargfunc sq_repeat; 0, // ssizeargfunc sq_item; 0, // void *was_sq_slice; 0, // ssizeobjargproc sq_ass_item; 0, // void *was_sq_ass_slice; (objobjproc)contains, // objobjproc sq_contains; 0, // binaryfunc sq_inplace_concat; 0 // ssizeargfunc sq_inplace_repeat; }; //////////////////////////////////////////////////////////////////////////////// // dict::Type - mapping protocol //////////////////////////////////////////////////////////////////////////////// Py_ssize_t length(dict::Object *self); PyObject *subscript(dict::Object *self, PyObject *key); int assign_subscript(dict::Object *self, PyObject *key, PyObject *value); PyMappingMethods as_mapping = { (lenfunc)length, // lenfunc mp_length; (binaryfunc)subscript, // binaryfunc mp_subscript; (objobjargproc)assign_subscript // objobjargproc mp_ass_subscript; }; //////////////////////////////////////////////////////////////////////////////// // dict::Type - methods //////////////////////////////////////////////////////////////////////////////// PyDoc_STRVAR(contains_doc, "D.__contains__(key) -- True if D has a key k, else False."); PyDoc_STRVAR(dir_doc, "D.__dir__() -- returns list of valid attributes"); PyDoc_STRVAR(get_item_doc, "D.__getitem__(y) <==> D[y]"); PyDoc_STRVAR( reversed_doc, "D.__reversed__() -- return a reverse iterator over the dict keys."); PyDoc_STRVAR(sizeof_doc, "D.__sizeof__() -> size of D in memory, in bytes"); PyDoc_STRVAR(clear_doc, "D.clear() -> None. Remove all items from D."); PyDoc_STRVAR(copy_doc, "D.copy() -> a shallow copy of D"); PyDoc_STRVAR(from_keys_doc, "fromkeys($type, iterable, value=None) -- Returns a new dict with " "keys from iterable and values equal to value."); PyDoc_STRVAR(get_doc, "D.get(k[,d]) -> D[k] if k in D, else d. d defaults to None."); PyDoc_STRVAR(has_key_doc, "D.has_key(key) -- True if D has a key k, else False."); PyDoc_STRVAR(items_doc, "D.items() -> a set-like object providing a view on D's items"); PyDoc_STRVAR(keys_doc, "D.keys() -> a set-like object providing a view on D's keys"); PyDoc_STRVAR(pop_doc, "D.pop(k[,d]) -> v, remove specified key and return the " "corresponding value.\nIf key is not found, d is returned if " "given, otherwise KeyError is raised"); PyDoc_STRVAR(pop_item_doc, "D.popitem() -> (k, v), remove and return some (key, value) pair " "as a\n2-tuple; but raise KeyError if D is empty."); PyDoc_STRVAR( set_default_doc, "D.setdefault(k[,d]) -> D.get(k,d), also set D[k]=d if k not in D"); PyDoc_STRVAR(update_doc, "D.update([E, ]**F) -> None. Update D from dict/iterable E and " "F.\nIf E is present and has a .keys() method, then does: for k " "in E: D[k] = E[k]\nIf E is present and lacks a .keys() method, " "then does: for k, v in E: D[k] = v\nIn either case, this is " "followed by: for k in F: D[k] = F[k]"); PyDoc_STRVAR(values_doc, "D.values() -> an object providing a view on D's values"); PyObject *dir(dict::Object *self); PyObject *reversed(dict::Object *self, PyObject *); PyObject *size_of(dict::Object *self); PyObject *clear(dict::Object *self); PyObject *copy(dict::Object *self); PyObject *from_keys(PyTypeObject *type, PyObject *args); PyObject *get(dict::Object *self, PyObject *args); PyObject *has_key(dict::Object *self, PyObject *key); PyObject *items(dict::Object *self); PyObject *keys(dict::Object *self); PyObject *pop(dict::Object *self, PyObject *args); PyObject *pop_item(dict::Object *self); PyObject *set_default(dict::Object *self, PyObject *args); PyObject *update(dict::Object *self, PyObject *args, PyObject *kwds); PyObject *values(dict::Object *self); #if __GNUC__ > 7 && !defined(__clang__) // -Wcast-function-type was added in GCC 8.0, needs to be suppressed here, // because we're casting functions with different number of parameters to // PyCFunction #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-function-type" #endif // __GNUC__ > 7 && !defined(__clang__) PyMethodDef methods[] = { {"__contains__", (PyCFunction)has_key, METH_O | METH_COEXIST, contains_doc}, {"__dir__", (PyCFunction)dir, METH_NOARGS, dir_doc}, {"__getitem__", (PyCFunction)subscript, METH_O | METH_COEXIST, get_item_doc}, {"__reversed__", (PyCFunction)reversed, METH_NOARGS, reversed_doc}, {"__sizeof__", (PyCFunction)size_of, METH_NOARGS, sizeof_doc}, {"clear", (PyCFunction)clear, METH_NOARGS, clear_doc}, {"copy", (PyCFunction)copy, METH_NOARGS, copy_doc}, {"fromkeys", (PyCFunction)from_keys, METH_VARARGS | METH_CLASS, from_keys_doc}, {"get", (PyCFunction)get, METH_VARARGS, get_doc}, {"has_key", (PyCFunction)has_key, METH_O, has_key_doc}, {"items", (PyCFunction)items, METH_NOARGS, items_doc}, {"keys", (PyCFunction)keys, METH_NOARGS, keys_doc}, {"pop", (PyCFunction)pop, METH_VARARGS, pop_doc}, {"popitem", (PyCFunction)pop_item, METH_NOARGS, pop_item_doc}, {"setdefault", (PyCFunction)set_default, METH_VARARGS, set_default_doc}, {"update", (PyCFunction)update, METH_VARARGS | METH_KEYWORDS, update_doc}, {"values", (PyCFunction)values, METH_NOARGS, values_doc}, {nullptr, nullptr, 0, nullptr}, }; #if __GNUC__ > 7 && !defined(__clang__) #pragma GCC diagnostic pop #endif // __GNUC__ > 7 && !defined(__clang__) //////////////////////////////////////////////////////////////////////////////// // dict::Type //////////////////////////////////////////////////////////////////////////////// PyDoc_STRVAR(dict_object_doc, "Dict() -> new empty shcore dictionary\n" "\n" "Creates a new instance of a shcore dictionary object."); void dealloc(dict::Object *self); PyObject *repr(dict::Object *self); PyObject *str(dict::Object *self); PyObject *get_attribute(dict::Object *self, PyObject *name); int gc_traverse(PyObject *, visitproc, void *); int gc_clear(PyObject *); PyObject *richcompare(dict::Object *self, PyObject *right, int op); PyObject *iterator(dict::Object *self); int init(dict::Object *self, PyObject *args, PyObject *kwds); PyObject *create(PyTypeObject *type, PyObject *args, PyObject *kwds); #if PY_VERSION_HEX >= 0x03080000 && PY_VERSION_HEX < 0x03090000 #ifdef __clang__ // The tp_print is marked as deprecated, which makes clang unhappy, 'cause it's // initialized below. Skipping initialization also makes clang unhappy, so we're // disabling the deprecated declarations warning. #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" #endif // __clang__ #endif // PY_VERSION_HEX PyTypeObject Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) // PyObject_VAR_HEAD // full name including the module is required for pickle support "mysqlsh.__shell_python_support__.Dict", // const char *tp_name; /* For allocation */ sizeof(dict::Object), // Py_ssize_t tp_basicsize; 0, // Py_ssize_t tp_itemsize; /* Methods to implement standard operations */ (destructor)dict::dealloc, // destructor tp_dealloc; 0, // printfunc tp_print; 0, // getattrfunc tp_getattr; 0, // setattrfunc tp_setattr; 0, // cmpfunc tp_compare; (reprfunc)dict::repr, // reprfunc tp_repr; /* Method suites for standard classes */ &dict::as_number, // PyNumberMethods *tp_as_number; &dict::as_sequence, // PySequenceMethods *tp_as_sequence; &dict::as_mapping, // PyMappingMethods *tp_as_mapping; /* More standard operations (here for binary compatibility) */ 0, // hashfunc tp_hash; 0, // ternaryfunc tp_call; (reprfunc)dict::str, // reprfunc tp_str; (getattrofunc)dict::get_attribute, // getattrofunc tp_getattro; 0, // setattrofunc tp_setattro; /* Functions to access object as input/output buffer */ 0, // PyBufferProcs *tp_as_buffer; /* Flags to define presence of optional/expanded features */ Py_TPFLAGS_DEFAULT, // long tp_flags; dict_object_doc, // char *tp_doc; /* Documentation string */ /* Assigned meaning in release 2.0 */ /* call function for all accessible objects */ dict::gc_traverse, // traverseproc tp_traverse; /* delete references to contained objects */ dict::gc_clear, // inquiry tp_clear; /* Assigned meaning in release 2.1 */ /* rich comparisons */ (richcmpfunc)dict::richcompare, // richcmpfunc tp_richcompare; /* weak reference enabler */ 0, // long tp_weaklistoffset; /* Added in release 2.2 */ /* Iterators */ (getiterfunc)dict::iterator, // getiterfunc tp_iter; 0, // iternextfunc tp_iternext; /* Attribute descriptor and subclassing stuff */ dict::methods, // struct PyMethodDef *tp_methods; 0, // struct PyMemberDef *tp_members; 0, // struct PyGetSetDef *tp_getset; 0, // struct _typeobject *tp_base; 0, // PyObject *tp_dict; 0, // descrgetfunc tp_descr_get; 0, // descrsetfunc tp_descr_set; 0, // long tp_dictoffset; (initproc)dict::init, // initproc tp_init; PyType_GenericAlloc, // allocfunc tp_alloc; dict::create, // newfunc tp_new; 0, // freefunc tp_free; /* Low-level free-memory routine */ 0, // inquiry tp_is_gc; /* For PyObject_IS_GC */ 0, // PyObject *tp_bases; 0, // PyObject *tp_mro; /* method resolution order */ 0, // PyObject *tp_cache; 0, // PyObject *tp_subclasses; 0, // PyObject *tp_weaklist; 0 // tp_del #if PY_VERSION_HEX >= 0x02060000 , 0 // tp_version_tag #endif #if PY_VERSION_HEX >= 0x03040000 , 0 // tp_finalize #endif #if PY_VERSION_HEX >= 0x03080000 , 0 // tp_vectorcall #if PY_VERSION_HEX < 0x03090000 , 0 // tp_print #endif #endif #if PY_VERSION_HEX >= 0x030C0000 , 0 // tp_watched #endif #if PY_VERSION_HEX >= 0x030D0000 , 0 // tp_versions_used #endif }; #if PY_VERSION_HEX >= 0x03080000 && PY_VERSION_HEX < 0x03090000 #ifdef __clang__ #pragma clang diagnostic pop #endif // __clang__ #endif // PY_VERSION_HEX //////////////////////////////////////////////////////////////////////////////// // dict::Iterator - common helper functions //////////////////////////////////////////////////////////////////////////////// namespace iter { Map_iterator begin(dict::Iterator *self); Map_reverse_iterator begin(dict::Reverse_iterator *self); Map_iterator end(dict::Iterator *self); Map_reverse_iterator end(dict::Reverse_iterator *self); template <typename It> PyObject *create(dict::Object *dict, PyTypeObject *type); template <typename It> void dealloc(dict::Iterator_base<It> *self); using convert_t = PyObject *(*)(const Map_value_type &); template <typename It> PyObject *next(dict::Iterator_base<It> *self, convert_t convert); #define DEFINE_ITERATOR(tp_name, it) \ PyTypeObject Type = { \ PyVarObject_HEAD_INIT(&PyType_Type, 0) /* PyObject_VAR_HEAD */ \ tp_name, /* const char *tp_name; */ \ sizeof(dict::Iterator_base<it>), /* Py_ssize_t tp_basicsize; */ \ 0, /* Py_ssize_t tp_itemsize; */ \ (destructor)dict::iter::dealloc<it>, /* destructor tp_dealloc; */ \ 0, /* printfunc tp_print; */ \ 0, /* getattrfunc tp_getattr; */ \ 0, /* setattrfunc tp_setattr; */ \ 0, /* cmpfunc tp_compare; */ \ 0, /* reprfunc tp_repr; */ \ 0, /* PyNumberMethods *tp_as_number; */ \ 0, /* PySequenceMethods *tp_as_sequence; */ \ 0, /* PyMappingMethods *tp_as_mapping; */ \ 0, /* hashfunc tp_hash; */ \ 0, /* ternaryfunc tp_call; */ \ 0, /* reprfunc tp_str; */ \ PyObject_GenericGetAttr, /* getattrofunc tp_getattro; */ \ 0, /* setattrofunc tp_setattro; */ \ 0, /* PyBufferProcs *tp_as_buffer; */ \ Py_TPFLAGS_DEFAULT, /* long tp_flags; */ \ 0, /* char *tp_doc; */ \ 0, /* traverseproc tp_traverse; */ \ 0, /* inquiry tp_clear; */ \ 0, /* richcmpfunc tp_richcompare; */ \ 0, /* long tp_weaklistoffset; */ \ PyObject_SelfIter, /* getiterfunc tp_iter; */ \ (iternextfunc)next<it>, /* iternextfunc tp_iternext; */ \ dict::iter::methods<it>, /* struct PyMethodDef *tp_methods; */ \ 0, /* struct PyMemberDef *tp_members; */ \ } } // namespace iter //////////////////////////////////////////////////////////////////////////////// // dict::Iterator - common type methods //////////////////////////////////////////////////////////////////////////////// namespace iter { PyDoc_STRVAR(length_hint_doc, "Private method returning an estimate of len(dict(it))."); PyDoc_STRVAR(reduce_doc, "Return state information for pickling."); template <typename It> PyObject *length(dict::Iterator_base<It> *self); template <typename It> PyObject *reduce(dict::Iterator_base<It> *self); #if __GNUC__ > 7 && !defined(__clang__) // -Wcast-function-type was added in GCC 8.0, needs to be suppressed here, // because we're casting functions with different number of parameters to // PyCFunction #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-function-type" #endif // __GNUC__ > 7 && !defined(__clang__) template <typename It> PyMethodDef methods[] = { {"__length_hint__", (PyCFunction)length<It>, METH_NOARGS, length_hint_doc}, {"__reduce__", (PyCFunction)reduce<It>, METH_NOARGS, reduce_doc}, {nullptr, nullptr, 0, nullptr}, }; #if __GNUC__ > 7 && !defined(__clang__) #pragma GCC diagnostic pop #endif // __GNUC__ > 7 && !defined(__clang__) } // namespace iter //////////////////////////////////////////////////////////////////////////////// // dict::iter::keys::Type //////////////////////////////////////////////////////////////////////////////// namespace iter::keys { PyObject *convert(const Map_value_type &pair); template <typename It> PyObject *next(dict::Iterator_base<It> *self); #ifdef __GNUC__ // We're using macro to initialize the PyTypeObject and skip initialization // of some of the fields to avoid problems with tp_print, which was added, // deprecated, and then removed from Python. #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wmissing-field-initializers" #endif // __GNUC__ DEFINE_ITERATOR("Dict_keyiterator", Map_iterator); namespace rev { DEFINE_ITERATOR("Dict_reversekeyiterator", Map_reverse_iterator); } // namespace rev #ifdef __GNUC__ #pragma GCC diagnostic pop #endif // __GNUC__ } // namespace iter::keys //////////////////////////////////////////////////////////////////////////////// // dict::iter::values::Type //////////////////////////////////////////////////////////////////////////////// namespace iter::values { PyObject *convert(const Map_value_type &pair); template <typename It> PyObject *next(dict::Iterator_base<It> *self); #ifdef __GNUC__ // We're using macro to initialize the PyTypeObject and skip initialization // of some of the fields to avoid problems with tp_print, which was added, // deprecated, and then removed from Python. #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wmissing-field-initializers" #endif // __GNUC__ DEFINE_ITERATOR("Dict_valueiterator", Map_iterator); namespace rev { DEFINE_ITERATOR("Dict_reversevalueiterator", Map_reverse_iterator); } // namespace rev #ifdef __GNUC__ #pragma GCC diagnostic pop #endif // __GNUC__ } // namespace iter::values //////////////////////////////////////////////////////////////////////////////// // dict::iter::items::Type //////////////////////////////////////////////////////////////////////////////// namespace iter::items { PyObject *convert(const Map_value_type &pair); template <typename It> PyObject *next(dict::Iterator_base<It> *self); #ifdef __GNUC__ // We're using macro to initialize the PyTypeObject and skip initialization // of some of the fields to avoid problems with tp_print, which was added, // deprecated, and then removed from Python. #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wmissing-field-initializers" #endif // __GNUC__ DEFINE_ITERATOR("Dict_itemiterator", Map_iterator); namespace rev { DEFINE_ITERATOR("Dict_reverseitemiterator", Map_reverse_iterator); } // namespace rev #ifdef __GNUC__ #pragma GCC diagnostic pop #endif // __GNUC__ } // namespace iter::items //////////////////////////////////////////////////////////////////////////////// // dict::View - common functions //////////////////////////////////////////////////////////////////////////////// namespace view { PyObject *create(dict::Object *dict, PyTypeObject *type); void dealloc(dict::View *self); PyObject *subtract(dict::View *self, PyObject *other); PyObject *nb_and(dict::View *self, PyObject *other); PyObject *nb_or(dict::View *self, PyObject *other); PyObject *nb_xor(dict::View *self, PyObject *other); Py_ssize_t length(dict::View *self); PyObject *subscript(dict::View *self, PyObject *item, dict::iter::convert_t convert); } // namespace view //////////////////////////////////////////////////////////////////////////////// // dict::view - common number protocol //////////////////////////////////////////////////////////////////////////////// namespace view { PyNumberMethods as_number = { 0, // binaryfunc nb_add; (binaryfunc)subtract, // binaryfunc nb_subtract; 0, // binaryfunc nb_multiply; 0, // binaryfunc nb_remainder; 0, // binaryfunc nb_divmod; 0, // ternaryfunc nb_power; 0, // unaryfunc nb_negative; 0, // unaryfunc nb_positive; 0, // unaryfunc nb_absolute; 0, // inquiry nb_bool; 0, // unaryfunc nb_invert; 0, // binaryfunc nb_lshift; 0, // binaryfunc nb_rshift; (binaryfunc)nb_and, // binaryfunc nb_and; (binaryfunc)nb_xor, // binaryfunc nb_xor; (binaryfunc)nb_or, // binaryfunc nb_or; 0, // unaryfunc nb_int; 0, // void *nb_reserved; 0, // unaryfunc nb_float; 0, // binaryfunc nb_inplace_add; 0, // binaryfunc nb_inplace_subtract; 0, // binaryfunc nb_inplace_multiply; 0, // binaryfunc nb_inplace_remainder; 0, // ternaryfunc nb_inplace_power; 0, // binaryfunc nb_inplace_lshift; 0, // binaryfunc nb_inplace_rshift; 0, // binaryfunc nb_inplace_and; 0, // binaryfunc nb_inplace_xor; 0, // binaryfunc nb_inplace_or; 0, // binaryfunc nb_floor_divide; 0, // binaryfunc nb_true_divide; 0, // binaryfunc nb_inplace_floor_divide; 0, // binaryfunc nb_inplace_true_divide; 0, // unaryfunc nb_index; 0, // binaryfunc nb_matrix_multiply; 0, // binaryfunc nb_inplace_matrix_multiply; }; } // namespace view //////////////////////////////////////////////////////////////////////////////// // dict::view - common getters/setters //////////////////////////////////////////////////////////////////////////////// namespace view { PyObject *mapping(dict::View *self, void *); PyGetSetDef getset[] = {{"mapping", (getter)mapping, nullptr, "dictionary that this view refers to", nullptr}, {nullptr, nullptr, nullptr, nullptr, nullptr}}; } // namespace view //////////////////////////////////////////////////////////////////////////////// // dict::view - common type methods //////////////////////////////////////////////////////////////////////////////// namespace view { PyDoc_STRVAR( isdisjoint_doc, "Return True if the view and the given iterable have a null intersection."); PyObject *richcompare(dict::View *self, PyObject *other, int op); PyObject *repr(PyObject *self); PyObject *isdisjoint(dict::View *self, PyObject *other); } // namespace view //////////////////////////////////////////////////////////////////////////////// // dict::view::keys::Type - sequence protocol //////////////////////////////////////////////////////////////////////////////// namespace view::keys { int contains(dict::View *self, PyObject *key); PySequenceMethods as_sequence = { (lenfunc)length, // lenfunc sq_length; 0, // binaryfunc sq_concat; 0, // ssizeargfunc sq_repeat; 0, // ssizeargfunc sq_item; 0, // void *was_sq_slice; 0, // ssizeobjargproc sq_ass_item; 0, // void *was_sq_ass_slice; (objobjproc)contains, // objobjproc sq_contains; 0, // binaryfunc sq_inplace_concat; 0 // ssizeargfunc sq_inplace_repeat; }; } // namespace view::keys //////////////////////////////////////////////////////////////////////////////// // dict::view::keys::Type - mapping protocol //////////////////////////////////////////////////////////////////////////////// namespace view::keys { PyObject *subscript(dict::View *self, PyObject *item); PyMappingMethods as_mapping = { (lenfunc)length, // lenfunc mp_length; (binaryfunc)subscript, // binaryfunc mp_subscript; 0 // objobjargproc mp_ass_subscript; }; } // namespace view::keys //////////////////////////////////////////////////////////////////////////////// // dict::view::keys::Type - methods //////////////////////////////////////////////////////////////////////////////// namespace view::keys { PyDoc_STRVAR(reversed_keys_doc, "Return a reverse iterator over the dict keys."); PyObject *iterator(dict::View *self); PyObject *reversed(dict::View *self, PyObject *); #if __GNUC__ > 7 && !defined(__clang__) // -Wcast-function-type was added in GCC 8.0, needs to be suppressed here, // because we're casting functions with different number of parameters to // PyCFunction #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-function-type" #endif // __GNUC__ > 7 && !defined(__clang__) PyMethodDef methods[] = { {"isdisjoint", (PyCFunction)isdisjoint, METH_O, isdisjoint_doc}, {"__reversed__", (PyCFunction)reversed, METH_NOARGS, reversed_keys_doc}, {nullptr, nullptr, 0, nullptr}, }; #if __GNUC__ > 7 && !defined(__clang__) #pragma GCC diagnostic pop #endif // __GNUC__ > 7 && !defined(__clang__) } // namespace view::keys //////////////////////////////////////////////////////////////////////////////// // dict::view::keys::Type //////////////////////////////////////////////////////////////////////////////// namespace view::keys { #if PY_VERSION_HEX >= 0x03080000 && PY_VERSION_HEX < 0x03090000 #ifdef __clang__ // The tp_print is marked as deprecated, which makes clang unhappy, 'cause it's // initialized below. Skipping initialization also makes clang unhappy, so we're // disabling the deprecated declarations warning. #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" #endif // __clang__ #endif // PY_VERSION_HEX PyTypeObject Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) // PyObject_VAR_HEAD "Dict_keys", // const char *tp_name; /* For allocation */ sizeof(dict::View), // Py_ssize_t tp_basicsize; 0, // Py_ssize_t tp_itemsize; /* Methods to implement standard operations */ (destructor)dict::view::dealloc, // destructor tp_dealloc; 0, // printfunc tp_print; 0, // getattrfunc tp_getattr; 0, // setattrfunc tp_setattr; 0, // cmpfunc tp_compare; dict::view::repr, // reprfunc tp_repr; /* Method suites for standard classes */ &dict::view::as_number, // PyNumberMethods *tp_as_number; &dict::view::keys::as_sequence, // PySequenceMethods *tp_as_sequence; &dict::view::keys::as_mapping, // PyMappingMethods *tp_as_mapping; /* More standard operations (here for binary compatibility) */ 0, // hashfunc tp_hash; 0, // ternaryfunc tp_call; 0, // reprfunc tp_str; PyObject_GenericGetAttr, // getattrofunc tp_getattro; 0, // setattrofunc tp_setattro; /* Functions to access object as input/output buffer */ 0, // PyBufferProcs *tp_as_buffer; /* Flags to define presence of optional/expanded features */ Py_TPFLAGS_DEFAULT, // long tp_flags; 0, // char *tp_doc; /* Documentation string */ /* Assigned meaning in release 2.0 */ /* call function for all accessible objects */ dict::gc_traverse, // traverseproc tp_traverse; /* delete references to contained objects */ dict::gc_clear, // inquiry tp_clear; /* Assigned meaning in release 2.1 */ /* rich comparisons */ (richcmpfunc)dict::view::richcompare, // richcmpfunc tp_richcompare; /* weak reference enabler */ 0, // long tp_weaklistoffset; /* Added in release 2.2 */ /* Iterators */ (getiterfunc)dict::view::keys::iterator, // getiterfunc tp_iter; 0, // iternextfunc tp_iternext; /* Attribute descriptor and subclassing stuff */ dict::view::keys::methods, // struct PyMethodDef *tp_methods; 0, // struct PyMemberDef *tp_members; dict::view::getset, // struct PyGetSetDef *tp_getset; 0, // struct _typeobject *tp_base; 0, // PyObject *tp_dict; 0, // descrgetfunc tp_descr_get; 0, // descrsetfunc tp_descr_set; 0, // long tp_dictoffset; 0, // initproc tp_init; 0, // allocfunc tp_alloc; 0, // newfunc tp_new; 0, // freefunc tp_free; /* Low-level free-memory routine */ 0, // inquiry tp_is_gc; /* For PyObject_IS_GC */ 0, // PyObject *tp_bases; 0, // PyObject *tp_mro; /* method resolution order */ 0, // PyObject *tp_cache; 0, // PyObject *tp_subclasses; 0, // PyObject *tp_weaklist; 0 // tp_del #if PY_VERSION_HEX >= 0x02060000 , 0 // tp_version_tag #endif #if PY_VERSION_HEX >= 0x03040000 , 0 // tp_finalize #endif #if PY_VERSION_HEX >= 0x03080000 , 0 // tp_vectorcall #if PY_VERSION_HEX < 0x03090000 , 0 // tp_print #endif #endif #if PY_VERSION_HEX >= 0x030C0000 , 0 // tp_watched #endif #if PY_VERSION_HEX >= 0x030D0000 , 0 // tp_versions_used #endif }; #if PY_VERSION_HEX >= 0x03080000 && PY_VERSION_HEX < 0x03090000 #ifdef __clang__ #pragma clang diagnostic pop #endif // __clang__ #endif // PY_VERSION_HEX } // namespace view::keys //////////////////////////////////////////////////////////////////////////////// // dict::view::values::Type - sequence protocol //////////////////////////////////////////////////////////////////////////////// namespace view::values { PySequenceMethods as_sequence = { (lenfunc)length, // lenfunc sq_length; 0, // binaryfunc sq_concat; 0, // ssizeargfunc sq_repeat; 0, // ssizeargfunc sq_item; 0, // void *was_sq_slice; 0, // ssizeobjargproc sq_ass_item; 0, // void *was_sq_ass_slice; 0, // objobjproc sq_contains; 0, // binaryfunc sq_inplace_concat; 0 // ssizeargfunc sq_inplace_repeat; }; } // namespace view::values //////////////////////////////////////////////////////////////////////////////// // dict::view::values::Type - mapping protocol //////////////////////////////////////////////////////////////////////////////// namespace view::values { PyObject *subscript(dict::View *self, PyObject *item); PyMappingMethods as_mapping = { (lenfunc)length, // lenfunc mp_length; (binaryfunc)subscript, // binaryfunc mp_subscript; 0 // objobjargproc mp_ass_subscript; }; } // namespace view::values //////////////////////////////////////////////////////////////////////////////// // dict::view::values::Type - methods //////////////////////////////////////////////////////////////////////////////// namespace view::values { PyDoc_STRVAR(reversed_values_doc, "Return a reverse iterator over the dict values."); PyObject *iterator(dict::View *self); PyObject *reversed(dict::View *self, PyObject *); #if __GNUC__ > 7 && !defined(__clang__) // -Wcast-function-type was added in GCC 8.0, needs to be suppressed here, // because we're casting functions with different number of parameters to // PyCFunction #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-function-type" #endif // __GNUC__ > 7 && !defined(__clang__) PyMethodDef methods[] = { {"__reversed__", (PyCFunction)reversed, METH_NOARGS, reversed_values_doc}, {nullptr, nullptr, 0, nullptr}, }; #if __GNUC__ > 7 && !defined(__clang__) #pragma GCC diagnostic pop #endif // __GNUC__ > 7 && !defined(__clang__) } // namespace view::values //////////////////////////////////////////////////////////////////////////////// // dict::view::values::Type //////////////////////////////////////////////////////////////////////////////// namespace view::values { #if PY_VERSION_HEX >= 0x03080000 && PY_VERSION_HEX < 0x03090000 #ifdef __clang__ // The tp_print is marked as deprecated, which makes clang unhappy, 'cause it's // initialized below. Skipping initialization also makes clang unhappy, so we're // disabling the deprecated declarations warning. #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" #endif // __clang__ #endif // PY_VERSION_HEX PyTypeObject Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) // PyObject_VAR_HEAD "Dict_values", // const char *tp_name; /* For allocation */ sizeof(dict::View), // Py_ssize_t tp_basicsize; 0, // Py_ssize_t tp_itemsize; /* Methods to implement standard operations */ (destructor)dict::view::dealloc, // destructor tp_dealloc; 0, // printfunc tp_print; 0, // getattrfunc tp_getattr; 0, // setattrfunc tp_setattr; 0, // cmpfunc tp_compare; dict::view::repr, // reprfunc tp_repr; /* Method suites for standard classes */ 0, // PyNumberMethods *tp_as_number; &dict::view::values::as_sequence, // PySequenceMethods *tp_as_sequence; &dict::view::values::as_mapping, // PyMappingMethods *tp_as_mapping; /* More standard operations (here for binary compatibility) */ 0, // hashfunc tp_hash; 0, // ternaryfunc tp_call; 0, // reprfunc tp_str; PyObject_GenericGetAttr, // getattrofunc tp_getattro; 0, // setattrofunc tp_setattro; /* Functions to access object as input/output buffer */ 0, // PyBufferProcs *tp_as_buffer; /* Flags to define presence of optional/expanded features */ Py_TPFLAGS_DEFAULT, // long tp_flags; 0, // char *tp_doc; /* Documentation string */ /* Assigned meaning in release 2.0 */ /* call function for all accessible objects */ dict::gc_traverse, // traverseproc tp_traverse; /* delete references to contained objects */ dict::gc_clear, // inquiry tp_clear; /* Assigned meaning in release 2.1 */ /* rich comparisons */ 0, // richcmpfunc tp_richcompare; /* weak reference enabler */ 0, // long tp_weaklistoffset; /* Added in release 2.2 */ /* Iterators */ (getiterfunc)dict::view::values::iterator, // getiterfunc tp_iter; 0, // iternextfunc tp_iternext; /* Attribute descriptor and subclassing stuff */ dict::view::values::methods, // struct PyMethodDef *tp_methods; 0, // struct PyMemberDef *tp_members; dict::view::getset, // struct PyGetSetDef *tp_getset; 0, // struct _typeobject *tp_base; 0, // PyObject *tp_dict; 0, // descrgetfunc tp_descr_get; 0, // descrsetfunc tp_descr_set; 0, // long tp_dictoffset; 0, // initproc tp_init; 0, // allocfunc tp_alloc; 0, // newfunc tp_new; 0, // freefunc tp_free; /* Low-level free-memory routine */ 0, // inquiry tp_is_gc; /* For PyObject_IS_GC */ 0, // PyObject *tp_bases; 0, // PyObject *tp_mro; /* method resolution order */ 0, // PyObject *tp_cache; 0, // PyObject *tp_subclasses; 0, // PyObject *tp_weaklist; 0 // tp_del #if PY_VERSION_HEX >= 0x02060000 , 0 // tp_version_tag #endif #if PY_VERSION_HEX >= 0x03040000 , 0 // tp_finalize #endif #if PY_VERSION_HEX >= 0x03080000 , 0 // tp_vectorcall #if PY_VERSION_HEX < 0x03090000 , 0 // tp_print #endif #endif #if PY_VERSION_HEX >= 0x030C0000 , 0 // tp_watched #endif #if PY_VERSION_HEX >= 0x030D0000 , 0 // tp_versions_used #endif }; #if PY_VERSION_HEX >= 0x03080000 && PY_VERSION_HEX < 0x03090000 #ifdef __clang__ #pragma clang diagnostic pop #endif // __clang__ #endif // PY_VERSION_HEX } // namespace view::values //////////////////////////////////////////////////////////////////////////////// // dict::view::items::Type - sequence protocol //////////////////////////////////////////////////////////////////////////////// namespace view::items { int contains(dict::View *self, PyObject *tuple); PySequenceMethods as_sequence = { (lenfunc)length, // lenfunc sq_length; 0, // binaryfunc sq_concat; 0, // ssizeargfunc sq_repeat; 0, // ssizeargfunc sq_item; 0, // void *was_sq_slice; 0, // ssizeobjargproc sq_ass_item; 0, // void *was_sq_ass_slice; (objobjproc)contains, // objobjproc sq_contains; 0, // binaryfunc sq_inplace_concat; 0 // ssizeargfunc sq_inplace_repeat; }; } // namespace view::items //////////////////////////////////////////////////////////////////////////////// // dict::view::items::Type - mapping protocol //////////////////////////////////////////////////////////////////////////////// namespace view::items { PyObject *subscript(dict::View *self, PyObject *item); PyMappingMethods as_mapping = { (lenfunc)length, // lenfunc mp_length; (binaryfunc)subscript, // binaryfunc mp_subscript; 0 // objobjargproc mp_ass_subscript; }; } // namespace view::items //////////////////////////////////////////////////////////////////////////////// // dict::view::items::Type - methods //////////////////////////////////////////////////////////////////////////////// namespace view::items { PyDoc_STRVAR(reversed_items_doc, "Return a reverse iterator over the dict items."); PyObject *iterator(dict::View *self); PyObject *reversed(dict::View *self, PyObject *); #if __GNUC__ > 7 && !defined(__clang__) // -Wcast-function-type was added in GCC 8.0, needs to be suppressed here, // because we're casting functions with different number of parameters to // PyCFunction #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-function-type" #endif // __GNUC__ > 7 && !defined(__clang__) PyMethodDef methods[] = { {"isdisjoint", (PyCFunction)isdisjoint, METH_O, isdisjoint_doc}, {"__reversed__", (PyCFunction)reversed, METH_NOARGS, reversed_items_doc}, {nullptr, nullptr, 0, nullptr}, }; #if __GNUC__ > 7 && !defined(__clang__) #pragma GCC diagnostic pop #endif // __GNUC__ > 7 && !defined(__clang__) } // namespace view::items //////////////////////////////////////////////////////////////////////////////// // dict::view::items::Type //////////////////////////////////////////////////////////////////////////////// namespace view::items { #if PY_VERSION_HEX >= 0x03080000 && PY_VERSION_HEX < 0x03090000 #ifdef __clang__ // The tp_print is marked as deprecated, which makes clang unhappy, 'cause it's // initialized below. Skipping initialization also makes clang unhappy, so we're // disabling the deprecated declarations warning. #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" #endif // __clang__ #endif // PY_VERSION_HEX PyTypeObject Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) // PyObject_VAR_HEAD "Dict_items", // const char *tp_name; /* For allocation */ sizeof(dict::View), // Py_ssize_t tp_basicsize; 0, // Py_ssize_t tp_itemsize; /* Methods to implement standard operations */ (destructor)dict::view::dealloc, // destructor tp_dealloc; 0, // printfunc tp_print; 0, // getattrfunc tp_getattr; 0, // setattrfunc tp_setattr; 0, // cmpfunc tp_compare; dict::view::repr, // reprfunc tp_repr; /* Method suites for standard classes */ &dict::view::as_number, // PyNumberMethods *tp_as_number; &dict::view::items::as_sequence, // PySequenceMethods *tp_as_sequence; &dict::view::items::as_mapping, // PyMappingMethods *tp_as_mapping; /* More standard operations (here for binary compatibility) */ 0, // hashfunc tp_hash; 0, // ternaryfunc tp_call; 0, // reprfunc tp_str; PyObject_GenericGetAttr, // getattrofunc tp_getattro; 0, // setattrofunc tp_setattro; /* Functions to access object as input/output buffer */ 0, // PyBufferProcs *tp_as_buffer; /* Flags to define presence of optional/expanded features */ Py_TPFLAGS_DEFAULT, // long tp_flags; 0, // char *tp_doc; /* Documentation string */ /* Assigned meaning in release 2.0 */ /* call function for all accessible objects */ dict::gc_traverse, // traverseproc tp_traverse; /* delete references to contained objects */ dict::gc_clear, // inquiry tp_clear; /* Assigned meaning in release 2.1 */ /* rich comparisons */ (richcmpfunc)dict::view::richcompare, // richcmpfunc tp_richcompare; /* weak reference enabler */ 0, // long tp_weaklistoffset; /* Added in release 2.2 */ /* Iterators */ (getiterfunc)dict::view::items::iterator, // getiterfunc tp_iter; 0, // iternextfunc tp_iternext; /* Attribute descriptor and subclassing stuff */ dict::view::items::methods, // struct PyMethodDef *tp_methods; 0, // struct PyMemberDef *tp_members; dict::view::getset, // struct PyGetSetDef *tp_getset; 0, // struct _typeobject *tp_base; 0, // PyObject *tp_dict; 0, // descrgetfunc tp_descr_get; 0, // descrsetfunc tp_descr_set; 0, // long tp_dictoffset; 0, // initproc tp_init; 0, // allocfunc tp_alloc; 0, // newfunc tp_new; 0, // freefunc tp_free; /* Low-level free-memory routine */ 0, // inquiry tp_is_gc; /* For PyObject_IS_GC */ 0, // PyObject *tp_bases; 0, // PyObject *tp_mro; /* method resolution order */ 0, // PyObject *tp_cache; 0, // PyObject *tp_subclasses; 0, // PyObject *tp_weaklist; 0 // tp_del #if PY_VERSION_HEX >= 0x02060000 , 0 // tp_version_tag #endif #if PY_VERSION_HEX >= 0x03040000 , 0 // tp_finalize #endif #if PY_VERSION_HEX >= 0x03080000 , 0 // tp_vectorcall #if PY_VERSION_HEX < 0x03090000 , 0 // tp_print #endif #endif #if PY_VERSION_HEX >= 0x030C0000 , 0 // tp_watched #endif #if PY_VERSION_HEX >= 0x030D0000 , 0 // tp_versions_used #endif }; #if PY_VERSION_HEX >= 0x03080000 && PY_VERSION_HEX < 0x03090000 #ifdef __clang__ #pragma clang diagnostic pop #endif // __clang__ #endif // PY_VERSION_HEX } // namespace view::items //////////////////////////////////////////////////////////////////////////////// // helpers //////////////////////////////////////////////////////////////////////////////// /** * Wraps the key into a tuple and sets it as an argument of the key error. This * protects from using a key which is already a tuple, as it would be unpacked * as the arguments of the exception. */ void set_key_error(PyObject *key) { const py::Release tuple{PyTuple_Pack(1, key)}; if (tuple) { PyErr_SetObject(PyExc_KeyError, tuple.get()); } else { std::string error; Python_context::pystring_to_string(key, &error, true); Python_context::set_python_error(PyExc_KeyError, error); } } /** * Sets Python RuntimeError with the message taken from the given exception. */ void set_exception(const std::exception &exc) { Python_context::set_python_error(PyExc_RuntimeError, exc.what()); } /** * Converts the given value to dictionary, expects a Python dictionary. */ Dictionary_t to_dict(PyObject *value) { Dictionary_t ret; if (dict_check(value)) { ret = *((dict::Object *)value)->dict; } else { ret = py::convert(value).as_map(); } return ret; } /** * Merges key-value pairs from other into self, overwriting existing keys. * Expects a dictionary or a mapping. */ bool merge(dict::Object *self, PyObject *other) { if (PyDict_Check(other)) { const auto dict = to_dict(other); for (const auto &item : *dict) { self->dict->get()->set(item.first, item.second); } } else { const py::Release keys{PyMapping_Keys(other)}; if (!keys) { return false; } const py::Release iter{PyObject_GetIter(keys.get())}; if (!iter) { return false; } Python_context *context = nullptr; while (const py::Release key{PyIter_Next(iter.get())}) { const py::Release value{PyObject_GetItem(other, key.get())}; if (!value) { return false; } py::set_item(*self->dict, key.get(), value.get(), &context); } } return true; } /** * Merges key-value pairs from other into self, overwriting existing keys. * Expects an iterable producing iterable objects of length 2. */ bool merge_iterable(dict::Object *self, PyObject *other) { const py::Release iter{PyObject_GetIter(other)}; if (!iter) { return false; } int index = 0; Python_context *context = nullptr; while (const py::Release item{PyIter_Next(iter.get())}) { const py::Release sequence{PySequence_Fast(item.get(), "")}; if (!sequence) { if (PyErr_ExceptionMatches(PyExc_TypeError)) { PyErr_Format(PyExc_TypeError, "cannot convert dictionary update sequence element #%zd " "to a sequence", index); } break; } const auto length = PySequence_Fast_GET_SIZE(sequence.get()); if (2 != length) { PyErr_Format(PyExc_ValueError, "dictionary update sequence element #%zd has length %zd; 2 " "is required", index, length); break; } // borrowed references const auto key = PySequence_Fast_GET_ITEM(sequence.get(), 0); const auto value = PySequence_Fast_GET_ITEM(sequence.get(), 1); py::set_item(*self->dict, key, value, &context); ++index; } return !PyErr_Occurred(); } /** * Updates self with key-value pairs from other, overwriting existing keys. * Expects a dictionary, a mapping or an iterable producing iterable objects of * length 2. */ bool update(dict::Object *self, PyObject *other) { if (PyDict_Check(other)) { return merge(self, other); } if (PyObject_HasAttrString(other, "keys")) { return merge(self, other); } return merge_iterable(self, other); } /** * Updates self with an optional object held in args, overwriting existing keys. * If keyword arguments are specified, the dictionary is then updated with those * key/value pairs. * * The optional object must be a dictionary, a mapping or an iterable producing * iterable objects of length 2. */ bool update(dict::Object *self, PyObject *args, PyObject *kwds, const char *method) { PyObject *arg = nullptr; if (!PyArg_UnpackTuple(args, method, 0, 1, &arg)) { return false; } if (arg && !update(self, arg)) { return false; } if (kwds) { if (PyArg_ValidateKeywordArguments(kwds)) { return merge(self, kwds); } else { return false; } } return true; } /** * Converts a dictionary view to a set. Returns a new reference. */ PyObject *as_set(dict::View *self) { return PySet_New(reinterpret_cast<PyObject *>(self)); } /** * Converts self to a set and calls the specified method using the other as an * argument. Returns a new reference holding self as a set. */ PyObject *call_set_method(dict::View *self, PyObject *other, const char *method_name) { py::Release result{as_set(self)}; if (!result) { return nullptr; } py::Release method{PyString_FromString(method_name)}; py::Release unused{ PyObject_CallMethodObjArgs(result.get(), method.get(), other, nullptr)}; if (!unused) { return nullptr; } return result.release(); } //////////////////////////////////////////////////////////////////////////////// // dict::Type - number protocol //////////////////////////////////////////////////////////////////////////////// /** * Create a new dictionary with the merged keys and values of d and other, which * must both be dictionaries. The values of other take priority when d and other * share keys. */ PyObject *nb_or(PyObject *self, PyObject *other) { if (!PyDict_Check(self) || !PyDict_Check(other)) { Py_RETURN_NOTIMPLEMENTED; } PyObject *result = nullptr; try { py::Release dict = dict_check(self) ? py::Release{copy(reinterpret_cast<dict::Object *>(self))} : wrap(to_dict(self)); if (update(reinterpret_cast<dict::Object *>(dict.get()), other)) { result = dict.release(); } } catch (const std::exception &exc) { set_exception(exc); } return result; } /** * Update the dictionary d with keys and values from other, which may be either * a mapping or an iterable of key/value pairs. The values of other take * priority when d and other share keys. */ PyObject *inplace_or(PyObject *self, PyObject *other) { assert(dict_check(self)); try { if (update(reinterpret_cast<dict::Object *>(self), other)) { Py_INCREF(self); return self; } } catch (const std::exception &exc) { set_exception(exc); } return nullptr; } //////////////////////////////////////////////////////////////////////////////// // dict::Type - sequence protocol //////////////////////////////////////////////////////////////////////////////// /** * Determine if o contains value. If an item in o is equal to value, return 1, * otherwise return 0. On error, return -1. This is equivalent to the Python * expression value in o. */ int contains(dict::Object *self, PyObject *key) { if (!key) { return -1; } std::string key_to_find; bool found = false; if (Python_context::pystring_to_string(key, &key_to_find)) { found = self->dict->get()->has_key(key_to_find); } return found; } //////////////////////////////////////////////////////////////////////////////// // dict::Type - mapping protocol //////////////////////////////////////////////////////////////////////////////// /** * Returns the number of objects in sequence o on success, and -1 on failure. * This is equivalent to the Python expression len(o). */ Py_ssize_t length(dict::Object *self) { return self->dict->get()->size(); } /** * Return element of o corresponding to the object key or NULL on failure. This * is the equivalent of the Python expression o[key]. */ PyObject *subscript(dict::Object *self, PyObject *key) { std::string key_to_find; if (Python_context::pystring_to_string(key, &key_to_find)) { try { const auto result = self->dict->get()->find(key_to_find); if (result != self->dict->get()->end()) { return py::convert(result->second).release(); } } catch (const std::exception &exc) { set_exception(exc); } } if (!PyErr_Occurred()) { // TODO(pawel): Python dict allows for subclasses to implement __missing__ // method, which handles missing keys (either returns some value or raises // a specific exception). Should we support this? // if there was no other exception, it means that the key was not found set_key_error(key); } return nullptr; } /** * Map the object key to the value v. Raise an exception and return -1 on * failure; return 0 on success. This is the equivalent of the Python statement * o[key] = v. * * v can also be set to NULL to delete an item. */ int assign_subscript(dict::Object *self, PyObject *key, PyObject *value) { std::string key_to_find; if (Python_context::pystring_to_string(key, &key_to_find)) { if (value) { // set the value try { self->dict->get()->set(key_to_find, py::convert(value)); } catch (const std::exception &exc) { set_exception(exc); } } else { // delete the value if (!self->dict->get()->erase(key_to_find)) { set_key_error(key); } } } else { if (value) { Python_context::set_python_error(PyExc_KeyError, "shell.Dict key must be a string"); } else { // this is a delete operation, simply report a missing key set_key_error(key); } } return PyErr_Occurred() ? -1 : 0; } //////////////////////////////////////////////////////////////////////////////// // dict::Type - methods //////////////////////////////////////////////////////////////////////////////// PyObject *dir(dict::Object *self) { py::Release class_methods{ PyObject_Dir(reinterpret_cast<PyObject *>(&dict::Type))}; const auto methods_size = PyObject_Size(class_methods.get()); py::Release keys{dict::keys(self)}; const auto keys_size = dict::length(self); const auto all_members = PyList_New(methods_size + keys_size); PyList_SetSlice(all_members, 0, methods_size, class_methods.get()); PyList_SetSlice(all_members, methods_size, methods_size + keys_size, keys.get()); return all_members; } /** * Return a reverse iterator over the keys of the dictionary. */ PyObject *reversed(dict::Object *self, PyObject *) { return dict::iter::create<Map_reverse_iterator>(self, &dict::iter::keys::rev::Type); } PyObject *size_of(dict::Object *self) { return PyLong_FromSsize_t(_PyObject_SIZE(Py_TYPE(self))); } /** * Remove all items from the dictionary. */ PyObject *clear(dict::Object *self) { self->dict->get()->clear(); Py_RETURN_NONE; } /** * Return a shallow copy of the dictionary. */ PyObject *copy(dict::Object *self) { const auto copy = std::make_shared<shcore::Value::Map_type>(*self->dict->get()); return wrap(copy).release(); } /** * Create a new dictionary with keys from iterable and values set to value. */ PyObject *from_keys(PyTypeObject *type, PyObject *args) { PyObject *iterable = nullptr; PyObject *value = Py_None; if (!PyArg_UnpackTuple(args, "fromkeys", 1, 2, &iterable, &value)) { return nullptr; } py::Release dict{PyObject_CallObject((PyObject *)type, nullptr)}; if (!dict) { return nullptr; } const py::Release iter{PyObject_GetIter(iterable)}; if (!iter) { return nullptr; } const bool is_shell_dict = dict_check(dict.get()); const bool is_dict = PyDict_CheckExact(dict.get()); Python_context *context = nullptr; while (const py::Release key{PyIter_Next(iter.get())}) { int result = 0; if (is_shell_dict) { try { py::set_item(*reinterpret_cast<dict::Object *>(dict.get())->dict, key.get(), value, &context); } catch (const std::exception &exc) { set_exception(exc); result = -1; } } else if (is_dict) { result = PyDict_SetItem(dict.get(), key.get(), value); } else { result = PyObject_SetItem(dict.get(), key.get(), value); } if (result < 0) { return nullptr; } } return dict.release(); } /** * Return the value for key if key is in the dictionary, else default. If * default is not given, it defaults to None, so that this method never raises * a KeyError. */ PyObject *get(dict::Object *self, PyObject *args) { PyObject *key = nullptr; PyObject *default_value = Py_None; if (!PyArg_UnpackTuple(args, "get", 1, 2, &key, &default_value)) { return nullptr; } std::string key_to_find; if (Python_context::pystring_to_string(key, &key_to_find)) { try { const auto result = self->dict->get()->find(key_to_find); if (result != self->dict->get()->end()) { return py::convert(result->second).release(); } } catch (const std::exception &exc) { set_exception(exc); return nullptr; } } Py_INCREF(default_value); return default_value; } /** * Return True if d has a key key, else False. */ PyObject *has_key(dict::Object *self, PyObject *key) { const auto result = contains(self, key); if (result < 0) { return nullptr; } else if (result > 0) { Py_RETURN_TRUE; } else { Py_RETURN_FALSE; } } /** * Return a new view of the dictionary’s items ((key, value) pairs). */ PyObject *items(dict::Object *self) { return dict::view::create(self, &dict::view::items::Type); } /** * Return a new view of the dictionary’s keys. */ PyObject *keys(dict::Object *self) { return dict::view::create(self, &dict::view::keys::Type); } /** * If key is in the dictionary, remove it and return its value, else return * default. If default is not given and key is not in the dictionary, a KeyError * is raised. */ PyObject *pop(dict::Object *self, PyObject *args) { PyObject *key = nullptr; PyObject *default_value = nullptr; if (!PyArg_UnpackTuple(args, "pop", 1, 2, &key, &default_value)) { return nullptr; } std::string key_to_find; if (Python_context::pystring_to_string(key, &key_to_find)) { try { const auto result = self->dict->get()->find(key_to_find); if (result != self->dict->get()->end()) { auto ret = py::convert(result->second); self->dict->get()->erase(result); return ret.release(); } } catch (const std::exception &exc) { set_exception(exc); return nullptr; } } if (default_value) { Py_INCREF(default_value); return default_value; } else { set_key_error(key); return nullptr; } } /** * Remove and return a (key, value) pair from the dictionary. If the dictionary * is empty, calling popitem() raises a KeyError. */ PyObject *pop_item(dict::Object *self) { if (self->dict->get()->empty()) { Python_context::set_python_error(PyExc_KeyError, "popitem(): dictionary is empty"); return nullptr; } py::Release result{PyTuple_New(2)}; const auto item = std::prev(self->dict->get()->end()); try { PyTuple_SET_ITEM(result.get(), 0, PyString_FromString(item->first.c_str())); PyTuple_SET_ITEM(result.get(), 1, py::convert(item->second).release()); } catch (const std::exception &exc) { set_exception(exc); return nullptr; } self->dict->get()->erase(item); return result.release(); } /** * If key is in the dictionary, return its value. If not, insert key with a * value of default and return default. default defaults to None. */ PyObject *set_default(dict::Object *self, PyObject *args) { PyObject *key = nullptr; PyObject *default_value = Py_None; if (!PyArg_UnpackTuple(args, "setdefault", 1, 2, &key, &default_value)) { return nullptr; } std::string key_to_find; if (Python_context::pystring_to_string(key, &key_to_find)) { try { const auto result = self->dict->get()->find(key_to_find); if (result != self->dict->get()->end()) { return py::convert(result->second).release(); } else { self->dict->get()->set(key_to_find, py::convert(default_value)); Py_INCREF(default_value); return default_value; } } catch (const std::exception &exc) { set_exception(exc); } } else { Python_context::set_python_error(PyExc_KeyError, "shell.Dict key must be a string"); } return nullptr; } /** * Update the dictionary with the key/value pairs from other, overwriting * existing keys. Return None. * * update() accepts either another dictionary object or an iterable of key/value * pairs (as tuples or other iterables of length two). If keyword arguments are * specified, the dictionary is then updated with those key/value pairs: * d.update(red=1, blue=2). */ PyObject *update(dict::Object *self, PyObject *args, PyObject *kwds) { try { if (update(self, args, kwds, "update")) { Py_RETURN_NONE; } } catch (const std::exception &exc) { set_exception(exc); } return nullptr; } /** * Return a new view of the dictionary’s values. */ PyObject *values(dict::Object *self) { return dict::view::create(self, &dict::view::values::Type); } //////////////////////////////////////////////////////////////////////////////// // dict::Type //////////////////////////////////////////////////////////////////////////////// void dealloc(dict::Object *self) { delete self->dict; Py_TYPE(self)->tp_free(self); } PyObject *repr(dict::Object *self) { return PyString_FromString(Value(*self->dict).repr().c_str()); } PyObject *str(dict::Object *self) { return PyString_FromString(Value(*self->dict).descr().c_str()); } PyObject *get_attribute(dict::Object *self, PyObject *name) { if (auto object = PyObject_GenericGetAttr(reinterpret_cast<PyObject *>(self), name)) { return object; } // PyObject_GenericGetAttr() has failed and set an exception, if we find the // key we need to clear the exception std::string key_to_find; if (Python_context::pystring_to_string(name, &key_to_find)) { try { const auto result = self->dict->get()->find(key_to_find); if (result != self->dict->get()->end()) { PyErr_Clear(); return py::convert(result->second).release(); } } catch (const std::exception &exc) { set_exception(exc); } } return nullptr; } int gc_traverse(PyObject *, visitproc, void *) { // used to override traverse function from Python's list // empty, as we don't hold any Python objects return 0; } int gc_clear(PyObject *) { // used to override clear function from Python's list // empty, as we don't hold any Python objects return 0; } /** * Dictionaries compare equal if and only if they have the same (key, value) * pairs (regardless of ordering). Order comparisons (‘<’, ‘<=’, ‘>=’, ‘>’) * raise TypeError. */ PyObject *richcompare(dict::Object *self, PyObject *right, int op) { if (!PyDict_Check(right) || !(Py_EQ == op || Py_NE == op)) { Py_RETURN_NOTIMPLEMENTED; } const auto r = to_dict(right); const auto sr = r->size(); const auto l = self->dict->get(); const auto sl = l->size(); const auto result = [op](bool b) { auto res = b == (Py_EQ == op) ? Py_True : Py_False; Py_INCREF(res); return res; }; if (sr != sl) { // lengths differ, dictionaries differ return result(false); } else { static_assert( std::is_same_v<std::map<std::string, Value>, std::remove_pointer_t<decltype(l)>::container_type>, "This algorithm assumes that items in the map are ordered"); for (auto li = l->begin(), ri = r->begin(); li != l->end(); ++li, ++ri) { if (li->first != ri->first || li->second != ri->second) { return result(false); } } } return result(true); } PyObject *iterator(dict::Object *self) { return dict::iter::create<Map_iterator>(self, &dict::iter::keys::Type); } int init(dict::Object *self, PyObject *args, PyObject *kwds) { { // we don't want the underlying dictionary to store anything, so we're // using arguments to initialize an empty dictionary py::Release dict_args{PyTuple_New(0)}; if (PyDict_Type.tp_init((PyObject *)self, dict_args.get(), nullptr) < 0) { return -1; } } try { if (update(self, args, kwds, "Dict")) { return 0; } } catch (const std::exception &exc) { set_exception(exc); } return -1; } PyObject *create(PyTypeObject *type, PyObject *args, PyObject *kwds) { auto self = (dict::Object *)PyType_GenericNew(type, args, kwds); assert(!self->dict); self->dict = new Dictionary_t(make_dict()); return reinterpret_cast<PyObject *>(self); } //////////////////////////////////////////////////////////////////////////////// // dict::Iterator - common helper functions //////////////////////////////////////////////////////////////////////////////// namespace iter { Map_iterator begin(dict::Iterator *self) { return self->o.get<dict::Object *>()->dict->get()->begin(); } Map_reverse_iterator begin(dict::Reverse_iterator *self) { return self->o.get<dict::Object *>()->dict->get()->rbegin(); } Map_iterator end(dict::Iterator *self) { return self->o.get<dict::Object *>()->dict->get()->end(); } Map_reverse_iterator end(dict::Reverse_iterator *self) { return self->o.get<dict::Object *>()->dict->get()->rend(); } template <typename It> PyObject *create(dict::Object *dict, PyTypeObject *type) { auto it = PyObject_New(dict::Iterator_base<It>, type); // placement new into the memory allocated for the iterator, assignment is not // safe, as memory was allocated by malloc() new (&it->o) py::Store{reinterpret_cast<PyObject *>(dict)}; new (&it->next) It{begin(it)}; it->initial_size = dict->dict->get()->size(); return reinterpret_cast<PyObject *>(it); } template <typename It> void dealloc(dict::Iterator_base<It> *self) { self->next.~It(); self->o.~Store(); PyObject_Del(self); } template <typename It> PyObject *next(dict::Iterator_base<It> *self, convert_t convert) { if (!self->o) { return nullptr; } const auto &dict = self->o.template get<dict::Object *>()->dict->get(); if (!dict) { return nullptr; } if (dict->size() != self->initial_size) { Python_context::set_python_error( PyExc_RuntimeError, "dictionary changed size during iteration"); // invalid size makes sure that this state is remembered self->initial_size = static_cast<size_t>(-1); return nullptr; } if (self->next == end(self)) { // we've reached the end, release the reference to the dictionary self->o.reset(); } else { try { const auto key = convert(*self->next); ++self->next; return key; } catch (const std::exception &exc) { set_exception(exc); } } return nullptr; } } // namespace iter //////////////////////////////////////////////////////////////////////////////// // dict::Iterator - common type methods //////////////////////////////////////////////////////////////////////////////// namespace iter { template <typename It> PyObject *length(dict::Iterator_base<It> *self) { Py_ssize_t length = 0; if (self->o) { length = std::distance(self->next, end(self)); assert(length >= 0); } return PyLong_FromSsize_t(length); } template <typename It> PyObject *reduce(dict::Iterator_base<It> *self) { // copy the state, this increases the reference count; when iterator reaches // the end, it will decrease it and release the ownership of the dictionary; // if there's an error during iteration, iterator will not release the // ownership and destructor of `copy` will do that auto copy = *self; // copy the values returned by the iterator to a Python list auto list = PySequence_List(reinterpret_cast<PyObject *>(&copy)); if (!list) { return nullptr; } return Py_BuildValue("N(N)", py::get_builtin("iter").release(), list); } } // namespace iter //////////////////////////////////////////////////////////////////////////////// // dict::iter::keys::Type //////////////////////////////////////////////////////////////////////////////// namespace iter::keys { PyObject *convert(const Map_value_type &pair) { return PyString_FromString(pair.first.c_str()); } template <typename It> PyObject *next(dict::Iterator_base<It> *self) { return dict::iter::next(self, dict::iter::keys::convert); } } // namespace iter::keys //////////////////////////////////////////////////////////////////////////////// // dict::iter::values::Type //////////////////////////////////////////////////////////////////////////////// namespace iter::values { PyObject *convert(const Map_value_type &pair) { return py::convert(pair.second).release(); } template <typename It> PyObject *next(dict::Iterator_base<It> *self) { return dict::iter::next(self, dict::iter::values::convert); } } // namespace iter::values //////////////////////////////////////////////////////////////////////////////// // dict::iter::items::Type //////////////////////////////////////////////////////////////////////////////// namespace iter::items { PyObject *convert(const Map_value_type &pair) { py::Release result{PyTuple_New(2)}; if (!result) { return nullptr; } PyTuple_SET_ITEM(result.get(), 0, dict::iter::keys::convert(pair)); PyTuple_SET_ITEM(result.get(), 1, dict::iter::values::convert(pair)); return result.release(); } template <typename It> PyObject *next(dict::Iterator_base<It> *self) { return dict::iter::next(self, dict::iter::items::convert); } } // namespace iter::items //////////////////////////////////////////////////////////////////////////////// // dict::View - common functions //////////////////////////////////////////////////////////////////////////////// namespace view { PyObject *create(dict::Object *dict, PyTypeObject *type) { auto view = (dict::View *)PyType_GenericAlloc(type, 0); // placement new into the memory allocated for the view, assignment is not // safe, as memory was allocated by malloc() new (&view->o) py::Store{reinterpret_cast<PyObject *>(dict)}; return reinterpret_cast<PyObject *>(view); } void dealloc(dict::View *self) { self->o.~Store(); PyObject_Del(self); } PyObject *subtract(dict::View *self, PyObject *other) { return call_set_method(self, other, "difference_update"); } PyObject *nb_and(dict::View *self, PyObject *other) { return call_set_method(self, other, "intersection_update"); } PyObject *nb_or(dict::View *self, PyObject *other) { return call_set_method(self, other, "update"); } PyObject *nb_xor(dict::View *self, PyObject *other) { return call_set_method(self, other, "symmetric_difference_update"); } Py_ssize_t length(dict::View *self) { Py_ssize_t length = 0; if (self->o) { length = dict::length(self->o.get<dict::Object *>()); } return length; } PyObject *subscript(dict::View *self, PyObject *item, dict::iter::convert_t convert) { if (!self->o) { return nullptr; } // emulate a list - backward compatibility if (PyIndex_Check(item)) { Py_ssize_t i = PyNumber_AsSsize_t(item, PyExc_IndexError); if (-1 == i && PyErr_Occurred()) { return nullptr; } const auto length = dict::length(self->o.get<dict::Object *>()); if (i < 0) { i += length; } if (i < 0 || i >= length) { Python_context::set_python_error(PyExc_IndexError, "list index out of range"); return nullptr; } auto it = self->o.get<dict::Object *>()->dict->get()->begin(); std::advance(it, i); return convert(*(it)); } else { Python_context::set_python_error( PyExc_TypeError, str_format("list indices must be integers or slices, not %.200s", item->ob_type->tp_name)); return nullptr; } } } // namespace view //////////////////////////////////////////////////////////////////////////////// // dict::view - common getters/setters //////////////////////////////////////////////////////////////////////////////// namespace view { PyObject *mapping(dict::View *self, void *) { if (!self->o) { return nullptr; } return PyDictProxy_New(self->o.get()); } } // namespace view //////////////////////////////////////////////////////////////////////////////// // dict::view - common type methods //////////////////////////////////////////////////////////////////////////////// namespace view { PyObject *richcompare(dict::View *self, PyObject *other, int op) { if (!PyAnySet_Check(other) && !PyDictViewSet_Check(other)) { Py_RETURN_NOTIMPLEMENTED; } py::Release left{as_set(self)}; if (!left) { return nullptr; } py::Release right{PySet_New(other)}; if (!right) { return nullptr; } return PyObject_RichCompare(left.get(), right.get(), op); } PyObject *repr(PyObject *self) { auto rc = Py_ReprEnter(self); if (rc != 0) { return rc > 0 ? PyUnicode_FromString("...") : nullptr; } shcore::on_leave_scope repr_leave{[self]() { Py_ReprLeave(self); }}; py::Release sequence{PySequence_List(self)}; if (!sequence) { return nullptr; } return PyUnicode_FromFormat("%s(%R)", Py_TYPE(self)->tp_name, sequence.get()); } PyObject *isdisjoint(dict::View *self, PyObject *other) { py::Release set{as_set(self)}; if (!set) { return nullptr; } py::Release method{PyString_FromString("isdisjoint")}; return PyObject_CallMethodObjArgs(set.get(), method.get(), other, nullptr); } } // namespace view //////////////////////////////////////////////////////////////////////////////// // dict::view::keys::Type - sequence protocol //////////////////////////////////////////////////////////////////////////////// namespace view::keys { int contains(dict::View *self, PyObject *key) { if (!self->o) { return 0; } return dict::contains(self->o.get<dict::Object *>(), key); } } // namespace view::keys //////////////////////////////////////////////////////////////////////////////// // dict::view::keys::Type - mapping protocol //////////////////////////////////////////////////////////////////////////////// namespace view::keys { PyObject *subscript(dict::View *self, PyObject *item) { return dict::view::subscript(self, item, dict::iter::keys::convert); } } // namespace view::keys //////////////////////////////////////////////////////////////////////////////// // dict::view::keys::Type - methods //////////////////////////////////////////////////////////////////////////////// namespace view::keys { PyObject *iterator(dict::View *self) { if (!self->o) { Py_RETURN_NONE; } return dict::iter::create<Map_iterator>(self->o.get<dict::Object *>(), &dict::iter::keys::Type); } PyObject *reversed(dict::View *self, PyObject *) { if (!self->o) { Py_RETURN_NONE; } return dict::iter::create<Map_reverse_iterator>(self->o.get<dict::Object *>(), &dict::iter::keys::rev::Type); } } // namespace view::keys //////////////////////////////////////////////////////////////////////////////// // dict::view::values::Type - mapping protocol //////////////////////////////////////////////////////////////////////////////// namespace view::values { PyObject *subscript(dict::View *self, PyObject *item) { return dict::view::subscript(self, item, dict::iter::values::convert); } } // namespace view::values //////////////////////////////////////////////////////////////////////////////// // dict::view::values::Type - methods //////////////////////////////////////////////////////////////////////////////// namespace view::values { PyObject *iterator(dict::View *self) { if (!self->o) { Py_RETURN_NONE; } return dict::iter::create<Map_iterator>(self->o.get<dict::Object *>(), &dict::iter::values::Type); } PyObject *reversed(dict::View *self, PyObject *) { if (!self->o) { Py_RETURN_NONE; } return dict::iter::create<Map_reverse_iterator>( self->o.get<dict::Object *>(), &dict::iter::values::rev::Type); } } // namespace view::values //////////////////////////////////////////////////////////////////////////////// // dict::view::items::Type - sequence protocol //////////////////////////////////////////////////////////////////////////////// namespace view::items { int contains(dict::View *self, PyObject *tuple) { if (!self->o || !PyTuple_Check(tuple) || 2 != PyTuple_GET_SIZE(tuple)) { return 0; } const auto key = PyTuple_GET_ITEM(tuple, 0); std::string key_to_find; if (Python_context::pystring_to_string(key, &key_to_find)) { try { const auto result = self->o.get<dict::Object *>()->dict->get()->find(key_to_find); if (result != self->o.get<dict::Object *>()->dict->get()->end()) { return py::convert(PyTuple_GET_ITEM(tuple, 1)) == result->second; } } catch (const std::exception &exc) { set_exception(exc); return -1; } } return 0; } } // namespace view::items //////////////////////////////////////////////////////////////////////////////// // dict::view::items::Type - mapping protocol //////////////////////////////////////////////////////////////////////////////// namespace view::items { PyObject *subscript(dict::View *self, PyObject *item) { return dict::view::subscript(self, item, dict::iter::items::convert); } } // namespace view::items //////////////////////////////////////////////////////////////////////////////// // dict::view::items::Type - methods //////////////////////////////////////////////////////////////////////////////// namespace view::items { PyObject *iterator(dict::View *self) { if (!self->o) { Py_RETURN_NONE; } return dict::iter::create<Map_iterator>(self->o.get<dict::Object *>(), &dict::iter::items::Type); } PyObject *reversed(dict::View *self, PyObject *) { if (!self->o) { Py_RETURN_NONE; } return dict::iter::create<Map_reverse_iterator>( self->o.get<dict::Object *>(), &dict::iter::items::rev::Type); } } // namespace view::items } // namespace dict } // namespace void Python_context::init_shell_dict_type() { // this has to be done at runtime dict::Type.tp_base = &PyDict_Type; dict::view::keys::Type.tp_base = &PyDictKeys_Type; dict::view::values::Type.tp_base = &PyDictValues_Type; dict::view::items::Type.tp_base = &PyDictItems_Type; if (PyType_Ready(&dict::Type) < 0) { throw std::runtime_error("Could not initialize Shcore Dict type in Python"); } if (PyType_Ready(&dict::view::keys::Type) < 0) { throw std::runtime_error( "Could not initialize Shcore Dict keys type in Python"); } if (PyType_Ready(&dict::view::values::Type) < 0) { throw std::runtime_error( "Could not initialize Shcore Dict values type in Python"); } if (PyType_Ready(&dict::view::items::Type) < 0) { throw std::runtime_error( "Could not initialize Shcore Dict items type in Python"); } Py_INCREF(&dict::Type); auto module = get_shell_python_support_module(); PyModule_AddObject(module.get(), "Dict", reinterpret_cast<PyObject *>(&dict::Type)); _shell_dict_class = py::Store{PyDict_GetItemString(PyModule_GetDict(module.get()), "Dict")}; } bool dict_check(PyObject *value) { return Py_TYPE(value) == &dict::Type; } py::Release wrap(const Dictionary_t &map) { auto wrapper = (dict::Object *)PyType_GenericAlloc(&dict::Type, 0); assert(!wrapper->dict); wrapper->dict = new Dictionary_t(map); return py::Release{reinterpret_cast<PyObject *>(wrapper)}; } bool unwrap(PyObject *value, Dictionary_t *ret_object) { if (dict_check(value)) { const auto dict = ((dict::Object *)value)->dict; assert(dict); *ret_object = *dict; return true; } return false; } } // namespace shcore