Loading...
Searching...
No Matches
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 PXR_USD_SDF_PY_SPEC_H
25#define PXR_USD_SDF_PY_SPEC_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
81#include "pxr/base/tf/tf.h"
85
86#include <string>
87#include <type_traits>
88
89namespace boost{
90namespace python {
91
92template <typename T>
93struct pointee<PXR_NS::SdfHandle<T> > {
94 typedef T type;
95};
96}
97}
98
99PXR_NAMESPACE_OPEN_SCOPE
100
101class SdfSpec;
102
103namespace Sdf_PySpecDetail {
104
105namespace bp = boost::python;
106
107SDF_API bp::object _DummyInit(bp::tuple const & /* args */, bp::dict const & /* kw */);
108
109template <typename CTOR>
110struct NewVisitor : bp::def_visitor<NewVisitor<CTOR> > {
111public:
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
165private:
166 const std::string _doc;
167
168 friend class bp::def_visitor_access;
169};
170
171template <typename SIG>
172struct CtorBase {
173public:
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
192template <typename SIG> SIG *CtorBase<SIG>::_func = 0;
193
194template <typename SIG> struct NewCtor;
195
196} // namespace Sdf_PySpecDetail
197
198template <typename T>
199Sdf_PySpecDetail::NewVisitor<typename Sdf_PySpecDetail::NewCtor<T> >
200SdfMakePySpecConstructor(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
207namespace Sdf_PySpecDetail {
208
209// Create the repr for a spec using Sdf.Find().
210SDF_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.
214typedef PyObject* (*_HolderCreator)(const SdfSpec&);
215SDF_API void _RegisterHolderCreator(const std::type_info&, _HolderCreator);
216SDF_API PyObject* _CreateHolder(const std::type_info&, const SdfSpec&);
217
218template <class _SpecType>
219struct _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.
232template <class _SpecType, class _Held, class _Holder>
233struct _HandleToPython {
234public:
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
251private:
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
289private:
290 static bp::converter::to_python_function_t _originalConverter;
291};
292template <class SpecType, class Held, class Holder>
293bp::converter::to_python_function_t
294_HandleToPython<SpecType, Held, Holder>::_originalConverter = 0;
295
296template <class _SpecType>
297struct _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().
335template <bool Abstract>
336struct 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, get_pointer(held));
350 }
351
352 static bool IsExpired(const HeldType& self)
353 {
354 return !self;
355 }
356
357 static bool IsValid(const HeldType& self)
358 {
359 return self;
360 }
361
362 static size_t __hash__(const HeldType& self)
363 {
364 return hash_value(self);
365 }
366
367 static bool __eq__(const HeldType& a, const HeldType& b)
368 {
369 return a == b;
370 }
371
372 static bool __ne__(const HeldType& a, const HeldType& b)
373 {
374 return a != b;
375 }
376
377 static bool __lt__(const HeldType& a, const HeldType& b)
378 {
379 return a < b;
380 }
381
382 static bool __le__(const HeldType& a, const HeldType& b)
383 {
384 return a <= b;
385 }
386
387 static bool __gt__(const HeldType& a, const HeldType& b)
388 {
389 return a > b;
390 }
391
392 static bool __ge__(const HeldType& a, const HeldType& b)
393 {
394 return a >= b;
395 }
396 };
397
398public:
399 SpecVisitor(bool addRepr = true) : _addRepr(addRepr) { }
400
401 template <typename CLS>
402 void visit(CLS& c) const
403 {
404 typedef typename CLS::wrapped_type SpecType;
405 typedef typename CLS::metadata::held_type HeldType;
406 typedef typename CLS::metadata::held_type_arg HeldArgType;
407 typedef typename CLS::metadata::holder HolderType;
408
409 static_assert(std::is_same<HeldType, SdfHandle<SpecType> >::value,
410 "HeldType must be SdfHandle<SpecType>.");
411
412 // Add methods.
413 c.add_property("expired", &_Helper<CLS>::IsExpired);
414 c.def("__bool__", &_Helper<CLS>::IsValid);
415 c.def("__hash__", &_Helper<CLS>::__hash__);
416 c.def("__eq__", &_Helper<CLS>::__eq__);
417 c.def("__ne__", &_Helper<CLS>::__ne__);
418 c.def("__lt__", &_Helper<CLS>::__lt__);
419 c.def("__le__", &_Helper<CLS>::__le__);
420 c.def("__gt__", &_Helper<CLS>::__gt__);
421 c.def("__ge__", &_Helper<CLS>::__ge__);
422
423 // Add python conversion to cast away constness.
424 _ConstHandleToPython<SpecType>();
425
426 // Add python conversion for SdfHandle<SpecType>.
427 _HandleFromPython<SpecType>();
428 _HandleFromPython<const SpecType>();
429 _HandleToPython<SpecType, HeldArgType, HolderType>::Register();
430
431 // Add __repr__.
432 if (_addRepr) {
433 c.def("__repr__", &_Helper<CLS>::Repr);
434 }
435 }
436
437private:
438 bool _addRepr;
439};
440
441} // namespace Sdf_PySpecDetail
442
443inline
444Sdf_PySpecDetail::SpecVisitor<false>
445SdfPySpec()
446{
447 return Sdf_PySpecDetail::SpecVisitor<false>();
448}
449
450inline
451Sdf_PySpecDetail::SpecVisitor<true>
452SdfPyAbstractSpec()
453{
454 return Sdf_PySpecDetail::SpecVisitor<true>();
455}
456
457inline
458Sdf_PySpecDetail::SpecVisitor<false>
459SdfPySpecNoRepr()
460{
461 return Sdf_PySpecDetail::SpecVisitor<false>(false);
462}
463
464inline
465Sdf_PySpecDetail::SpecVisitor<true>
466SdfPyAbstractSpecNoRepr()
467{
468 return Sdf_PySpecDetail::SpecVisitor<true>(false);
469}
470
471
472namespace Sdf_PySpecDetail
473{
474
475// This generates multi-argument specializations for NewCtor.
476
477template <typename R, typename... Args>
478struct NewCtor<R(Args...)> : CtorBase<R(Args...)> {
479 typedef CtorBase<R(Args...)> Base;
480 typedef typename Base::Sig Sig;
481 NewCtor(Sig *func) { Base::SetFunc(func); }
482
483 template <class CLS>
484 static bp::object __new__(bp::object &cls, Args... args) {
485 typedef typename CLS::metadata::held_type HeldType;
486 TfErrorMark m;
487 HeldType specHandle(Base::_func(args...));
488 if (TfPyConvertTfErrorsToPythonException(m))
489 bp::throw_error_already_set();
490 bp::object result = TfPyObject(specHandle);
491 if (TfPyIsNone(result))
492 TfPyThrowRuntimeError("could not construct " +
493 ArchGetDemangled(typeid(HeldType)));
494
495 bp::detail::initialize_wrapper(result.ptr(), get_pointer(specHandle));
496 // make the object have the right class.
497 bp::setattr(result, "__class__", cls);
498
499 return result;
500 }
501};
502
503} // namespace Sdf_PySpecDetail
504
505PXR_NAMESPACE_CLOSE_SCOPE
506
507#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:144
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:50
Class used to record the end of the error-list.
Definition: errorMark.h:65
Demangle C++ typenames generated by the typeid() facility.
std::string ArchGetDemangled()
Return demangled RTTI generated-type name.
Definition: demangle.h:103
#define TF_CODING_ERROR(fmt, args)
Issue an internal programming error, but continue execution.
Definition: diagnostic.h:85
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:454