├── .github └── workflows │ └── rebase_checker.yaml ├── .gitignore ├── LICENSE ├── README.md ├── include ├── basics.hpp ├── containers.hpp └── extensions.hpp ├── python2-c-api ├── README.md ├── challenge │ ├── __init__.py │ ├── converters.i │ ├── py_basics.cpp │ ├── py_containers.cpp │ ├── py_extensions.cpp │ └── utilities.cpp ├── include │ ├── basics_typemaps.i │ ├── py_basics.hpp │ ├── py_containers.hpp │ ├── py_extensions.hpp │ └── utilities.hpp ├── setup.py └── tests │ ├── __init__.py │ ├── basics_test.py │ ├── containers_test.py │ ├── converters_test.py │ └── extensions_test.py ├── python2-cython ├── challenge │ ├── __init__.py │ ├── _basics.pxd │ ├── _containers.pxd │ ├── basics.pxd │ ├── basics.pyx │ ├── containers.pyx │ └── converters.i ├── include │ └── basics_typemaps.i ├── setup.py └── tests │ ├── __init__.py │ ├── basics_test.py │ ├── containers_test.py │ └── converters_test.py ├── python2-pybind11 ├── challenge │ ├── __init__.py │ ├── basics.cpp │ ├── containers.cpp │ ├── converters.i │ └── include │ │ └── basics_typemaps.i ├── setup.py └── tests │ ├── __init__.py │ ├── basics_test.py │ ├── containers_test.py │ └── converters_test.py ├── src ├── basics.cpp ├── containers.cpp ├── converters.i └── extensions.cpp └── tests ├── basics_test.py ├── containers_test.py ├── converters_test.py └── extensions_test.py /.github/workflows/rebase_checker.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Check that 'main' is not merged into the development branch 3 | 4 | on: pull_request 5 | 6 | jobs: 7 | call-workflow: 8 | uses: lsst/rubin_workflows/.github/workflows/rebase_checker.yaml@main 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | build/ 3 | *.so 4 | *.egg-info 5 | *_wrap.cpp 6 | python2-cython/challenge/*.cpp 7 | python2-cython/challenge/*.h 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Jim Bosch 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # C++/Python Bindings Challenge 2 | 3 | This repository contains a small suite of C++ classes designed to highlight any of the most common challenges involved in providing Python bindings to a C++ library, as well as (coming soon) a suite of Python unit tests that attempt to measure the quality of the Python bindings. 4 | 5 | The C++ classes themselves are highly artificial, but their interfaces contain represent important and common problems in binding C++ to Python, ranging from trivial (e.g. convert C++ strings to Python str or unicode) to extremely difficult (wrap const references to C++ objects while protecting against dangling pointers and guaranteeing constness). 6 | 7 | Submission of both solutions and additional challenges is welcome. There's no formal competition, but I'm hoping submissions will be enthusiastically discussed and groups (including mine) will find them useful for selecting which binding tools and approaches to consider in the future. 8 | 9 | ## The Challenge 10 | 11 | - Each of the C++ header/src pairs must be wrapped into a separate Python module within the same package, with both "containers" and "extensions" depending on (i.e. internally importing) "basics", but not depending on each other. 12 | 13 | - Make as many of the unit tests pass as possible, using the smallest amount of *readible*, *class-specific* code; we're trying to use this small package to guess how much code would be required for a much larger suite of libraries with the same conventions, so code that just sets up rules for code generators is in some sense not counted - or at least amortized down. 14 | 15 | Some of the tests are *extremely hard*, and many judges will consider a lightweight solution that does not handle some edge case preferable to a heavyweight solution that does, so just use your best judgement. 16 | 17 | The formal definition of the desired Python interface is defined by the unit tests, but the C++ header files also contain notes that indicate most of this (as they're a lot easier to read than the tests). 18 | 19 | ## Submitting a Solution 20 | 21 | 1. Fork this repository. 22 | 2. Create a directory with the same name as the branch. 23 | 3. Write some bindings, putting all new code in the new directory. 24 | 4. Add a build system: autotools, cmake, scons, and any other standard build tool is welcome, but utilizing just Python distutils/setuptools is a bonus. Include a way to run the scripts in the test directory, and if you're attempting the Swig conversions test, be sure to build conversions.i as well. 25 | 5. Add a README file to your directory, summarizing your approach and the 26 | test results (useful submissions need not pass all tests). 27 | 6. Submit your solution as a PR for discussion. 28 | 29 | You can also use the python2-c-api directory as an example; this contains a mostly-complete (but not at all concise) solution that just uses the Python C API, along with a setuptools build that works if you create symlinks for tests and the Swig source (as is done in this directory). 30 | 31 | ## Submitting a Challenge 32 | 33 | 1. Fork this repository. 34 | 2. Add new C++ code and unit tests as necessary, on the fork's master branch. 35 | 3. Update this README if necessary. 36 | 4. Submit your challenge as a PR. 37 | -------------------------------------------------------------------------------- /include/basics.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CHALLENGE_basics_hpp_INCLUDED 2 | #define CHALLENGE_basics_hpp_INCLUDED 3 | 4 | #include 5 | #include 6 | 7 | namespace basics { 8 | 9 | // Instead of actually creating WhatsIt objects in Python, we'd like to just 10 | // map them to two-element Python tuples, both as arguments and return values. 11 | struct WhatsIt { 12 | std::string a; 13 | int b; 14 | }; 15 | 16 | // A class that should be completely opaque in Python, but can nevertheless 17 | // be round-tripped through the Python layer. 18 | class Secret { 19 | public: 20 | 21 | Secret(Secret const &) = delete; 22 | Secret(Secret &&) = delete; 23 | 24 | Secret & operator=(Secret const&) = delete; 25 | Secret & operator=(Secret &&) = delete; 26 | 27 | private: 28 | 29 | friend class Doodad; 30 | 31 | friend bool compare(Secret const & a, Secret const & b); 32 | friend bool adjacent(Secret const & a, Secret const & b); 33 | 34 | Secret(); 35 | 36 | std::size_t _index; 37 | }; 38 | 39 | // A few free functions that compare two Secret objects. 40 | // Must be wrapped, but mostly just to allow Secrets to be used to check other 41 | // aspects of the bindings. 42 | bool compare(Secret const & a, Secret const & b); 43 | bool adjacent(Secret const & a, Secret const & b); 44 | 45 | // This should be wrapped as a standard Python type. 46 | // It should have an overloaded constructor, taking WhatsIt (tuple/sequence 47 | // in Python) or str and optionally int. 48 | // We should also define Python comparison (equality and inequality) operators 49 | // that compare by *pointer*, not value. This will be used by some tests. 50 | // Doodad is noncopyable but moveable; this allows us to guarantee that the 51 | // binding layer isn't doing any unnecessary copies. We do not expect Python 52 | // bindings to provide access to the move construction or assignment, as there 53 | // is no Python interface for which invalidation of a variable can be 54 | // considered acceptable behavior. 55 | class Doodad { 56 | public: 57 | 58 | // Keyword arguments (without the trailing underscore) should work in 59 | // Python, as should the default argument. 60 | explicit Doodad(std::string const & name_, int value_=1); 61 | 62 | // As we map WhatsIt to tuple, this should accept a tuple in Python. 63 | explicit Doodad(WhatsIt const & it); 64 | 65 | // Copy construction is disabled. 66 | Doodad(Doodad const &) = delete; 67 | Doodad& operator=(Doodad const &) = delete; 68 | 69 | // Move construction is allowed, but should not be exposed to Python. 70 | Doodad(Doodad &&) = default; 71 | Doodad& operator=(Doodad &&) = default; 72 | 73 | virtual ~Doodad(); 74 | 75 | // In Python, the return value should be indistinguishable from an 76 | // instance created any other way. 77 | // This option should not require any extra copies beyond the one 78 | // done in C++. 79 | virtual std::unique_ptr clone() const; 80 | 81 | // This should accept any (str, int) Python sequence. 82 | void read(WhatsIt const & it); 83 | 84 | // This should return a (str, int) tuple in Python. 85 | WhatsIt write() const; 86 | 87 | // Return the opaque Secret object associated with this Doodad. 88 | Secret const & get_secret() const { return _secret; } 89 | 90 | // Return a shared_ptr to a Doodad that cannot be modified. 91 | static std::shared_ptr get_const(); 92 | 93 | // We'd like Python properties to be generated for both of these. 94 | std::string name; 95 | int value; 96 | 97 | private: 98 | Secret _secret; 99 | }; 100 | 101 | } // namespace basics 102 | 103 | #endif //! CHALLENGE_basics_hpp_INCLUDED 104 | -------------------------------------------------------------------------------- /include/containers.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CHALLENGE_containers_hpp_INCLUDED 2 | #define CHALLENGE_containers_hpp_INCLUDED 3 | 4 | #include "basics.hpp" 5 | #include 6 | #include 7 | 8 | namespace containers { 9 | 10 | // This class should have a default constructor and copy constructor defined 11 | // in Python, as well as a few special methods for containers (see below). 12 | // It must be wrapped in a way that supports Thingamajigs-as-Doodads when the 13 | // extensions module is imported, but the wrapper code for containers module 14 | // must not #include extensions.hpp. 15 | class DoodadSet { 16 | public: 17 | 18 | // In Python, this should be a class attribute set to the Doodad 19 | // type object. 20 | typedef basics::Doodad Item; 21 | 22 | // No need to make a class attribute for this. 23 | typedef std::vector>::const_iterator iterator; 24 | 25 | // Transform to __len__ in Python. 26 | std::size_t size() const { return _items.size(); } 27 | 28 | // Transform to __iter__ in Python (while being careful about dangling 29 | // pointers.) 30 | iterator begin() const { return _items.begin(); } 31 | iterator end() const { return _items.end(); } 32 | 33 | // In Python, this should return as a new list that contains the original 34 | // items, and should be renamed to "as_list()"". 35 | std::vector> as_vector() const { return _items; } 36 | 37 | // In Python, this should accept any Python sequence. 38 | void assign(std::vector> const & items) { 39 | _items = items; 40 | } 41 | 42 | // This should accept any Python Doodad, including Thingamajigs. 43 | void add(std::shared_ptr item); 44 | 45 | // This should accept a (str, int) tuple (or other sequence). 46 | // There should be a single add_item Python method that accepts both 47 | // Doodads and tuples. 48 | void add(basics::WhatsIt const & it); 49 | 50 | // This should be transformed into a Python dict on return, and renamed 51 | // to "as_dict()". 52 | std::map> as_map() const; 53 | 54 | private: 55 | std::vector> _items; 56 | }; 57 | 58 | } // namespace containers 59 | 60 | 61 | #endif //! CHALLENGE_containers_hpp_INCLUDED 62 | -------------------------------------------------------------------------------- /include/extensions.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CHALLENGE_extensions_hpp_INCLUDED 2 | #define CHALLENGE_extensions_hpp_INCLUDED 3 | 4 | #include "basics.hpp" 5 | 6 | namespace extensions { 7 | 8 | // In Python, we'd like to wrap this as a single Thingamajig type with 9 | // a "dtype" attribute and constructor argument that can be either of 10 | // (double, std::shared_ptr), mimicking the way numpy.ndarray 11 | // handles multiple types. Actually using NumPy is optional. 12 | template 13 | class Thingamajig : public basics::Doodad { 14 | public: 15 | 16 | // Keyword arguments and default values should work in Python. 17 | Thingamajig(T extra, std::string const & name, int value=1); 18 | 19 | // Copy construction is disabled to ensure bindings don't make unnecessary 20 | // copies. 21 | Thingamajig(Thingamajig const &) = delete; 22 | Thingamajig& operator=(Thingamajig const &) = delete; 23 | 24 | // Move construction is allowed, but is not expected to be exposed to 25 | // Python. 26 | Thingamajig(Thingamajig &&) = default; 27 | Thingamajig& operator=(Thingamajig &&) = default; 28 | 29 | // In Python, this shouldn't have to be wrapped for Thingamajig, as 30 | // Python inheritance from Doodad should delegate to the Thingamajig 31 | // implementation anyway. It should, however, return a Thingamajig 32 | // instance in Python, not a Doodad that would have to be casted 33 | // somehow. 34 | virtual std::unique_ptr clone() const; 35 | 36 | // Return the extra object. 37 | T get_extra() const { return _extra; } 38 | 39 | private: 40 | T _extra; 41 | }; 42 | 43 | } // namespace things 44 | 45 | #endif //! CHALLENGE_extensions_hpp_INCLUDED 46 | -------------------------------------------------------------------------------- /python2-c-api/README.md: -------------------------------------------------------------------------------- 1 | # Hand-written bindings with the Python 2 C API 2 | 3 | This directory includes a challenge solution that uses hand-written wrappers that utilize just the Python C API, as well as a build system utilizing the ubiquitous setuptools module. To try it out, just do: 4 | 5 | python setup.py build 6 | python setup.py test 7 | 8 | Note that you'll need to have both setuptools and Swig installed to run all the tests. 9 | 10 | This solution to the challenge is currently incomplete, and what's present has a few flaws that aren't apparent from the tests: 11 | 12 | - Bindings for basics::Doodad::get_secret are not sufficiently careful about memory. The returned Secret object does not hold a reference to the Doodad that owns it, making it possible for the latter to be destroyed first, causing the Secret to dangle. This could be fixed by defining a new Python type for Secret instead of using Capsules, which is just a bit more work (and demonstrating Capsule is also useful, even if it isn't a perfect fit). 13 | 14 | - The way C++ objects (namely Doodads) are held in Python instances may not be strictly legal - we put a std::shared_ptr in a C struct that must start with the same layout as PyObject. std::shared_ptr isn't guaranteed to be Standard Layout, so I think it formally breaks that guarantee. But I believe this works with all existing C++ ABIs. 15 | 16 | - The C API provided for downstream use by Swig typemaps requires setting RTLD flags, which is messy and not completely portable. AFAIK, it is also required for any use of Swig, but we could have written a C API using the macro-heavy approach Capsule approach advocated in the Python docs, which would have made downstream use by something other than Swig not rely on that approach to linking. 17 | 18 | This approach also obviously requires more code than most people would want to write for anything but the simplest C++ libraries. It produces a polished, complete Python interface, and there's little guesswork as the Python C API is very well documented, but there are very clear tradeoffs in readibility and sheer quantity of code for the bindings developer. 19 | -------------------------------------------------------------------------------- /python2-c-api/challenge/__init__.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | # This sequence will get a lot cleaner for Python 3, for which the necessary 4 | # flags should all be in the os module. 5 | import ctypes 6 | flags = ctypes.RTLD_GLOBAL 7 | try: 8 | import DLFCN 9 | flags |= DLFCN.RTLD_NOW 10 | except ImportError: 11 | flags |= 0x2 # works for Linux and Mac, only platforms I care about now. 12 | sys.setdlopenflags(flags) 13 | 14 | # Ensure basics and utilities modules are loaded first, since we need its 15 | # symbols for anything else. 16 | from . import utilities 17 | from . import basics 18 | -------------------------------------------------------------------------------- /python2-c-api/challenge/converters.i: -------------------------------------------------------------------------------- 1 | ../../src/converters.i -------------------------------------------------------------------------------- /python2-c-api/challenge/py_basics.cpp: -------------------------------------------------------------------------------- 1 | #include "py_basics.hpp" 2 | 3 | using utilities::Py; 4 | 5 | 6 | namespace utilities { 7 | 8 | template class Py; 9 | 10 | } // namespace utilities 11 | 12 | namespace basics { 13 | 14 | // ---------------------------------------------------------------------------- 15 | // WhatsIt 16 | // ---------------------------------------------------------------------------- 17 | 18 | PyObject * PyWhatsIt::to_python(WhatsIt const & it) { 19 | return Py_BuildValue( 20 | "s#i", 21 | it.a.data(), static_cast(it.a.size()), 22 | it.b 23 | ); 24 | } 25 | 26 | bool PyWhatsIt::from_python(PyObject * p, WhatsIt * it) { 27 | WhatsIt * result = reinterpret_cast(it); 28 | int size = 0; 29 | int b = 0; 30 | const char * str = nullptr; 31 | if (!PyArg_ParseTuple(p, "s#i", &str, &size, &b)) { 32 | return false; 33 | } 34 | result->a = std::string(str, size); 35 | result->b = b; 36 | return true; 37 | } 38 | 39 | // ---------------------------------------------------------------------------- 40 | // Secret 41 | // ---------------------------------------------------------------------------- 42 | 43 | PyObject * PySecret::to_python(Secret const * s) { 44 | return PyCapsule_New( 45 | const_cast(s), 46 | "challenge.basics.Secret", 47 | nullptr 48 | ); 49 | } 50 | 51 | bool PySecret::cptr_from_python(PyObject * p, Secret const ** s) { 52 | void * v = PyCapsule_GetPointer(p, "challenge.basics.Secret"); 53 | if (!v) { 54 | return false; 55 | } 56 | *s = reinterpret_cast(v); 57 | return true; 58 | } 59 | 60 | static PyObject * Py_compare(PyObject * self, PyObject * args) { 61 | Secret * a = nullptr; 62 | Secret * b = nullptr; 63 | if ( 64 | !PyArg_ParseTuple( 65 | args, "O&O&", 66 | &PySecret::cptr_from_python, &a, 67 | &PySecret::cptr_from_python, &b 68 | ) 69 | ) { 70 | return nullptr; 71 | } 72 | bool result = compare(*a, *b); 73 | return PyBool_FromLong(result); 74 | } 75 | 76 | static PyObject * Py_adjacent(PyObject * self, PyObject * args) { 77 | Secret const * a = nullptr; 78 | Secret const * b = nullptr; 79 | if ( 80 | !PyArg_ParseTuple( 81 | args, "O&O&", 82 | &PySecret::cptr_from_python, &a, 83 | &PySecret::cptr_from_python, &b 84 | ) 85 | ) { 86 | return nullptr; 87 | } 88 | bool result = adjacent(*a, *b); 89 | return PyBool_FromLong(result); 90 | } 91 | 92 | // ---------------------------------------------------------------------------- 93 | // Doodad 94 | // ---------------------------------------------------------------------------- 95 | 96 | static Py * PyDoodad_new(PyTypeObject * type, PyObject *, PyObject *) { 97 | return Py::create(type); 98 | } 99 | 100 | static bool PyDoodad_init_1( 101 | Py * self, PyObject * args, PyObject * kwds 102 | ) { 103 | static char const * kwd_names[] = {"name", "value", nullptr}; 104 | int name_size = 0; 105 | int value = 1; 106 | const char * name_str = nullptr; 107 | if ( 108 | PyArg_ParseTupleAndKeywords( 109 | args, kwds, "s#|i", const_cast(kwd_names), 110 | &name_str, &name_size, 111 | &value 112 | ) 113 | ) { 114 | std::string name(name_str, name_size); 115 | self->instance = std::shared_ptr( 116 | new Doodad(std::move(name), value) 117 | ); 118 | return true; 119 | } 120 | return false; 121 | } 122 | 123 | static bool PyDoodad_init_2(Py * self, PyObject * args, PyObject * kwds) { 124 | WhatsIt it; 125 | if ( 126 | PyArg_ParseTuple( 127 | args, "O&", 128 | &PyWhatsIt::from_python, &it 129 | ) 130 | ) { 131 | self->instance = std::shared_ptr(new Doodad(it)); 132 | return true; 133 | } 134 | return false; 135 | } 136 | 137 | static int PyDoodad_init(Py * self, PyObject * args, PyObject * kwds) { 138 | if (PyDoodad_init_1(self, args, kwds)) { 139 | return 0; 140 | } 141 | PyErr_Clear(); 142 | if (PyDoodad_init_2(self, args, kwds)) { 143 | return 0; 144 | } 145 | return -1; 146 | } 147 | 148 | static PyObject * PyDoodad_clone(Py * self, PyObject *) { 149 | if (!self->instance) { 150 | PyErr_SetString(PyExc_TypeError, "Uninitialized Doodad"); 151 | return nullptr; 152 | } 153 | std::shared_ptr copy(self->instance->clone()); 154 | return PyDoodad::to_python(std::move(copy)); 155 | } 156 | 157 | static PyObject * PyDoodad_read(Py * self, PyObject * a) { 158 | if (!self->instance) { 159 | PyErr_SetString(PyExc_TypeError, "Uninitialized Doodad"); 160 | return nullptr; 161 | } 162 | if (self->frozen) { 163 | PyErr_SetString(PyExc_TypeError, "Cannot modify frozen Doodad."); 164 | return nullptr; 165 | } 166 | WhatsIt it; 167 | if (!PyWhatsIt::from_python(a, &it)) { 168 | return nullptr; 169 | } 170 | self->instance->read(it); 171 | Py_RETURN_NONE; 172 | } 173 | 174 | static PyObject * PyDoodad_write(Py * self, PyObject *) { 175 | if (!self->instance) { 176 | PyErr_SetString(PyExc_TypeError, "Uninitialized Doodad"); 177 | return nullptr; 178 | } 179 | return PyWhatsIt::to_python(self->instance->write()); 180 | } 181 | 182 | static PyObject * PyDoodad_get_secret(Py * self, PyObject *) { 183 | if (!self->instance) { 184 | PyErr_SetString(PyExc_TypeError, "Uninitialized Doodad"); 185 | return nullptr; 186 | } 187 | return PySecret::to_python(&self->instance->get_secret()); 188 | } 189 | 190 | static PyObject * PyDoodad_get_const(PyObject *, PyObject *) { 191 | return PyDoodad::to_python(Doodad::get_const()); 192 | } 193 | 194 | static PyMethodDef PyDoodad_methods[] = { 195 | {"clone", (PyCFunction)&PyDoodad_clone, METH_NOARGS, 196 | "Return a copy of the Doodad."}, 197 | {"read", (PyCFunction)&PyDoodad_read, METH_O, 198 | "Read a WhatsIt into this Doodad."}, 199 | {"write", (PyCFunction)&PyDoodad_write, METH_NOARGS, 200 | "Write this Doodad into a WhatsIt."}, 201 | {"get_secret", (PyCFunction)&PyDoodad_get_secret, METH_NOARGS, 202 | "Extract the Secret from this Doodad."}, 203 | {"get_const", (PyCFunction)&PyDoodad_get_const, METH_NOARGS | METH_STATIC, 204 | "Return a Doodad that cannot be modified."}, 205 | {nullptr} 206 | }; 207 | 208 | static PyObject * PyDoodad_get_name(Py * self, void *) { 209 | if (!self->instance) { 210 | PyErr_SetString(PyExc_TypeError, "Uninitialized Doodad"); 211 | return nullptr; 212 | } 213 | return PyString_FromStringAndSize( 214 | self->instance->name.data(), 215 | self->instance->name.size() 216 | ); 217 | } 218 | 219 | static int PyDoodad_set_name(Py * self, PyObject * name, void *) { 220 | if (!self->instance) { 221 | PyErr_SetString(PyExc_TypeError, "Uninitialized Doodad"); 222 | return -1; 223 | } 224 | if (self->frozen) { 225 | PyErr_SetString(PyExc_TypeError, "Cannot modify frozen Doodad."); 226 | return -1; 227 | } 228 | Py_ssize_t length = 0; 229 | char * buffer; 230 | if (PyString_AsStringAndSize(name, &buffer, &length) < 0) return -1; 231 | self->instance->name = std::string(buffer, length); 232 | return 0; 233 | } 234 | 235 | static PyObject * PyDoodad_get_value(Py * self, void *) { 236 | if (!self->instance) { 237 | PyErr_SetString(PyExc_TypeError, "Uninitialized Doodad"); 238 | return nullptr; 239 | } 240 | return PyInt_FromLong(self->instance->value); 241 | } 242 | 243 | static int PyDoodad_set_value(Py * self, PyObject * value, void *) { 244 | if (!self->instance) { 245 | PyErr_SetString(PyExc_TypeError, "Uninitialized Doodad"); 246 | return -1; 247 | } 248 | if (self->frozen) { 249 | PyErr_SetString(PyExc_TypeError, "Cannot modify frozen Doodad."); 250 | return -1; 251 | } 252 | long tmp = PyInt_AsLong(value); 253 | if (tmp == -1 && PyErr_Occurred()) return -1; 254 | self->instance->value = tmp; 255 | return 0; 256 | } 257 | 258 | struct PyGetSetDef PyDoodad_getset[] = { 259 | { 260 | const_cast("name"), 261 | (getter)PyDoodad_get_name,(setter)PyDoodad_set_name, 262 | const_cast("name of the Doodad"), nullptr 263 | }, { 264 | const_cast("value"), 265 | (getter)PyDoodad_get_value, (setter)PyDoodad_set_value, 266 | const_cast("value of the Doodad"), nullptr 267 | }, { 268 | nullptr 269 | } 270 | }; 271 | 272 | PyObject * PyDoodad_richcompare(PyObject * a, PyObject * b, int op) { 273 | std::shared_ptr ca; 274 | std::shared_ptr cb; 275 | if (!PyDoodad::csptr_from_python(a, &ca)) { 276 | PyErr_Clear(); 277 | Py_INCREF(Py_NotImplemented); 278 | return Py_NotImplemented; 279 | } 280 | if (!PyDoodad::csptr_from_python(b, &cb)) { 281 | PyErr_Clear(); 282 | Py_INCREF(Py_NotImplemented); 283 | return Py_NotImplemented; 284 | } 285 | switch (op) { 286 | case Py_EQ: 287 | return PyBool_FromLong(ca == cb); 288 | case Py_NE: 289 | return PyBool_FromLong(ca != cb); 290 | default: 291 | PyErr_SetString(PyExc_TypeError, "Comparison not supported."); 292 | return nullptr; 293 | } 294 | } 295 | 296 | PyTypeObject * PyDoodad::get_type() { 297 | static PyTypeObject t = { 298 | PyObject_HEAD_INIT(NULL) 299 | 0, /*ob_size*/ 300 | "challenge.basics.Doodad", /*tp_name*/ 301 | sizeof(Py), /*tp_basicsize*/ 302 | 0, /*tp_itemsize*/ 303 | (destructor)Py::destroy, /*tp_dealloc*/ 304 | 0, /*tp_print*/ 305 | 0, /*tp_getattr*/ 306 | 0, /*tp_setattr*/ 307 | 0, /*tp_compare*/ 308 | 0, /*tp_repr*/ 309 | 0, /*tp_as_number*/ 310 | 0, /*tp_as_sequence*/ 311 | 0, /*tp_as_mapping*/ 312 | 0, /*tp_hash */ 313 | 0, /*tp_call*/ 314 | 0, /*tp_str*/ 315 | 0, /*tp_getattro*/ 316 | 0, /*tp_setattro*/ 317 | 0, /*tp_as_buffer*/ 318 | Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ 319 | "Doodad", /* tp_doc */ 320 | 0, /* tp_traverse */ 321 | 0, /* tp_clear */ 322 | PyDoodad_richcompare, /* tp_richcompare */ 323 | 0, /* tp_weaklistoffset */ 324 | 0, /* tp_iter */ 325 | 0, /* tp_iternext */ 326 | PyDoodad_methods, /* tp_methods */ 327 | 0, /* tp_members */ 328 | PyDoodad_getset, /* tp_getset */ 329 | 0, /* tp_base */ 330 | 0, /* tp_dict */ 331 | 0, /* tp_descr_get */ 332 | 0, /* tp_descr_set */ 333 | 0, /* tp_dictoffset */ 334 | (initproc)PyDoodad_init, /* tp_init */ 335 | 0, /* tp_alloc */ 336 | (newfunc)PyDoodad_new, /* tp_new */ 337 | }; 338 | return &t; 339 | } 340 | 341 | bool PyDoodad::check(PyObject * p) { 342 | int r = PyObject_IsSubclass( 343 | (PyObject*)p->ob_type, 344 | (PyObject*)PyDoodad::get_type() 345 | ); 346 | if (r < 0) { 347 | PyErr_Clear(); 348 | return false; 349 | } 350 | return r; 351 | } 352 | 353 | PyObject * PyDoodad::to_python(std::shared_ptr s) { 354 | if (!s) { 355 | Py_RETURN_NONE; 356 | } 357 | PyTypeObject * type = get_tree()->find(s.get()); 358 | Py * result = PyDoodad_new(type, nullptr, nullptr); 359 | if (result) { 360 | result->instance = std::move(s); 361 | } 362 | return (PyObject*)result; 363 | } 364 | 365 | PyObject * PyDoodad::to_python(std::shared_ptr s) { 366 | if (!s) { 367 | Py_RETURN_NONE; 368 | } 369 | PyTypeObject * type = get_tree()->find(s.get()); 370 | Py * result = PyDoodad_new(type, nullptr, nullptr); 371 | if (result) { 372 | result->instance = std::const_pointer_cast(std::move(s)); 373 | result->frozen = true; 374 | } 375 | return (PyObject*)result; 376 | } 377 | 378 | bool PyDoodad::sptr_from_python(PyObject * p, std::shared_ptr * s) { 379 | if (check(p)) { 380 | Py * d = reinterpret_cast*>(p); 381 | *reinterpret_cast*>(s) = d->instance; 382 | if (d->frozen) { 383 | PyErr_SetString(PyExc_TypeError, "Non-frozen Doodad required."); 384 | return false; 385 | } 386 | return true; 387 | } 388 | if (p == Py_None) { 389 | s->reset(); 390 | return true; 391 | } 392 | PyErr_SetString(PyExc_TypeError, "Could not convert object to Doodad."); 393 | return false; 394 | } 395 | 396 | bool PyDoodad::csptr_from_python( 397 | PyObject * p, std::shared_ptr * s 398 | ) { 399 | if (check(p)) { 400 | Py * d = reinterpret_cast*>(p); 401 | *reinterpret_cast*>(s) = d->instance; 402 | return true; 403 | } 404 | if (p == Py_None) { 405 | s->reset(); 406 | return true; 407 | } 408 | PyErr_SetString(PyExc_TypeError, "Could not convert object to Doodad."); 409 | return false; 410 | } 411 | 412 | utilities::InheritanceTree * PyDoodad::get_tree() { 413 | static std::unique_ptr tree = 414 | utilities::InheritanceTree::make_root(get_type()); 415 | return tree.get(); 416 | } 417 | 418 | } // namespace basics 419 | 420 | // ---------------------------------------------------------------------------- 421 | // Module Bindings 422 | // ---------------------------------------------------------------------------- 423 | 424 | static PyMethodDef methods[] = { 425 | {"compare", &basics::Py_compare, METH_VARARGS, 426 | "Return true if two Secrets are the same."}, 427 | {"adjacent", &basics::Py_adjacent, METH_VARARGS, 428 | "Return true if two Secrets were constructed consecutively."}, 429 | {nullptr} 430 | }; 431 | 432 | extern "C" { 433 | 434 | #ifndef PyMODINIT_FUNC /* declarations for DLL import/export */ 435 | #define PyMODINIT_FUNC void 436 | #endif 437 | PyMODINIT_FUNC 438 | initbasics(void) { 439 | if (PyType_Ready(basics::PyDoodad::get_type()) < 0) return; 440 | 441 | PyObject* m = Py_InitModule3( 442 | "basics", methods, 443 | "wrappers for classes in basics.hpp" 444 | ); 445 | 446 | if (!m) return; 447 | 448 | Py_INCREF(basics::PyDoodad::get_type()); 449 | PyModule_AddObject(m, "Doodad", (PyObject*)basics::PyDoodad::get_type()); 450 | } 451 | 452 | } // extern "C" 453 | -------------------------------------------------------------------------------- /python2-c-api/challenge/py_containers.cpp: -------------------------------------------------------------------------------- 1 | #include "py_containers.hpp" 2 | 3 | using basics::Doodad; 4 | using basics::PyDoodad; 5 | using utilities::Py; 6 | 7 | namespace utilities { 8 | 9 | template class Py; 10 | 11 | } // namespace utilities 12 | 13 | namespace containers { 14 | 15 | static Py * PyDoodadSet_new( 16 | PyTypeObject * type, PyObject *, PyObject * 17 | ) { 18 | return Py::create(type); 19 | } 20 | 21 | static int PyDoodadSet_init( 22 | Py * self, PyObject * args, PyObject * kwds 23 | ) { 24 | static char const * kwd_names[] = {"other", nullptr}; 25 | std::shared_ptr other; 26 | if ( 27 | PyArg_ParseTupleAndKeywords( 28 | args, kwds, "|O&", const_cast(kwd_names), 29 | &PyDoodadSet::csptr_from_python, &other 30 | ) 31 | ) { 32 | if (other) { 33 | self->instance = std::shared_ptr(new DoodadSet(*other)); 34 | } else if (PyErr_Occurred()) { 35 | return -1; 36 | } else { 37 | self->instance = std::shared_ptr(new DoodadSet()); 38 | } 39 | return 0; 40 | } 41 | return -1; 42 | } 43 | 44 | Py_ssize_t PyDoodadSet_Size(Py * self) { 45 | if (!self->instance) { 46 | PyErr_SetString(PyExc_TypeError, "Uninitialized DoodadSet"); 47 | return -1; 48 | } 49 | return self->instance->size(); 50 | } 51 | 52 | static PySequenceMethods PyDoodadSet_sequence = { 53 | (lenfunc)PyDoodadSet_Size 54 | }; 55 | 56 | static PyMappingMethods PyDoodadSet_mapping = { 57 | (lenfunc)PyDoodadSet_Size 58 | }; 59 | 60 | PyObject * PyDoodadSet_as_list(Py * self, PyObject *) { 61 | if (!self->instance) { 62 | PyErr_SetString(PyExc_TypeError, "Uninitialized DoodadSet"); 63 | return nullptr; 64 | } 65 | PyObject * result = PyList_New(self->instance->size()); 66 | if (!result) return nullptr; 67 | Py_ssize_t index = 0; 68 | for ( 69 | auto iter = self->instance->begin(); 70 | iter != self->instance->end(); 71 | ++iter, ++index 72 | ) { 73 | PyObject * py_item = PyDoodad::to_python(*iter); 74 | if (py_item) { 75 | PyList_SET_ITEM(result, index, py_item); 76 | } else { 77 | Py_DECREF(result); 78 | return nullptr; 79 | } 80 | } 81 | return result; 82 | } 83 | 84 | PyObject * PyDoodadSet_assign(Py * self, PyObject * sequence) { 85 | if (!self->instance) { 86 | PyErr_SetString(PyExc_TypeError, "Uninitialized DoodadSet"); 87 | return nullptr; 88 | } 89 | std::vector> vector; 90 | Py_ssize_t len = PyObject_Size(sequence); 91 | if (len < 0) { 92 | PyErr_Clear(); 93 | } else { 94 | vector.reserve(len); 95 | } 96 | PyObject * iterator = PyObject_GetIter(sequence); 97 | if (!iterator) return nullptr; 98 | while (PyObject * item = PyIter_Next(iterator)) { 99 | std::shared_ptr d; 100 | if (PyDoodad::sptr_from_python(item, &d)) { 101 | vector.push_back(std::move(d)); 102 | } else { 103 | Py_DECREF(item); 104 | break; 105 | } 106 | Py_DECREF(item); 107 | } 108 | Py_DECREF(iterator); 109 | if (PyErr_Occurred()) { 110 | return nullptr; 111 | } 112 | self->instance->assign(vector); 113 | Py_RETURN_NONE; 114 | } 115 | 116 | PyObject * PyDoodadSet_add(Py * self, PyObject * item) { 117 | if (!self->instance) { 118 | PyErr_SetString(PyExc_TypeError, "Uninitialized DoodadSet"); 119 | return nullptr; 120 | } 121 | std::shared_ptr d; 122 | if (!PyDoodad::sptr_from_python(item, &d)) { 123 | PyErr_Clear(); 124 | basics::WhatsIt it; 125 | if (basics::PyWhatsIt::from_python(item, &it)) { 126 | d.reset(new Doodad(it)); 127 | } 128 | } 129 | self->instance->add(d); 130 | Py_RETURN_NONE; 131 | } 132 | 133 | PyObject * PyDoodadSet_as_dict(Py * self, PyObject *) { 134 | if (!self->instance) { 135 | PyErr_SetString(PyExc_TypeError, "Uninitialized DoodadSet"); 136 | return nullptr; 137 | } 138 | PyObject * result = PyDict_New(); 139 | if (!result) return nullptr; 140 | for (auto c_item : *self->instance) { 141 | PyObject * py_item = PyDoodad::to_python(c_item); 142 | if (py_item) { 143 | PyObject * key = PyString_FromStringAndSize( 144 | c_item->name.data(), c_item->name.size() 145 | ); 146 | if (!key) { 147 | Py_DECREF(py_item); 148 | Py_DECREF(result); 149 | return nullptr; 150 | } 151 | PyDict_SetItem(result, key, py_item); 152 | Py_DECREF(key); 153 | Py_DECREF(py_item); 154 | } else { 155 | Py_DECREF(result); 156 | return nullptr; 157 | } 158 | } 159 | return result; 160 | } 161 | 162 | static PyMethodDef PyDoodadSet_methods[] = { 163 | {"as_list", (PyCFunction)&PyDoodadSet_as_list, METH_NOARGS, 164 | "Return a copy of the DoodadSet as a list."}, 165 | {"assign", (PyCFunction)&PyDoodadSet_assign, METH_O, 166 | "Copy an arbitrary iterable into the DoodadSet."}, 167 | {"add", (PyCFunction)&PyDoodadSet_add, METH_O, 168 | "Add an item to the DoodadSet."}, 169 | {"as_dict", (PyCFunction)&PyDoodadSet_as_dict, METH_NOARGS, 170 | "Return a copy of the DoodadSet as a {str: Doodad} dict."}, 171 | {nullptr} 172 | }; 173 | 174 | PyObject * PyDoodadSet_iter(Py * self) { 175 | typedef PyObject * (*Converter)(std::shared_ptr); 176 | Converter converter = &PyDoodad::to_python; 177 | return utilities::wrapIterator( 178 | (PyObject*)self, converter, 179 | self->instance->begin(), self->instance->end() 180 | ); 181 | } 182 | 183 | PyTypeObject * PyDoodadSet::get_type() { 184 | static PyTypeObject t = { 185 | PyObject_HEAD_INIT(NULL) 186 | 0, /*ob_size*/ 187 | "challenge.containers.DoodadSet", /*tp_name*/ 188 | sizeof(Py), /*tp_basicsize*/ 189 | 0, /*tp_itemsize*/ 190 | (destructor)Py::destroy, /*tp_dealloc*/ 191 | 0, /*tp_print*/ 192 | 0, /*tp_getattr*/ 193 | 0, /*tp_setattr*/ 194 | 0, /*tp_compare*/ 195 | 0, /*tp_repr*/ 196 | 0, /*tp_as_number*/ 197 | &PyDoodadSet_sequence, /*tp_as_sequence*/ 198 | &PyDoodadSet_mapping, /*tp_as_mapping*/ 199 | 0, /*tp_hash */ 200 | 0, /*tp_call*/ 201 | 0, /*tp_str*/ 202 | 0, /*tp_getattro*/ 203 | 0, /*tp_setattro*/ 204 | 0, /*tp_as_buffer*/ 205 | Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ 206 | "DoodadSet", /* tp_doc */ 207 | 0, /* tp_traverse */ 208 | 0, /* tp_clear */ 209 | 0, /* tp_richcompare */ 210 | 0, /* tp_weaklistoffset */ 211 | (getiterfunc)PyDoodadSet_iter, /* tp_iter */ 212 | 0, /* tp_iternext */ 213 | PyDoodadSet_methods, /* tp_methods */ 214 | 0, /* tp_members */ 215 | 0, /* tp_getset */ 216 | 0, /* tp_base */ 217 | 0, /* tp_dict */ 218 | 0, /* tp_descr_get */ 219 | 0, /* tp_descr_set */ 220 | 0, /* tp_dictoffset */ 221 | (initproc)PyDoodadSet_init,/* tp_init */ 222 | 0, /* tp_alloc */ 223 | (newfunc)PyDoodadSet_new, /* tp_new */ 224 | }; 225 | return &t; 226 | } 227 | 228 | bool PyDoodadSet::check(PyObject * p) { 229 | int r = PyObject_IsSubclass( 230 | (PyObject*)p->ob_type, 231 | (PyObject*)PyDoodadSet::get_type() 232 | ); 233 | if (r < 0) { 234 | PyErr_Clear(); 235 | return false; 236 | } 237 | return r; 238 | } 239 | 240 | PyObject * PyDoodadSet::to_python(std::shared_ptr s) { 241 | if (!s) { 242 | Py_RETURN_NONE; 243 | } 244 | Py * result = Py::create(PyDoodadSet::get_type()); 245 | if (result) { 246 | result->instance = std::move(s); 247 | } 248 | return (PyObject*)result; 249 | } 250 | 251 | PyObject * PyDoodadSet::to_python(std::shared_ptr s) { 252 | if (!s) { 253 | Py_RETURN_NONE; 254 | } 255 | Py * result = Py::create(PyDoodadSet::get_type()); 256 | if (result) { 257 | result->instance = std::const_pointer_cast(std::move(s)); 258 | result->frozen = true; 259 | } 260 | return (PyObject*)result; 261 | } 262 | 263 | bool PyDoodadSet::sptr_from_python( 264 | PyObject * p, std::shared_ptr * s 265 | ) { 266 | if (check(p)) { 267 | Py * d = reinterpret_cast*>(p); 268 | *reinterpret_cast*>(s) = d->instance; 269 | if (d->frozen) { 270 | PyErr_SetString(PyExc_TypeError, "Non-frozen DoodadSet required."); 271 | return false; 272 | } 273 | return true; 274 | } 275 | if (p == Py_None) { 276 | s->reset(); 277 | return true; 278 | } 279 | PyErr_SetString(PyExc_TypeError, "Could not convert object to DoodadSet."); 280 | return false; 281 | } 282 | 283 | bool PyDoodadSet::csptr_from_python( 284 | PyObject * p, std::shared_ptr * s 285 | ) { 286 | if (check(p)) { 287 | Py * d = reinterpret_cast*>(p); 288 | *reinterpret_cast*>(s) = d->instance; 289 | return true; 290 | } 291 | if (p == Py_None) { 292 | s->reset(); 293 | return true; 294 | } 295 | PyErr_SetString(PyExc_TypeError, "Could not convert object to DoodadSet."); 296 | return false; 297 | } 298 | 299 | } // namespace containers 300 | 301 | // ---------------------------------------------------------------------------- 302 | // Module Bindings 303 | // ---------------------------------------------------------------------------- 304 | 305 | static PyMethodDef methods[] = { 306 | {nullptr} 307 | }; 308 | 309 | extern "C" { 310 | 311 | #ifndef PyMODINIT_FUNC /* declarations for DLL import/export */ 312 | #define PyMODINIT_FUNC void 313 | #endif 314 | PyMODINIT_FUNC 315 | initcontainers(void) { 316 | PyObject * basics_module = PyImport_ImportModule("challenge.basics"); 317 | if (!basics_module) return; 318 | 319 | if (PyType_Ready(containers::PyDoodadSet::get_type()) < 0) return; 320 | 321 | Py_INCREF((PyObject*)basics::PyDoodad::get_type()); 322 | if ( 323 | PyDict_SetItemString( 324 | containers::PyDoodadSet::get_type()->tp_dict, 325 | "Item", 326 | (PyObject*)basics::PyDoodad::get_type() 327 | ) < 0 328 | ) return; 329 | 330 | PyObject* m = Py_InitModule3( 331 | "containers", methods, 332 | "wrappers for classes in containers.hpp" 333 | ); 334 | 335 | if (!m) return; 336 | 337 | Py_INCREF(containers::PyDoodadSet::get_type()); 338 | PyModule_AddObject( 339 | m, "DoodadSet", 340 | (PyObject*)containers::PyDoodadSet::get_type() 341 | ); 342 | } 343 | 344 | } // extern "C" 345 | -------------------------------------------------------------------------------- /python2-c-api/challenge/py_extensions.cpp: -------------------------------------------------------------------------------- 1 | #include "py_extensions.hpp" 2 | 3 | using basics::Doodad; 4 | using basics::PyDoodad; 5 | using utilities::Py; 6 | 7 | namespace utilities { 8 | 9 | extern template class Py; 10 | 11 | } // namespace utilities 12 | 13 | namespace extensions { 14 | 15 | // ---------------------------------------------------------------------------- 16 | // Thingamajig 17 | // ---------------------------------------------------------------------------- 18 | 19 | static int PyThingamajig_init( 20 | Py * self, PyObject * args, PyObject * kwds 21 | ) { 22 | static char const * kwd_names[] = { 23 | "extra", "name", "value", "dtype", nullptr 24 | }; 25 | PyObject * extra = nullptr; 26 | int name_size = 0; 27 | int value = 1; 28 | const char * name_str = nullptr; 29 | PyObject * dtype = nullptr; 30 | if ( 31 | PyArg_ParseTupleAndKeywords( 32 | args, kwds, "Os#|iO", const_cast(kwd_names), 33 | &extra, &name_str, &name_size, &value, &dtype 34 | ) 35 | ) { 36 | std::string name(name_str, name_size); 37 | if (!dtype) { 38 | if (PyObject_IsInstance(extra, (PyObject*)PyDoodad::get_type())) { 39 | dtype = (PyObject*)PyDoodad::get_type(); 40 | } else if (PyNumber_Check(extra)) { 41 | dtype = (PyObject*)&PyFloat_Type; 42 | } else { 43 | PyErr_SetString( 44 | PyExc_TypeError, 45 | "Unrecognized type for extra argument to Thingamajig." 46 | ); 47 | return -1; 48 | } 49 | } 50 | if (dtype == (PyObject*)PyDoodad::get_type()) { 51 | std::shared_ptr ex; 52 | if (!PyDoodad::sptr_from_python(extra, &ex)) { 53 | return -1; 54 | } 55 | std::shared_ptr instance( 56 | new Thingamajig>(ex, name, value) 57 | ); 58 | self->instance = std::move(instance); 59 | return 0; 60 | } else if (dtype == (PyObject*)&PyFloat_Type) { 61 | double ex = PyFloat_AsDouble(extra); 62 | if (ex == -1.0 && PyErr_Occurred()) { 63 | return -1; 64 | } 65 | std::shared_ptr instance( 66 | new Thingamajig(ex, name, value) 67 | ); 68 | self->instance = std::move(instance); 69 | return 0; 70 | } else { 71 | PyErr_SetString(PyExc_TypeError, "Unrecognized dtype."); 72 | return -1; 73 | } 74 | } 75 | return -1; 76 | } 77 | 78 | static PyObject * PyThingamajig_get_extra(Py * self, PyObject *) { 79 | typedef Thingamajig> T1; 80 | typedef Thingamajig T2; 81 | if (auto t = std::dynamic_pointer_cast(self->instance)) { 82 | return PyDoodad::to_python(t->get_extra()); 83 | } 84 | if (auto t = std::dynamic_pointer_cast(self->instance)) { 85 | return PyFloat_FromDouble(t->get_extra()); 86 | } 87 | PyErr_SetString(PyExc_SystemError, "Object is not a valid Thingamajig."); 88 | return nullptr; 89 | } 90 | 91 | static PyMethodDef PyThingamajig_methods[] = { 92 | {"get_extra", (PyCFunction)&PyThingamajig_get_extra, METH_NOARGS, 93 | "Return the extra object attached to the Thingamajig."}, 94 | {nullptr} 95 | }; 96 | 97 | static PyObject * PyThingamajig_get_dtype(Py * self, void *) { 98 | typedef Thingamajig> T1; 99 | typedef Thingamajig T2; 100 | PyObject * result = nullptr; 101 | if (auto t = std::dynamic_pointer_cast(self->instance)) { 102 | result = (PyObject*)PyDoodad::get_type(); 103 | } 104 | if (auto t = std::dynamic_pointer_cast(self->instance)) { 105 | result = (PyObject*)&PyFloat_Type; 106 | } 107 | Py_INCREF(result); 108 | return result; 109 | } 110 | 111 | struct PyGetSetDef PyThingamajig_getset[] = { 112 | {const_cast("dtype"), (getter)PyThingamajig_get_dtype, nullptr, 113 | const_cast("dtype of the Thingamajig"), nullptr}, 114 | {nullptr} 115 | }; 116 | 117 | PyTypeObject * PyThingamajig::get_type() { 118 | static PyTypeObject t = { 119 | PyObject_HEAD_INIT(NULL) 120 | 0, /*ob_size*/ 121 | "challenge.extensions.Thingamajig", /*tp_name*/ 122 | 0, /*tp_basicsize*/ 123 | 0, /*tp_itemsize*/ 124 | 0, /*tp_dealloc*/ 125 | 0, /*tp_print*/ 126 | 0, /*tp_getattr*/ 127 | 0, /*tp_setattr*/ 128 | 0, /*tp_compare*/ 129 | 0, /*tp_repr*/ 130 | 0, /*tp_as_number*/ 131 | 0, /*tp_as_sequence*/ 132 | 0, /*tp_as_mapping*/ 133 | 0, /*tp_hash */ 134 | 0, /*tp_call*/ 135 | 0, /*tp_str*/ 136 | 0, /*tp_getattro*/ 137 | 0, /*tp_setattro*/ 138 | 0, /*tp_as_buffer*/ 139 | Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ 140 | "Thingamajig", /* tp_doc */ 141 | 0, /* tp_traverse */ 142 | 0, /* tp_clear */ 143 | 0, /* tp_richcompare */ 144 | 0, /* tp_weaklistoffset */ 145 | 0, /* tp_iter */ 146 | 0, /* tp_iternext */ 147 | PyThingamajig_methods, /* tp_methods */ 148 | 0, /* tp_members */ 149 | PyThingamajig_getset, /* tp_getset */ 150 | PyDoodad::get_type(), /* tp_base */ 151 | 0, /* tp_dict */ 152 | 0, /* tp_descr_get */ 153 | 0, /* tp_descr_set */ 154 | 0, /* tp_dictoffset */ 155 | (initproc)PyThingamajig_init, /* tp_init */ 156 | 0, /* tp_alloc */ 157 | 0, /* tp_new */ 158 | }; 159 | return &t; 160 | } 161 | 162 | bool PyThingamajig::check(PyObject * p) { 163 | int r = PyObject_IsSubclass( 164 | (PyObject*)p->ob_type, 165 | (PyObject*)PyThingamajig::get_type() 166 | ); 167 | if (r < 0) { 168 | PyErr_Clear(); 169 | return false; 170 | } 171 | return r; 172 | } 173 | 174 | template 175 | bool PyThingamajig::sptr_from_python( 176 | PyObject * p, 177 | std::shared_ptr> * s 178 | ) { 179 | std::shared_ptr d; 180 | if (!PyDoodad::sptr_from_python(p, &d)) { 181 | return false; 182 | } 183 | *s = std::dynamic_pointer_cast>(d); 184 | if (!s) { 185 | PyErr_SetString(PyExc_TypeError, "Incorrect type for Thingamajig."); 186 | return false; 187 | } 188 | return true; 189 | } 190 | 191 | template 192 | bool PyThingamajig::csptr_from_python( 193 | PyObject * p, 194 | std::shared_ptr const> * s 195 | ) { 196 | std::shared_ptr d; 197 | if (!PyDoodad::csptr_from_python(p, &d)) { 198 | return false; 199 | } 200 | *s = std::dynamic_pointer_cast const>(d); 201 | if (!s) { 202 | PyErr_SetString(PyExc_TypeError, "Incorrect type for Thingamajig."); 203 | return false; 204 | } 205 | return true; 206 | } 207 | 208 | #define INSTANTIATE(T) \ 209 | template bool PyThingamajig::sptr_from_python( \ 210 | PyObject * p, \ 211 | std::shared_ptr> * s \ 212 | ); \ 213 | template bool PyThingamajig::csptr_from_python( \ 214 | PyObject * p, \ 215 | std::shared_ptr const> * s \ 216 | ) 217 | 218 | INSTANTIATE(Doodad); 219 | INSTANTIATE(double); 220 | 221 | } // namespace extensions 222 | 223 | // ---------------------------------------------------------------------------- 224 | // Module Bindings 225 | // ---------------------------------------------------------------------------- 226 | 227 | static PyMethodDef methods[] = { 228 | {nullptr} 229 | }; 230 | 231 | extern "C" { 232 | 233 | #ifndef PyMODINIT_FUNC /* declarations for DLL import/export */ 234 | #define PyMODINIT_FUNC void 235 | #endif 236 | PyMODINIT_FUNC 237 | initextensions(void) { 238 | 239 | if (PyType_Ready(extensions::PyThingamajig::get_type()) < 0) return; 240 | 241 | typedef extensions::Thingamajig> T1; 242 | typedef extensions::Thingamajig T2; 243 | 244 | PyDoodad::register_subclass(extensions::PyThingamajig::get_type()); 245 | PyDoodad::register_subclass(extensions::PyThingamajig::get_type()); 246 | 247 | PyObject* m = Py_InitModule3( 248 | "extensions", methods, 249 | "wrappers for classes in extensions.hpp" 250 | ); 251 | 252 | if (!m) return; 253 | 254 | Py_INCREF(extensions::PyThingamajig::get_type()); 255 | PyModule_AddObject( 256 | m, "Thingamajig", 257 | (PyObject*)extensions::PyThingamajig::get_type() 258 | ); 259 | } 260 | 261 | } // extern "C" 262 | -------------------------------------------------------------------------------- /python2-c-api/challenge/utilities.cpp: -------------------------------------------------------------------------------- 1 | #include "utilities.hpp" 2 | 3 | namespace utilities { 4 | 5 | InheritanceTree::InheritanceTree(PyTypeObject * type) : 6 | _type(type), 7 | _sibling(nullptr), 8 | _child(nullptr) 9 | {} 10 | 11 | PyTypeObject * InheritanceTree::find(void const * v) const { 12 | if (_try_cast(v)) { 13 | if (_child) { 14 | PyTypeObject * r = _child->find(v); 15 | if (r) return r; 16 | } 17 | return _type; 18 | } 19 | if (_sibling) { 20 | return _sibling->find(v); 21 | } 22 | return nullptr; 23 | } 24 | 25 | InheritanceTree * InheritanceTree::_add_child( 26 | std::unique_ptr child 27 | ) { 28 | if (_child) { 29 | return _child->_add_sibling(std::move(child)); 30 | } 31 | _child = std::move(child); 32 | return _child.get(); 33 | } 34 | 35 | InheritanceTree * InheritanceTree::_add_sibling( 36 | std::unique_ptr sibling 37 | ) { 38 | if (_sibling) { 39 | return _sibling->_add_sibling(std::move(sibling)); 40 | } 41 | _sibling = std::move(sibling); 42 | return _sibling.get(); 43 | } 44 | 45 | struct WrappedIterator { 46 | PyObject_HEAD 47 | PyObject * owner; 48 | std::unique_ptr holder; 49 | }; 50 | 51 | static void WrappedIterator_destroy(WrappedIterator * self) { 52 | Py_XDECREF(self->owner); 53 | typedef std::unique_ptr Holder; 54 | self->holder.~Holder(); 55 | self->ob_type->tp_free((PyObject*)self); 56 | } 57 | 58 | static PyObject * WrappedIterator_iter(PyObject * self) { 59 | Py_INCREF(self); 60 | return self; 61 | } 62 | 63 | static PyObject * WrappedIterator_next(WrappedIterator * self) { 64 | return self->holder->next(); 65 | } 66 | 67 | static PyTypeObject WrappedIterator_Type = { 68 | PyObject_HEAD_INIT(NULL) 69 | 0, /*ob_size*/ 70 | "challenge.basics.Iterator", /*tp_name*/ 71 | sizeof(WrappedIterator), /*tp_basicsize*/ 72 | 0, /*tp_itemsize*/ 73 | (destructor)WrappedIterator_destroy, /*tp_dealloc*/ 74 | 0, /*tp_print*/ 75 | 0, /*tp_getattr*/ 76 | 0, /*tp_setattr*/ 77 | 0, /*tp_compare*/ 78 | 0, /*tp_repr*/ 79 | 0, /*tp_as_number*/ 80 | 0, /*tp_as_sequence*/ 81 | 0, /*tp_as_mapping*/ 82 | 0, /*tp_hash */ 83 | 0, /*tp_call*/ 84 | 0, /*tp_str*/ 85 | 0, /*tp_getattro*/ 86 | 0, /*tp_setattro*/ 87 | 0, /*tp_as_buffer*/ 88 | Py_TPFLAGS_DEFAULT, /*tp_flags*/ 89 | "Wrapper for C++ iterators", /* tp_doc */ 90 | 0, /* tp_traverse */ 91 | 0, /* tp_clear */ 92 | 0, /* tp_richcompare */ 93 | 0, /* tp_weaklistoffset */ 94 | (getiterfunc)WrappedIterator_iter, /* tp_iter */ 95 | (iternextfunc)WrappedIterator_next, /* tp_iternext */ 96 | 0, /* tp_methods */ 97 | 0, /* tp_members */ 98 | 0, /* tp_getset */ 99 | 0, /* tp_base */ 100 | 0, /* tp_dict */ 101 | 0, /* tp_descr_get */ 102 | 0, /* tp_descr_set */ 103 | 0, /* tp_dictoffset */ 104 | 0, /* tp_init */ 105 | 0, /* tp_alloc */ 106 | 0, /* tp_new */ 107 | }; 108 | 109 | PyObject * wrapIterator( 110 | PyObject * owner, 111 | std::unique_ptr holder 112 | ) { 113 | WrappedIterator * self = 114 | (WrappedIterator*)WrappedIterator_Type.tp_alloc( 115 | &WrappedIterator_Type, 0 116 | ); 117 | self->owner = owner; 118 | new (&self->holder) std::unique_ptr(std::move(holder)); 119 | return (PyObject*)self; 120 | } 121 | 122 | } // utilities 123 | 124 | // ---------------------------------------------------------------------------- 125 | // Module Bindings 126 | // ---------------------------------------------------------------------------- 127 | 128 | static PyMethodDef methods[] = { 129 | {nullptr} 130 | }; 131 | 132 | extern "C" { 133 | 134 | #ifndef PyMODINIT_FUNC /* declarations for DLL import/export */ 135 | #define PyMODINIT_FUNC void 136 | #endif 137 | PyMODINIT_FUNC 138 | initutilities(void) { 139 | if (PyType_Ready(&utilities::WrappedIterator_Type) < 0) return; 140 | 141 | PyObject* m = Py_InitModule3( 142 | "utilities", methods, 143 | "utilities for wrapping C++ classes" 144 | ); 145 | 146 | if (!m) return; 147 | 148 | Py_INCREF(&utilities::WrappedIterator_Type); 149 | PyModule_AddObject(m, "WrappedIterator", 150 | (PyObject*)&utilities::WrappedIterator_Type); 151 | } 152 | 153 | } // extern "C" 154 | -------------------------------------------------------------------------------- /python2-c-api/include/basics_typemaps.i: -------------------------------------------------------------------------------- 1 | %{ 2 | #include "py_basics.hpp" 3 | %} 4 | 5 | %typemap(out) std::shared_ptr { 6 | $result = basics::PyDoodad::to_python($1); 7 | } 8 | 9 | %typemap(out) std::shared_ptr { 10 | $result = basics::PyDoodad::to_python($1); 11 | } 12 | 13 | %typemap(in) basics::Doodad const & 14 | (std::shared_ptr tmp) 15 | { 16 | if (!basics::PyDoodad::csptr_from_python($input, &tmp)) { 17 | return nullptr; 18 | } 19 | // const_cast below shouldn't be necessary; it's a Swig weirdness. 20 | $1 = const_cast(tmp.get()); 21 | } 22 | 23 | %typemap(in) basics::Doodad & (std::shared_ptr tmp) { 24 | if (!basics::PyDoodad::sptr_from_python($input, &tmp)) { 25 | return nullptr; 26 | } 27 | $1 = tmp.get(); 28 | } 29 | 30 | %typemap(in) std::shared_ptr { 31 | if (!basics::PyDoodad::sptr_from_python($input, &$1)) { 32 | return nullptr; 33 | } 34 | } 35 | 36 | %typemap(in) std::shared_ptr { 37 | std::shared_ptr tmp; 38 | if (!basics::PyDoodad::csptr_from_python($input, &tmp)) { 39 | return nullptr; 40 | } 41 | $1 = tmp; 42 | } 43 | -------------------------------------------------------------------------------- /python2-c-api/include/py_basics.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CHALLENGE_py_basics_hpp_INCLUDED 2 | #define CHALLENGE_py_basics_hpp_INCLUDED 3 | 4 | #include "utilities.hpp" 5 | #include "basics.hpp" 6 | 7 | namespace basics { 8 | 9 | struct PyWhatsIt { 10 | 11 | // Convert a WhatsIt to its Python representation (a tuple). 12 | static PyObject * to_python(WhatsIt const & it); 13 | 14 | // Conversion function for use with PyArg_* functions. 15 | // Read a WhatsIt into the given variable, if the given Python object 16 | // can be converted to one. Return true on success, false (with an 17 | // exception raised) on failure. 18 | static bool from_python(PyObject * p, WhatsIt * it); 19 | 20 | }; 21 | 22 | struct PySecret { 23 | 24 | // Convert a Secret to its Python representation (a Capsule). 25 | static PyObject * to_python(Secret const * s); 26 | 27 | // Conversion function for use with PyArg_* functions. 28 | // Read a Secret* into the given variable, if the given Python object 29 | // holds one. Return true on success, false (with an exception raised) 30 | // on failure. 31 | static bool cptr_from_python(PyObject * p, Secret const ** s); 32 | 33 | }; 34 | 35 | struct PyDoodad { 36 | 37 | // Python type object used to wrap Doodad. 38 | static PyTypeObject * get_type(); 39 | 40 | // Return true if the given Python object holds a Doodad. 41 | static bool check(PyObject * p); 42 | 43 | // Convert a Doodad shared_ptr to Python. 44 | static PyObject * to_python(std::shared_ptr s); 45 | 46 | // Convert a const Doodad shared_ptr to Python. 47 | static PyObject * to_python(std::shared_ptr s); 48 | 49 | // Conversion function for use with PyArg_* functions. 50 | // Read a shared_ptr into the given variable, if the given 51 | // Python object holds one. Return true on success, false (with an 52 | // exception raised) on failure. 53 | static bool sptr_from_python( 54 | PyObject * p, 55 | std::shared_ptr * s 56 | ); 57 | 58 | // Read a shared_ptr into the given variable, if the given 59 | // Python object holds one. Return true on success, false (with an 60 | // exception raised) on failure. 61 | static bool csptr_from_python( 62 | PyObject * p, 63 | std::shared_ptr * s 64 | ); 65 | 66 | static utilities::InheritanceTree * get_tree(); 67 | 68 | template 69 | static utilities::InheritanceTree * register_subclass( 70 | PyTypeObject * type 71 | ) { 72 | return get_tree()->register_subclass(type); 73 | } 74 | 75 | }; 76 | 77 | } // namespace basics 78 | 79 | #endif // !CHALLENGE_py_basics_hpp_INCLUDED 80 | -------------------------------------------------------------------------------- /python2-c-api/include/py_containers.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CHALLENGE_py_containers_hpp_INCLUDED 2 | #define CHALLENGE_py_containers_hpp_INCLUDED 3 | 4 | #include "py_basics.hpp" 5 | #include "containers.hpp" 6 | 7 | namespace containers { 8 | 9 | struct PyDoodadSet { 10 | 11 | // Python type object used to wrap Doodad. 12 | static PyTypeObject * get_type(); 13 | 14 | // Return true if the given Python object holds a DoodadSet. 15 | static bool check(PyObject * p); 16 | 17 | // Convert a Doodad shared_ptr to Python. 18 | static PyObject * to_python(std::shared_ptr s); 19 | 20 | // Convert a const Doodad shared_ptr to Python. 21 | static PyObject * to_python(std::shared_ptr s); 22 | 23 | // Conversion function for use with PyArg_* functions. 24 | // Read a shared_ptr into the given variable, if the given 25 | // Python object holds one. Return true on success, false (with an 26 | // exception raised) on failure. 27 | static bool sptr_from_python( 28 | PyObject * p, 29 | std::shared_ptr * s 30 | ); 31 | 32 | // Read a shared_ptr into the given variable, if the given 33 | // Python object holds one. Return true on success, false (with an 34 | // exception raised) on failure. 35 | static bool csptr_from_python( 36 | PyObject * p, 37 | std::shared_ptr * s 38 | ); 39 | 40 | }; 41 | 42 | } // namespace containers 43 | 44 | #endif // !CHALLENGE_py_containers_hpp_INCLUDED 45 | -------------------------------------------------------------------------------- /python2-c-api/include/py_extensions.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CHALLENGE_py_extensions_hpp_INCLUDED 2 | #define CHALLENGE_py_extensions_hpp_INCLUDED 3 | 4 | #include "py_basics.hpp" 5 | #include "extensions.hpp" 6 | 7 | namespace extensions { 8 | 9 | class PyThingamajig : public basics::PyDoodad { 10 | public: 11 | 12 | // Python type object used to wrap the base Thingamajig. 13 | static PyTypeObject * get_type(); 14 | 15 | // Return true if the given Python object holds a Thingamajig. 16 | static bool check(PyObject * p); 17 | 18 | // Conversion function for use with PyArg_* functions. 19 | // Read a shared_ptr into the given variable, if the given 20 | // Python object holds one. Return true on success, false (with an 21 | // exception raised) on failure. 22 | template 23 | static bool sptr_from_python( 24 | PyObject * p, 25 | std::shared_ptr> * s 26 | ); 27 | 28 | // Read a shared_ptr const> into the given variable, if the 29 | // given Python object holds one. Return true on success, false (with an 30 | // exception raised) on failure. 31 | template 32 | static bool csptr_from_python( 33 | PyObject * p, 34 | std::shared_ptr const> * s 35 | ); 36 | 37 | }; 38 | 39 | } // namespace extensions 40 | 41 | #endif // !CHALLENGE_py_extensions_hpp_INCLUDED 42 | -------------------------------------------------------------------------------- /python2-c-api/include/utilities.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CHALLENGE_utilities_hpp_INCLUDED 2 | #define CHALLENGE_utilities_hpp_INCLUDED 3 | 4 | #include "Python.h" 5 | #include 6 | 7 | namespace utilities { 8 | 9 | class InheritanceTree { 10 | public: 11 | 12 | explicit InheritanceTree(PyTypeObject * type); 13 | 14 | PyTypeObject * find(void const * v) const; 15 | 16 | template 17 | static std::unique_ptr make_root(PyTypeObject * type); 18 | 19 | template 20 | InheritanceTree * register_subclass(PyTypeObject * type); 21 | 22 | virtual ~InheritanceTree() {} 23 | 24 | protected: 25 | 26 | InheritanceTree * _add_child( 27 | std::unique_ptr child 28 | ); 29 | 30 | InheritanceTree * _add_sibling( 31 | std::unique_ptr sibling 32 | ); 33 | 34 | virtual bool _try_cast(void const * v) const = 0; 35 | 36 | template class Impl; 37 | 38 | PyTypeObject * _type; 39 | std::unique_ptr _sibling; 40 | std::unique_ptr _child;; 41 | }; 42 | 43 | template 44 | class InheritanceTree::Impl : public InheritanceTree { 45 | public: 46 | 47 | explicit Impl(PyTypeObject * type) : InheritanceTree(type) {} 48 | 49 | private: 50 | 51 | virtual bool _try_cast(void const * v) const { 52 | Base const * b = reinterpret_cast(v); 53 | return dynamic_cast(b); 54 | } 55 | 56 | }; 57 | 58 | template 59 | inline std::unique_ptr InheritanceTree::make_root( 60 | PyTypeObject * type 61 | ) { 62 | std::unique_ptr r(new Impl(type)); 63 | return std::move(r); 64 | } 65 | 66 | template 67 | inline InheritanceTree * InheritanceTree::register_subclass( 68 | PyTypeObject * type 69 | ) { 70 | std::unique_ptr r(new Impl(type)); 71 | return _add_child(std::move(r)); 72 | } 73 | 74 | 75 | template 76 | struct Py { 77 | PyObject_HEAD 78 | bool frozen; 79 | std::shared_ptr instance; 80 | 81 | static Py * create(PyTypeObject * type) { 82 | Py * self = (Py*)type->tp_alloc(type, 0); 83 | self->frozen = false; 84 | if (self) { 85 | new (&self->instance) std::shared_ptr(); 86 | } 87 | return self; 88 | } 89 | 90 | static void destroy(Py * self) { 91 | typedef std::shared_ptr Holder; 92 | self->instance.~Holder(); 93 | self->ob_type->tp_free((PyObject*)self); 94 | } 95 | 96 | }; 97 | 98 | 99 | class IteratorHolder { 100 | public: 101 | 102 | template 103 | static std::unique_ptr create( 104 | Converter converter, Iterator begin, Iterator end 105 | ); 106 | 107 | virtual PyObject * next() = 0; 108 | 109 | virtual ~IteratorHolder() {} 110 | 111 | protected: 112 | template class Impl; 113 | }; 114 | 115 | 116 | template 117 | class IteratorHolder::Impl : public IteratorHolder { 118 | public: 119 | 120 | Impl(Converter converter, Iterator begin, Iterator end) : 121 | _converter(std::move(converter)), 122 | _current(std::move(begin)), 123 | _end(std::move(end)) 124 | {} 125 | 126 | virtual PyObject * next() { 127 | if (_current == _end) { 128 | return nullptr; 129 | } 130 | PyObject * r = _converter(*_current); 131 | ++_current; 132 | return r; 133 | } 134 | 135 | private: 136 | Converter _converter; 137 | Iterator _current; 138 | Iterator const _end; 139 | }; 140 | 141 | 142 | template 143 | inline std::unique_ptr IteratorHolder::create( 144 | Converter converter, Iterator begin, Iterator end 145 | ) { 146 | return std::unique_ptr( 147 | new Impl( 148 | std::move(converter), std::move(begin), std::move(end) 149 | ) 150 | ); 151 | } 152 | 153 | PyObject * wrapIterator( 154 | PyObject * owner, 155 | std::unique_ptr holder 156 | ); 157 | 158 | template 159 | PyObject * wrapIterator( 160 | PyObject * owner, Converter converter, 161 | Iterator begin, Iterator end 162 | ) { 163 | return wrapIterator(owner, IteratorHolder::create(converter, begin, end)); 164 | } 165 | 166 | } // utilities 167 | 168 | #endif // !CHALLENGE_utilities_hpp_INCLUDED 169 | -------------------------------------------------------------------------------- /python2-c-api/setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | from setuptools import setup, Extension 4 | 5 | # Remove the "-Wstrict-prototypes" compiler option, which isn't valid for C++. 6 | import distutils.sysconfig 7 | cfg_vars = distutils.sysconfig.get_config_vars() 8 | for key, value in cfg_vars.items(): 9 | if type(value) == str: 10 | cfg_vars[key] = value.replace("-Wstrict-prototypes", "") 11 | 12 | kwds = dict( 13 | extra_compile_args=['-std=c++11'], 14 | include_dirs=[ 15 | os.path.join('..', 'include'), 16 | os.path.join('include') 17 | ], 18 | ) 19 | 20 | if sys.platform == "darwin": 21 | # Miniconda 3.19 provided Python on OSX is built against OSX deployment target version 10.5 22 | # this doesn't work with C++11 in libc++. Compiling without the following directive 23 | # then gives a clang: error: 24 | # invalid deployment target for -stdlib=libc++ (requires OS X 10.7 or later) 25 | kwds["extra_compile_args"].append('-mmacosx-version-min=10.7') 26 | kwds["extra_compile_args"].append('-stdlib=libc++') 27 | 28 | utilities_module = Extension( 29 | 'challenge.utilities', 30 | sources=[ 31 | os.path.join('challenge', 'utilities.cpp'), 32 | ], 33 | **kwds 34 | ) 35 | 36 | basics_module = Extension( 37 | 'challenge.basics', 38 | sources=[ 39 | os.path.join('challenge', 'py_basics.cpp'), 40 | os.path.join('challenge', 'utilities.cpp'), 41 | os.path.join('..', 'src', 'basics.cpp') 42 | ], 43 | **kwds 44 | ) 45 | 46 | extensions_module = Extension( 47 | 'challenge.extensions', 48 | sources=[ 49 | os.path.join('challenge', 'py_extensions.cpp'), 50 | os.path.join('..', 'src', 'extensions.cpp') 51 | ], 52 | **kwds 53 | ) 54 | 55 | containers_module = Extension( 56 | 'challenge.containers', 57 | sources=[ 58 | os.path.join('challenge', 'py_containers.cpp'), 59 | os.path.join('..', 'src', 'containers.cpp') 60 | ], 61 | **kwds 62 | ) 63 | 64 | converters_module = Extension( 65 | 'challenge.converters', 66 | sources=[ 67 | os.path.join('challenge', 'converters.i') 68 | ], 69 | swig_opts = ["-modern", "-c++", "-Iinclude", "-noproxy"], 70 | **kwds 71 | ) 72 | 73 | setup( 74 | name='challenge', 75 | packages=['challenge'], 76 | version='1.0', 77 | test_suite = 'tests', 78 | description='C++/Python bindings challenge with raw Python C API', 79 | ext_modules=[utilities_module, basics_module, containers_module, 80 | extensions_module, converters_module], 81 | ) 82 | -------------------------------------------------------------------------------- /python2-c-api/tests/__init__.py: -------------------------------------------------------------------------------- 1 | from . import basics_test 2 | from . import converters_test 3 | from . import extensions_test 4 | from . import containers_test 5 | -------------------------------------------------------------------------------- /python2-c-api/tests/basics_test.py: -------------------------------------------------------------------------------- 1 | ../../tests/basics_test.py -------------------------------------------------------------------------------- /python2-c-api/tests/containers_test.py: -------------------------------------------------------------------------------- 1 | ../../tests/containers_test.py -------------------------------------------------------------------------------- /python2-c-api/tests/converters_test.py: -------------------------------------------------------------------------------- 1 | ../../tests/converters_test.py -------------------------------------------------------------------------------- /python2-c-api/tests/extensions_test.py: -------------------------------------------------------------------------------- 1 | ../../tests/extensions_test.py -------------------------------------------------------------------------------- /python2-cython/challenge/__init__.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | # This sequence will get a lot cleaner for Python 3, for which the necessary 4 | # flags should all be in the os module. 5 | import ctypes 6 | flags = ctypes.RTLD_GLOBAL 7 | try: 8 | import DLFCN 9 | flags |= DLFCN.RTLD_NOW 10 | except ImportError: 11 | flags |= 0x2 # works for Linux and Mac, only platforms I care about now. 12 | sys.setdlopenflags(flags) 13 | 14 | # Ensure basics is loaded first, since we need its 15 | # symbols for anything else. 16 | from . import basics 17 | -------------------------------------------------------------------------------- /python2-cython/challenge/_basics.pxd: -------------------------------------------------------------------------------- 1 | """Declarations of C++ types for basics module to make them visible to Cython 2 | """ 3 | from libcpp.memory cimport unique_ptr 4 | from libcpp.memory cimport shared_ptr 5 | from libcpp.string cimport string 6 | from libcpp cimport bool 7 | 8 | 9 | cdef extern from "basics.hpp" namespace "basics": 10 | struct WhatsIt: 11 | string a 12 | int b 13 | 14 | cdef cppclass Secret: 15 | pass 16 | 17 | cdef cppclass Doodad: 18 | Doodad(string, int) 19 | Doodad(WhatsIt) 20 | void read(WhatsIt) 21 | WhatsIt write() 22 | unique_ptr[Doodad] clone() 23 | @staticmethod 24 | shared_ptr[const Doodad] get_const() 25 | const Secret& get_secret() 26 | string name 27 | int value 28 | 29 | cdef bool compare(Secret &a, Secret &b) 30 | cdef bool adjacent(Secret &a, Secret &b) 31 | 32 | cdef extern from "" namespace "std" nogil: 33 | # having two different declarations with the same argument 34 | # type but different return type is not allowed by Cython 35 | # fortunately not needed for this test... 36 | # cdef unique_ptr[Doodad] move(unique_ptr[Doodad]) 37 | cdef shared_ptr[Doodad] move(unique_ptr[Doodad]) 38 | cdef shared_ptr[Doodad] move(shared_ptr[Doodad]) 39 | 40 | -------------------------------------------------------------------------------- /python2-cython/challenge/_containers.pxd: -------------------------------------------------------------------------------- 1 | """Declarations of C++ types for containers module to make them visible to Cython 2 | """ 3 | from libcpp.memory cimport unique_ptr 4 | from libcpp.memory cimport shared_ptr 5 | from libcpp.string cimport string 6 | from libcpp.vector cimport vector 7 | from libcpp.map cimport map 8 | from libcpp cimport bool 9 | 10 | from _basics cimport Doodad 11 | from _basics cimport WhatsIt 12 | 13 | 14 | cdef extern from "containers.hpp" namespace "containers": 15 | cdef cppclass DoodadSet: 16 | 17 | size_t size() const 18 | vector[shared_ptr[Doodad]].const_iterator begin() const 19 | vector[shared_ptr[Doodad]].const_iterator end() const 20 | vector[shared_ptr[Doodad]] as_vector() const 21 | void assign(vector[shared_ptr[Doodad]] &items) 22 | void add(shared_ptr[Doodad] item) 23 | map[string, shared_ptr[Doodad]] as_map() const; 24 | 25 | -------------------------------------------------------------------------------- /python2-cython/challenge/basics.pxd: -------------------------------------------------------------------------------- 1 | """Declarations of Python extension types for basics module 2 | """ 3 | from libcpp.memory cimport shared_ptr 4 | from libcpp.string cimport string 5 | 6 | from _basics cimport Doodad as _Doodad 7 | 8 | 9 | cdef class Doodad: 10 | 11 | cdef shared_ptr[_Doodad] thisptr 12 | -------------------------------------------------------------------------------- /python2-cython/challenge/basics.pyx: -------------------------------------------------------------------------------- 1 | """Definitions of Python extension types for basics module 2 | """ 3 | from libcpp cimport bool 4 | from libcpp.memory cimport shared_ptr 5 | from cython.operator cimport dereference as deref 6 | from cpython.object cimport Py_EQ, Py_NE 7 | 8 | from _basics cimport move 9 | from _basics cimport compare as _compare 10 | from _basics cimport adjacent as _adjacent 11 | from basics cimport Doodad 12 | from _basics cimport Doodad as _Doodad 13 | from _basics cimport Secret as _Secret 14 | from _basics cimport WhatsIt as _WhatsIt 15 | 16 | 17 | cpdef compare(Secret a, Secret b): 18 | return _compare(deref(a.thisptr), deref(b.thisptr)) 19 | 20 | 21 | cpdef adjacent(Secret a, Secret b): 22 | return _adjacent(deref(a.thisptr), deref(b.thisptr)) 23 | 24 | 25 | cdef _WhatsIt from_tuple(tuple t): 26 | """Helper function only visible from Cython. 27 | """ 28 | cdef _WhatsIt w = _WhatsIt(t[0], t[1]) 29 | return w 30 | 31 | 32 | cdef class Secret: 33 | """Opaque type only for use by Doodad. 34 | """ 35 | cdef const _Secret *thisptr 36 | 37 | 38 | cdef class Doodad: 39 | """Python interface to C++ type Doodad. 40 | 41 | Parameters 42 | ---------- 43 | name : str | tuple 44 | When tuple, a pair of name, value. Otherwise a string. 45 | value : int 46 | A value 47 | """ 48 | def __init__(self, name=None, value=1, init=True): 49 | 50 | if init: 51 | if isinstance(name, tuple): 52 | self.thisptr.reset(new _Doodad(from_tuple(name))) 53 | elif isinstance(name, str): 54 | self.thisptr.reset(new _Doodad(name, value)) 55 | else: 56 | raise TypeError("a string or tuple is required") 57 | 58 | def __richcmp__(self, other, int op): 59 | """Comparison operator 60 | 61 | provides '==' and '!=' 62 | """ 63 | if op == Py_EQ and isinstance(other, Doodad): 64 | return isEqualDD(self, other) 65 | elif op == Py_EQ and isinstance(other, ImmutableDoodad): 66 | return isEqualDI(self, other) 67 | elif op == Py_NE and isinstance(other, Doodad): 68 | return isNotEqualDD(self, other) 69 | elif op == Py_NE and isinstance(other, ImmutableDoodad): 70 | return isNotEqualDI(self, other) 71 | else: 72 | raise NotImplementedError 73 | 74 | 75 | def clone(self): 76 | """Calls C++ clone method and returns a new Python Doodad 77 | """ 78 | d = Doodad(init=False) 79 | d.thisptr = move(deref(self.thisptr).clone()) 80 | return d 81 | 82 | @staticmethod 83 | def get_const(): 84 | """Returns an ImmutableDoodad instance 85 | """ 86 | d = ImmutableDoodad(init=False) 87 | d.constptr = _Doodad.get_const() 88 | return d 89 | 90 | def read(self, t): 91 | """Read new data 92 | 93 | Parameters 94 | ---------- 95 | t : tuple 96 | (name, value) pair of (str, int) type. 97 | """ 98 | deref(self.thisptr).read(from_tuple(t)) 99 | 100 | def write(self): 101 | """Write current data 102 | 103 | Returns tuple with (name, value) 104 | """ 105 | cdef _WhatsIt w = deref(self.thisptr).write() 106 | return (w.a, w.b) 107 | 108 | def get_secret(self): 109 | """Get opaque Secret object 110 | """ 111 | s = Secret() 112 | s.thisptr = &deref(self.thisptr).get_secret() 113 | return s 114 | 115 | property name: 116 | def __get__(self): 117 | return deref(self.thisptr).name 118 | def __set__(self, _name): 119 | deref(self.thisptr).name = _name 120 | 121 | property value: 122 | def __get__(self): 123 | return deref(self.thisptr).value 124 | def __set__(self, _value): 125 | deref(self.thisptr).value = _value 126 | 127 | 128 | cdef class ImmutableDoodad: 129 | """Python interface to C++ type 'const Doodad'. 130 | 131 | Parameters 132 | ---------- 133 | name : str | tuple 134 | When tuple, a pair of name, value. Otherwise a string. 135 | value : int 136 | A value 137 | """ 138 | cdef shared_ptr[const _Doodad] constptr 139 | 140 | def __init__(self, name=None, value=1, init=True): 141 | 142 | if init: 143 | if isinstance(name, tuple): 144 | self.constptr.reset(new _Doodad(from_tuple(name))) 145 | elif isinstance(name, str): 146 | self.constptr.reset(new _Doodad(name, value)) 147 | else: 148 | raise TypeError("a string or tuple is required") 149 | 150 | def __richcmp__(self, other, int op): 151 | """Comparison operator 152 | 153 | provides '==' and '!=' 154 | """ 155 | if op == Py_EQ and isinstance(other, ImmutableDoodad): 156 | return isEqualII(self, other) 157 | elif op == Py_EQ and isinstance(other, Doodad): 158 | return isEqualID(self, other) 159 | elif op == Py_NE and isinstance(other, ImmutableDoodad): 160 | return isNotEqualII(self, other) 161 | elif op == Py_NE and isinstance(other, Doodad): 162 | return isNotEqualID(self, other) 163 | else: 164 | raise NotImplementedError 165 | 166 | @staticmethod 167 | def get_const(): 168 | """Returns an ImmutableDoodad instance 169 | """ 170 | d = ImmutableDoodad(init=False) 171 | d.constptr = _Doodad.get_const() 172 | return d 173 | 174 | def write(self): 175 | """Write current data 176 | 177 | Returns tuple with (name, value) 178 | """ 179 | cdef _WhatsIt w = deref(self.constptr).write() 180 | return (w.a, w.b) 181 | 182 | property name: 183 | def __get__(self): 184 | return deref(self.constptr).name 185 | 186 | property value: 187 | def __get__(self): 188 | return deref(self.constptr).value 189 | 190 | 191 | # Helper functions for comparison operator 192 | cdef isEqualDD(Doodad a, Doodad b): 193 | return a.thisptr.get() == b.thisptr.get() 194 | 195 | 196 | cdef isNotEqualDD(Doodad a, Doodad b): 197 | return a.thisptr.get() != b.thisptr.get() 198 | 199 | 200 | cdef isEqualII(ImmutableDoodad a, ImmutableDoodad b): 201 | return a.constptr.get() == b.constptr.get() 202 | 203 | 204 | cdef isNotEqualII(ImmutableDoodad a, ImmutableDoodad b): 205 | return a.constptr.get() != b.constptr.get() 206 | 207 | 208 | cdef isEqualDI(Doodad a, ImmutableDoodad b): 209 | return a.thisptr.get() == b.constptr.get() 210 | 211 | 212 | cdef isNotEqualDI(Doodad a, ImmutableDoodad b): 213 | return a.thisptr.get() != b.constptr.get() 214 | 215 | 216 | cdef isEqualID(ImmutableDoodad a, Doodad b): 217 | return a.constptr.get() == b.thisptr.get() 218 | 219 | 220 | cdef isNotEqualID(ImmutableDoodad a, Doodad b): 221 | return a.constptr.get() != b.thisptr.get() 222 | 223 | 224 | cdef public newDoodadFromSptr(shared_ptr[_Doodad] _d): 225 | """Create new Doodad from shared_ptr 226 | """ 227 | d = Doodad(init=False) 228 | d.thisptr = move(_d) 229 | return d 230 | 231 | 232 | cdef public newImmutableDoodadFromCsptr(shared_ptr[const _Doodad] _d): 233 | """Create new ImmutableDoodad from shared_ptr 234 | """ 235 | d = ImmutableDoodad(init=False) 236 | d.constptr = _d # should really be move, but cython doesn't like this 237 | return d 238 | 239 | 240 | # Cast might fail so marked with except + 241 | cdef public bool sptrFromDoodad(object _d, shared_ptr[_Doodad] *ptr) except + : 242 | """Get shared_ptr from input Python object if it is a Doodad 243 | """ 244 | d = _d 245 | ptr[0] = d.thisptr 246 | return True # cannot catch exception here 247 | 248 | 249 | # Cast might fail so marked with except + 250 | cdef public bool csptrFromImmutableDoodad(object _d, shared_ptr[const _Doodad] *ptr) except + : 251 | """Get shared_ptr from input Python object if it is an ImmutableDoodad 252 | """ 253 | d = _d 254 | ptr[0] = d.constptr 255 | return True # cannot catch exception here 256 | 257 | -------------------------------------------------------------------------------- /python2-cython/challenge/containers.pyx: -------------------------------------------------------------------------------- 1 | """Definitions of Python extension types for containers module 2 | """ 3 | from libcpp cimport bool 4 | from libcpp.memory cimport shared_ptr 5 | from libcpp.vector cimport vector 6 | from libcpp.string cimport string 7 | from libcpp.map cimport map 8 | from cython.operator cimport dereference as deref 9 | from cython.operator cimport preincrement as incr 10 | 11 | from basics import Doodad 12 | from basics cimport Doodad 13 | from _basics cimport move 14 | from _basics cimport Doodad as _Doodad 15 | from _containers cimport DoodadSet as _DoodadSet 16 | 17 | 18 | cdef class DoodadSet: 19 | """Python interface to C++ type DoodadSet 20 | """ 21 | cdef _DoodadSet inst 22 | cdef vector[shared_ptr[_Doodad]].const_iterator it 23 | Item = Doodad 24 | def __len__(self): 25 | return self.inst.size() 26 | 27 | def __iter__(self): 28 | # New iterators start at beginning. 29 | # Note that this is not thread-safe! 30 | # If thread safety is required represent the 31 | # iterator as a separate type or return a new object 32 | self.it = self.inst.begin() 33 | return self 34 | 35 | def __next__(self): 36 | if self.it == self.inst.end(): 37 | raise StopIteration() 38 | d = Doodad(init=False) 39 | d.thisptr = deref(self.it) 40 | incr(self.it) 41 | return d 42 | 43 | cpdef add(self, item) except +: 44 | """Add Doodad to set 45 | 46 | Parameters 47 | ---------- 48 | item : Doodad 49 | The item to add 50 | """ 51 | if isinstance(item, tuple): 52 | d = Doodad(item) 53 | else: 54 | d = item 55 | self.inst.add(d.thisptr) 56 | 57 | cpdef as_list(self): 58 | """Return Python list of objects in set 59 | 60 | Note that the new Python objects that will be created will 61 | share C++ Doodads with the set. 62 | """ 63 | cdef vector[shared_ptr[_Doodad]] v = self.inst.as_vector() 64 | results = [] 65 | for item in v: 66 | d = Doodad(init=False) 67 | d.thisptr = move(item) 68 | results.append(d) 69 | return results 70 | 71 | cpdef as_dict(self): 72 | """Return Python dict of objects in set 73 | 74 | Note that the new Python objects that will be created will 75 | share C++ Doodads with the set. 76 | """ 77 | cdef map[string, shared_ptr[_Doodad]] m = self.inst.as_map() 78 | results = {} 79 | for k in m: 80 | d = Doodad(init=False) 81 | d.thisptr = move(k.second) 82 | results[k.first] = d 83 | return results 84 | 85 | cpdef assign(self, seq) except +: 86 | """Assign Doodads to set 87 | 88 | Parameters 89 | ---------- 90 | seq : sequence 91 | Any Python sequence (e.g. list, tuple) of Doodads 92 | """ 93 | cdef vector[shared_ptr[_Doodad]] v 94 | for item in seq: 95 | d = item 96 | v.push_back(d.thisptr) 97 | self.inst.assign(v) 98 | -------------------------------------------------------------------------------- /python2-cython/challenge/converters.i: -------------------------------------------------------------------------------- 1 | %module(package="challenge") converters 2 | 3 | %{ 4 | #include "basics.hpp" 5 | #include "Python.h" 6 | %} 7 | 8 | %include "std_string.i" 9 | 10 | // Challenge solutions must provide only the following file, made available 11 | // on the Swig include path. It should contain the necessary Swig typmaps to 12 | // convert a Doodad C++ object to Python and back, for all of the functions 13 | // below. 14 | %include "basics_typemaps.i" 15 | 16 | %init %{ 17 | PyImport_ImportModule("challenge.basics"); 18 | %} 19 | 20 | %inline %{ 21 | std::shared_ptr make_sptr( 22 | std::string const & name, int value 23 | ) { 24 | return std::shared_ptr(new basics::Doodad(name, value)); 25 | } 26 | 27 | std::shared_ptr make_csptr( 28 | std::string const & name, int value 29 | ) { 30 | return std::shared_ptr( 31 | new basics::Doodad(name, value) 32 | ); 33 | } 34 | 35 | bool accept_ref( 36 | basics::Doodad & d, 37 | std::string const & name, int value 38 | ) { 39 | return d.name == name && d.value == value; 40 | } 41 | 42 | bool accept_cref( 43 | basics::Doodad const & d, 44 | std::string const & name, int value 45 | ) { 46 | return d.name == name && d.value == value; 47 | } 48 | 49 | bool accept_sptr( 50 | std::shared_ptr d, 51 | std::string const & name, int value 52 | ) { 53 | return d->name == name && d->value == value; 54 | } 55 | 56 | bool accept_csptr( 57 | std::shared_ptr d, 58 | std::string const & name, int value 59 | ) { 60 | return d->name == name && d->value == value; 61 | } 62 | 63 | %} 64 | -------------------------------------------------------------------------------- /python2-cython/include/basics_typemaps.i: -------------------------------------------------------------------------------- 1 | %{ 2 | #include "Python.h" 3 | #include "basics.hpp" 4 | #include "basics.h" 5 | %} 6 | 7 | %typemap(out) std::shared_ptr { 8 | $result = newDoodadFromSptr($1); 9 | } 10 | 11 | %typemap(out) std::shared_ptr { 12 | $result = newImmutableDoodadFromCsptr($1); 13 | } 14 | 15 | %typemap(in) basics::Doodad const & (std::shared_ptr tmp) { 16 | if (!csptrFromImmutableDoodad($input, &tmp)) { 17 | PyErr_Clear(); 18 | std::shared_ptr tmp2; 19 | if (!sptrFromDoodad($input, &tmp2)) { 20 | return nullptr; 21 | } 22 | tmp = tmp2; 23 | } 24 | $1 = const_cast(tmp.get()); 25 | } 26 | 27 | %typemap(in) basics::Doodad & (std::shared_ptr tmp) { 28 | if (!sptrFromDoodad($input, &tmp)) { 29 | return nullptr; 30 | } 31 | $1 = tmp.get(); 32 | } 33 | 34 | %typemap(in) std::shared_ptr { 35 | if (!sptrFromDoodad($input, &$1)) { 36 | return nullptr; 37 | } 38 | } 39 | 40 | %typemap(in) std::shared_ptr { 41 | std::shared_ptr tmp; 42 | if (!csptrFromImmutableDoodad($input, &tmp)) { 43 | PyErr_Clear(); 44 | std::shared_ptr tmp2; 45 | if (!sptrFromDoodad($input, &tmp2)) { 46 | return nullptr; 47 | } 48 | tmp = tmp2; 49 | } 50 | $1 = tmp; 51 | } 52 | -------------------------------------------------------------------------------- /python2-cython/setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | from setuptools import setup, Extension 4 | from Cython.Build import cythonize 5 | 6 | # Remove the "-Wstrict-prototypes" compiler option, which isn't valid for C++. 7 | import distutils.sysconfig 8 | cfg_vars = distutils.sysconfig.get_config_vars() 9 | for key, value in cfg_vars.items(): 10 | if type(value) == str: 11 | cfg_vars[key] = value.replace("-Wstrict-prototypes", "") 12 | 13 | compile_args = ['-g', '-std=c++11'] 14 | 15 | if sys.platform == 'darwin': 16 | # Miniconda 3.19 provided Python on OSX is built against OSX deployment target version 10.5 17 | # this doesn't work with C++11 in libc++. Compiling without the following directive 18 | # then gives a clang: error: 19 | # invalid deployment target for -stdlib=libc++ (requires OS X 10.7 or later) 20 | compile_args.append('-mmacosx-version-min=10.7') 21 | compile_args.append('-stdlib=libc++') 22 | 23 | basics_module = Extension('challenge.basics', 24 | sources=[ 25 | os.path.join('challenge', 'basics.pyx'), 26 | os.path.join('..', 'src', 'basics.cpp') 27 | ], 28 | include_dirs=[ 29 | os.path.join('..', 'include') 30 | ], 31 | extra_compile_args=compile_args, 32 | language='c++') 33 | 34 | containers_module = Extension('challenge.containers', 35 | sources=[ 36 | os.path.join('challenge', 'containers.pyx'), 37 | os.path.join('..', 'src', 'containers.cpp') 38 | ], 39 | include_dirs=[ 40 | os.path.join('..', 'include') 41 | ], 42 | extra_compile_args=compile_args, 43 | language='c++') 44 | 45 | converters_module = Extension( 46 | 'challenge.converters', 47 | sources=[ 48 | os.path.join('challenge', 'converters.i'), 49 | ], 50 | include_dirs=[ 51 | os.path.join('..', 'include'), 52 | os.path.join('include') 53 | ], 54 | swig_opts = ['-modern', '-c++', '-Iinclude', '-noproxy'], 55 | extra_compile_args=compile_args, 56 | ) 57 | 58 | setup( 59 | name='challenge', 60 | packages=['challenge'], 61 | version='1.0', 62 | test_suite = 'tests', 63 | description='C++/Python bindings challenge with Cython', 64 | ext_modules=cythonize(basics_module) + cythonize(containers_module) + [converters_module, ], 65 | ) 66 | -------------------------------------------------------------------------------- /python2-cython/tests/__init__.py: -------------------------------------------------------------------------------- 1 | from . import basics_test 2 | from . import converters_test 3 | from . import containers_test 4 | 5 | import unittest 6 | 7 | containers_test.DoodadSetTestCase.test_downcast = \ 8 | unittest.skip("extensions module not implemented")(containers_test.DoodadSetTestCase.test_downcast) -------------------------------------------------------------------------------- /python2-cython/tests/basics_test.py: -------------------------------------------------------------------------------- 1 | ../../tests/basics_test.py -------------------------------------------------------------------------------- /python2-cython/tests/containers_test.py: -------------------------------------------------------------------------------- 1 | ../../tests/containers_test.py -------------------------------------------------------------------------------- /python2-cython/tests/converters_test.py: -------------------------------------------------------------------------------- 1 | ../../tests/converters_test.py -------------------------------------------------------------------------------- /python2-pybind11/challenge/__init__.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | # This sequence will get a lot cleaner for Python 3, for which the necessary 4 | # flags should all be in the os module. 5 | import ctypes 6 | flags = ctypes.RTLD_GLOBAL 7 | try: 8 | import DLFCN 9 | flags |= DLFCN.RTLD_NOW 10 | except ImportError: 11 | flags |= 0x2 # works for Linux and Mac, only platforms I care about now. 12 | sys.setdlopenflags(flags) 13 | 14 | # Ensure basics modules is loaded first, since we need its 15 | # symbols for anything else. 16 | from . import basics 17 | -------------------------------------------------------------------------------- /python2-pybind11/challenge/basics.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "basics.hpp" 4 | 5 | namespace py = pybind11; 6 | 7 | PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr); 8 | 9 | PYBIND11_PLUGIN(basics) { 10 | py::module m("basics", "wrapped C++ basics module"); 11 | 12 | m.def("compare", &basics::compare); 13 | m.def("adjacent", &basics::adjacent); 14 | 15 | py::class_(m, "Secret"); 16 | 17 | py::class_>(m, "Doodad") 18 | .def(py::init(), py::arg("name"), py::arg("value") = 1) 19 | .def("__init__", 20 | [](basics::Doodad &instance, std::pair p) { 21 | new (&instance) basics::Doodad(basics::WhatsIt{p.first, p.second}); 22 | } 23 | ) 24 | .def_readwrite("name", &basics::Doodad::name) 25 | .def_readwrite("value", &basics::Doodad::value) 26 | .def_static("get_const", &basics::Doodad::get_const) // does not preserve const :( 27 | .def("clone", &basics::Doodad::clone) 28 | .def("get_secret", &basics::Doodad::get_secret, py::return_value_policy::reference_internal) 29 | .def("write", [](const basics::Doodad &d) { auto tmp = d.write(); return make_pair(tmp.a, tmp.b); }) 30 | .def("read", [](basics::Doodad &d, std::pair p) { d.read(basics::WhatsIt{p.first, p.second}) ; }); 31 | 32 | return m.ptr(); 33 | } 34 | 35 | -------------------------------------------------------------------------------- /python2-pybind11/challenge/containers.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "basics.hpp" 5 | #include "containers.hpp" 6 | 7 | namespace py = pybind11; 8 | 9 | PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr); 10 | 11 | PYBIND11_PLUGIN(containers) { 12 | py::module m("containers", "wrapped C++ containers module"); 13 | 14 | py::class_ c(m, "DoodadSet"); 15 | 16 | c.def(py::init<>()) 17 | .def("__len__", &containers::DoodadSet::size) 18 | .def("add", (void (containers::DoodadSet::*)(std::shared_ptr)) &containers::DoodadSet::add) 19 | .def("add", [](containers::DoodadSet &ds, std::pair p) { ds.add(basics::WhatsIt{p.first, p.second}) ; }) 20 | .def("__iter__", [](containers::DoodadSet &ds) { return py::make_iterator(ds.begin(), ds.end()); }, 21 | py::keep_alive<0, 1>()) 22 | .def("as_dict", &containers::DoodadSet::as_map) 23 | .def("as_list", &containers::DoodadSet::as_vector) 24 | .def("assign", &containers::DoodadSet::assign); 25 | 26 | c.attr("Item") = py::module::import("challenge.basics").attr("Doodad"); 27 | 28 | return m.ptr(); 29 | } 30 | 31 | -------------------------------------------------------------------------------- /python2-pybind11/challenge/converters.i: -------------------------------------------------------------------------------- 1 | %module(package="challenge") converters 2 | 3 | %{ 4 | #include "basics.hpp" 5 | #include 6 | 7 | /* Needed for casting to work with shared_ptr */ 8 | PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr); 9 | %} 10 | 11 | %include "std_string.i" 12 | 13 | // Challenge solutions must provide only the following file, made available 14 | // on the Swig include path. It should contain the necessary Swig typmaps to 15 | // convert a Doodad C++ object to Python and back, for all of the functions 16 | // below. 17 | %include "basics_typemaps.i" 18 | 19 | %init %{ 20 | /* Get registered types from other pybind11 modules */ 21 | pybind11::detail::get_internals(); 22 | %} 23 | 24 | %inline %{ 25 | 26 | std::shared_ptr make_sptr( 27 | std::string const & name, int value 28 | ) { 29 | return std::shared_ptr(new basics::Doodad(name, value)); 30 | } 31 | 32 | std::shared_ptr make_csptr( 33 | std::string const & name, int value 34 | ) { 35 | return std::shared_ptr( 36 | new basics::Doodad(name, value) 37 | ); 38 | } 39 | 40 | bool accept_ref( 41 | basics::Doodad & d, 42 | std::string const & name, int value 43 | ) { 44 | return d.name == name && d.value == value; 45 | } 46 | 47 | bool accept_cref( 48 | basics::Doodad const & d, 49 | std::string const & name, int value 50 | ) { 51 | return d.name == name && d.value == value; 52 | } 53 | 54 | bool accept_sptr( 55 | std::shared_ptr d, 56 | std::string const & name, int value 57 | ) { 58 | return d->name == name && d->value == value; 59 | } 60 | 61 | bool accept_csptr( 62 | std::shared_ptr d, 63 | std::string const & name, int value 64 | ) { 65 | return d->name == name && d->value == value; 66 | } 67 | 68 | %} 69 | -------------------------------------------------------------------------------- /python2-pybind11/challenge/include/basics_typemaps.i: -------------------------------------------------------------------------------- 1 | %{ 2 | #include "basics.hpp" 3 | #include 4 | %} 5 | 6 | %typemap(out) std::shared_ptr { 7 | // Use a pybind11 typecaster to create a PyObject from a shared_ptr 8 | pybind11::detail::type_caster> caster; 9 | pybind11::handle out = caster.cast($1, pybind11::return_value_policy::take_ownership, pybind11::handle()); 10 | $result = out.ptr(); 11 | } 12 | 13 | %typemap(out) std::shared_ptr { 14 | // Use a pybind11 typecaster to create a PyObject from a shared_ptr 15 | pybind11::detail::type_caster> caster; 16 | pybind11::handle out = caster.cast($1, pybind11::return_value_policy::take_ownership, pybind11::handle()); 17 | $result = out.ptr(); 18 | } 19 | 20 | %typemap(in) basics::Doodad const & (std::shared_ptr tmp) 21 | { 22 | // First make a pybind11 object handler around the PyObject* 23 | // Then, cast it to a shared_ptr using the pybind11 caster. 24 | // We declare that shared_ptr above so Swig keeps it alive for the duration 25 | // of the wrapped function. 26 | pybind11::object p{$input, true}; 27 | try { 28 | tmp = p.cast>(); 29 | $1 = const_cast(tmp.get()); 30 | } catch(...) { 31 | PyErr_SetString(PyExc_RuntimeError, "could not cast to Doodad const &"); 32 | return nullptr; 33 | } 34 | } 35 | 36 | %typemap(in) basics::Doodad & (std::shared_ptr tmp) { 37 | // First make a pybind11 object handler around the PyObject* 38 | // Then, cast it to a shared_ptr using the pybind11 caster. 39 | // We declare that shared_ptr above so Swig keeps it alive for the duration 40 | // of the wrapped function. 41 | pybind11::object p{$input, true}; 42 | try { 43 | tmp = p.cast>(); 44 | $1 = tmp.get(); 45 | } catch(...) { 46 | PyErr_SetString(PyExc_RuntimeError, "could not cast to Doodad &"); 47 | return nullptr; 48 | } 49 | } 50 | 51 | %typemap(in) std::shared_ptr { 52 | // First make a pybind11 object handler around the PyObject* 53 | // Then, cast it to a shared_ptr using the pybind11 caster. 54 | pybind11::object p{$input, true}; 55 | try { 56 | std::shared_ptr ptr(p.cast>()); 57 | $1 = ptr; 58 | } catch(...) { 59 | PyErr_SetString(PyExc_RuntimeError, "could not cast to shared_ptr"); 60 | return nullptr; 61 | } 62 | } 63 | 64 | %typemap(in) std::shared_ptr { 65 | // First make a pybind11 object handler around the PyObject* 66 | // Then, cast it to a shared_ptr using the pybind11 caster. 67 | pybind11::object p{$input, true}; 68 | try { 69 | std::shared_ptr ptr(p.cast>()); 70 | $1 = ptr; 71 | } catch(...) { 72 | PyErr_SetString(PyExc_RuntimeError, "could not cast to shared_ptr"); 73 | return nullptr; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /python2-pybind11/setup.py: -------------------------------------------------------------------------------- 1 | import os, sys 2 | from pip import locations 3 | from setuptools import setup, Extension 4 | from distutils import sysconfig 5 | 6 | # Remove the "-Wstrict-prototypes" compiler option, which isn't valid for C++. 7 | import distutils.sysconfig 8 | cfg_vars = distutils.sysconfig.get_config_vars() 9 | for key, value in cfg_vars.items(): 10 | if type(value) == str: 11 | cfg_vars[key] = value.replace("-Wstrict-prototypes", "") 12 | 13 | kwds = dict( 14 | # pybind11 produces smaller binaries with C++14, but that's not available 15 | # on our target platform and I'm too lazy to add conditional flags for 16 | # a package that's mostly about reading rather than using the code. 17 | # Using -fvisibility-hidden has similar benefits, but that's thwarted 18 | # by distutils' requirement that the same compiler options be used for 19 | # all object files in an Extension, combined with my preference for putting 20 | # the C++ library code in the Python modules themselves instead of a 21 | # separate shared library. 22 | # In other words, don't use this as an example for how to compile pybind11 23 | # code; look at the pybind11 FAQ instead. 24 | extra_compile_args=['-std=c++11'], 25 | include_dirs=[ 26 | os.path.join('..', 'include'), 27 | os.path.join('include'), 28 | os.path.dirname(locations.distutils_scheme('pybind11')['headers']) 29 | ], 30 | ) 31 | 32 | if sys.platform == 'darwin': 33 | # Miniconda 3.19 provided Python on OSX is built against OSX deployment target version 10.5 34 | # this doesn't work with C++11 in libc++. Compiling without the following directive 35 | # then gives a clang: error: 36 | # invalid deployment target for -stdlib=libc++ (requires OS X 10.7 or later) 37 | kwds["extra_compile_args"].append('-mmacosx-version-min=10.7') 38 | kwds["extra_compile_args"].append('-stdlib=libc++') 39 | 40 | 41 | ext_modules = [ 42 | Extension( 43 | 'challenge.basics', 44 | sources=[ 45 | os.path.join('challenge', 'basics.cpp'), 46 | os.path.join('..', 'src', 'basics.cpp') 47 | ], 48 | **kwds 49 | ), 50 | Extension( 51 | 'challenge.containers', 52 | sources=[ 53 | os.path.join('challenge', 'containers.cpp'), 54 | os.path.join('..', 'src', 'containers.cpp') 55 | ], 56 | **kwds 57 | ), 58 | Extension( 59 | 'challenge.converters', ['challenge/converters.i'], 60 | swig_opts=["-modern", "-c++", "-Ichallenge/include", "-noproxy"], 61 | **kwds 62 | ), 63 | ] 64 | 65 | setup( 66 | name='challenge', 67 | version='0.0.1', 68 | author='Pim Schellart', 69 | author_email='P.Schellart@princeton.edu', 70 | test_suite="tests", 71 | description='Solution to the Python C++ bindings challenge with pybind11.', 72 | ext_modules=ext_modules, 73 | ) 74 | -------------------------------------------------------------------------------- /python2-pybind11/tests/__init__.py: -------------------------------------------------------------------------------- 1 | from . import basics_test 2 | from . import converters_test 3 | from . import containers_test 4 | 5 | import unittest 6 | 7 | basics_test.DoodadTestCase.test_const = \ 8 | unittest.skip("const correctness not supported")( 9 | basics_test.DoodadTestCase.test_const 10 | ) 11 | 12 | containers_test.DoodadSetTestCase.test_downcast = \ 13 | unittest.skip("extensions module not implemented")( 14 | containers_test.DoodadSetTestCase.test_downcast 15 | ) 16 | 17 | converters_test.SwigTestCase.test_const_guarantees = \ 18 | unittest.skip("const correctness not supported")( 19 | converters_test.SwigTestCase.test_const_guarantees 20 | ) 21 | -------------------------------------------------------------------------------- /python2-pybind11/tests/basics_test.py: -------------------------------------------------------------------------------- 1 | ../../tests/basics_test.py -------------------------------------------------------------------------------- /python2-pybind11/tests/containers_test.py: -------------------------------------------------------------------------------- 1 | ../../tests/containers_test.py -------------------------------------------------------------------------------- /python2-pybind11/tests/converters_test.py: -------------------------------------------------------------------------------- 1 | ../../tests/converters_test.py -------------------------------------------------------------------------------- /src/basics.cpp: -------------------------------------------------------------------------------- 1 | #include "basics.hpp" 2 | 3 | namespace basics { 4 | 5 | namespace { 6 | 7 | std::size_t & internal_secret_count() { 8 | static std::size_t count = 0; 9 | return count; 10 | } 11 | 12 | } // anonymous 13 | 14 | Secret::Secret() : _index(++internal_secret_count()) {} 15 | 16 | bool compare(Secret const & a, Secret const & b) { 17 | return a._index == b._index; 18 | } 19 | 20 | bool adjacent(Secret const & a, Secret const & b) { 21 | return a._index + 1u == b._index; 22 | } 23 | 24 | Doodad::Doodad(std::string const & name_, int value_) : 25 | name(name_), value(value_) 26 | {} 27 | 28 | Doodad::Doodad(WhatsIt const & it) : name(it.a), value(it.b) {} 29 | 30 | Doodad::~Doodad() {} 31 | 32 | std::unique_ptr Doodad::clone() const { 33 | return std::unique_ptr(new Doodad(name, value)); 34 | } 35 | 36 | void Doodad::read(WhatsIt const & it) { 37 | name = it.a; 38 | value = it.b; 39 | } 40 | 41 | WhatsIt Doodad::write() const { 42 | WhatsIt it = {name, value}; 43 | return it; 44 | } 45 | 46 | std::shared_ptr Doodad::get_const() { 47 | static std::shared_ptr instance(new Doodad("frozen", 50)); 48 | return instance; 49 | } 50 | 51 | } // namespace basics 52 | -------------------------------------------------------------------------------- /src/containers.cpp: -------------------------------------------------------------------------------- 1 | #include "containers.hpp" 2 | 3 | namespace containers { 4 | 5 | void DoodadSet::add(std::shared_ptr item) { 6 | _items.push_back(std::move(item)); 7 | } 8 | 9 | void DoodadSet::add(basics::WhatsIt const & it) { 10 | std::shared_ptr doodad(new basics::Doodad(it)); 11 | _items.push_back(std::move(doodad)); 12 | } 13 | 14 | std::map> 15 | DoodadSet::as_map() const { 16 | std::map> result; 17 | for (auto const & item : _items) { 18 | result[item->name] = item; 19 | } 20 | return result; 21 | } 22 | 23 | } // namespace containers 24 | -------------------------------------------------------------------------------- /src/converters.i: -------------------------------------------------------------------------------- 1 | %module(package="challenge") converters 2 | 3 | %{ 4 | #include "basics.hpp" 5 | %} 6 | 7 | %include "std_string.i" 8 | 9 | // Challenge solutions must provide only the following file, made available 10 | // on the Swig include path. It should contain the necessary Swig typmaps to 11 | // convert a Doodad C++ object to Python and back, for all of the functions 12 | // below. 13 | %include "basics_typemaps.i" 14 | 15 | %inline %{ 16 | 17 | std::shared_ptr make_sptr( 18 | std::string const & name, int value 19 | ) { 20 | return std::shared_ptr(new basics::Doodad(name, value)); 21 | } 22 | 23 | std::shared_ptr make_csptr( 24 | std::string const & name, int value 25 | ) { 26 | return std::shared_ptr( 27 | new basics::Doodad(name, value) 28 | ); 29 | } 30 | 31 | bool accept_ref( 32 | basics::Doodad & d, 33 | std::string const & name, int value 34 | ) { 35 | return d.name == name && d.value == value; 36 | } 37 | 38 | bool accept_cref( 39 | basics::Doodad const & d, 40 | std::string const & name, int value 41 | ) { 42 | return d.name == name && d.value == value; 43 | } 44 | 45 | bool accept_sptr( 46 | std::shared_ptr d, 47 | std::string const & name, int value 48 | ) { 49 | return d->name == name && d->value == value; 50 | } 51 | 52 | bool accept_csptr( 53 | std::shared_ptr d, 54 | std::string const & name, int value 55 | ) { 56 | return d->name == name && d->value == value; 57 | } 58 | 59 | %} 60 | -------------------------------------------------------------------------------- /src/extensions.cpp: -------------------------------------------------------------------------------- 1 | #include "extensions.hpp" 2 | #include 3 | 4 | namespace extensions { 5 | 6 | template 7 | Thingamajig::Thingamajig(T extra, std::string const & name, int value) : 8 | basics::Doodad(name, value), _extra(extra) {} 9 | 10 | template 11 | std::unique_ptr Thingamajig::clone() const { 12 | return std::unique_ptr( 13 | new Thingamajig( 14 | _extra, 15 | this->name, 16 | this->value 17 | ) 18 | ); 19 | } 20 | 21 | template class Thingamajig; 22 | template class Thingamajig>; 23 | 24 | } // namespace things 25 | -------------------------------------------------------------------------------- /tests/basics_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import unittest 4 | import challenge.basics 5 | 6 | class DoodadTestCase(unittest.TestCase): 7 | """Test case for for Doodad bindings. 8 | 9 | All tests also require public data members to be wrapped with at least 10 | getter properties, and most require the standard constructor to be 11 | wrapped to support at least positional arguments. 12 | """ 13 | 14 | def test_standard_ctor_pos_all(self): 15 | """Test standard constructor with all positional arguments. 16 | """ 17 | d = challenge.basics.Doodad("a", 0) 18 | self.assertIsInstance(d, challenge.basics.Doodad) 19 | self.assertEqual(d.name, "a") 20 | self.assertEqual(d.value, 0) 21 | 22 | def test_standard_ctor_pos_default(self): 23 | """Test standard constructor with positional arguments, with a default. 24 | """ 25 | d = challenge.basics.Doodad("b") 26 | self.assertIsInstance(d, challenge.basics.Doodad) 27 | self.assertEqual(d.name, "b") 28 | self.assertEqual(d.value, 1) 29 | 30 | def test_standard_ctor_kwarg_al(self): 31 | """Test standard constructor with all keyword arguments. 32 | """ 33 | d = challenge.basics.Doodad(name="c", value=2) 34 | self.assertIsInstance(d, challenge.basics.Doodad) 35 | self.assertEqual(d.name, "c") 36 | self.assertEqual(d.value, 2) 37 | 38 | def test_standard_ctor_kwarg_default(self): 39 | """Test standard constructor with kwargs and a default value. 40 | """ 41 | d = challenge.basics.Doodad(name="d") 42 | self.assertIsInstance(d, challenge.basics.Doodad) 43 | self.assertEqual(d.name, "d") 44 | self.assertEqual(d.value, 1) 45 | 46 | def test_standard_ctor_mixed(self): 47 | """Test standard constructor with a combination of positional and 48 | keyword arguments. 49 | """ 50 | d = challenge.basics.Doodad("e", value=3) 51 | self.assertIsInstance(d, challenge.basics.Doodad) 52 | self.assertEqual(d.name, "e") 53 | self.assertEqual(d.value, 3) 54 | 55 | def test_tuple_ctor(self): 56 | """Test construction from tuple (what we've mapped C++ WhatsIt to). 57 | """ 58 | w = ("f", 4) 59 | d = challenge.basics.Doodad(w) 60 | self.assertIsInstance(d, challenge.basics.Doodad) 61 | self.assertEqual(d.name, "f") 62 | self.assertEqual(d.value, 4) 63 | 64 | def test_clone(self): 65 | """Test calling the clone() method. 66 | """ 67 | d1 = challenge.basics.Doodad("g", 5) 68 | d2 = d1.clone() 69 | self.assertEqual(d1.name, d2.name) 70 | self.assertEqual(d1.value, d2.value) 71 | self.assertTrue( 72 | challenge.basics.adjacent(d1.get_secret(), d2.get_secret()) 73 | ) 74 | 75 | def test_read(self): 76 | """Test reading a WhatsIt (tuple) into a Doodad. 77 | """ 78 | d = challenge.basics.Doodad("h", 6) 79 | d.read(("i", 7)) 80 | self.assertEqual(d.name, "i") 81 | self.assertEqual(d.value, 7) 82 | 83 | def test_write(self): 84 | """Test writing a Doodad into a WhatsIt (tuple). 85 | """ 86 | d = challenge.basics.Doodad("j", 8) 87 | self.assertEqual(d.write(), ("j", 8)) 88 | 89 | def test_const(self): 90 | """Test that modifying const Doodds is not allowed. 91 | """ 92 | d = challenge.basics.Doodad.get_const() 93 | self.assertRaises((AttributeError, TypeError), setattr, d, "name", "k") 94 | self.assertRaises((AttributeError, TypeError), setattr, d, "value", 9) 95 | 96 | def test_equality(self): 97 | """Test that equality comparison works at the C++ pointer level. 98 | """ 99 | d1 = challenge.basics.Doodad.get_const() 100 | d2 = challenge.basics.Doodad.get_const() 101 | self.assertEqual(d1, d2) 102 | self.assertNotEqual(d1, challenge.basics.Doodad(d1.name, d1.value)) 103 | 104 | if __name__ == "__main__": 105 | unittest.main() 106 | -------------------------------------------------------------------------------- /tests/containers_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import unittest 4 | 5 | import challenge.basics 6 | import challenge.containers 7 | 8 | 9 | class DoodadSetTestCase(unittest.TestCase): 10 | """Test case for for Doodad bindings. 11 | """ 12 | 13 | def compare_Doodads(self, a, b, msg=None): 14 | if a.name != b.name or a.value != b.value: 15 | if msg is not None: 16 | template = "({a.name}, {a.value}) != ({b.name}, {b.value})" 17 | msg = template.format(a=a, b=b) 18 | self.failureException(msg) 19 | 20 | def __init__(self, *args, **kwds): 21 | super(DoodadSetTestCase, self).__init__(*args, **kwds) 22 | self.addTypeEqualityFunc(challenge.basics.Doodad, self.compare_Doodads) 23 | 24 | def setUp(self): 25 | self.item0 = challenge.basics.Doodad("a", 0) 26 | self.item1 = challenge.basics.Doodad("b", 1) 27 | self.container = challenge.containers.DoodadSet() 28 | self.container.add(self.item0) 29 | self.container.add(self.item1) 30 | 31 | def test_as_list(self): 32 | """Test converting the DoodadSet to list. 33 | """ 34 | self.assertEqual(self.container.as_list(), [self.item0, self.item1]) 35 | 36 | def test_as_dict(self): 37 | """Test converting the DoodadSet to dict. 38 | """ 39 | self.assertEqual( 40 | self.container.as_dict(), 41 | {self.item0.name: self.item0, self.item1.name: self.item1} 42 | ) 43 | 44 | def test_len(self): 45 | """Test that len(DoodadSet) works. 46 | """ 47 | self.assertEqual(len(self.container), 2) 48 | 49 | def test_iterator(self): 50 | """Test that the DoodadSet iterator works. 51 | """ 52 | self.assertEqual(self.container.as_list(), list(self.container)) 53 | 54 | def test_assign(self): 55 | """Test that we can assign a full list to the Doodad. 56 | """ 57 | r = [ 58 | challenge.basics.Doodad("c", 4), 59 | challenge.basics.Doodad("d", 5), 60 | challenge.basics.Doodad("e", 6) 61 | ] 62 | self.container.assign(r) 63 | self.assertEqual(self.container.as_list(), r) 64 | 65 | def test_tuple_convert(self): 66 | """Test that we accept a WhatsIt (a Python tuple) in add(). 67 | """ 68 | self.container.add(("g", 13)) 69 | item = self.container.as_dict()["g"] 70 | self.assertEqual(item.name, "g") 71 | self.assertEqual(item.value, 13) 72 | 73 | def test_downcast(self): 74 | """Test that Thingamajigs round-trip through DoodadSet. 75 | """ 76 | t1 = challenge.extensions.Thingamajig(5.4, "f", 12) 77 | self.container.add(t1) 78 | t2 = self.container.as_dict()["f"] 79 | self.assertEqual(t1, t2) 80 | self.assertIsInstance(t2, challenge.extensions.Thingamajig) 81 | self.assertEqual(t1.get_extra(), t2.get_extra()) 82 | 83 | def test_class_attr(self): 84 | """Test that we've defined the C++ Item typedef as a class attribute. 85 | """ 86 | self.assertEqual(challenge.containers.DoodadSet.Item, 87 | challenge.basics.Doodad) 88 | 89 | if __name__ == "__main__": 90 | unittest.main() 91 | -------------------------------------------------------------------------------- /tests/converters_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import unittest 4 | import challenge.basics 5 | import challenge.converters 6 | 7 | class SwigTestCase(unittest.TestCase): 8 | """Test case for for Swig typemaps for Doodads. 9 | """ 10 | 11 | def test_shared_ptr_return(self): 12 | """Test that a Swig-built module can return a "shared_ptr". 13 | """ 14 | r = challenge.converters.make_sptr("a", 5) 15 | self.assertIsInstance(r, challenge.basics.Doodad) 16 | self.assertEqual(r.name, "a") 17 | self.assertEqual(r.value, 5) 18 | 19 | def test_const_shared_ptr_return(self): 20 | """Test that a Swig-built module can return a "shared_ptr". 21 | """ 22 | r = challenge.converters.make_csptr("a", 5) 23 | self.assertEqual(r.name, "a") 24 | self.assertEqual(r.value, 5) 25 | 26 | def test_pass_reference(self): 27 | """Test that a Swig-built module can accept "Doodad &". 28 | """ 29 | d = challenge.basics.Doodad("b", 6) 30 | self.assertTrue(challenge.converters.accept_ref(d, "b", 6)) 31 | 32 | def test_pass_const_reference(self): 33 | """Test that a Swig-built module can accept "Doodad const &", 34 | even when the passed object is not const. 35 | """ 36 | d = challenge.basics.Doodad("c", 7) 37 | self.assertTrue(challenge.converters.accept_cref(d, "c", 7)) 38 | 39 | def test_pass_reference(self): 40 | """Test that a Swig-built module can accept "shared_ptr". 41 | """ 42 | d = challenge.basics.Doodad("d", 8) 43 | self.assertTrue(challenge.converters.accept_sptr(d, "d", 8)) 44 | 45 | def test_pass_const_shared_ptr(self): 46 | """Test that a Swig-built module can accept "shared_ptr", 47 | even when the passed object is not const. 48 | """ 49 | d = challenge.basics.Doodad("e", 9) 50 | self.assertTrue(challenge.converters.accept_csptr(d, "e", 9)) 51 | 52 | def test_const_guarantees(self): 53 | """Test that C++ constness restrictions are propagated to Python.""" 54 | r = challenge.converters.make_csptr("a", 5) 55 | self.assertRaises(TypeError, r, setattr, "name", "f") 56 | self.assertRaises(TypeError, r, setattr, "value", "100") 57 | self.assertTrue(challenge.converters.accept_cref(r, "a", 5)) 58 | self.assertTrue(challenge.converters.accept_csptr(r, "a", 5)) 59 | self.assertRaises(TypeError, challenge.converters.accept_sptr, 60 | r, "a", 5) 61 | self.assertRaises(TypeError, challenge.converters.accept_ref, 62 | r, "a", 5) 63 | 64 | 65 | if __name__ == "__main__": 66 | unittest.main() 67 | -------------------------------------------------------------------------------- /tests/extensions_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import unittest 4 | 5 | import challenge.basics 6 | import challenge.extensions 7 | 8 | class ThingamajigTestMixin(object): 9 | """Base class for all Thingamajig TestCases. 10 | 11 | Derived classes must define a setUp() method that defines a self.extra 12 | attribute that matches the template type of the Thingamajig being tested. 13 | """ 14 | 15 | def test_standard_ctor_pos_all(self): 16 | """Test standard constructor with all positional arguments. 17 | """ 18 | t = challenge.extensions.Thingamajig(self.extra, "b", 0, 19 | dtype=self.dtype) 20 | self.assertIsInstance(t, challenge.extensions.Thingamajig) 21 | self.assertEqual(t.get_extra(), self.extra) 22 | self.assertEqual(t.name, "b") 23 | self.assertEqual(t.value, 0) 24 | 25 | def test_standard_ctor_pos_default(self): 26 | """Test standard constructor with positional arguments, with a default. 27 | """ 28 | t = challenge.extensions.Thingamajig(self.extra, "b") 29 | self.assertIsInstance(t, challenge.extensions.Thingamajig) 30 | self.assertEqual(t.get_extra(), self.extra) 31 | self.assertEqual(t.name, "b") 32 | self.assertEqual(t.value, 1) 33 | 34 | def test_standard_ctor_kwarg_al(self): 35 | """Test standard constructor with all keyword arguments. 36 | """ 37 | t = challenge.extensions.Thingamajig(name="c", value=2, 38 | extra=self.extra) 39 | self.assertIsInstance(t, challenge.extensions.Thingamajig) 40 | self.assertEqual(t.get_extra(), self.extra) 41 | self.assertEqual(t.name, "c") 42 | self.assertEqual(t.value, 2) 43 | 44 | def test_standard_ctor_kwarg_default(self): 45 | """Test standard constructor with keyword arguments, with a default. 46 | """ 47 | t = challenge.extensions.Thingamajig(name="d", extra=self.extra) 48 | self.assertIsInstance(t, challenge.extensions.Thingamajig) 49 | self.assertEqual(t.get_extra(), self.extra) 50 | self.assertEqual(t.name, "d") 51 | self.assertEqual(t.value, 1) 52 | 53 | def test_standard_ctor_mixed(self): 54 | """Test standard constructor with a mix of positional and keyword args. 55 | """ 56 | t = challenge.extensions.Thingamajig(self.extra, name="e", value=3) 57 | self.assertIsInstance(t, challenge.extensions.Thingamajig) 58 | self.assertEqual(t.get_extra(), self.extra) 59 | self.assertEqual(t.name, "e") 60 | self.assertEqual(t.value, 3) 61 | 62 | def test_clone(self): 63 | """Test calling the clone() method, including downcasting. 64 | """ 65 | t1 = challenge.extensions.Thingamajig(self.extra, "g", 5) 66 | t2 = t1.clone() 67 | self.assertIsInstance(t2, challenge.extensions.Thingamajig) 68 | self.assertEqual(t1.name, t2.name) 69 | self.assertEqual(t1.value, t2.value) 70 | self.assertEqual(t1.get_extra(), t2.get_extra()) 71 | self.assertTrue( 72 | challenge.basics.adjacent(t1.get_secret(), t2.get_secret()) 73 | ) 74 | 75 | 76 | class FloatThingamajigTestCase(ThingamajigTestMixin, unittest.TestCase): 77 | """Test case for for Thingamajig bindings. 78 | 79 | All tests also require Doodad to be fully wrapped, and most require the 80 | standard constructor to be wrapped to support at least positional arguments. 81 | """ 82 | 83 | def setUp(self): 84 | self.extra = 0.5 85 | self.dtype = float 86 | 87 | def test_types(self): 88 | """Test the inheritance relationships and dtype status of Thingamajig. 89 | """ 90 | t = challenge.extensions.Thingamajig(-0.5, "a", 0, dtype=self.dtype) 91 | self.assertIsInstance(t, challenge.basics.Doodad) 92 | self.assertIsInstance(t, challenge.extensions.Thingamajig) 93 | # Since "numpy.dtype(float) == float", we're not requiring 94 | # the more restrictive "t.dtype is float". 95 | self.assertEqual(t.dtype, float) 96 | 97 | 98 | class DoodadThingamajigTestCase(ThingamajigTestMixin, unittest.TestCase): 99 | """Test case for for Thingamajig bindings. 100 | 101 | All tests also require Doodad to be fully wrapped, and most require the 102 | standard constructor to be wrapped to support at least positional arguments. 103 | """ 104 | 105 | def compare_Doodads(self, a, b, msg=None): 106 | if a.name != b.name or a.value != b.value: 107 | if msg is not None: 108 | template = "({a.name}, {a.value}) != ({b.name}, {b.value})" 109 | msg = template.format(a=a, b=b) 110 | self.failureException(msg) 111 | 112 | def __init__(self, *args, **kwds): 113 | super(DoodadThingamajigTestCase, self).__init__(*args, **kwds) 114 | self.addTypeEqualityFunc(challenge.basics.Doodad, self.compare_Doodads) 115 | 116 | def setUp(self): 117 | self.extra = challenge.basics.Doodad("asdf", 120) 118 | self.dtype = challenge.basics.Doodad 119 | 120 | def test_types(self): 121 | """Test the inheritance relationships and dtype status of Thingamajig. 122 | """ 123 | t = challenge.extensions.Thingamajig( 124 | self.extra, "a", 0, 125 | dtype=self.dtype 126 | ) 127 | self.assertIsInstance(t, challenge.basics.Doodad) 128 | self.assertIsInstance(t, challenge.extensions.Thingamajig) 129 | self.assertEqual(t.dtype, challenge.basics.Doodad) 130 | 131 | 132 | if __name__ == "__main__": 133 | unittest.main() 134 | --------------------------------------------------------------------------------