HDK
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups 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 
10 /// \file
11 /// Flexible, high-level interface for calling Python functions.
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"
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 
33 
34 ////////////////////////////////////////////////////////////////////////////////
35 // To-Python arg conversion
36 
37 #ifndef doxygen
38 
39 // Convert any type to pxr_boost::python::object.
40 template <typename T>
42 {
43  return pxr_boost::python::object(value);
44 }
45 
46 // Convert nullptr to None.
47 TF_API pxr_boost::python::object Tf_ArgToPy(const std::nullptr_t &value);
48 
49 #endif // !doxygen
50 
51 ////////////////////////////////////////////////////////////////////////////////
52 // Keyword arg specification
53 
54 /// Wrapper object for a keyword-argument pair in a call to TfPyInvoke*. Any
55 /// value type may be provided, as long as it is convertible to Python.
56 /// Typically passed as an inline temporary object:
57 ///
58 /// \code
59 /// const bool ok = TfPyInvoke(
60 /// "MyModule", "MyFunction",
61 /// arg1value, arg2value, TfPyKwArg("arg4", arg4value));
62 /// \endcode
63 ///
64 /// \code{.py}
65 /// def MyFunction(arg1, arg2, arg3 = None, arg4 = None, arg5 = None):
66 /// # ...
67 /// \endcode
68 ///
69 struct TfPyKwArg
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;
84 };
85 
86 ////////////////////////////////////////////////////////////////////////////////
87 // Argument collection by variadic template functions
88 
89 #ifndef doxygen
90 
91 // Variadic helper: trivial base case.
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.
97 template <typename Arg, typename... RestArgs>
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(
107  "Non-keyword args not allowed after keyword args");
108 }
109 
110 // Recursive variadic template helper for keyword args.
111 template <typename... RestArgs>
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.
126  pxr_boost::python::list *posArgsOut,
127  pxr_boost::python::dict *kwArgsOut);
128 
129 // Recursive general-purpose variadic template helper.
130 template <typename Arg, typename... RestArgs>
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.
146 template <typename... RestArgs>
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 
161 ////////////////////////////////////////////////////////////////////////////////
162 // Declarations
163 
164 #ifndef doxygen
165 
166 // Helper for TfPyInvokeAndExtract.
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.
175 template <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 
184 ////////////////////////////////////////////////////////////////////////////////
185 // Main entry points
186 
187 /// Call a Python function and obtain its return value.
188 ///
189 /// Example:
190 /// \code
191 /// // Call MyModule.MyFunction(arg1, arg2), which returns a string.
192 /// std::string result;
193 /// const bool ok = TfPyInvokeAndExtract(
194 /// "MyModule", "MyFunction", &result, arg1Value, arg2Value);
195 /// \endcode
196 ///
197 /// \p moduleName is the name of the module in which to find the function. This
198 /// name will be directly imported in an \c import statement, so anything that
199 /// you know is in \c sys.path should work. The module name will also be
200 /// prepended to \p callableExpr to look up the function.
201 ///
202 /// \p callableExpr is a Python expression that, when appended to \p moduleName
203 /// (with an intervening dot), yields a callable object. Typically this is just
204 /// a function name, optionally prefixed with object names (such as a class in
205 /// which the callable resides).
206 ///
207 /// \p resultOut is a pointer that will receive the Python function's return
208 /// value. A from-Python converter must be registered for the type of \c
209 /// *resultOut.
210 ///
211 /// \p args is zero or more function arguments, of any types for which to-Python
212 /// conversions are registered. Any \c nullptr arguments are converted to \c
213 /// None. \p args may also contain TfPyKwArg objects to pass keyword arguments.
214 /// As in Python, once a keyword argument is passed, all remaining arguments
215 /// must also be keyword arguments.
216 ///
217 /// The return value of TfPyInvokeAndExtract is true if the call completed,
218 /// false otherwise. When the return value is false, at least one TfError
219 /// should have been raised, describing the failure. TfPyInvokeAndExtract never
220 /// raises exceptions.
221 ///
222 /// It should be safe to call this function without doing any other setup
223 /// first. It is not necessary to call TfPyInitialize or lock the GIL; this
224 /// function does those things itself.
225 ///
226 /// If you don't need the function's return value, call TfPyInvoke instead.
227 ///
228 /// If you need the function's return value, but the return value isn't
229 /// guaranteed to be a consistent type that's convertible to C++, call
230 /// TfPyInvokeAndReturn instead. This includes cases where the function's
231 /// return value may be \c None.
232 ///
233 template <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.
246  TfPyInitialize();
247  TfPyLock lock;
248 
249  pxr_boost::python::object resultObj;
250  if (!TfPyInvokeAndReturn(
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 
266 /// A version of TfPyInvokeAndExtract that provides the Python function's return
267 /// value as a \c pxr_boost::python::object, rather than extracting a particular C++
268 /// type from it.
269 ///
270 template <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.
283  TfPyInitialize();
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.
301  PyErr_Clear();
302  return false;
303  }
304 
305  return true;
306 }
307 
308 /// A version of TfPyInvokeAndExtract that ignores the Python function's return
309 /// value.
310 ///
311 template <typename... Args>
313  const std::string &moduleName,
314  const std::string &callableExpr,
315  Args... args)
316 {
317  // Init Python and grab the GIL.
318  TfPyInitialize();
319  TfPyLock lock;
320 
321  pxr_boost::python::object ignoredResult;
322  return TfPyInvokeAndReturn(
323  moduleName, callableExpr, &ignoredResult, args...);
324 }
325 
327 
328 #endif // PXR_BASE_TF_PY_INVOKE_H
#define TF_API
Definition: api.h:23
GLsizei const GLfloat * value
Definition: glcorearb.h:824
#define TF_CODING_ERROR
TF_API void Tf_BuildPyInvokeKwArgs(pxr_boost::python::dict *kwArgsOut)
TF_API void Tf_BuildPyInvokeArgs(pxr_boost::python::list *posArgsOut, pxr_boost::python::dict *kwArgsOut)
PXR_NAMESPACE_OPEN_SCOPE TF_API void TfPyInitialize()
std::string name
Definition: pyInvoke.h:82
auto arg(const Char *name, const T &arg) -> detail::named_arg< Char, T >
Definition: core.h:1859
bool TfPyInvoke(const std::string &moduleName, const std::string &callableExpr, Args...args)
Definition: pyInvoke.h:312
PXR_NAMESPACE_OPEN_SCOPE pxr_boost::python::object Tf_ArgToPy(const T &value)
Definition: pyInvoke.h:41
TF_API bool Tf_PyInvokeImpl(const std::string &moduleName, const std::string &callableExpr, const pxr_boost::python::list &posArgs, const pxr_boost::python::dict &kwArgs, pxr_boost::python::object *resultObjOut)
TF_API void TfPyConvertPythonExceptionToTfErrors()
TfPyObjWrapper value
Definition: pyInvoke.h:83
GLuint const GLchar * name
Definition: glcorearb.h:786
PXR_NAMESPACE_CLOSE_SCOPE PXR_NAMESPACE_OPEN_SCOPE
Definition: path.h:1425
TfPyKwArg(const std::string &nameIn, const T &valueIn)
Definition: pyInvoke.h:72
#define PXR_NAMESPACE_CLOSE_SCOPE
Definition: pxr.h:74
**If you just want to fire and args
Definition: thread.h:618
GA_API const UT_StringHolder rest
bool TfPyInvokeAndReturn(const std::string &moduleName, const std::string &callableExpr, pxr_boost::python::object *resultOut, Args...args)
Definition: pyInvoke.h:271
bool TfPyInvokeAndExtract(const std::string &moduleName, const std::string &callableExpr, Result *resultOut, Args...args)
Definition: pyInvoke.h:234