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