1 //
2 // Copyright 2016 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_UTILS_H
25 #define PXR_BASE_TF_PY_UTILS_H
26 
27 /// \file tf/pyUtils.h
28 /// Miscellaneous Utilities for dealing with script.
29 
30 #include "pxr/pxr.h"
31 
32 #include "pxr/base/tf/refPtr.h"
33 #include "pxr/base/tf/weakPtr.h"
34 #include "pxr/base/tf/diagnosticLite.h"
35 #include "pxr/base/tf/preprocessorUtilsLite.h"
36 #include "pxr/base/tf/py3Compat.h"
37 #include "pxr/base/tf/pyInterpreter.h"
38 #include "pxr/base/tf/pyLock.h"
39 #include "pxr/base/tf/api.h"
40 
41 #include <functional>
42 #include <typeinfo>
43 #include <string>
44 #include <vector>
45 
46 #include <boost/python/dict.hpp>
47 #include <boost/python/extract.hpp>
48 #include <boost/python/handle.hpp>
49 #include <boost/python/object.hpp>
50 #include <boost/python/type_id.hpp>
51 
52 PXR_NAMESPACE_OPEN_SCOPE
53 
54 /// A macro which expands to the proper __repr__ prefix for a library.  This is
55 /// the "canonical" name of the module that the system uses to identify it
56 /// followed by a '.'.  This can be used in the implementation of __repr__
57 ///
58 /// \hideinitializer
59 #define TF_PY_REPR_PREFIX \
60     std::string(TF_PP_STRINGIZE(MFB_PACKAGE_MODULE) ".")
61 
62 /// Returns true if python is initialized.
63 TF_API bool TfPyIsInitialized();
64 
65 /// Raises a python \c IndexError and throws a C++ exception.
66 /// Intended to be used in wrapper code.
67 TF_API void TfPyThrowIndexError(std::string const &msg);
68 
69 /// Raises a python \c RuntimError and throws a C++ exception.
70 /// Intended to be used in wrapper code.
71 TF_API void TfPyThrowRuntimeError(std::string const &msg);
72 
73 /// Raises a python \c StopIteration exception and throws a C++ exception.
74 /// Intended to be used in wrapper code.
75 TF_API void TfPyThrowStopIteration(std::string const &msg);
76 
77 /// Raises a python \c KeyError and throws a C++ exception.
78 /// Intended to be used in wrapper code.
79 TF_API void TfPyThrowKeyError(std::string const &msg);
80 
81 /// Raises a python \c ValueError and throws a C++ exception.
82 /// Intended to be used in wrapper code.
83 TF_API void TfPyThrowValueError(std::string const &msg);
84 
85 /// Raises a python \c TypeError and throws a C++ exception.
86 /// Intended to be used in wrapper code.
87 TF_API void TfPyThrowTypeError(std::string const &msg);
88 
89 /// Return true iff \a obj is None.
90 TF_API bool TfPyIsNone(boost::python::object const &obj);
91 
92 /// Return true iff \a obj is None.
93 TF_API bool TfPyIsNone(boost::python::handle<> const &obj);
94 
95 // Helper for \c TfPyObject().
96 TF_API void Tf_PyObjectError(bool printError);
97 
98 /// Return a python object for the given C++ object, loading the appropriate
99 /// wrapper code if necessary. Spams users if complainOnFailure is true and
100 /// conversion fails.
101 template <typename T>
102 boost::python::object TfPyObject(T const &t, bool complainOnFailure = true) {
103     // initialize python if it isn't already, so at least we can try to return
104     // an object
105     if (!TfPyIsInitialized()) {
106         TF_CODING_ERROR("Called TfPyObject without python being initialized!");
107         TfPyInitialize();
108     }
109 
110     TfPyLock pyLock;
111 
112     // Will only be able to return objects which have been wrapped.
113     // Returns None otherwise
114     try {
115         return boost::python::object(t);
catch(boost::python::error_already_set const &)116     } catch (boost::python::error_already_set const &) {
117         Tf_PyObjectError(complainOnFailure);
118         return boost::python::object();
119    }
120 }
121 
122 inline
123 boost::python::object TfPyObject(PyObject* t, bool complainOnFailure = true) {
124     TfPyLock pyLock;
125     return boost::python::object(boost::python::handle<>(t));
126 }
127 
128 /// Return repr(t).
129 ///
130 /// Calls PyObject_Repr on the given python object.
131 TF_API std::string TfPyObjectRepr(boost::python::object const &t);
132 
133 /// Return repr(t).
134 ///
135 /// Converts t to its equivalent python object and then calls PyObject_Repr on
136 /// that.
137 template <typename T>
TfPyRepr(T const & t)138 std::string TfPyRepr(T const &t) {
139     if (!TfPyIsInitialized())
140         return "<python not initialized>";
141     TfPyLock lock;
142     return TfPyObjectRepr(TfPyObject(t));
143 }
144 
145 /// Return repr(t) for a vector as a python list.
146 template <typename T>
TfPyRepr(const std::vector<T> & v)147 std::string TfPyRepr(const std::vector<T> &v) {
148     std::string result("[");
149     typename std::vector<T>::const_iterator i = v.begin();
150     if (i != v.end()) {
151         result += TfPyRepr(*i);
152         ++i;
153     }
154     while (i != v.end()) {
155         result += ", " + TfPyRepr(*i);
156         ++i;
157     }
158     result += "]";
159     return result;
160 }
161 
162 /// Evaluate python expression \a expr with all the known script modules
163 /// imported under their standard names. Additional globals may be provided in
164 /// the \p extraGlobals dictionary.
165 TF_API
166 boost::python::object
167 TfPyEvaluate(
168     std::string const &expr,
169     boost::python::dict const &extraGlobals = boost::python::dict());
170 
171 /// Return a positive index in the range [0,size).  If \a throwError is true,
172 /// this will throw an index error if the resulting index is out of range.
173 TF_API
174 int64_t
175 TfPyNormalizeIndex(int64_t index, uint64_t size, bool throwError = false);
176 
177 /// Return the name of the class of \a obj.
178 TF_API std::string TfPyGetClassName(boost::python::object const &obj);
179 
180 
181 /// Return the python class object for \a type if \a type has been wrapped.
182 /// Otherwise return None.
183 TF_API boost::python::object
184 TfPyGetClassObject(std::type_info const &type);
185 
186 /// Return the python class object for T if T has been wrapped.
187 /// Otherwise return None.
188 template <typename T>
189 boost::python::object
TfPyGetClassObject()190 TfPyGetClassObject() {
191     return TfPyGetClassObject(typeid(T));
192 }
193 
194 TF_API
195 void
196 Tf_PyWrapOnceImpl(boost::python::type_info const &,
197                   std::function<void()> const&,
198                   bool *);
199 
200 /// Invokes \p wrapFunc to wrap type \c T if \c T is not already wrapped.
201 ///
202 /// Executing \p wrapFunc *must* register \c T with boost python.  Otherwise,
203 /// \p wrapFunc may be executed more than once.
204 ///
205 /// TfPyWrapOnce will acquire the GIL prior to invoking \p wrapFunc. Does not
206 /// invoke \p wrapFunc if Python has not been initialized.
207 template <typename T>
208 void
TfPyWrapOnce(std::function<void ()> const & wrapFunc)209 TfPyWrapOnce(std::function<void()> const &wrapFunc)
210 {
211     // Don't try to wrap if python isn't initialized.
212     if (!TfPyIsInitialized()) {
213         return;
214     }
215 
216     static bool isTypeWrapped = false;
217     if (isTypeWrapped) {
218         return;
219     }
220 
221     Tf_PyWrapOnceImpl(boost::python::type_id<T>(), wrapFunc, &isTypeWrapped);
222 }
223 
224 /// Load the python module \a moduleName.  This is used by some low-level
225 /// infrastructure code to load python wrapper modules corresponding to C++
226 /// shared libraries when they are needed.  It should generally not need to be
227 /// called from normal user code.
228 TF_API
229 void Tf_PyLoadScriptModule(std::string const &name);
230 
231 /// Creates a python dictionary from a std::map.
232 template <class Map>
TfPyCopyMapToDictionary(Map const & map)233 boost::python::dict TfPyCopyMapToDictionary(Map const &map) {
234     TfPyLock lock;
235     boost::python::dict d;
236     for (typename Map::const_iterator i = map.begin(); i != map.end(); ++i)
237         d[i->first] = i->second;
238     return d;
239 }
240 
241 template<class Seq>
TfPyCopySequenceToList(Seq const & seq)242 boost::python::list TfPyCopySequenceToList(Seq const &seq) {
243     TfPyLock lock;
244     boost::python::list l;
245     for (typename Seq::const_iterator i = seq.begin();
246          i != seq.end(); ++i)
247         l.append(*i);
248     return l;
249 }
250 
251 /// Create a python set from an iterable sequence.
252 ///
253 /// If Seq::value_type is not hashable, TypeError is raised via throwing
254 /// boost::python::error_already_set.
255 template <class Seq>
TfPyCopySequenceToSet(Seq const & seq)256 boost::python::object TfPyCopySequenceToSet(Seq const &seq) {
257     TfPyLock lock;
258     boost::python::handle<> set{boost::python::allow_null(PySet_New(nullptr))};
259     if (!set) {
260         boost::python::throw_error_already_set();
261     }
262     for (auto const& item : seq) {
263         boost::python::object obj(item);
264         if (PySet_Add(set.get(), obj.ptr()) == -1) {
265             boost::python::throw_error_already_set();
266         }
267     }
268     return boost::python::object(set);
269 }
270 
271 template<class Seq>
TfPyCopySequenceToTuple(Seq const & seq)272 boost::python::tuple TfPyCopySequenceToTuple(Seq const &seq) {
273     return boost::python::tuple(TfPyCopySequenceToList(seq));
274 }
275 
276 /// Create a python bytearray from an input buffer and size.
277 ///
278 /// If a size of zero is passed in this function will return a valid python
279 /// bytearray of size zero.
280 ///
281 /// An invalid object handle is returned on failure.
282 TF_API
283 boost::python::object TfPyCopyBufferToByteArray(const char* buffer, size_t size);
284 
285 /// Return a vector of strings containing the current python traceback.
286 ///
287 /// The vector contains the same strings that python's traceback.format_stack()
288 /// returns.
289 TF_API
290 std::vector<std::string> TfPyGetTraceback();
291 
292 /// Populates the vector passed in with pointers to strings containing the
293 /// python interpreter stack frames.
294 /// Note that TfPyGetStackFrames allocates these strings on the heap and its
295 /// the caller's responsibility to free them.
296 TF_API
297 void TfPyGetStackFrames(std::vector<uintptr_t> *frames);
298 
299 /// Print the current python traceback to stdout.
300 TF_API
301 void TfPyDumpTraceback();
302 
303 /// Set an environment variable in \c os.environ.
304 ///
305 /// This function is equivalent to
306 ///
307 /// \code
308 ///    def PySetenv(name, value):
309 ///        try:
310 ///            import os
311 ///            os.environ[name] = value
312 ///            return True
313 ///        except:
314 ///            return False
315 /// \endcode
316 ///
317 /// Calling this function without first initializing Python is an error and
318 /// returns \c false.
319 ///
320 /// Note that this function will import the \c os module, causing \c
321 /// os.environ to be poputated.  All modifications to the environment after \c
322 /// os has been imported must be made with this function or \c TfSetenv if it
323 /// important that they appear in \c os.environ.
324 TF_API
325 bool TfPySetenv(const std::string & name, const std::string & value);
326 
327 /// Remove an environment variable from \c os.environ.
328 ///
329 /// This function is equivalent to
330 ///
331 /// \code
332 ///    def PyUnsetenv(name):
333 ///        try:
334 ///            import os
335 ///            if name in os.environ:
336 ///                del os.environ[name]
337 ///            return True
338 ///        except:
339 ///            return False
340 /// \endcode
341 ///
342 /// Calling this function without first initializing Python is an error and
343 /// returns \c false.
344 ///
345 /// Note that this function will import the \c os module, causing \c
346 /// os.environ to be poputated.  All modifications to the environment after \c
347 /// os has been imported must be made with this function or \c TfUnsetenv if
348 /// it important that they appear in \c os.environ.
349 TF_API
350 bool TfPyUnsetenv(const std::string & name);
351 
352 // Private helper method to TfPyEvaluateAndExtract.
353 //
354 TF_API bool Tf_PyEvaluateWithErrorCheck(
355     const std::string & expr, boost::python::object * obj);
356 
357 /// Safely evaluates \p expr and extracts the return object of type T. If
358 /// successful, returns \c true and sets *t to the return value, otherwise
359 /// returns \c false.
360 template <typename T>
TfPyEvaluateAndExtract(const std::string & expr,T * t)361 bool TfPyEvaluateAndExtract(const std::string & expr, T * t)
362 {
363     if (expr.empty())
364         return false;
365 
366     // Take the lock before doing anything with boost::python.
367     TfPyLock lock;
368 
369     // Though TfPyEvaluate (called by Tf_PyEvaluateWithErroCheck) takes the
370     // python lock, it is important that we lock before we initialize the
371     // boost::python::object, since it will increment and decrement ref counts
372     // outside of the call to TfPyEvaluate.
373     boost::python::object obj;
374     if (!Tf_PyEvaluateWithErrorCheck(expr, &obj))
375         return false;
376 
377     boost::python::extract<T> extractor(obj);
378 
379     if (!extractor.check())
380         return false;
381 
382     *t = extractor();
383 
384     return true;
385 }
386 
387 /// Print a standard traceback to sys.stderr and clear the error indicator.
388 /// If the error is a KeyboardInterrupt then this does nothing.  Call this
389 /// function only when the error indicator is set.
390 TF_API
391 void TfPyPrintError();
392 
393 PXR_NAMESPACE_CLOSE_SCOPE
394 
395 #endif // PXR_BASE_TF_PY_UTILS_H
396