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