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""" 25Tf -- Tools Foundation 26""" 27 28# Type to help handle DLL import paths on Windows with python interpreters v3.8 29# and newer. These interpreters don't search for DLLs in the path anymore, you 30# have to provide a path explicitly. This re-enables path searching for USD 31# dependency libraries 32import platform, sys 33if sys.version_info >= (3, 8) and platform.system() == "Windows": 34 import contextlib 35 36 @contextlib.contextmanager 37 def WindowsImportWrapper(): 38 import os 39 dirs = [] 40 import_paths = os.getenv('PXR_USD_WINDOWS_DLL_PATH') 41 if import_paths is None: 42 import_paths = os.getenv('PATH', '') 43 for path in import_paths.split(os.pathsep): 44 # Calling add_dll_directory raises an exception if paths don't 45 # exist, or if you pass in dot 46 if os.path.exists(path) and path != '.': 47 dirs.append(os.add_dll_directory(path)) 48 # This block guarantees we clear the dll directories if an exception 49 # is raised in the with block. 50 try: 51 yield 52 finally: 53 for dll_dir in dirs: 54 dll_dir.close() 55 del os 56 del contextlib 57else: 58 class WindowsImportWrapper(object): 59 def __enter__(self): 60 pass 61 def __exit__(self, exc_type, ex_val, exc_tb): 62 pass 63del platform, sys 64 65 66def PreparePythonModule(moduleName=None): 67 """Prepare an extension module at import time. This will import the 68 Python module associated with the caller's module (e.g. '_tf' for 'pxr.Tf') 69 or the module with the specified moduleName and copy its contents into 70 the caller's local namespace. 71 72 Generally, this should only be called by the __init__.py script for a module 73 upon loading a boost python module (generally '_libName.so').""" 74 import importlib 75 import inspect 76 frame = inspect.currentframe().f_back 77 try: 78 f_locals = frame.f_locals 79 80 # If an explicit moduleName is not supplied, construct it from the 81 # caller's module name, like "pxr.Tf", and our naming conventions, 82 # which results in "_tf". 83 if moduleName is None: 84 moduleName = f_locals["__name__"].split(".")[-1] 85 moduleName = "_" + moduleName[0].lower() + moduleName[1:] 86 87 with WindowsImportWrapper(): 88 module = importlib.import_module( 89 "." + moduleName, f_locals["__name__"]) 90 91 PrepareModule(module, f_locals) 92 try: 93 del f_locals[moduleName] 94 except KeyError: 95 pass 96 97 try: 98 module = importlib.import_module(".__DOC", f_locals["__name__"]) 99 module.Execute(f_locals) 100 try: 101 del f_locals["__DOC"] 102 except KeyError: 103 pass 104 except Exception: 105 pass 106 107 finally: 108 del frame 109 110def PrepareModule(module, result): 111 """PrepareModule(module, result) -- Prepare an extension module at import 112 time. Generally, this should only be called by the __init__.py script for a 113 module upon loading a boost python module (generally '_libName.so').""" 114 # inject into result. 115 ignore = frozenset(['__name__', '__package__', '__builtins__', 116 '__doc__', '__file__', '__path__']) 117 newModuleName = result.get('__name__') 118 119 for key, value in module.__dict__.items(): 120 if not key in ignore: 121 result[key] = value 122 123 # Lie about the module from which value came. 124 if newModuleName and hasattr(value, '__module__'): 125 try: 126 setattr(value, '__module__', newModuleName) 127 except AttributeError as e: 128 # The __module__ attribute of Boost.Python.function 129 # objects is not writable, so we get this exception 130 # a lot. Just ignore it. We're really only concerned 131 # about the data objects like enum values and such. 132 # 133 pass 134 135def GetCodeLocation(framesUp): 136 """Returns a tuple (moduleName, functionName, fileName, lineNo). 137 138 To trace the current location of python execution, use GetCodeLocation(). 139 By default, the information is returned at the current stack-frame; thus 140 141 info = GetCodeLocation() 142 143 will return information about the line that GetCodeLocation() was called 144 from. One can write: 145 146 def genericDebugFacility(): 147 info = GetCodeLocation(1) 148 # print out data 149 150 151 def someCode(): 152 ... 153 if bad: 154 genericDebugFacility() 155 156 and genericDebugFacility() will get information associated with its caller, 157 i.e. the function someCode().""" 158 import sys 159 f_back = sys._getframe(framesUp).f_back 160 return (f_back.f_globals['__name__'], f_back.f_code.co_name, 161 f_back.f_code.co_filename, f_back.f_lineno) 162 163PreparePythonModule() 164 165# Need to provide an exception type that tf errors will show up as. 166class ErrorException(RuntimeError): 167 def __init__(self, *args): 168 RuntimeError.__init__(self, *args) 169 self.__TfException = True 170 171 def __str__(self): 172 return '\n\t' + '\n\t'.join([str(e) for e in self.args]) 173__SetErrorExceptionClass(ErrorException) 174 175def Warn(msg, template=""): 176 """Issue a warning via the TfDiagnostic system. 177 178 At this time, template is ignored. 179 """ 180 codeInfo = GetCodeLocation(framesUp=1) 181 _Warn(msg, codeInfo[0], codeInfo[1], codeInfo[2], codeInfo[3]) 182 183def Status(msg, verbose=True): 184 """Issues a status update to the Tf diagnostic system. 185 186 If verbose is True (the default) then information about where in the code 187 the status update was issued from is included. 188 """ 189 if verbose: 190 codeInfo = GetCodeLocation(framesUp=1) 191 _Status(msg, codeInfo[0], codeInfo[1], codeInfo[2], codeInfo[3]) 192 else: 193 _Status(msg, "", "", "", 0) 194 195def RaiseCodingError(msg): 196 """Raise a coding error to the Tf Diagnostic system.""" 197 codeInfo = GetCodeLocation(framesUp=1) 198 _RaiseCodingError(msg, codeInfo[0], codeInfo[1], codeInfo[2], codeInfo[3]) 199 200def RaiseRuntimeError(msg): 201 """Raise a runtime error to the Tf Diagnostic system.""" 202 codeInfo = GetCodeLocation(framesUp=1) 203 _RaiseRuntimeError(msg, codeInfo[0], codeInfo[1], codeInfo[2], codeInfo[3]) 204 205def Fatal(msg): 206 """Raise a fatal error to the Tf Diagnostic system.""" 207 codeInfo = GetCodeLocation(framesUp=1) 208 _Fatal(msg, codeInfo[0], codeInfo[1], codeInfo[2], codeInfo[3]) 209 210 211class NamedTemporaryFile(object): 212 """A named temporary file which keeps the internal file handle closed. 213 A class which constructs a temporary file(that isn't open) on __enter__, 214 provides its name as an attribute, and deletes it on __exit__. 215 216 Note: The constructor args for this object match those of 217 python's tempfile.mkstemp() function, and will have the same effect on 218 the underlying file created.""" 219 220 def __init__(self, suffix='', prefix='', dir=None, text=False): 221 # Note that we defer creation until the enter block to 222 # prevent users from unintentionally creating a bunch of 223 # temp files that don't get cleaned up. 224 self._args = (suffix, prefix, dir, text) 225 226 def __enter__(self): 227 from tempfile import mkstemp 228 from os import close 229 230 fd, path = mkstemp(*self._args) 231 close(fd) 232 233 # XXX: We currently only expose the name attribute 234 # more can be added based on client needs in the future. 235 self._name = path 236 237 return self 238 239 def __exit__(self, *args): 240 import os 241 os.remove(self.name) 242 243 @property 244 def name(self): 245 """The path for the temporary file created.""" 246 return self._name 247