1 //
2 // Copyright 2016 Pixar
3 //
4 // Licensed under the Apache License, Version 2.0 (the "Apache License")
5 // with the following modification; you may not use this file except in
6 // compliance with the Apache License and the following modification to it:
7 // Section 6. Trademarks. is deleted and replaced with:
8 //
9 // 6. Trademarks. This License does not grant permission to use the trade
10 //    names, trademarks, service marks, or product names of the Licensor
11 //    and its affiliates, except as required to comply with Section 4(c) of
12 //    the License and to reproduce the content of the NOTICE file.
13 //
14 // You may obtain a copy of the Apache License at
15 //
16 //     http://www.apache.org/licenses/LICENSE-2.0
17 //
18 // Unless required by applicable law or agreed to in writing, software
19 // distributed under the Apache License with the above modification is
20 // distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
21 // KIND, either express or implied. See the Apache License for the specific
22 // language governing permissions and limitations under the Apache License.
23 //
24 #ifndef PXR_BASE_TF_PY_CONTAINER_CONVERSIONS_H
25 #define PXR_BASE_TF_PY_CONTAINER_CONVERSIONS_H
26 
27 /// \file tf/pyContainerConversions.h
28 /// Utilities for providing C++ <-> Python container support.
29 
30 /*
31  * Adapted (modified) from original at http://cctbx.sourceforge.net
32  * Original file:
33  * cctbx/scitbx/include/scitbx/boost_python/container_conversions.h
34  * License:
35  * http://cvs.sourceforge.net/viewcvs.py/cctbx/cctbx/
36  *                                      LICENSE.txt?rev=1.2&view=markup
37  */
38 
39 #include "pxr/pxr.h"
40 
41 #include "pxr/base/tf/refPtr.h"
42 #include "pxr/base/tf/weakPtr.h"
43 #include "pxr/base/tf/diagnostic.h"
44 #include "pxr/base/tf/iterator.h"
45 #include "pxr/base/tf/pyUtils.h"
46 
47 #include <boost/python/list.hpp>
48 #include <boost/python/tuple.hpp>
49 #include <boost/python/extract.hpp>
50 #include <boost/python/to_python_converter.hpp>
51 
52 #include <deque>
53 #include <list>
54 #include <set>
55 #include <vector>
56 
57 PXR_NAMESPACE_OPEN_SCOPE
58 
59 // Converter from vector<string> to python list.
60 template <typename ContainerType>
61 struct TfPySequenceToPython
62 {
convertTfPySequenceToPython63     static PyObject* convert(ContainerType const &c)
64     {
65         boost::python::list result;
66         TF_FOR_ALL(i, c) {
67             result.append(*i);
68         }
69         return boost::python::incref(result.ptr());
70     }
71 };
72 
73 template <typename ContainerType>
74 struct TfPyMapToPythonDict
75 {
convertTfPyMapToPythonDict76     static PyObject* convert(ContainerType const &c)
77     {
78         return boost::python::incref(TfPyCopyMapToDictionary(c).ptr());
79     }
80 };
81 
82 namespace TfPyContainerConversions {
83 
84   template <typename ContainerType>
85   struct to_tuple
86   {
convertto_tuple87     static PyObject* convert(ContainerType const& a)
88     {
89       boost::python::list result;
90       typedef typename ContainerType::const_iterator const_iter;
91       for(const_iter p=a.begin();p!=a.end();p++) {
92         result.append(boost::python::object(*p));
93       }
94       return boost::python::incref(boost::python::tuple(result).ptr());
95     }
96   };
97 
98   template <typename First, typename Second>
99   struct to_tuple<std::pair<First, Second> > {
100     static PyObject* convert(std::pair<First, Second> const& a)
101     {
102       boost::python::tuple result =
103         boost::python::make_tuple(a.first, a.second);
104       return boost::python::incref(result.ptr());
105     }
106   };
107 
108   struct default_policy
109   {
110     static bool check_convertibility_per_element() { return false; }
111 
112     template <typename ContainerType>
113     static bool check_size(boost::type<ContainerType>, std::size_t sz)
114     {
115       return true;
116     }
117 
118     template <typename ContainerType>
119     static void assert_size(boost::type<ContainerType>, std::size_t sz) {}
120 
121     template <typename ContainerType>
122     static void reserve(ContainerType& a, std::size_t sz) {}
123   };
124 
125   struct fixed_size_policy
126   {
127     static bool check_convertibility_per_element() { return true; }
128 
129     template <typename ContainerType>
130     static bool check_size(boost::type<ContainerType>, std::size_t sz)
131     {
132       return ContainerType::size() == sz;
133     }
134 
135     template <typename ContainerType>
136     static void assert_size(boost::type<ContainerType>, std::size_t sz)
137     {
138       if (!check_size(boost::type<ContainerType>(), sz)) {
139         PyErr_SetString(PyExc_RuntimeError,
140           "Insufficient elements for fixed-size array.");
141         boost::python::throw_error_already_set();
142       }
143     }
144 
145     template <typename ContainerType>
146     static void reserve(ContainerType& a, std::size_t sz)
147     {
148       if (sz > ContainerType::size()) {
149         PyErr_SetString(PyExc_RuntimeError,
150           "Too many elements for fixed-size array.");
151         boost::python::throw_error_already_set();
152       }
153     }
154 
155     template <typename ContainerType, typename ValueType>
156     static void set_value(ContainerType& a, std::size_t i, ValueType const& v)
157     {
158       reserve(a, i+1);
159       a[i] = v;
160     }
161   };
162 
163   struct variable_capacity_policy : default_policy
164   {
165     template <typename ContainerType>
166     static void reserve(ContainerType& a, std::size_t sz)
167     {
168       a.reserve(sz);
169     }
170 
171     template <typename ContainerType, typename ValueType>
172     static void set_value(ContainerType& a, std::size_t i, ValueType const& v)
173     {
174       TF_AXIOM(a.size() == i);
175       a.push_back(v);
176     }
177   };
178 
179   struct variable_capacity_all_items_convertible_policy : variable_capacity_policy
180   {
181       static bool check_convertibility_per_element() { return true; }
182   };
183 
184   struct fixed_capacity_policy : variable_capacity_policy
185   {
186     template <typename ContainerType>
187     static bool check_size(boost::type<ContainerType>, std::size_t sz)
188     {
189       return ContainerType::max_size() >= sz;
190     }
191   };
192 
193   struct linked_list_policy : default_policy
194   {
195     template <typename ContainerType, typename ValueType>
196     static void set_value(ContainerType& a, std::size_t i, ValueType const& v)
197     {
198       a.push_back(v);
199     }
200   };
201 
202   struct set_policy : default_policy
203   {
204     template <typename ContainerType, typename ValueType>
205     static void set_value(ContainerType& a, std::size_t i, ValueType const& v)
206     {
207       a.insert(v);
208     }
209   };
210 
211   template <typename ContainerType, typename ConversionPolicy>
212   struct from_python_sequence
213   {
214     typedef typename ContainerType::value_type container_element_type;
215 
216     from_python_sequence()
217     {
218       boost::python::converter::registry::push_back(
219         &convertible,
220         &construct,
221         boost::python::type_id<ContainerType>());
222     }
223 
224     static void* convertible(PyObject* obj_ptr)
225     {
226       if (!(   PyList_Check(obj_ptr)
227             || PyTuple_Check(obj_ptr)
228             || PySet_Check(obj_ptr)
229             || PyFrozenSet_Check(obj_ptr)
230             || PyIter_Check(obj_ptr)
231             || PyRange_Check(obj_ptr)
232             || (   !PyBytes_Check(obj_ptr)
233                 && !PyUnicode_Check(obj_ptr)
234                 && (   Py_TYPE(obj_ptr) == 0
235                     || Py_TYPE(Py_TYPE(obj_ptr)) == 0
236                     || Py_TYPE(Py_TYPE(obj_ptr))->tp_name == 0
237                     || std::strcmp(
238                          Py_TYPE(Py_TYPE(obj_ptr))->tp_name,
239                          "Boost.Python.class") != 0)
240                 && PyObject_HasAttrString(obj_ptr, "__len__")
241                 && PyObject_HasAttrString(obj_ptr, "__getitem__")))) return 0;
242       boost::python::handle<> obj_iter(
243         boost::python::allow_null(PyObject_GetIter(obj_ptr)));
244       if (!obj_iter.get()) { // must be convertible to an iterator
245         PyErr_Clear();
246         return 0;
247       }
248       if (ConversionPolicy::check_convertibility_per_element()) {
249         Py_ssize_t obj_size = PyObject_Length(obj_ptr);
250         if (obj_size < 0) { // must be a measurable sequence
251           PyErr_Clear();
252           return 0;
253         }
254         if (!ConversionPolicy::check_size(
255           boost::type<ContainerType>(), obj_size)) return 0;
256         bool is_range = PyRange_Check(obj_ptr);
257         std::size_t i=0;
258         if (!all_elements_convertible(obj_iter, is_range, i)) return 0;
259         if (!is_range) assert(i == (std::size_t)obj_size);
260       }
261       return obj_ptr;
262     }
263 
264     // This loop factored out by Achim Domma to avoid Visual C++
265     // Internal Compiler Error.
266     static bool
267     all_elements_convertible(
268       boost::python::handle<>& obj_iter,
269       bool is_range,
270       std::size_t& i)
271     {
272       for(;;i++) {
273         boost::python::handle<> py_elem_hdl(
274           boost::python::allow_null(PyIter_Next(obj_iter.get())));
275         if (PyErr_Occurred()) {
276           PyErr_Clear();
277           return false;
278         }
279         if (!py_elem_hdl.get()) break; // end of iteration
280         boost::python::object py_elem_obj(py_elem_hdl);
281         boost::python::extract<container_element_type>
282           elem_proxy(py_elem_obj);
283         if (!elem_proxy.check()) return false;
284         if (is_range) break; // in a range all elements are of the same type
285       }
286       return true;
287     }
288 
289     static void construct(
290       PyObject* obj_ptr,
291       boost::python::converter::rvalue_from_python_stage1_data* data)
292     {
293       boost::python::handle<> obj_iter(PyObject_GetIter(obj_ptr));
294       void* storage = (
295         (boost::python::converter::rvalue_from_python_storage<ContainerType>*)
296           data)->storage.bytes;
297       new (storage) ContainerType();
298       data->convertible = storage;
299       ContainerType& result = *((ContainerType*)storage);
300       std::size_t i=0;
301       for(;;i++) {
302         boost::python::handle<> py_elem_hdl(
303           boost::python::allow_null(PyIter_Next(obj_iter.get())));
304         if (PyErr_Occurred()) boost::python::throw_error_already_set();
305         if (!py_elem_hdl.get()) break; // end of iteration
306         boost::python::object py_elem_obj(py_elem_hdl);
307         boost::python::extract<container_element_type> elem_proxy(py_elem_obj);
308         ConversionPolicy::set_value(result, i, elem_proxy());
309       }
310       ConversionPolicy::assert_size(boost::type<ContainerType>(), i);
311     }
312   };
313 
314   template <typename PairType>
315   struct from_python_tuple_pair {
316     typedef typename PairType::first_type first_type;
317     typedef typename PairType::second_type second_type;
318 
319     from_python_tuple_pair()
320     {
321       boost::python::converter::registry::push_back(
322         &convertible,
323         &construct,
324         boost::python::type_id<PairType>());
325     }
326 
327     static void* convertible(PyObject* obj_ptr)
328     {
329       if (!PyTuple_Check(obj_ptr) || PyTuple_Size(obj_ptr) != 2) {
330         return 0;
331       }
332       boost::python::extract<first_type> e1(PyTuple_GetItem(obj_ptr, 0));
333       boost::python::extract<second_type> e2(PyTuple_GetItem(obj_ptr, 1));
334       if (!e1.check() || !e2.check()) {
335         return 0;
336       }
337       return obj_ptr;
338     }
339 
340     static void construct(
341       PyObject* obj_ptr,
342       boost::python::converter::rvalue_from_python_stage1_data* data)
343     {
344       void* storage = (
345         (boost::python::converter::rvalue_from_python_storage<PairType>*)
346           data)->storage.bytes;
347       boost::python::extract<first_type>  e1(PyTuple_GetItem(obj_ptr, 0));
348       boost::python::extract<second_type> e2(PyTuple_GetItem(obj_ptr, 1));
349       new (storage) PairType(e1(), e2());
350       data->convertible = storage;
351     }
352   };
353 
354   template <typename ContainerType>
355   struct to_tuple_mapping
356   {
357     to_tuple_mapping() {
358       boost::python::to_python_converter<
359         ContainerType,
360         to_tuple<ContainerType> >();
361     }
362   };
363 
364   template <typename ContainerType, typename ConversionPolicy>
365   struct tuple_mapping : to_tuple_mapping<ContainerType>
366   {
367     tuple_mapping() {
368       from_python_sequence<
369         ContainerType,
370         ConversionPolicy>();
371     }
372   };
373 
374   template <typename ContainerType>
375   struct tuple_mapping_fixed_size
376   {
377     tuple_mapping_fixed_size() {
378       tuple_mapping<
379         ContainerType,
380         fixed_size_policy>();
381     }
382   };
383 
384   template <typename ContainerType>
385   struct tuple_mapping_fixed_capacity
386   {
387     tuple_mapping_fixed_capacity() {
388       tuple_mapping<
389         ContainerType,
390         fixed_capacity_policy>();
391     }
392   };
393 
394   template <typename ContainerType>
395   struct tuple_mapping_variable_capacity
396   {
397     tuple_mapping_variable_capacity() {
398       tuple_mapping<
399         ContainerType,
400         variable_capacity_policy>();
401     }
402   };
403 
404   template <typename ContainerType>
405   struct tuple_mapping_set
406   {
407     tuple_mapping_set() {
408       tuple_mapping<
409         ContainerType,
410         set_policy>();
411     }
412   };
413 
414   template <typename ContainerType>
415   struct tuple_mapping_pair
416   {
417     tuple_mapping_pair() {
418       boost::python::to_python_converter<
419         ContainerType,
420         to_tuple<ContainerType> >();
421       from_python_tuple_pair<ContainerType>();
422     }
423   };
424 
425 } // namespace TfPyContainerConversions
426 
427 template <class T>
428 void TfPyRegisterStlSequencesFromPython()
429 {
430     using namespace TfPyContainerConversions;
431     from_python_sequence<
432         std::vector<T>, variable_capacity_all_items_convertible_policy>();
433     from_python_sequence<
434         std::list<T>, variable_capacity_all_items_convertible_policy>();
435     from_python_sequence<
436         std::deque<T>, variable_capacity_all_items_convertible_policy>();
437 }
438 
439 PXR_NAMESPACE_CLOSE_SCOPE
440 
441 #endif // PXR_BASE_TF_PY_CONTAINER_CONVERSIONS_H
442