This document is for a version of USD that is under development. See this page for the current release.
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
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