├── .gitignore ├── CMakeLists.txt ├── README.md ├── external_googletest.cmake ├── src └── pie │ ├── error.cc │ ├── error.h │ ├── interpreter.cc │ ├── iterator.cc │ ├── iterator.h │ ├── object.cc │ ├── object.h │ ├── object.inl │ ├── parse.h │ ├── parse.inl │ └── pie.h └── test ├── test_error.cc ├── test_object.cc └── test_parse.cc /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | .*.swp 3 | .DS_Store 4 | 5 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0) 2 | 3 | set(CMAKE_C_COMPILER "gcc-7") 4 | set(CMAKE_CXX_COMPILER "g++-7") 5 | 6 | set(CMAKE_CXX_STANDARD 17) 7 | set(CMAKE_CXX_STANDARD_REQUIRED YES) 8 | set(CMAKE_CXX_EXTENSIONS NO) 9 | 10 | project(Pie) 11 | 12 | set(CMAKE_BUILD_TYPE Release) 13 | set(CMAKE_CXX_FLAGS "-Wall -Werror") 14 | set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g") 15 | set(CMAKE_CXX_FLAGS_RELEASE "-O3") 16 | 17 | find_package(PythonLibs 3 REQUIRED) 18 | 19 | set(PIE_TEST_DIR "test") 20 | set(PIE_SOURCE_DIR "src") 21 | 22 | set(PIE_HEADER_FILES 23 | "${PIE_SOURCE_DIR}/pie/parse.inl" 24 | "${PIE_SOURCE_DIR}/pie/parse.h" 25 | "${PIE_SOURCE_DIR}/pie/object.h" 26 | "${PIE_SOURCE_DIR}/pie/object.inl" 27 | "${PIE_SOURCE_DIR}/pie/error.h" 28 | "${PIE_SOURCE_DIR}/pie/iterator.h" 29 | ) 30 | 31 | set(PIE_SOURCE_FILES 32 | "${PIE_SOURCE_DIR}/pie/object.cc" 33 | "${PIE_SOURCE_DIR}/pie/error.cc" 34 | "${PIE_SOURCE_DIR}/pie/iterator.cc" 35 | "${PIE_SOURCE_DIR}/pie/interpreter.cc" 36 | ) 37 | 38 | include_directories( 39 | ${PIE_SOURCE_DIR} 40 | ${PYTHON_INCLUDE_DIRS} 41 | ) 42 | 43 | add_library(pie SHARED ${PIE_SOURCE_FILES}) 44 | target_link_libraries(pie ${PYTHON_LIBRARY}) 45 | 46 | install( 47 | TARGETS pie 48 | DESTINATION "lib" 49 | ) 50 | install( 51 | FILES ${PIE_HEADER_FILES} 52 | DESTINATION "include" 53 | ) 54 | 55 | option(BUILD_TESTS "Build the test files as well" OFF) 56 | 57 | if(${BUILD_TESTS}) 58 | set(PIE_TEST_FILES 59 | "${PIE_TEST_DIR}/test_parse.cc" 60 | "${PIE_TEST_DIR}/test_object.cc" 61 | "${PIE_TEST_DIR}/test_error.cc" 62 | ) 63 | 64 | include(external_googletest.cmake) 65 | 66 | include_directories(${GTEST_INCLUDE_DIRS}) 67 | 68 | add_executable(tests ${PIE_TEST_FILES}) 69 | target_link_libraries(tests pie ${GTEST_BOTH_LIBRARIES}) 70 | 71 | enable_testing() 72 | add_test(NAME tests COMMAND tests) 73 | endif() 74 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pie # 2 | 3 | Welcome to **Pie**, a wrapper for CPython's C API in C++. 4 | 5 | CPython's C API is really nice when writing C, but when writing C++ it leaves 6 | much to be desired. The API passes around `PyObject*` objects between functions 7 | in an object oriented fashion. **Pie** wraps Python's `PyObject*` with its own 8 | `object` class, it leverages C++'s operator overloading to call CPython API 9 | functions on the `PyObject*` and it automatically keeps track of the object's 10 | reference count using constructors and destructors. **Pie** can also parse 11 | certain C++ objects into Python objects, for example, vectors turn into lists, 12 | maps turn into dictionaries, etc. 13 | 14 | ## Features ## 15 | 16 | 1. An idiomatic C++ wrapper around `PyObject*` objects, using classes and 17 | operator overloading. 18 | 1. Automatic incrementing and decrementing of `PyObject*` reference counts. 19 | 1. Parsing of C++ types into Python objects. 20 | 1. Wrapping of Python exceptions into C++ exceptions. 21 | 22 | ## Usage ## 23 | 24 | See the [example](https://github.com/ronmrdechai/Pie#example) for more 25 | information on how to use **Pie**. Just link with `libpie` and you should be 26 | ready to go. 27 | 28 | > _Note:_ **Pie** is not a replacement for the CPython API, it is intended to 29 | > compliment it and make it easier to use in C++. 30 | 31 | ## The Parser ## 32 | 33 | **Pie's** C++ type parser is very simple, it handles the following cases: 34 | 35 | 1. C integers become Python integers. 36 | 1. C strings (`const char *`) become Python strings. 37 | 1. C++ strings (`std::string`) become Python strings. 38 | 1. Container types with an `std::pair` value type become Python dictionaries. 39 | 1. Container types without an `std::pair` value type become Python lists. 40 | 41 | ## Python Version ## 42 | 43 | **Pie** currently works on with Python 3 only. There are plans to port **Pie** 44 | to Python 2 in the future. 45 | 46 | ## Building ## 47 | 48 | **Pie** requires the following: 49 | 50 | 1. Compiler with C++17 (gcc-7 should work fine). 51 | 1. Python 3 and development headers. 52 | 1. CMake version 3+ 53 | 54 | To build issue the following: 55 | 56 | ```bash 57 | git clone git@github.com:ronmrdechai/Pie.git 58 | mkdir build 59 | cd build 60 | cmake .. 61 | make 62 | sudo make install 63 | ``` 64 | 65 | Building the tests clones and builds 66 | [googletest](https://github.com/google/googletest) which is used to run the 67 | tests. To build the tests pass an additional `-DBUILD_TESTS=ON` to the `cmake` 68 | command: 69 | 70 | ```bash 71 | cmake .. -DBUILD_TESTS=ON 72 | make 73 | make test 74 | ``` 75 | 76 | ## Platforms ## 77 | 78 | **Pie** is developed and tested on macOS but should work on any platform with 79 | Python and C++17. 80 | 81 | ## Example ## 82 | 83 | See [tests](test/test_object.cc) for more detailed examples. 84 | 85 | ```c++ 86 | #include 87 | 88 | #include 89 | 90 | int main() { 91 | pie::object os = PyImport_ImportModule("os"); 92 | pie::object os_environ = pie::getattr(os, "environ"); 93 | 94 | std::cout << "The following directories are in the PATH:" << std::endl; 95 | for (auto dir: getattr(os_environ["PATH"], "split")(":")) { 96 | std::cout << dir << std::endl; 97 | } 98 | 99 | pie::object zero = 0; 100 | pie::object one = 1; 101 | try { 102 | one / zero; 103 | } catch (pie::error& e) { 104 | std::cout << "Caught Python exception:" << std::endl; 105 | std::cout << e.what() << std::endl; 106 | } 107 | return 0; 108 | } 109 | ``` 110 | 111 | This is equivalent to the following Python code: 112 | 113 | ```python 114 | import os 115 | 116 | print("The following directories are in the PATH:") 117 | for dir in os.environ["PATH"].split(":"): 118 | print(dir) 119 | 120 | try: 121 | 1 / 0 122 | except BaseException as e: 123 | print("Caught Python exception:") 124 | print(e.__class__.__name__ + ": " + str(e)) 125 | ``` 126 | 127 | ## Why not Boost.Python, PyBind11 or SWIG? ## 128 | 129 | Boost.Python, PyBind11 and SWIG are wonderful packages, but they different from 130 | **Pie**. These packages wrap C++ classes and functions into Python classes and 131 | functions, allowing you call them from Python. Some of them have support for 132 | calling Python code from C++, but this functionality has been added as an 133 | afterthought. 134 | 135 | **Pie** has been built from the ground up to wrap Python objects into C++ 136 | objects, allowing you to easily call Python from your C++ code, and embed it in 137 | your applications. 138 | 139 | ## To Do ## 140 | 141 | - [ ] Provide a complete documentation for **Pie** 142 | - [ ] Wrap Python builtin types with `pie::objects`. 143 | -------------------------------------------------------------------------------- /external_googletest.cmake: -------------------------------------------------------------------------------- 1 | include(externalproject) 2 | ExternalProject_Add(googletest 3 | GIT_REPOSITORY "https://github.com/google/googletest.git" 4 | UPDATE_COMMAND "" 5 | INSTALL_COMMAND "" 6 | CMAKE_ARGS -DCMAKE_C_COMPILER=gcc-7 7 | -DCMAKE_CXX_COMPILER=g++-7 8 | LOG_DOWNLOAD ON 9 | LOG_CONFIGURE ON 10 | LOG_BUILD ON 11 | ) 12 | 13 | ExternalProject_Get_Property(googletest source_dir) 14 | set(GTEST_INCLUDE_DIRS ${source_dir}/googletest/include) 15 | set(GMOCK_INCLUDE_DIRS ${source_dir}/googlemock/include) 16 | 17 | ExternalProject_Get_Property(googletest binary_dir) 18 | set(GTEST_LIBRARY_PATH ${binary_dir}/googlemock/gtest/${CMAKE_FIND_LIBRARY_PREFIXES}gtest.a) 19 | set(GTEST_LIBRARY gtest) 20 | add_library(${GTEST_LIBRARY} UNKNOWN IMPORTED) 21 | set_target_properties(${GTEST_LIBRARY} PROPERTIES 22 | IMPORTED_LOCATION ${GTEST_LIBRARY_PATH}) 23 | add_dependencies(${GTEST_LIBRARY} googletest) 24 | 25 | set(GTEST_MAIN_LIBRARY_PATH ${binary_dir}/googlemock/gtest/${CMAKE_FIND_LIBRARY_PREFIXES}gtest_main.a) 26 | set(GTEST_MAIN_LIBRARY gtest_main) 27 | add_library(${GTEST_MAIN_LIBRARY} UNKNOWN IMPORTED) 28 | set_target_properties(${GTEST_MAIN_LIBRARY} PROPERTIES 29 | IMPORTED_LOCATION ${GTEST_MAIN_LIBRARY_PATH}) 30 | add_dependencies(${GTEST_MAIN_LIBRARY} googletest) 31 | set(GTEST_BOTH_LIBRARIES ${GTEST_LIBRARY} ${GTEST_MAIN_LIBRARY}) 32 | 33 | set(GMOCK_LIBRARY_PATH ${binary_dir}/googlemock/${CMAKE_FIND_LIBRARY_PREFIXES}gmock.a) 34 | set(GMOCK_LIBRARY gmock) 35 | add_library(${GMOCK_LIBRARY} UNKNOWN IMPORTED) 36 | set_target_properties(${GMOCK_LIBRARY} PROPERTIES 37 | IMPORTED_LOCATION ${GMOCK_LIBRARY_PATH}) 38 | add_dependencies(${GMOCK_LIBRARY} googletest) 39 | 40 | set(GMOCK_MAIN_LIBRARY_PATH ${binary_dir}/googlemock/${CMAKE_FIND_LIBRARY_PREFIXES}gmock_main.a) 41 | set(GMOCK_MAIN_LIBRARY gmock_main) 42 | add_library(${GMOCK_MAIN_LIBRARY} UNKNOWN IMPORTED) 43 | set_target_properties(${GMOCK_MAIN_LIBRARY} PROPERTIES 44 | IMPORTED_LOCATION ${GMOCK_MAIN_LIBRARY_PATH}) 45 | add_dependencies(${GMOCK_MAIN_LIBRARY} ${GTEST_LIBRARY}) 46 | set(GMOCK_BOTH_LIBRARIES ${GMOCK_LIBRARY} ${GMOCK_MAIN_LIBRARY}) 47 | -------------------------------------------------------------------------------- /src/pie/error.cc: -------------------------------------------------------------------------------- 1 | #include "error.h" 2 | 3 | #include 4 | #include 5 | 6 | namespace pie { 7 | 8 | void error::throw_exception() { 9 | PyObject* type; 10 | PyObject* err; 11 | PyObject* traceback; 12 | PyErr_Fetch(&type, &err, &traceback); 13 | 14 | object type_o = type; 15 | object err_o = err; 16 | object traceback_o = traceback; 17 | 18 | std::ostringstream ss; 19 | ss << ((PyTypeObject*)type)->tp_name << ": " << err_o; 20 | 21 | throw error(type_o, err_o, traceback_o, ss.str()); 22 | } 23 | 24 | void error::conditional_throw() { 25 | PyObject* occurred = PyErr_Occurred(); 26 | if (occurred) { 27 | throw_exception(); 28 | } 29 | } 30 | 31 | const object& error::type() const { 32 | return m_type; 33 | } 34 | 35 | const object& error::traceback() const { 36 | return m_traceback; 37 | } 38 | 39 | error::error(const object& type, 40 | const object& error, 41 | const object& traceback, 42 | const std::string& what_arg) : 43 | object(error), 44 | std::runtime_error(what_arg), 45 | m_type(type), 46 | m_traceback(traceback) { } 47 | 48 | } // namespace pie 49 | -------------------------------------------------------------------------------- /src/pie/error.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "object.h" 6 | 7 | namespace pie { 8 | 9 | class error : public object, public std::runtime_error { 10 | public: 11 | static void throw_exception(); 12 | static void conditional_throw(); 13 | 14 | const object& type() const; 15 | const object& traceback() const; 16 | 17 | private: 18 | error(const object& type, 19 | const object& error, 20 | const object& traceback, 21 | const std::string& what_arg); 22 | object m_type; 23 | object m_traceback; 24 | }; 25 | 26 | } // namespace pie 27 | -------------------------------------------------------------------------------- /src/pie/interpreter.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | struct Interpreter { 4 | Interpreter() : should_finalize(false) { 5 | if (!Py_IsInitialized()) { 6 | Py_Initialize(); 7 | should_finalize = true; 8 | } 9 | } 10 | 11 | ~Interpreter() { 12 | if (should_finalize) { 13 | Py_Finalize(); 14 | } 15 | } 16 | 17 | bool should_finalize; 18 | }; 19 | 20 | Interpreter interpreter; 21 | -------------------------------------------------------------------------------- /src/pie/iterator.cc: -------------------------------------------------------------------------------- 1 | #include "iterator.h" 2 | 3 | namespace pie { 4 | 5 | iterator::iterator(object o, ssize_t i) : o(o), i(i) { } 6 | iterator::iterator(const iterator& other) : 7 | iterator(other.o, other.i) { } 8 | 9 | iterator& iterator::operator++() { 10 | ++i; 11 | return *this; 12 | } 13 | 14 | iterator iterator::operator++(int) { 15 | iterator temp(*this); 16 | operator++(); 17 | return temp; 18 | } 19 | 20 | bool iterator::operator==(const iterator& other) { 21 | return (o.get() == other.o.get()) && (i == other.i); 22 | } 23 | 24 | bool iterator::operator!=(const iterator& other) { 25 | return !((*this) == other); 26 | } 27 | 28 | object iterator::operator*() { 29 | return PySequence_GetItem(o.get(), i); 30 | } 31 | 32 | } // namespace pie 33 | -------------------------------------------------------------------------------- /src/pie/iterator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "object.h" 6 | 7 | namespace pie { 8 | 9 | struct iterator : std::iterator { 10 | iterator(object o, ssize_t i); 11 | iterator(const iterator& other); 12 | 13 | iterator& operator++(); 14 | iterator operator++(int); 15 | 16 | bool operator==(const iterator& other); 17 | bool operator!=(const iterator& other); 18 | 19 | object operator*(); 20 | 21 | object o; 22 | ssize_t i; 23 | }; 24 | 25 | } // namespace pie 26 | 27 | -------------------------------------------------------------------------------- /src/pie/object.cc: -------------------------------------------------------------------------------- 1 | #include "object.h" 2 | #include "error.h" 3 | 4 | #include 5 | 6 | namespace pie { 7 | 8 | object::object() : m_obj(nullptr) { } 9 | 10 | object::~object() { 11 | if (m_obj) { 12 | Py_DECREF(m_obj); 13 | } 14 | } 15 | 16 | object::object(PyObject* o) : m_obj(o) { 17 | error::conditional_throw(); 18 | } 19 | 20 | object::object(object& other) : object(static_cast(other)) { } 21 | 22 | object::object(object&& other) : m_obj(other.m_obj) { 23 | other.m_obj = nullptr; 24 | } 25 | 26 | object::object(const object& other) : m_obj(other.m_obj) { 27 | if (m_obj) { 28 | Py_INCREF(m_obj); 29 | } 30 | } 31 | 32 | object::object(proxy other) : object(PyObject_GetItem(other.o.m_obj, 33 | other.i.m_obj)) { } 34 | 35 | object& object::operator=(object other) { 36 | std::swap(m_obj, other.m_obj); 37 | return *this; 38 | } 39 | 40 | object object::repr() const { 41 | return PyObject_Repr(m_obj); 42 | } 43 | 44 | object object::str() const { 45 | return PyObject_Str(m_obj); 46 | } 47 | 48 | object object::bytes() const { 49 | return PyObject_Bytes(m_obj); 50 | } 51 | 52 | object object::operator< (const object& other) const { 53 | return PyObject_RichCompare(m_obj, other.m_obj, Py_LT); 54 | } 55 | 56 | object object::operator<=(const object& other) const { 57 | return PyObject_RichCompare(m_obj, other.m_obj, Py_LE); 58 | } 59 | 60 | object object::operator==(const object& other) const { 61 | return PyObject_RichCompare(m_obj, other.m_obj, Py_EQ); 62 | } 63 | 64 | object object::operator!=(const object& other) const { 65 | return PyObject_RichCompare(m_obj, other.m_obj, Py_NE); 66 | } 67 | 68 | object object::operator> (const object& other) const { 69 | return PyObject_RichCompare(m_obj, other.m_obj, Py_GT); 70 | } 71 | 72 | object object::operator>=(const object& other) const { 73 | return PyObject_RichCompare(m_obj, other.m_obj, Py_GE); 74 | } 75 | 76 | ssize_t object::hash() const { 77 | return PyObject_Hash(m_obj); 78 | } 79 | 80 | object::operator bool() const { 81 | return PyObject_IsTrue(m_obj); 82 | } 83 | 84 | ssize_t object::length() const { 85 | return PyObject_Length(m_obj); 86 | } 87 | 88 | const object::proxy object::operator[](const object& item) const { 89 | return proxy(*this, item); 90 | } 91 | 92 | object::proxy object::operator[](const object& item) { 93 | return proxy(*this, item); 94 | } 95 | 96 | object object::operator+ (const object& other) const { 97 | return PyNumber_Add(m_obj, other.m_obj); 98 | } 99 | 100 | object& object::operator+=(const object& other) { 101 | *this = PyNumber_InPlaceAdd(m_obj, other.m_obj); 102 | return *this; 103 | } 104 | 105 | object object::operator- (const object& other) const { 106 | return PyNumber_Subtract(m_obj, other.m_obj); 107 | } 108 | 109 | object& object::operator-=(const object& other) { 110 | *this = PyNumber_InPlaceSubtract(m_obj, other.m_obj); 111 | return *this; 112 | } 113 | 114 | object object::operator* (const object& other) const { 115 | return PyNumber_Multiply(m_obj, other.m_obj); 116 | } 117 | 118 | object& object::operator*=(const object& other) { 119 | *this = PyNumber_InPlaceMultiply(m_obj, other.m_obj); 120 | return *this; 121 | } 122 | 123 | object object::operator/ (const object& other) const { 124 | return PyNumber_TrueDivide(m_obj, other.m_obj); 125 | } 126 | 127 | object& object::operator/=(const object& other) { 128 | *this = PyNumber_InPlaceTrueDivide(m_obj, other.m_obj); 129 | return *this; 130 | } 131 | 132 | object object::operator% (const object& other) const { 133 | return PyNumber_Remainder(m_obj, other.m_obj); 134 | } 135 | 136 | object& object::operator%=(const object& other) { 137 | *this = PyNumber_InPlaceRemainder(m_obj, other.m_obj); 138 | return *this; 139 | } 140 | 141 | object object::operator<< (const object& other) const { 142 | return PyNumber_Lshift(m_obj, other.m_obj); 143 | } 144 | 145 | object& object::operator<<=(const object& other) { 146 | *this = PyNumber_InPlaceLshift(m_obj, other.m_obj); 147 | return *this; 148 | } 149 | 150 | object object::operator>> (const object& other) const { 151 | return PyNumber_Rshift(m_obj, other.m_obj); 152 | } 153 | 154 | object& object::operator>>=(const object& other) { 155 | *this = PyNumber_InPlaceRshift(m_obj, other.m_obj); 156 | return *this; 157 | } 158 | 159 | object object::operator& (const object& other) const { 160 | return PyNumber_And(m_obj, other.m_obj); 161 | } 162 | 163 | object& object::operator&=(const object& other) { 164 | *this = PyNumber_InPlaceAnd(m_obj, other.m_obj); 165 | return *this; 166 | } 167 | 168 | object object::operator^ (const object& other) const { 169 | return PyNumber_Xor(m_obj, other.m_obj); 170 | } 171 | 172 | object& object::operator^=(const object& other) { 173 | *this = PyNumber_InPlaceXor(m_obj, other.m_obj); 174 | return *this; 175 | } 176 | 177 | object object::operator| (const object& other) const { 178 | return PyNumber_Or(m_obj, other.m_obj); 179 | } 180 | 181 | object& object::operator|=(const object& other) { 182 | *this = PyNumber_InPlaceOr(m_obj, other.m_obj); 183 | return *this; 184 | } 185 | 186 | object object::operator-() const { 187 | return PyNumber_Negative(m_obj); 188 | } 189 | 190 | object object::operator+() const { 191 | return PyNumber_Positive(m_obj); 192 | } 193 | 194 | object object::operator~() const { 195 | return PyNumber_Invert(m_obj); 196 | } 197 | 198 | iterator object::begin() { 199 | if (PySequence_Check(m_obj)) { 200 | return iterator(*this, 0); 201 | } 202 | throw std::runtime_error("object is not a sequence"); 203 | } 204 | 205 | iterator object::end() { 206 | if (PySequence_Check(m_obj)) { 207 | ssize_t size = PySequence_Length(m_obj); 208 | return iterator(*this, size); 209 | } 210 | throw std::runtime_error("object is not a sequence"); 211 | } 212 | 213 | PyObject* object::get() const { 214 | return m_obj; 215 | } 216 | 217 | PyObject* object::operator->() const { 218 | return get(); 219 | } 220 | 221 | object::proxy::proxy(const object& o, const object& i) : o(o), i(i) { } 222 | 223 | object::proxy& object::proxy::operator=(const object& item) { 224 | PyObject_SetItem(o.m_obj, i.m_obj, item.m_obj); 225 | return *this; 226 | } 227 | 228 | object getattr(const object& o, const object& attr) { 229 | return PyObject_GetAttr(o.get(), attr.get()); 230 | } 231 | 232 | object setattr(object& o, const object& attr, const object& value) { 233 | return PyObject_SetAttr(o.get(), attr.get(), value.get()); 234 | } 235 | 236 | bool hasattr(const object& o, const object& attr) { 237 | return PyObject_HasAttr(o.get(), attr.get()); 238 | } 239 | 240 | object pow(const object& b, const object& e, const object& o) { 241 | return PyNumber_Power(b.get(), e.get(), o.get()); 242 | } 243 | 244 | object abs(const object& o) { 245 | return PyNumber_Absolute(o.get()); 246 | } 247 | 248 | iterator begin(object&& o) { 249 | return o.begin(); 250 | } 251 | 252 | iterator end(object&& o) { 253 | return o.end(); 254 | } 255 | 256 | std::ostream& operator<<(std::ostream& os, const object& o) { 257 | object str = o.str(); 258 | object ascii = PyUnicode_AsASCIIString(str.get()); 259 | const char* data = PyBytes_AsString(ascii.get()); 260 | ssize_t size = PyBytes_Size(ascii.get()); 261 | os << std::string_view(data, size); 262 | return os; 263 | } 264 | 265 | } // namespace pie 266 | -------------------------------------------------------------------------------- /src/pie/object.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace pie { 7 | 8 | struct iterator; 9 | 10 | class object { 11 | struct proxy { 12 | proxy(const object& o, const object& i); 13 | proxy& operator=(const object& item); 14 | 15 | const object& o; 16 | const object& i; 17 | }; 18 | 19 | public: 20 | object(); 21 | ~object(); 22 | 23 | object(PyObject* o); 24 | template 25 | object(Args&&... args); 26 | 27 | object(object& other); 28 | object(object&& other); 29 | object(const object& other); 30 | 31 | object(proxy other); 32 | 33 | object& operator=(object other); 34 | 35 | // __repr__ 36 | object repr() const; 37 | // __str__ 38 | object str() const; 39 | // __bytes__ 40 | object bytes() const; 41 | // __lt__ 42 | object operator< (const object& other) const; 43 | // __le__ 44 | object operator<=(const object& other) const; 45 | // __eq__ 46 | object operator==(const object& other) const; 47 | // __ne__ 48 | object operator!=(const object& other) const; 49 | // __gt__ 50 | object operator> (const object& other) const; 51 | // __ge__ 52 | object operator>=(const object& other) const; 53 | // __hash__ 54 | ssize_t hash() const; 55 | // __bool__ 56 | explicit operator bool() const; 57 | 58 | // __call__ 59 | template 60 | object operator()(Args&&... args) const; 61 | 62 | // __len__ 63 | ssize_t length() const; 64 | // __getitem__ 65 | const proxy operator[](const object& item) const; 66 | // __setitem__ 67 | proxy operator[](const object& item); 68 | 69 | // __add__ 70 | object operator+ (const object& other) const; 71 | // __iadd__ 72 | object& operator+=(const object& other); 73 | // __sub__ 74 | object operator- (const object& other) const; 75 | // __isub__ 76 | object& operator-=(const object& other); 77 | // __mul__ 78 | object operator* (const object& other) const; 79 | // __imul__ 80 | object& operator*=(const object& other); 81 | // __truediv__ 82 | object operator/ (const object& other) const; 83 | // __itruediv__ 84 | object& operator/=(const object& other); 85 | // __mod__ 86 | object operator% (const object& other) const; 87 | // __imod__ 88 | object& operator%=(const object& other); 89 | 90 | // __lshift__ 91 | object operator<< (const object& other) const; 92 | // __ilshift__ 93 | object& operator<<=(const object& other); 94 | // __rshift__ 95 | object operator>> (const object& other) const; 96 | // __irshift__ 97 | object& operator>>=(const object& other); 98 | // __and__ 99 | object operator& (const object& other) const; 100 | // __iand__ 101 | object& operator&=(const object& other); 102 | // __xor__ 103 | object operator^ (const object& other) const; 104 | // __ixor__ 105 | object& operator^=(const object& other); 106 | // __or__ 107 | object operator| (const object& other) const; 108 | // __ior__ 109 | object& operator|=(const object& other); 110 | 111 | // __neg__ 112 | object operator-() const; 113 | // __pos__ 114 | object operator+() const; 115 | // __invert__ 116 | object operator~() const; 117 | 118 | iterator begin(); 119 | iterator end(); 120 | 121 | // Non-Python methods 122 | 123 | /// Access the underlying pointer 124 | PyObject* get() const; 125 | /// Access the underlying pointer 126 | PyObject* operator->() const; 127 | 128 | private: 129 | PyObject* m_obj; 130 | }; 131 | 132 | iterator begin(object&& o); 133 | iterator end(object&& o); 134 | 135 | // __getattr__ 136 | object getattr(const object& o, const object& attr); 137 | // __setattr__ 138 | object setattr(object& o, const object& attr, const object& value); 139 | // __hasattr__ 140 | bool hasattr(const object& o, const object& attr); 141 | 142 | // print 143 | std::ostream& operator<<(std::ostream& os, const object& o); 144 | 145 | object pow(const object& b, const object& e, const object& o = Py_None); 146 | object abs(const object& o); 147 | 148 | } // namespace pie 149 | 150 | #include "iterator.h" 151 | #include "object.inl" 152 | -------------------------------------------------------------------------------- /src/pie/object.inl: -------------------------------------------------------------------------------- 1 | #include "parse.h" 2 | 3 | namespace pie { 4 | 5 | template 6 | object::object(Args&&... args) : 7 | object(parse_object(std::forward(args)...)) { } 8 | 9 | template 10 | object object::operator()(Args&&... args) const { 11 | object args_tuple = parse_object( 12 | std::make_tuple(std::forward(args)...)); 13 | return PyObject_CallObject(m_obj, args_tuple.get()); 14 | } 15 | 16 | } // namespace pie 17 | -------------------------------------------------------------------------------- /src/pie/parse.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace pie { 6 | 7 | template 8 | PyObject* parse_object(T&& t); 9 | 10 | template 11 | PyObject* parse_object(T&& t, Ts&&... ts); 12 | 13 | } // namespace pie 14 | 15 | #include "parse.inl" 16 | -------------------------------------------------------------------------------- /src/pie/parse.inl: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace pie { 5 | 6 | namespace detail { 7 | namespace traits { 8 | 9 | template 10 | struct is_pair : std::false_type { }; 11 | 12 | template 13 | struct is_pair> : std::true_type { }; 14 | 15 | template 16 | constexpr bool is_pair_v = is_pair::value; 17 | 18 | template 19 | struct is_mapping : std::false_type { }; 20 | 21 | template 22 | struct is_mapping>> : 24 | std::true_type { }; 25 | 26 | template 27 | constexpr bool is_mapping_v = is_mapping::value; 28 | 29 | template 30 | struct is_sequence : std::false_type { }; 31 | 32 | template 33 | struct is_sequence>> : 35 | std::true_type { }; 36 | 37 | template 38 | constexpr bool is_sequence_v = is_sequence::value; 39 | 40 | template 41 | struct is_string : std::false_type { }; 42 | 43 | template <> 44 | struct is_string : std::true_type { }; 45 | 46 | template 47 | constexpr bool is_string_v = is_string::value; 48 | 49 | template 50 | struct is_tuple : std::false_type { }; 51 | 52 | template 53 | struct is_tuple> : std::true_type { }; 54 | 55 | template 56 | constexpr bool is_tuple_v = is_tuple::value; 57 | 58 | } // namespace traits 59 | 60 | template struct parse_object_format { }; 61 | 62 | template <> 63 | struct parse_object_format { static constexpr char value = 'i'; }; 64 | 65 | template <> 66 | struct parse_object_format { static constexpr char value = 'I'; }; 67 | 68 | template <> 69 | struct parse_object_format { static constexpr char value = 'i'; }; 70 | 71 | template <> 72 | struct parse_object_format { static constexpr char value = 'I'; }; 73 | 74 | template <> 75 | struct parse_object_format { static constexpr char value = 'i'; }; 76 | 77 | template <> 78 | struct parse_object_format { static constexpr char value = 'I'; }; 79 | 80 | template <> 81 | struct parse_object_format { static constexpr char value = 'i'; }; 82 | 83 | template <> 84 | struct parse_object_format { static constexpr char value = 'I'; }; 85 | 86 | template <> 87 | struct parse_object_format { static constexpr char value = 'f'; }; 88 | 89 | template <> 90 | struct parse_object_format { static constexpr char value = 'd'; }; 91 | 92 | template <> 93 | struct parse_object_format { static constexpr char value = 's'; }; 94 | 95 | template <> 96 | struct parse_object_format { static constexpr char value = 's'; }; 97 | 98 | template 99 | struct set_tuple_helper { 100 | static void help(PyObject* o, Tuple t) { 101 | PyTuple_SetItem(o, I, parse_object(std::get(t))); 102 | set_tuple_helper::help(o, t); 103 | } 104 | }; 105 | 106 | template 107 | struct set_tuple_helper<0, Tuple> { 108 | static void help(PyObject* o, Tuple t) { 109 | PyTuple_SetItem(o, 0, parse_object(std::get<0>(t))); 110 | } 111 | }; 112 | 113 | template 114 | struct parse_object_helper { 115 | static PyObject* help(T&& t) { 116 | char format[2] = {0}; 117 | format[0] = parse_object_format>::value; 118 | return Py_BuildValue(format, t); 119 | } 120 | }; 121 | 122 | template 123 | struct parse_object_helper>>> { 125 | static PyObject* help(Tuple&& t) { 126 | constexpr size_t n = std::tuple_size>::value; 127 | PyObject* o = PyTuple_New(n); 128 | if constexpr (n > 0) { 129 | set_tuple_helper::help(o, t); 130 | } 131 | return o; 132 | } 133 | }; 134 | 135 | template 136 | struct parse_object_helper>>> { 138 | static PyObject* help(String&& s) { 139 | char format[2] = {0}; 140 | format[0] = parse_object_format::value; 141 | return Py_BuildValue(format, s.c_str()); 142 | } 143 | }; 144 | 145 | template 146 | struct parse_object_helper>>> { 148 | static PyObject* help(Container c) { 149 | PyObject* o = PyDict_New(); 150 | for (auto& [_key, _value]: c) { 151 | PyObject* key = parse_object(_key); 152 | PyObject* value = parse_object(_value); 153 | Py_INCREF(key); 154 | Py_INCREF(value); 155 | PyDict_SetItem(o, key, value); 156 | } 157 | return o; 158 | } 159 | }; 160 | 161 | template 162 | struct parse_object_helper> && 164 | !traits::is_string_v >>> { 165 | static PyObject* help(Container c) { 166 | PyObject* o = PyList_New(0); 167 | for (auto& e: c) { 168 | PyList_Append(o, parse_object(e)); 169 | } 170 | return o; 171 | } 172 | }; 173 | 174 | template 175 | struct parse_object_helper_multi { 176 | static void help(PyObject* o, T&& t, Ts&&... ts) { 177 | PyList_Append(o, parse_object(t)); 178 | parse_object_helper_multi::help(std::forward(o), 179 | std::forward(ts)...); 180 | } 181 | }; 182 | 183 | template 184 | struct parse_object_helper_multi { 185 | static void help(PyObject* o, T&& t) { 186 | PyList_Append(o, parse_object(std::forward(t))); 187 | } 188 | }; 189 | 190 | } // namespace detail 191 | 192 | template 193 | PyObject* parse_object(T&& t) { 194 | return detail::parse_object_helper::help(std::forward(t)); 195 | } 196 | 197 | template 198 | PyObject* parse_object(T&& t, Ts&&... ts) { 199 | PyObject* o = PyList_New(0); 200 | detail::parse_object_helper_multi::help( 201 | o, 202 | std::forward(t), 203 | std::forward(ts)...); 204 | return o; 205 | } 206 | 207 | } // namespace pie 208 | -------------------------------------------------------------------------------- /src/pie/pie.h: -------------------------------------------------------------------------------- 1 | #include "object.h" 2 | #include "parse.h" 3 | #include "error.h" 4 | #include "iterator.h" 5 | -------------------------------------------------------------------------------- /test/test_error.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using pie::object; 5 | using pie::error; 6 | 7 | TEST(error, division_by_zero) { 8 | object zero = 0; 9 | object one = 1; 10 | EXPECT_THROW(one / zero, error); 11 | } 12 | -------------------------------------------------------------------------------- /test/test_object.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using pie::object; 5 | using pie::parse_object; 6 | 7 | TEST(object, default_constructor) { 8 | object o; 9 | EXPECT_EQ(nullptr, o.get()); 10 | } 11 | 12 | TEST(object, pyobject_constructor) { 13 | PyObject* po = parse_object(42); 14 | object o(po); 15 | EXPECT_EQ(po, o.get()); 16 | } 17 | 18 | TEST(object, parse_constructor) { 19 | object o(10); 20 | EXPECT_TRUE(PyNumber_Check(o.get())); 21 | } 22 | 23 | TEST(object, copy_contructor) { 24 | object o(10); 25 | ssize_t refcount = o->ob_refcnt; 26 | object copy(o); 27 | EXPECT_EQ(refcount + 1, o->ob_refcnt); 28 | EXPECT_EQ(refcount + 1, copy->ob_refcnt); 29 | } 30 | 31 | TEST(object, move_contructor) { 32 | object o(10); 33 | ssize_t refcount = o->ob_refcnt; 34 | object move(std::move(o)); 35 | EXPECT_EQ(nullptr, o.get()); 36 | EXPECT_EQ(refcount, move->ob_refcnt); 37 | } 38 | 39 | TEST(object, parse_assignment) { 40 | object o = 10; 41 | EXPECT_TRUE(PyNumber_Check(o.get())); 42 | } 43 | 44 | TEST(object, copy_assignment) { 45 | object o(10); 46 | ssize_t refcount = o->ob_refcnt; 47 | object copy = o; 48 | EXPECT_EQ(refcount + 1, o->ob_refcnt); 49 | EXPECT_EQ(refcount + 1, copy->ob_refcnt); 50 | } 51 | 52 | TEST(object, move_assignment) { 53 | object o(10); 54 | ssize_t refcount = o->ob_refcnt; 55 | object move = std::move(o); 56 | EXPECT_EQ(nullptr, o.get()); 57 | EXPECT_EQ(refcount, move->ob_refcnt); 58 | } 59 | 60 | TEST(object, comparison) { 61 | object one = 1; 62 | object two = 2; 63 | EXPECT_LT(one, two); 64 | EXPECT_LE(one, two); 65 | EXPECT_LE(one, one); 66 | EXPECT_EQ(one, one); 67 | EXPECT_EQ(two, two); 68 | EXPECT_NE(one, two); 69 | EXPECT_NE(two, one); 70 | EXPECT_GT(two, one); 71 | EXPECT_GE(two, one); 72 | EXPECT_GE(one, one); 73 | } 74 | 75 | TEST(object, hash) { 76 | object o1 = 1; 77 | object o2 = 1; 78 | EXPECT_EQ(o1.hash(), o2.hash()); 79 | } 80 | 81 | TEST(object, call) { 82 | object os_listdir = pie::getattr(PyImport_ImportModule("os"), "listdir"); 83 | EXPECT_TRUE(os_listdir()); 84 | EXPECT_TRUE(os_listdir("/")); 85 | } 86 | 87 | TEST(object, sequences) { 88 | object seq(1, 2, 3, 4, 5); 89 | EXPECT_EQ(5, seq.length()); 90 | for (size_t i = 0; i < 5; ++i) { 91 | EXPECT_EQ(object(i + 1), seq[i]); 92 | seq[i] = i; 93 | EXPECT_EQ(object(i), seq[i]); 94 | } 95 | 96 | size_t i = 0; 97 | for (auto o: seq) { 98 | EXPECT_EQ(object(i), o); 99 | ++i; 100 | } 101 | } 102 | 103 | TEST(object, numeric_operators) { 104 | object zero = 0; 105 | object one = 1; 106 | object two = 2; 107 | object three = 3; 108 | object four = 4; 109 | { 110 | object new_one = one; 111 | EXPECT_EQ(three, one + two); 112 | EXPECT_EQ(three, new_one += two); 113 | } 114 | { 115 | object new_three = three; 116 | EXPECT_EQ(one, three - two); 117 | EXPECT_EQ(one, new_three -= two); 118 | } 119 | { 120 | object new_one = one; 121 | EXPECT_EQ(two, one * two); 122 | EXPECT_EQ(two, new_one *= two); 123 | } 124 | { 125 | object new_four = four; 126 | EXPECT_EQ(two, four / two); 127 | EXPECT_EQ(two, new_four /= two); 128 | } 129 | { 130 | object new_four = four; 131 | EXPECT_EQ(zero, four % two); 132 | EXPECT_EQ(zero, new_four %= two); 133 | } 134 | object negative_one = -1; 135 | 136 | EXPECT_EQ(negative_one, -one); 137 | EXPECT_EQ(one, +one); 138 | EXPECT_EQ(four, pow(two, two)); 139 | EXPECT_EQ(one, abs(negative_one)); 140 | } 141 | 142 | TEST(object, bitwise_operators) { 143 | object zero = 0; 144 | object one = 1; 145 | object two = 2; 146 | object three = 3; 147 | object four = 4; 148 | 149 | { 150 | object new_one = one; 151 | EXPECT_EQ(four, one << two); 152 | EXPECT_EQ(four, new_one <<= two); 153 | } 154 | { 155 | object new_two = two; 156 | EXPECT_EQ(one, two >> one); 157 | EXPECT_EQ(one, new_two >>= one); 158 | } 159 | { 160 | object new_two = two; 161 | EXPECT_EQ(zero, two & one); 162 | EXPECT_EQ(zero, new_two &= one); 163 | } 164 | { 165 | object new_three = three; 166 | EXPECT_EQ(two, three ^ one); 167 | EXPECT_EQ(two, new_three ^= one); 168 | } 169 | { 170 | object new_two = two; 171 | EXPECT_EQ(three, two | one); 172 | EXPECT_EQ(three, new_two |= one); 173 | } 174 | EXPECT_EQ(-four, ~three); 175 | } 176 | -------------------------------------------------------------------------------- /test/test_parse.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | using pie::parse_object; 11 | 12 | TEST(parse, numbers) { 13 | { 14 | char i = 1; 15 | PyObject* o = parse_object(i); 16 | EXPECT_TRUE(PyNumber_Check(o)); 17 | EXPECT_EQ(1, PyNumber_AsSsize_t(o, nullptr)); 18 | } 19 | { 20 | short i = 2; 21 | PyObject* o = parse_object(i); 22 | EXPECT_TRUE(PyNumber_Check(o)); 23 | EXPECT_EQ(2, PyNumber_AsSsize_t(o, nullptr)); 24 | } 25 | { 26 | int i = 3; 27 | PyObject* o = parse_object(i); 28 | EXPECT_TRUE(PyNumber_Check(o)); 29 | EXPECT_EQ(3, PyNumber_AsSsize_t(o, nullptr)); 30 | } 31 | { 32 | long i = 4; 33 | PyObject* o = parse_object(i); 34 | EXPECT_TRUE(PyNumber_Check(o)); 35 | EXPECT_EQ(4, PyNumber_AsSsize_t(o, nullptr)); 36 | } 37 | { 38 | float i = 0.1f; 39 | PyObject* o = parse_object(i); 40 | EXPECT_TRUE(PyNumber_Check(o)); 41 | EXPECT_EQ(0.1f, PyFloat_AsDouble(o)); 42 | } 43 | { 44 | double i = 0.2; 45 | PyObject* o = parse_object(i); 46 | EXPECT_TRUE(PyNumber_Check(o)); 47 | EXPECT_EQ(0.2, PyFloat_AsDouble(o)); 48 | } 49 | } 50 | 51 | TEST(parse, strings) { 52 | std::string foobar = "foobar"; 53 | { 54 | const char* s = "foobar"; 55 | PyObject* o = parse_object(s); 56 | EXPECT_TRUE(PyUnicode_Check(o)); 57 | EXPECT_STREQ("foobar", PyBytes_AsString(PyUnicode_AsASCIIString(o))); 58 | } 59 | { 60 | char s[7] = "foobar"; 61 | PyObject* o = parse_object(s); 62 | EXPECT_TRUE(PyUnicode_Check(o)); 63 | EXPECT_STREQ("foobar", PyBytes_AsString(PyUnicode_AsASCIIString(o))); 64 | } 65 | { 66 | std::string s = "foobar"; 67 | PyObject* o = parse_object(s); 68 | EXPECT_TRUE(PyUnicode_Check(o)); 69 | EXPECT_STREQ("foobar", PyBytes_AsString(PyUnicode_AsASCIIString(o))); 70 | } 71 | { 72 | PyObject* o = parse_object("foobar"); 73 | EXPECT_TRUE(PyUnicode_Check(o)); 74 | EXPECT_STREQ("foobar", PyBytes_AsString(PyUnicode_AsASCIIString(o))); 75 | } 76 | } 77 | 78 | TEST(parse, sequences) { 79 | { 80 | std::vector v{1, 2, 3, 4, 5}; 81 | PyObject* o = parse_object(v); 82 | EXPECT_TRUE(PySequence_Check(o)); 83 | EXPECT_EQ(5, PySequence_Length(o)); 84 | for (ssize_t i = 0; i < PySequence_Length(o); ++i) { 85 | EXPECT_EQ(i + 1, 86 | PyNumber_AsSsize_t(PySequence_GetItem(o, i), nullptr)); 87 | } 88 | } 89 | { 90 | std::deque v{1, 2, 3, 4, 5}; 91 | PyObject* o = parse_object(v); 92 | EXPECT_TRUE(PySequence_Check(o)); 93 | EXPECT_EQ(5, PySequence_Length(o)); 94 | for (ssize_t i = 0; i < PySequence_Length(o); ++i) { 95 | EXPECT_EQ(i + 1, 96 | PyNumber_AsSsize_t(PySequence_GetItem(o, i), nullptr)); 97 | } 98 | } 99 | { 100 | std::set v{1, 2, 3, 4, 5}; 101 | PyObject* o = parse_object(v); 102 | EXPECT_TRUE(PySequence_Check(o)); 103 | EXPECT_EQ(5, PySequence_Length(o)); 104 | for (ssize_t i = 0; i < PySequence_Length(o); ++i) { 105 | EXPECT_EQ(i + 1, 106 | PyNumber_AsSsize_t(PySequence_GetItem(o, i), nullptr)); 107 | } 108 | } 109 | } 110 | 111 | TEST(parse, maps) { 112 | { 113 | std::map m{ {"foo", 1}, {"bar", 2} }; 114 | PyObject* o = parse_object(m); 115 | EXPECT_TRUE(PyDict_Check(o)); 116 | EXPECT_EQ(1, PyNumber_AsSsize_t(PyDict_GetItem(o, parse_object("foo")), 117 | nullptr)); 118 | EXPECT_EQ(2, PyNumber_AsSsize_t(PyDict_GetItem(o, parse_object("bar")), 119 | nullptr)); 120 | } 121 | { 122 | std::multimap m{ {"foo", 1}, {"bar", 2} }; 123 | PyObject* o = parse_object(m); 124 | EXPECT_TRUE(PyDict_Check(o)); 125 | EXPECT_EQ(1, PyNumber_AsSsize_t(PyDict_GetItem(o, parse_object("foo")), 126 | nullptr)); 127 | EXPECT_EQ(2, PyNumber_AsSsize_t(PyDict_GetItem(o, parse_object("bar")), 128 | nullptr)); 129 | } 130 | } 131 | 132 | TEST(parse, tuples) { 133 | { 134 | std::tuple t{1, 2, "foobar"}; 135 | PyObject* o = parse_object(t); 136 | EXPECT_TRUE(PyTuple_Check(o)); 137 | EXPECT_EQ(1, PyNumber_AsSsize_t(PyTuple_GetItem(o, 0), 138 | nullptr)); 139 | EXPECT_EQ(2, PyNumber_AsSsize_t(PyTuple_GetItem(o, 1), 140 | nullptr)); 141 | EXPECT_EQ(std::string("foobar"), 142 | PyBytes_AsString(PyUnicode_AsASCIIString(PyTuple_GetItem(o, 2)))); 143 | } 144 | 145 | { 146 | PyObject* o = parse_object(std::make_tuple 147 | (1, 2, "foobar")); 148 | EXPECT_TRUE(PyTuple_Check(o)); 149 | EXPECT_EQ(1, PyNumber_AsSsize_t(PyTuple_GetItem(o, 0), 150 | nullptr)); 151 | EXPECT_EQ(2, PyNumber_AsSsize_t(PyTuple_GetItem(o, 1), 152 | nullptr)); 153 | EXPECT_EQ(std::string("foobar"), 154 | PyBytes_AsString(PyUnicode_AsASCIIString(PyTuple_GetItem(o, 2)))); 155 | } 156 | } 157 | 158 | TEST(parse, multi) { 159 | { 160 | PyObject* o = parse_object(1, 2, 3); 161 | EXPECT_TRUE(PySequence_Check(o)); 162 | EXPECT_EQ(3, PySequence_Length(o)); 163 | for (ssize_t i = 0; i < PySequence_Length(o); ++i) { 164 | EXPECT_EQ(i + 1, 165 | PyNumber_AsSsize_t(PySequence_GetItem(o, i), nullptr)); 166 | } 167 | } 168 | { 169 | PyObject* o = parse_object(1, std::vector{2, 3, 4}, 170 | std::map{ {5, 6}, {7, 8} }); 171 | EXPECT_TRUE(PySequence_Check(o)); 172 | EXPECT_EQ(3, PySequence_Length(o)); 173 | 174 | EXPECT_TRUE(PyNumber_Check(PySequence_GetItem(o, 0))); 175 | EXPECT_TRUE(PySequence_Check(PySequence_GetItem(o, 1))); 176 | EXPECT_TRUE(PyDict_Check(PySequence_GetItem(o, 2))); 177 | } 178 | } 179 | --------------------------------------------------------------------------------