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
27 /// \file
28 /// Flexible, high-level interface for calling Python functions.
29
30 #include "pxr/pxr.h"
31 #include "pxr/base/tf/api.h"
32
33 #include "pxr/base/tf/diagnosticLite.h"
34 #include "pxr/base/tf/pyError.h"
35 #include "pxr/base/tf/pyInterpreter.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
51 ////////////////////////////////////////////////////////////////////////////////
52 // To-Python arg conversion
53
54 #ifndef doxygen
55
56 // Convert any type to boost::python::object.
57 template <typename T>
Tf_ArgToPy(const T & value)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
68 ////////////////////////////////////////////////////////////////////////////////
69 // Keyword arg specification
70
71 /// Wrapper object for a keyword-argument pair in a call to TfPyInvoke*. Any
72 /// value type may be provided, as long as it is convertible to Python.
73 /// Typically passed as an inline temporary object:
74 ///
75 /// \code
76 /// const bool ok = TfPyInvoke(
77 /// "MyModule", "MyFunction",
78 /// arg1value, arg2value, TfPyKwArg("arg4", arg4value));
79 /// \endcode
80 ///
81 /// \code{.py}
82 /// def MyFunction(arg1, arg2, arg3 = None, arg4 = None, arg5 = None):
83 /// # ...
84 /// \endcode
85 ///
86 struct TfPyKwArg
87 {
88 template <typename T>
TfPyKwArgTfPyKwArg89 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
103 ////////////////////////////////////////////////////////////////////////////////
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>
Tf_BuildPyInvokeKwArgs(boost::python::dict * kwArgsOut,const Arg & kwArg,RestArgs...rest)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>
Tf_BuildPyInvokeKwArgs(boost::python::dict * kwArgsOut,const TfPyKwArg & kwArg,RestArgs...rest)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>
Tf_BuildPyInvokeArgs(boost::python::list * posArgsOut,boost::python::dict * kwArgsOut,const Arg & arg,RestArgs...rest)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>
Tf_BuildPyInvokeArgs(boost::python::list * posArgsOut,boost::python::dict * kwArgsOut,const TfPyKwArg & kwArg,RestArgs...rest)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
178 ////////////////////////////////////////////////////////////////////////////////
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>
193 bool TfPyInvokeAndReturn(
194 const std::string &moduleName,
195 const std::string &callableExpr,
196 boost::python::object *resultOut,
197 Args... args);
198
199 #endif // !doxygen
200
201 ////////////////////////////////////////////////////////////////////////////////
202 // Main entry points
203
204 /// Call a Python function and obtain its return value.
205 ///
206 /// Example:
207 /// \code
208 /// // Call MyModule.MyFunction(arg1, arg2), which returns a string.
209 /// std::string result;
210 /// const bool ok = TfPyInvokeAndExtract(
211 /// "MyModule", "MyFunction", &result, arg1Value, arg2Value);
212 /// \endcode
213 ///
214 /// \p moduleName is the name of the module in which to find the function. This
215 /// name will be directly imported in an \c import statement, so anything that
216 /// you know is in \c sys.path should work. The module name will also be
217 /// prepended to \p callableExpr to look up the function.
218 ///
219 /// \p callableExpr is a Python expression that, when appended to \p moduleName
220 /// (with an intervening dot), yields a callable object. Typically this is just
221 /// a function name, optionally prefixed with object names (such as a class in
222 /// which the callable resides).
223 ///
224 /// \p resultOut is a pointer that will receive the Python function's return
225 /// value. A from-Python converter must be registered for the type of \c
226 /// *resultOut.
227 ///
228 /// \p args is zero or more function arguments, of any types for which to-Python
229 /// conversions are registered. Any \c nullptr arguments are converted to \c
230 /// None. \p args may also contain TfPyKwArg objects to pass keyword arguments.
231 /// As in Python, once a keyword argument is passed, all remaining arguments
232 /// must also be keyword arguments.
233 ///
234 /// The return value of TfPyInvokeAndExtract is true if the call completed,
235 /// false otherwise. When the return value is false, at least one TfError
236 /// should have been raised, describing the failure. TfPyInvokeAndExtract never
237 /// raises exceptions.
238 ///
239 /// It should be safe to call this function without doing any other setup
240 /// first. It is not necessary to call TfPyInitialize or lock the GIL; this
241 /// function does those things itself.
242 ///
243 /// If you don't need the function's return value, call TfPyInvoke instead.
244 ///
245 /// If you need the function's return value, but the return value isn't
246 /// guaranteed to be a consistent type that's convertible to C++, call
247 /// TfPyInvokeAndReturn instead. This includes cases where the function's
248 /// return value may be \c None.
249 ///
250 template <typename Result, typename... Args>
TfPyInvokeAndExtract(const std::string & moduleName,const std::string & callableExpr,Result * resultOut,Args...args)251 bool TfPyInvokeAndExtract(
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
283 /// A version of TfPyInvokeAndExtract that provides the Python function's return
284 /// value as a \c boost::python::object, rather than extracting a particular C++
285 /// type from it.
286 ///
287 template <typename... Args>
TfPyInvokeAndReturn(const std::string & moduleName,const std::string & callableExpr,boost::python::object * resultOut,Args...args)288 bool TfPyInvokeAndReturn(
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
325 /// A version of TfPyInvokeAndExtract that ignores the Python function's return
326 /// value.
327 ///
328 template <typename... Args>
TfPyInvoke(const std::string & moduleName,const std::string & callableExpr,Args...args)329 bool TfPyInvoke(
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
346