pyInvoke.h
Go to the documentation of this file.
1 //
2 // Copyright 2021 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_BASE_TF_PY_INVOKE_H
25 #define PXR_BASE_TF_PY_INVOKE_H
26 
29 
30 #include "pxr/pxr.h"
31 #include "pxr/base/tf/api.h"
32 
34 #include "pxr/base/tf/pyError.h"
36 #include "pxr/base/tf/pyLock.h"
37 #include "pxr/base/tf/pyObjWrapper.h"
38 
39 #include <boost/python/dict.hpp>
40 #include <boost/python/extract.hpp>
41 #include <boost/python/list.hpp>
42 #include <boost/python/object.hpp>
43 
44 #include <cstddef>
45 #include <memory>
46 #include <string>
47 #include <type_traits>
48 
49 PXR_NAMESPACE_OPEN_SCOPE
50 
52 // To-Python arg conversion
53 
54 #ifndef doxygen
55 
56 // Convert any type to boost::python::object.
57 template <typename T>
58 boost::python::object Tf_ArgToPy(const T &value)
59 {
60  return boost::python::object(value);
61 }
62 
63 // Convert nullptr to None.
64 TF_API boost::python::object Tf_ArgToPy(const std::nullptr_t &value);
65 
66 #endif // !doxygen
67 
69 // Keyword arg specification
70 
86 struct TfPyKwArg
87 {
88  template <typename T>
89  TfPyKwArg(const std::string &nameIn, const T &valueIn)
90  : name(nameIn)
91  {
92  // Constructing boost::python::object requires the GIL.
93  TfPyLock lock;
94 
95  // The object constructor throws if the type is not convertible.
96  value = Tf_ArgToPy(valueIn);
97  }
98 
99  std::string name;
100  TfPyObjWrapper value;
101 };
102 
104 // Argument collection by variadic template functions
105 
106 #ifndef doxygen
107 
108 // Variadic helper: trivial base case.
109 TF_API void Tf_BuildPyInvokeKwArgs(
110  boost::python::dict *kwArgsOut);
111 
112 // Poisoned variadic template helper that provides an error message when
113 // non-keyword args are used after keyword args.
114 template <typename Arg, typename... RestArgs>
115 void Tf_BuildPyInvokeKwArgs(
116  boost::python::dict *kwArgsOut,
117  const Arg &kwArg,
118  RestArgs... rest)
119 {
120  // This assertion will always be false, since TfPyKwArg will select the
121  // overload below instead.
122  static_assert(
123  std::is_same<Arg, TfPyKwArg>::value,
124  "Non-keyword args not allowed after keyword args");
125 }
126 
127 // Recursive variadic template helper for keyword args.
128 template <typename... RestArgs>
129 void Tf_BuildPyInvokeKwArgs(
130  boost::python::dict *kwArgsOut,
131  const TfPyKwArg &kwArg,
132  RestArgs... rest)
133 {
134  // Store mapping in kwargs dict.
135  (*kwArgsOut)[kwArg.name] = kwArg.value.Get();
136 
137  // Recurse to handle next arg.
138  Tf_BuildPyInvokeKwArgs(kwArgsOut, rest...);
139 }
140 
141 // Variadic helper: trivial base case.
142 TF_API void Tf_BuildPyInvokeArgs(
143  boost::python::list *posArgsOut,
144  boost::python::dict *kwArgsOut);
145 
146 // Recursive general-purpose variadic template helper.
147 template <typename Arg, typename... RestArgs>
148 void Tf_BuildPyInvokeArgs(
149  boost::python::list *posArgsOut,
150  boost::python::dict *kwArgsOut,
151  const Arg &arg,
152  RestArgs... rest)
153 {
154  // Convert value to Python, and store in args list.
155  // The object constructor throws if the type is not convertible.
156  posArgsOut->append(Tf_ArgToPy(arg));
157 
158  // Recurse to handle next arg.
159  Tf_BuildPyInvokeArgs(posArgsOut, kwArgsOut, rest...);
160 }
161 
162 // Recursive variadic template helper for keyword args.
163 template <typename... RestArgs>
164 void Tf_BuildPyInvokeArgs(
165  boost::python::list *posArgsOut,
166  boost::python::dict *kwArgsOut,
167  const TfPyKwArg &kwArg,
168  RestArgs... rest)
169 {
170  // Switch to kwargs-only processing, enforcing (at compile time) the Python
171  // rule that there may not be non-kwargs after kwargs. If we relaxed this
172  // rule, some strange argument ordering could occur.
173  Tf_BuildPyInvokeKwArgs(kwArgsOut, kwArg, rest...);
174 }
175 
176 #endif // !doxygen
177 
179 // Declarations
180 
181 #ifndef doxygen
182 
183 // Helper for TfPyInvokeAndExtract.
184 TF_API bool Tf_PyInvokeImpl(
185  const std::string &moduleName,
186  const std::string &callableExpr,
187  const boost::python::list &posArgs,
188  const boost::python::dict &kwArgs,
189  boost::python::object *resultObjOut);
190 
191 // Forward declaration.
192 template <typename... Args>
194  const std::string &moduleName,
195  const std::string &callableExpr,
196  boost::python::object *resultOut,
197  Args... args);
198 
199 #endif // !doxygen
200 
202 // Main entry points
203 
250 template <typename Result, typename... Args>
252  const std::string &moduleName,
253  const std::string &callableExpr,
254  Result *resultOut,
255  Args... args)
256 {
257  if (!resultOut) {
258  TF_CODING_ERROR("Bad pointer to TfPyInvokeAndExtract");
259  return false;
260  }
261 
262  // Init Python and grab the GIL.
263  TfPyInitialize();
264  TfPyLock lock;
265 
266  boost::python::object resultObj;
267  if (!TfPyInvokeAndReturn(
268  moduleName, callableExpr, &resultObj, args...)) {
269  return false;
270  }
271 
272  // Extract return value.
273  boost::python::extract<Result> extractor(resultObj);
274  if (!extractor.check()) {
275  TF_CODING_ERROR("Result type mismatched or not convertible");
276  return false;
277  }
278  *resultOut = extractor();
279 
280  return true;
281 }
282 
287 template <typename... Args>
289  const std::string &moduleName,
290  const std::string &callableExpr,
291  boost::python::object *resultOut,
292  Args... args)
293 {
294  if (!resultOut) {
295  TF_CODING_ERROR("Bad pointer to TfPyInvokeAndExtract");
296  return false;
297  }
298 
299  // Init Python and grab the GIL.
300  TfPyInitialize();
301  TfPyLock lock;
302 
303  try {
304  // Convert args to Python and store in list+dict form.
305  boost::python::list posArgs;
306  boost::python::dict kwArgs;
307  Tf_BuildPyInvokeArgs(&posArgs, &kwArgs, args...);
308 
309  // Import, find callable, and call.
310  if (!Tf_PyInvokeImpl(
311  moduleName, callableExpr, posArgs, kwArgs, resultOut)) {
312  return false;
313  }
314  }
315  catch (boost::python::error_already_set const &) {
316  // Handle exceptions.
317  TfPyConvertPythonExceptionToTfErrors();
318  PyErr_Clear();
319  return false;
320  }
321 
322  return true;
323 }
324 
328 template <typename... Args>
330  const std::string &moduleName,
331  const std::string &callableExpr,
332  Args... args)
333 {
334  // Init Python and grab the GIL.
335  TfPyInitialize();
336  TfPyLock lock;
337 
338  boost::python::object ignoredResult;
339  return TfPyInvokeAndReturn(
340  moduleName, callableExpr, &ignoredResult, args...);
341 }
342 
343 PXR_NAMESPACE_CLOSE_SCOPE
344 
345 #endif // PXR_BASE_TF_PY_INVOKE_H
#define TF_CODING_ERROR(fmt, args)
Issue an internal programming error, but continue execution.
Definition: diagnostic.h:85
bool TfPyInvokeAndReturn(const std::string &moduleName, const std::string &callableExpr, boost::python::object *resultOut, Args... args)
A version of TfPyInvokeAndExtract that provides the Python function's return value as a boost::python...
Definition: pyInvoke.h:288
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:329
Boost Python object wrapper.
Definition: pyObjWrapper.h:161
Wrapper object for a keyword-argument pair in a call to TfPyInvoke*.
Definition: pyInvoke.h:86
Python runtime utilities.
TF_API void TfPyInitialize()
Starts up the python runtime.
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:251
Stripped down version of diagnostic.h that doesn't define std::string.