All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
pyInvoke.h
Go to the documentation of this file.
1//
2// Copyright 2021 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_BASE_TF_PY_INVOKE_H
8#define PXR_BASE_TF_PY_INVOKE_H
9
12
13#include "pxr/pxr.h"
14#include "pxr/base/tf/api.h"
15
17#include "pxr/base/tf/pyError.h"
19#include "pxr/base/tf/pyLock.h"
20#include "pxr/base/tf/pyObjWrapper.h"
21
22#include "pxr/external/boost/python/dict.hpp"
23#include "pxr/external/boost/python/extract.hpp"
24#include "pxr/external/boost/python/list.hpp"
25#include "pxr/external/boost/python/object.hpp"
26
27#include <cstddef>
28#include <memory>
29#include <string>
30#include <type_traits>
31
32PXR_NAMESPACE_OPEN_SCOPE
33
35// To-Python arg conversion
36
37#ifndef doxygen
38
39// Convert any type to pxr_boost::python::object.
40template <typename T>
41pxr_boost::python::object Tf_ArgToPy(const T &value)
42{
43 return pxr_boost::python::object(value);
44}
45
46// Convert nullptr to None.
47TF_API pxr_boost::python::object Tf_ArgToPy(const std::nullptr_t &value);
48
49#endif // !doxygen
50
52// Keyword arg specification
53
70{
71 template <typename T>
72 TfPyKwArg(const std::string &nameIn, const T &valueIn)
73 : name(nameIn)
74 {
75 // Constructing pxr_boost::python::object requires the GIL.
76 TfPyLock lock;
77
78 // The object constructor throws if the type is not convertible.
79 value = Tf_ArgToPy(valueIn);
80 }
81
82 std::string name;
83 TfPyObjWrapper value;
84};
85
87// Argument collection by variadic template functions
88
89#ifndef doxygen
90
91// Variadic helper: trivial base case.
92TF_API void Tf_BuildPyInvokeKwArgs(
93 pxr_boost::python::dict *kwArgsOut);
94
95// Poisoned variadic template helper that provides an error message when
96// non-keyword args are used after keyword args.
97template <typename Arg, typename... RestArgs>
98void Tf_BuildPyInvokeKwArgs(
99 pxr_boost::python::dict *kwArgsOut,
100 const Arg &kwArg,
101 RestArgs... rest)
102{
103 // This assertion will always be false, since TfPyKwArg will select the
104 // overload below instead.
105 static_assert(
106 std::is_same<Arg, TfPyKwArg>::value,
107 "Non-keyword args not allowed after keyword args");
108}
109
110// Recursive variadic template helper for keyword args.
111template <typename... RestArgs>
112void Tf_BuildPyInvokeKwArgs(
113 pxr_boost::python::dict *kwArgsOut,
114 const TfPyKwArg &kwArg,
115 RestArgs... rest)
116{
117 // Store mapping in kwargs dict.
118 (*kwArgsOut)[kwArg.name] = kwArg.value.Get();
119
120 // Recurse to handle next arg.
121 Tf_BuildPyInvokeKwArgs(kwArgsOut, rest...);
122}
123
124// Variadic helper: trivial base case.
125TF_API void Tf_BuildPyInvokeArgs(
126 pxr_boost::python::list *posArgsOut,
127 pxr_boost::python::dict *kwArgsOut);
128
129// Recursive general-purpose variadic template helper.
130template <typename Arg, typename... RestArgs>
131void Tf_BuildPyInvokeArgs(
132 pxr_boost::python::list *posArgsOut,
133 pxr_boost::python::dict *kwArgsOut,
134 const Arg &arg,
135 RestArgs... rest)
136{
137 // Convert value to Python, and store in args list.
138 // The object constructor throws if the type is not convertible.
139 posArgsOut->append(Tf_ArgToPy(arg));
140
141 // Recurse to handle next arg.
142 Tf_BuildPyInvokeArgs(posArgsOut, kwArgsOut, rest...);
143}
144
145// Recursive variadic template helper for keyword args.
146template <typename... RestArgs>
147void Tf_BuildPyInvokeArgs(
148 pxr_boost::python::list *posArgsOut,
149 pxr_boost::python::dict *kwArgsOut,
150 const TfPyKwArg &kwArg,
151 RestArgs... rest)
152{
153 // Switch to kwargs-only processing, enforcing (at compile time) the Python
154 // rule that there may not be non-kwargs after kwargs. If we relaxed this
155 // rule, some strange argument ordering could occur.
156 Tf_BuildPyInvokeKwArgs(kwArgsOut, kwArg, rest...);
157}
158
159#endif // !doxygen
160
162// Declarations
163
164#ifndef doxygen
165
166// Helper for TfPyInvokeAndExtract.
167TF_API bool Tf_PyInvokeImpl(
168 const std::string &moduleName,
169 const std::string &callableExpr,
170 const pxr_boost::python::list &posArgs,
171 const pxr_boost::python::dict &kwArgs,
172 pxr_boost::python::object *resultObjOut);
173
174// Forward declaration.
175template <typename... Args>
177 const std::string &moduleName,
178 const std::string &callableExpr,
179 pxr_boost::python::object *resultOut,
180 Args... args);
181
182#endif // !doxygen
183
185// Main entry points
186
233template <typename Result, typename... Args>
235 const std::string &moduleName,
236 const std::string &callableExpr,
237 Result *resultOut,
238 Args... args)
239{
240 if (!resultOut) {
241 TF_CODING_ERROR("Bad pointer to TfPyInvokeAndExtract");
242 return false;
243 }
244
245 // Init Python and grab the GIL.
247 TfPyLock lock;
248
249 pxr_boost::python::object resultObj;
251 moduleName, callableExpr, &resultObj, args...)) {
252 return false;
253 }
254
255 // Extract return value.
256 pxr_boost::python::extract<Result> extractor(resultObj);
257 if (!extractor.check()) {
258 TF_CODING_ERROR("Result type mismatched or not convertible");
259 return false;
260 }
261 *resultOut = extractor();
262
263 return true;
264}
265
270template <typename... Args>
272 const std::string &moduleName,
273 const std::string &callableExpr,
274 pxr_boost::python::object *resultOut,
275 Args... args)
276{
277 if (!resultOut) {
278 TF_CODING_ERROR("Bad pointer to TfPyInvokeAndExtract");
279 return false;
280 }
281
282 // Init Python and grab the GIL.
284 TfPyLock lock;
285
286 try {
287 // Convert args to Python and store in list+dict form.
288 pxr_boost::python::list posArgs;
289 pxr_boost::python::dict kwArgs;
290 Tf_BuildPyInvokeArgs(&posArgs, &kwArgs, args...);
291
292 // Import, find callable, and call.
293 if (!Tf_PyInvokeImpl(
294 moduleName, callableExpr, posArgs, kwArgs, resultOut)) {
295 return false;
296 }
297 }
298 catch (pxr_boost::python::error_already_set const &) {
299 // Handle exceptions.
300 TfPyConvertPythonExceptionToTfErrors();
301 PyErr_Clear();
302 return false;
303 }
304
305 return true;
306}
307
311template <typename... Args>
313 const std::string &moduleName,
314 const std::string &callableExpr,
315 Args... args)
316{
317 // Init Python and grab the GIL.
319 TfPyLock lock;
320
321 pxr_boost::python::object ignoredResult;
322 return TfPyInvokeAndReturn(
323 moduleName, callableExpr, &ignoredResult, args...);
324}
325
326PXR_NAMESPACE_CLOSE_SCOPE
327
328#endif // PXR_BASE_TF_PY_INVOKE_H
Convenience class for accessing the Python Global Interpreter Lock.
Definition: pyLock.h:105
Boost Python object wrapper.
Definition: pyObjWrapper.h:79
object const & Get() const
Underlying object access.
Definition: pyObjWrapper.h:98
Stripped down version of diagnostic.h that doesn't define std::string.
#define TF_CODING_ERROR(fmt, args)
Issue an internal programming error, but continue execution.
Definition: diagnostic.h:68
Python runtime utilities.
TF_API void TfPyInitialize()
Starts up the python runtime.
bool TfPyInvokeAndReturn(const std::string &moduleName, const std::string &callableExpr, pxr_boost::python::object *resultOut, Args... args)
A version of TfPyInvokeAndExtract that provides the Python function's return value as a pxr_boost::py...
Definition: pyInvoke.h:271
bool TfPyInvoke(const std::string &moduleName, const std::string &callableExpr, Args... args)
A version of TfPyInvokeAndExtract that ignores the Python function's return value.
Definition: pyInvoke.h:312
bool TfPyInvokeAndExtract(const std::string &moduleName, const std::string &callableExpr, Result *resultOut, Args... args)
Call a Python function and obtain its return value.
Definition: pyInvoke.h:234
Wrapper object for a keyword-argument pair in a call to TfPyInvoke*.
Definition: pyInvoke.h:70