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
25 #include "pxr/pxr.h"
26
27 #include "pxr/base/tf/error.h"
28 #include "pxr/base/tf/errorMark.h"
29 #include "pxr/base/tf/pyEnum.h"
30 #include "pxr/base/tf/pyError.h"
31 #include "pxr/base/tf/pyErrorInternal.h"
32 #include "pxr/base/tf/pyInterpreter.h"
33 #include "pxr/base/tf/pyLock.h"
34 #include "pxr/base/tf/pyUtils.h"
35 #include "pxr/base/tf/scriptModuleLoader.h"
36
37 #include "pxr/base/arch/defines.h"
38 #include "pxr/base/tf/registryManager.h"
39
40 #include <boost/python.hpp>
41 #include <boost/python/detail/api_placeholder.hpp>
42
43 #include <functional>
44 #include <vector>
45
46 using namespace boost::python;
47
48 using std::string;
49 using std::vector;
50
51 PXR_NAMESPACE_OPEN_SCOPE
52
53 void
TfPyThrowIndexError(string const & msg)54 TfPyThrowIndexError(string const &msg)
55 {
56 TfPyLock pyLock;
57 PyErr_SetString(PyExc_IndexError, msg.c_str());
58 boost::python::throw_error_already_set();
59 }
60
61 void
TfPyThrowRuntimeError(string const & msg)62 TfPyThrowRuntimeError(string const &msg)
63 {
64 TfPyLock pyLock;
65 PyErr_SetString(PyExc_RuntimeError, msg.c_str());
66 boost::python::throw_error_already_set();
67 }
68
69 void
TfPyThrowStopIteration(string const & msg)70 TfPyThrowStopIteration(string const &msg)
71 {
72 TfPyLock pyLock;
73 PyErr_SetString(PyExc_StopIteration, msg.c_str());
74 boost::python::throw_error_already_set();
75 }
76
77 void
TfPyThrowKeyError(string const & msg)78 TfPyThrowKeyError(string const &msg)
79 {
80 TfPyLock pyLock;
81 PyErr_SetString(PyExc_KeyError, msg.c_str());
82 boost::python::throw_error_already_set();
83 }
84
85 void
TfPyThrowValueError(string const & msg)86 TfPyThrowValueError(string const &msg)
87 {
88 TfPyLock pyLock;
89 PyErr_SetString(PyExc_ValueError, msg.c_str());
90 boost::python::throw_error_already_set();
91 }
92
93 void
TfPyThrowTypeError(string const & msg)94 TfPyThrowTypeError(string const &msg)
95 {
96 TfPyLock pyLock;
97 PyErr_SetString(PyExc_TypeError, msg.c_str());
98 boost::python::throw_error_already_set();
99 }
100
101 bool
TfPyIsNone(boost::python::object const & obj)102 TfPyIsNone(boost::python::object const &obj)
103 {
104 return obj.ptr() == Py_None;
105 }
106
107 bool
TfPyIsNone(boost::python::handle<> const & obj)108 TfPyIsNone(boost::python::handle<> const &obj)
109 {
110 return !obj.get() || obj.get() == Py_None;
111 }
112
Tf_PyLoadScriptModule(std::string const & moduleName)113 void Tf_PyLoadScriptModule(std::string const &moduleName)
114 {
115 if (TfPyIsInitialized()) {
116 TfPyLock pyLock;
117 string tmp(moduleName);
118 PyObject *result =
119 PyImport_ImportModule(const_cast<char *>(tmp.c_str()));
120 if (!result) {
121 // CODE_COVERAGE_OFF
122 TF_WARN("Import failed for module '%s'!", moduleName.c_str());
123 TfPyPrintError();
124 // CODE_COVERAGE_ON
125 }
126 } else {
127 // CODE_COVERAGE_OFF
128 TF_WARN("Attempted to load module '%s' but Python is not initialized.",
129 moduleName.c_str());
130 // CODE_COVERAGE_ON
131 }
132 }
133
134 bool
TfPyIsInitialized()135 TfPyIsInitialized()
136 {
137 return Py_IsInitialized();
138 }
139
140 string
TfPyObjectRepr(boost::python::object const & t)141 TfPyObjectRepr(boost::python::object const &t)
142 {
143 if (!TfPyIsInitialized()) {
144 // CODE_COVERAGE_OFF
145 TF_CODING_ERROR("Called TfPyRepr without python being initialized!");
146 return "<error: python not initialized>";
147 // CODE_COVERAGE_ON
148 }
149 // Take the interpreter lock as we're about to call back to Python.
150 TfPyLock pyLock;
151
152 // In case the try block throws, we'll return this string.
153 string reprString("<invalid repr>");
154
155 try {
156 handle<> repr(PyObject_Repr(t.ptr()));
157 reprString = extract<string>(repr.get());
158
159 // Python's repr() for NaN and Inf are not valid python which evaluates
160 // to themselves. Special case them here to produce python which has
161 // this property. This is unpleasant since we're not producing the real
162 // python repr, but we want everything coming out of here (if at all
163 // possible) to have this property.
164 if (reprString == "nan")
165 reprString = "float('nan')";
166 if (reprString == "inf")
167 reprString = "float('inf')";
168 if (reprString == "-inf")
169 reprString = "-float('inf')";
170
171 } catch (error_already_set const &) {
172 PyErr_Clear();
173 }
174 return reprString;
175 }
176
177
178 boost::python::object
TfPyEvaluate(std::string const & expr,dict const & extraGlobals)179 TfPyEvaluate(std::string const &expr, dict const& extraGlobals)
180 {
181 TfPyLock lock;
182 try {
183 // Get the modules dict for the loaded script modules.
184 dict modulesDict =
185 TfScriptModuleLoader::GetInstance().GetModulesDict();
186
187 // Make sure the builtins are available
188 handle<> modHandle(PyImport_ImportModule(TfPyBuiltinModuleName));
189 modulesDict["__builtins__"] = object(modHandle);
190 modulesDict.update(extraGlobals);
191
192 // Eval the expression in that enviornment.
193 return object(TfPyRunString(expr, Py_eval_input,
194 modulesDict, modulesDict));
195 } catch (boost::python::error_already_set const &) {
196 TfPyConvertPythonExceptionToTfErrors();
197 PyErr_Clear();
198 }
199 return boost::python::object();
200 }
201
202
203 int64_t
TfPyNormalizeIndex(int64_t index,uint64_t size,bool throwError)204 TfPyNormalizeIndex(int64_t index, uint64_t size, bool throwError)
205 {
206 if (index < 0)
207 index += size;
208
209 if (throwError && (index < 0 || static_cast<uint64_t>(index) >= size)) {
210 TfPyThrowIndexError("Index out of range.");
211 }
212
213 return index < 0 ? 0 :
214 static_cast<uint64_t>(index) >= size ? size - 1 : index;
215 }
216
217
218 TF_API void
Tf_PyWrapOnceImpl(boost::python::type_info const & type,std::function<void ()> const & wrapFunc,bool * isTypeWrapped)219 Tf_PyWrapOnceImpl(
220 boost::python::type_info const &type,
221 std::function<void()> const &wrapFunc,
222 bool * isTypeWrapped)
223 {
224 static std::mutex pyWrapOnceMutex;
225
226 if (!wrapFunc) {
227 TF_CODING_ERROR("Got null wrapFunc");
228 return;
229 }
230
231 // Acquire the GIL here, just so we can be sure that it is released before
232 // attempting to acquire our internal mutex.
233 TfPyLock pyLock;
234 pyLock.BeginAllowThreads();
235 std::lock_guard<std::mutex> lock(pyWrapOnceMutex);
236 pyLock.EndAllowThreads();
237
238 // XXX: Double-checked locking
239 if (*isTypeWrapped) {
240 return;
241 }
242
243 boost::python::type_handle pyType =
244 boost::python::objects::registered_class_object(type);
245
246 if (!pyType) {
247 wrapFunc();
248 }
249
250 *isTypeWrapped = true;
251 }
252
253
254 boost::python::object
TfPyGetClassObject(std::type_info const & type)255 TfPyGetClassObject(std::type_info const &type) {
256 TfPyLock pyLock;
257 return boost::python::object
258 (boost::python::objects::registered_class_object(type));
259 }
260
TfPyGetClassName(object const & obj)261 string TfPyGetClassName(object const &obj)
262 {
263 // Take the interpreter lock as we're about to call back to Python.
264 TfPyLock pyLock;
265
266 object classObject(obj.attr("__class__"));
267 if (classObject) {
268 object typeNameObject(classObject.attr("__name__"));
269 extract<string> typeName(typeNameObject);
270 if (typeName.check())
271 return typeName();
272 }
273
274 // CODE_COVERAGE_OFF This shouldn't really happen.
275 TF_WARN("Couldn't get class name for python object '%s'",
276 TfPyRepr(obj).c_str());
277 return "<unknown>";
278 // CODE_COVERAGE_ON
279 }
280
281 boost::python::object
TfPyCopyBufferToByteArray(const char * buffer,size_t size)282 TfPyCopyBufferToByteArray(const char* buffer, size_t size)
283 {
284 TfPyLock lock;
285 boost::python::object result;
286
287 try {
288 // boost python doesn't include a bytearray object, but we can return
289 // one through the C api directly. The c api takes an array of char
290 // and a size, so uses the name FromString, but this is really just
291 // a buffer.
292 PyObject* buf = PyByteArray_FromStringAndSize(buffer, size);
293 boost::python::handle<> hbuf(buf);
294 result = boost::python::object(hbuf);
295 } catch (boost::python::error_already_set const &) {
296 TfPyConvertPythonExceptionToTfErrors();
297 PyErr_Clear();
298 }
299
300 return result;
301 }
302
TfPyGetTraceback()303 vector<string> TfPyGetTraceback()
304 {
305 vector<string> result;
306
307 if (!TfPyIsInitialized())
308 return result;
309
310 TfPyLock lock;
311 // Save the exception state so we can restore it -- getting a traceback
312 // should not affect the exception state.
313 TfPyExceptionStateScope exceptionStateScope;
314 try {
315 object tbModule(handle<>(PyImport_ImportModule("traceback")));
316 object stack = tbModule.attr("format_stack")();
317 size_t size = len(stack);
318 result.reserve(size);
319 for (size_t i = 0; i < size; ++i) {
320 string s = extract<string>(stack[i]);
321 result.push_back(s);
322 }
323 } catch (boost::python::error_already_set const &) {
324 TfPyConvertPythonExceptionToTfErrors();
325 }
326 return result;
327 }
328
329 void
TfPyGetStackFrames(vector<uintptr_t> * frames)330 TfPyGetStackFrames(vector<uintptr_t> *frames)
331 {
332 if (!TfPyIsInitialized())
333 return;
334
335 TfPyLock lock;
336 try {
337 object tbModule(handle<>(PyImport_ImportModule("traceback")));
338 object stack = tbModule.attr("format_stack")();
339 size_t size = len(stack);
340 frames->reserve(size);
341 // Reverse the order of stack frames so that the stack is ordered
342 // like the output of ArchGetStackFrames() (deepest function call at
343 // the top of stack).
344 for (long i = static_cast<long>(size)-1; i >= 0; --i) {
345 string *s = new string(extract<string>(stack[i]));
346 frames->push_back((uintptr_t)s);
347 }
348 } catch (boost::python::error_already_set const &) {
349 TfPyConvertPythonExceptionToTfErrors();
350 PyErr_Clear();
351 }
352 }
353
TfPyDumpTraceback()354 void TfPyDumpTraceback() {
355 printf("Traceback (most recent call last):\n");
356 vector<string> tb = TfPyGetTraceback();
357 TF_FOR_ALL(i, tb)
358 printf("%s", i->c_str());
359 }
360
361
362 static object
_GetOsEnviron()363 _GetOsEnviron()
364 {
365 // In theory, we could just check that the os module has been imported,
366 // rather than forcing an import ourself. However, it's possible that
367 // os.environ is actually a re-export from another module (ie. posix,
368 // which is the case as of CPython 2.6) that may have been imported
369 // without importing os. Rather than check a hardcoded list of potential
370 // modules, we always import os if Python is initialized. If this turns
371 // out to be problematic, we may want to consider the other approach.
372 boost::python::object
373 module(boost::python::handle<>(PyImport_ImportModule("os")));
374 boost::python::object environObj(module.attr("environ"));
375 return environObj;
376 }
377
378 bool
TfPySetenv(const std::string & name,const std::string & value)379 TfPySetenv(const std::string & name, const std::string & value)
380 {
381 if (!TfPyIsInitialized()) {
382 TF_CODING_ERROR("Python is uninitialized.");
383 return false;
384 }
385
386 TfPyLock lock;
387
388 try {
389 object environObj(_GetOsEnviron());
390 environObj[name] = value;
391 return true;
392 }
393 catch (boost::python::error_already_set&) {
394 PyErr_Clear();
395 }
396
397 return false;
398 }
399
400 bool
TfPyUnsetenv(const std::string & name)401 TfPyUnsetenv(const std::string & name)
402 {
403 if (!TfPyIsInitialized()) {
404 TF_CODING_ERROR("Python is uninitialized.");
405 return false;
406 }
407
408 TfPyLock lock;
409
410 try {
411 object environObj(_GetOsEnviron());
412 object has_key = environObj.attr("__contains__");
413 if (has_key(name)) {
414 environObj[name].del();
415 }
416 return true;
417 }
418 catch (boost::python::error_already_set&) {
419 PyErr_Clear();
420 }
421
422 return false;
423 }
424
Tf_PyEvaluateWithErrorCheck(const std::string & expr,boost::python::object * obj)425 bool Tf_PyEvaluateWithErrorCheck(
426 const std::string & expr, boost::python::object * obj)
427 {
428 TfErrorMark m;
429 *obj = TfPyEvaluate(expr);
430 return m.IsClean();
431 }
432
433 void
TfPyPrintError()434 TfPyPrintError()
435 {
436 if (!PyErr_ExceptionMatches(PyExc_KeyboardInterrupt)) {
437 PyErr_Print();
438 }
439 }
440
441 void
Tf_PyObjectError(bool printError)442 Tf_PyObjectError(bool printError)
443 {
444 // Silently pass these exceptions through.
445 if (PyErr_ExceptionMatches(PyExc_SystemExit) ||
446 PyErr_ExceptionMatches(PyExc_KeyboardInterrupt)) {
447 return;
448 }
449
450 // Report and clear.
451 if (printError) {
452 PyErr_Print();
453 }
454 else {
455 PyErr_Clear();
456 }
457 }
458
459 PXR_NAMESPACE_CLOSE_SCOPE
460