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