All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
pySpec.h
Go to the documentation of this file.
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 SDF_PYSPEC_H
25 #define SDF_PYSPEC_H
26 
65 
66 #include "pxr/pxr.h"
67 #include "pxr/usd/sdf/api.h"
68 
69 #include <boost/python/def_visitor.hpp>
70 #include <boost/python/dict.hpp>
71 #include <boost/python/errors.hpp>
72 #include <boost/python/raw_function.hpp>
73 #include <boost/python/pointee.hpp>
74 #include <boost/python/to_python_converter.hpp>
75 #include <boost/python/tuple.hpp>
76 
77 #include "pxr/base/tf/pyError.h"
78 #include "pxr/base/tf/pyUtils.h"
79 
80 #include "pxr/usd/sdf/declareHandles.h"
81 #include "pxr/base/tf/tf.h"
82 #include "pxr/base/tf/diagnostic.h"
83 #include "pxr/base/tf/stringUtils.h"
84 #include "pxr/base/arch/demangle.h"
85 
86 #include <string>
87 #include <type_traits>
88 
89 namespace boost{
90 namespace python {
91 
92 template <typename T>
93 struct pointee<PXR_NS::SdfHandle<T> > {
94  typedef T type;
95 };
96 }
97 }
98 
99 PXR_NAMESPACE_OPEN_SCOPE
100 
101 class SdfSpec;
102 
103 namespace Sdf_PySpecDetail {
104 
105 namespace bp = boost::python;
106 
107 SDF_API bp::object _DummyInit(bp::tuple const & /* args */, bp::dict const & /* kw */);
108 
109 template <typename CTOR>
110 struct NewVisitor : bp::def_visitor<NewVisitor<CTOR> > {
111 public:
112  NewVisitor(const std::string &doc = std::string()) : _doc(doc) {}
113 
114  template <typename CLS>
115  void visit(CLS& c) const
116  {
117  // If there's already a __new__ method, look through the staticmethod to
118  // get the underlying function, replace __new__ with that, then add the
119  // overload, and recreate the staticmethod. This is required because
120  // boost python needs to have all overloads exported before you say
121  // .staticmethod.
122 
123  // Note that it looks like this should do nothing, but it actually does
124  // something! Here's what it does: looking up __new__ on c doesn't
125  // actually produce the staticmethod object -- it does a "descriptor
126  // __get__" which produces the underlying function. Replacing __new__
127  // with that underlying thing has the effect of unwrapping the
128  // staticmethod, which is exactly what we want.
129  if (PyObject_HasAttrString(c.ptr(), "__new__"))
130  c.attr("__new__") = c.attr("__new__");
131  c.def("__new__", CTOR::template __new__<CLS>, _doc.c_str());
132  c.staticmethod("__new__");
133 
134  c.def("__init__", bp::raw_function(_DummyInit));
135  }
136 
137  template <class CLS, class Options>
138  void visit(CLS& c, char const* name, Options& options) const
139  {
140  // If there's already a __new__ method, look through the staticmethod to
141  // get the underlying function, replace __new__ with that, then add the
142  // overload, and recreate the staticmethod. This is required because
143  // boost python needs to have all overloads exported before you say
144  // .staticmethod.
145 
146  // Note that it looks like this should do nothing, but it actually does
147  // something! Here's what it does: looking up __new__ on c doesn't
148  // actually produce the staticmethod object -- it does a "descriptor
149  // __get__" which produces the underlying function. Replacing __new__
150  // with that underlying thing has the effect of unwrapping the
151  // staticmethod, which is exactly what we want.
152  if (PyObject_HasAttrString(c.ptr(), "__new__"))
153  c.attr("__new__") = c.attr("__new__");
154  c.def("__new__", CTOR::template __new__<CLS>,
155  // Note: we ignore options.doc() in favor of _doc
156  _doc.c_str(),
157  options.keywords(),
158  options.policies()
159  );
160  c.staticmethod("__new__");
161 
162  c.def("__init__", bp::raw_function(_DummyInit));
163  }
164 
165 private:
166  const std::string _doc;
167 
168  friend class bp::def_visitor_access;
169 };
170 
171 template <typename SIG>
172 struct CtorBase {
173 public:
174  typedef SIG Sig;
175  static Sig *_func;
176 
177  static void SetFunc(Sig *func)
178  {
179  if (! _func) {
180  _func = func;
181  }
182  else {
183  // CODE_COVERAGE_OFF
184  TF_CODING_ERROR("Ctor with signature '%s' is already registered. "
185  "Duplicate will be ignored.",
186  ArchGetDemangled(typeid(Sig)).c_str());
187  // CODE_COVERAGE_ON
188  }
189  }
190 };
191 
192 template <typename SIG> SIG *CtorBase<SIG>::_func = 0;
193 
194 template <typename SIG> struct NewCtor;
195 
196 } // namespace Sdf_PySpecDetail
197 
198 template <typename T>
199 Sdf_PySpecDetail::NewVisitor<typename Sdf_PySpecDetail::NewCtor<T> >
200 SdfMakePySpecConstructor(T *func, const std::string &doc = std::string())
201 {
202  // Instantiate to set static constructor pointer, then return the visitor.
203  Sdf_PySpecDetail::NewCtor<T> Ctor(func);
204  return Sdf_PySpecDetail::NewVisitor<Sdf_PySpecDetail::NewCtor<T> >(doc);
205 }
206 
207 namespace Sdf_PySpecDetail {
208 
209 // Create the repr for a spec using Sdf.Find().
210 SDF_API std::string _SpecRepr(const bp::object&, const SdfSpec*);
211 
212 // Registration for spec types to functions to create a holder with the spec
213 // corresponding to the spec type.
214 typedef PyObject* (*_HolderCreator)(const SdfSpec&);
215 SDF_API void _RegisterHolderCreator(const std::type_info&, _HolderCreator);
216 SDF_API PyObject* _CreateHolder(const std::type_info&, const SdfSpec&);
217 
218 template <class _SpecType>
219 struct _ConstHandleToPython {
220  typedef _SpecType SpecType;
221  typedef SdfHandle<SpecType> Handle;
222  typedef SdfHandle<const SpecType> ConstHandle;
223  _ConstHandleToPython() {
224  bp::to_python_converter<ConstHandle, _ConstHandleToPython<SpecType> >();
225  }
226  static PyObject *convert(ConstHandle const &p) {
227  return bp::incref(bp::object(TfConst_cast<Handle>(p)).ptr());
228  }
229 };
230 
231 // Register and perform python conversions of SdfHandles to holders.
232 template <class _SpecType, class _Held, class _Holder>
233 struct _HandleToPython {
234 public:
235  typedef _SpecType SpecType;
236  typedef _Holder Holder;
237  typedef _Held Handle;
238  typedef _HandleToPython<SpecType, Handle, Holder> This;
239 
240  static void Register()
241  {
242  _originalConverter = _RegisterConverter<Handle>(&This::_Convert);
243  _RegisterHolderCreator(typeid(SpecType), &This::_Creator);
244  }
245 
246  static PyObject* convert(const Handle& x)
247  {
248  return _CreateHolder(typeid(SpecType), x.GetSpec());
249  }
250 
251 private:
252  static PyObject* _Creator(const SdfSpec& spec)
253  {
254  Handle x(Sdf_CastAccess::CastSpec<SpecType,SdfSpec>(spec));
255  return bp::objects::make_ptr_instance<SpecType, Holder>::execute(x);
256  }
257 
258  template <class T>
259  static
260  bp::converter::to_python_function_t
261  _RegisterConverter(bp::converter::to_python_function_t f)
262  {
263  // Replace the old converter, installed automatically when we
264  // registered the class. WBN if boost python let us do this
265  // without playing games.
266  bp::converter::registration* r =
267  const_cast<bp::converter::registration*>(
268  bp::converter::registry::query(bp::type_id<T>()));
269  if (r) {
270  bp::converter::to_python_function_t old = r->m_to_python;
271  r->m_to_python = f;
272  return old;
273  }
274  else {
275  // CODE_COVERAGE_OFF Can only happen if there's a bug.
276  TF_CODING_ERROR("No python registration for '%s'!",
277  ArchGetDemangled(typeid(Handle)).c_str());
278  return 0;
279  // CODE_COVERAGE_ON
280  }
281  }
282 
283  static PyObject* _Convert(const void* p)
284  {
285  const Handle& x = *static_cast<const Handle*>(p);
286  return _CreateHolder(typeid(SpecType), x.GetSpec());
287  }
288 
289 private:
290  static bp::converter::to_python_function_t _originalConverter;
291 };
292 template <class SpecType, class Held, class Holder>
293 bp::converter::to_python_function_t
294 _HandleToPython<SpecType, Held, Holder>::_originalConverter = 0;
295 
296 template <class _SpecType>
297 struct _HandleFromPython {
298  typedef _SpecType SpecType;
299  typedef SdfHandle<SpecType> Handle;
300 
301  _HandleFromPython()
302  {
303  bp::converter::registry::insert(&convertible, &construct,
304  bp::type_id<Handle>());
305  }
306 
307  private:
308  static void *convertible(PyObject *p)
309  {
310  if (p == Py_None)
311  return p;
312  void *result =
313  bp::converter::get_lvalue_from_python(p,
314  bp::converter::registered<SpecType>::converters);
315  return result;
316  }
317 
318  static void construct(PyObject* source,
319  bp::converter::rvalue_from_python_stage1_data* data)
320  {
321  void* const storage =
322  ((bp::converter::rvalue_from_python_storage<Handle>*)
323  data)->storage.bytes;
324  // Deal with the "None" case.
325  if (data->convertible == source)
326  new (storage) Handle();
327  else {
328  new (storage) Handle(*static_cast<SpecType*>(data->convertible));
329  }
330  data->convertible = storage;
331  }
332 };
333 
334 // Visitor for def().
335 template <bool Abstract>
336 struct SpecVisitor : bp::def_visitor<SpecVisitor<Abstract> > {
337 
338  template<typename CLS>
339  struct _Helper {
340  typedef typename CLS::wrapped_type SpecType;
341  typedef typename CLS::metadata::held_type HeldType;
342  typedef typename CLS::metadata::held_type_arg HeldArgType;
343  typedef typename CLS::metadata::holder HolderType;
344 
345  public:
346  static std::string Repr(const bp::object& self)
347  {
348  const HeldType& held = bp::extract<const HeldType&>(self);
349  return _SpecRepr(self,
350  held ? boost::get_pointer(held) : NULL);
351  }
352 
353  static bool IsExpired(const HeldType& self)
354  {
355  return !self;
356  }
357 
358  static bool NonZero(const HeldType& self)
359  {
360  return self;
361  }
362 
363  static size_t __hash__(const HeldType& self)
364  {
365  return hash_value(self);
366  }
367 
368  static bool __eq__(const HeldType& a, const HeldType& b)
369  {
370  return a == b;
371  }
372 
373  static bool __ne__(const HeldType& a, const HeldType& b)
374  {
375  return a != b;
376  }
377 
378  static bool __lt__(const HeldType& a, const HeldType& b)
379  {
380  return a < b;
381  }
382 
383  static bool __le__(const HeldType& a, const HeldType& b)
384  {
385  return a <= b;
386  }
387 
388  static bool __gt__(const HeldType& a, const HeldType& b)
389  {
390  return a > b;
391  }
392 
393  static bool __ge__(const HeldType& a, const HeldType& b)
394  {
395  return a >= b;
396  }
397  };
398 
399 public:
400  SpecVisitor(bool addRepr = true) : _addRepr(addRepr) { }
401 
402  template <typename CLS>
403  void visit(CLS& c) const
404  {
405  typedef typename CLS::wrapped_type SpecType;
406  typedef typename CLS::metadata::held_type HeldType;
407  typedef typename CLS::metadata::held_type_arg HeldArgType;
408  typedef typename CLS::metadata::holder HolderType;
409 
410  static_assert(std::is_same<HeldType, SdfHandle<SpecType> >::value,
411  "HeldType must be SdfHandle<SpecType>.");
412 
413  // Add methods.
414  c.add_property("expired", &_Helper<CLS>::IsExpired);
415  c.def("__nonzero__", &_Helper<CLS>::NonZero);
416  c.def("__hash__", &_Helper<CLS>::__hash__);
417  c.def("__eq__", &_Helper<CLS>::__eq__);
418  c.def("__ne__", &_Helper<CLS>::__ne__);
419  c.def("__lt__", &_Helper<CLS>::__lt__);
420  c.def("__le__", &_Helper<CLS>::__le__);
421  c.def("__gt__", &_Helper<CLS>::__gt__);
422  c.def("__ge__", &_Helper<CLS>::__ge__);
423 
424  // Add python conversion to cast away constness.
425  _ConstHandleToPython<SpecType>();
426 
427  // Add python conversion for SdfHandle<SpecType>.
428  _HandleFromPython<SpecType>();
429  _HandleFromPython<const SpecType>();
430  _HandleToPython<SpecType, HeldArgType, HolderType>::Register();
431 
432  // Add __repr__.
433  if (_addRepr) {
434  c.def("__repr__", &_Helper<CLS>::Repr);
435  }
436  }
437 
438 private:
439  bool _addRepr;
440 };
441 
442 } // namespace Sdf_PySpecDetail
443 
444 inline
445 Sdf_PySpecDetail::SpecVisitor<false>
446 SdfPySpec()
447 {
448  return Sdf_PySpecDetail::SpecVisitor<false>();
449 }
450 
451 inline
452 Sdf_PySpecDetail::SpecVisitor<true>
453 SdfPyAbstractSpec()
454 {
455  return Sdf_PySpecDetail::SpecVisitor<true>();
456 }
457 
458 inline
459 Sdf_PySpecDetail::SpecVisitor<false>
460 SdfPySpecNoRepr()
461 {
462  return Sdf_PySpecDetail::SpecVisitor<false>(false);
463 }
464 
465 inline
466 Sdf_PySpecDetail::SpecVisitor<true>
467 SdfPyAbstractSpecNoRepr()
468 {
469  return Sdf_PySpecDetail::SpecVisitor<true>(false);
470 }
471 
472 
473 namespace Sdf_PySpecDetail
474 {
475 
476 // This generates multi-argument specializations for NewCtor.
477 
478 template <typename R, typename... Args>
479 struct NewCtor<R(Args...)> : CtorBase<R(Args...)> {
480  typedef CtorBase<R(Args...)> Base;
481  typedef typename Base::Sig Sig;
482  NewCtor(Sig *func) { Base::SetFunc(func); }
483 
484  template <class CLS>
485  static bp::object __new__(bp::object &cls, Args... args) {
486  typedef typename CLS::metadata::held_type HeldType;
487  TfErrorMark m;
488  HeldType specHandle(Base::_func(args...));
489  if (TfPyConvertTfErrorsToPythonException(m))
490  bp::throw_error_already_set();
491  bp::object result = TfPyObject(specHandle);
492  if (TfPyIsNone(result))
493  TfPyThrowRuntimeError("could not construct " +
494  ArchGetDemangled(typeid(HeldType)));
495 
496  bp::detail::initialize_wrapper(result.ptr(), get_pointer(specHandle));
497  // make the object have the right class.
498  bp::setattr(result, "__class__", cls);
499 
500  return result;
501  }
502 };
503 
504 } // namespace Sdf_PySpecDetail
505 
506 PXR_NAMESPACE_CLOSE_SCOPE
507 
508 #endif // SDF_PYSPEC_H
#define TF_CODING_ERROR(fmt, args)
Issue an internal programming error, but continue execution.
Definition: diagnostic.h:87
ARCH_API std::string ArchGetDemangled(const std::string &typeName)
Return demangled RTTI-generated type name.
Base class for all Sdf spec classes.
Definition: spec.h:51
SdfHandle is a smart ptr that calls IsDormant() on the pointed-to object as an extra expiration check...
Class used to record the end of the error-list.
Definition: errorMark.h:66
TF_API void TfPyThrowRuntimeError(std::string const &msg)
Raises a python RuntimError and throws a C++ exception.
TF_API bool TfPyIsNone(boost::python::object const &obj)
Return true iff obj is None.
boost::python::object TfPyObject(T const &t, bool complainOnFailure=true)
Return a python object for the given C++ object, loading the appropriate wrapper code if necessary...
Definition: pyUtils.h:100