1# Licensed under a 3-clause BSD style license - see LICENSE.rst 2 3import contextlib 4import imp 5import os 6import sys 7import glob 8 9from importlib import machinery as import_machinery 10 11 12# Note: The following Warning subclasses are simply copies of the Warnings in 13# Astropy of the same names. 14class AstropyWarning(Warning): 15 """ 16 The base warning class from which all Astropy warnings should inherit. 17 18 Any warning inheriting from this class is handled by the Astropy logger. 19 """ 20 21 22class AstropyDeprecationWarning(AstropyWarning): 23 """ 24 A warning class to indicate a deprecated feature. 25 """ 26 27 28class AstropyPendingDeprecationWarning(PendingDeprecationWarning, 29 AstropyWarning): 30 """ 31 A warning class to indicate a soon-to-be deprecated feature. 32 """ 33 34 35def _get_platlib_dir(cmd): 36 """ 37 Given a build command, return the name of the appropriate platform-specific 38 build subdirectory directory (e.g. build/lib.linux-x86_64-2.7) 39 """ 40 41 plat_specifier = '.{0}-{1}'.format(cmd.plat_name, sys.version[0:3]) 42 return os.path.join(cmd.build_base, 'lib' + plat_specifier) 43 44 45def get_numpy_include_path(): 46 """ 47 Gets the path to the numpy headers. 48 """ 49 # We need to go through this nonsense in case setuptools 50 # downloaded and installed Numpy for us as part of the build or 51 # install, since Numpy may still think it's in "setup mode", when 52 # in fact we're ready to use it to build astropy now. 53 54 import builtins 55 if hasattr(builtins, '__NUMPY_SETUP__'): 56 del builtins.__NUMPY_SETUP__ 57 import imp 58 import numpy 59 imp.reload(numpy) 60 61 try: 62 numpy_include = numpy.get_include() 63 except AttributeError: 64 numpy_include = numpy.get_numpy_include() 65 return numpy_include 66 67 68class _DummyFile(object): 69 """A noop writeable object.""" 70 71 errors = '' 72 73 def write(self, s): 74 pass 75 76 def flush(self): 77 pass 78 79 80@contextlib.contextmanager 81def silence(): 82 """A context manager that silences sys.stdout and sys.stderr.""" 83 84 old_stdout = sys.stdout 85 old_stderr = sys.stderr 86 sys.stdout = _DummyFile() 87 sys.stderr = _DummyFile() 88 exception_occurred = False 89 try: 90 yield 91 except: 92 exception_occurred = True 93 # Go ahead and clean up so that exception handling can work normally 94 sys.stdout = old_stdout 95 sys.stderr = old_stderr 96 raise 97 98 if not exception_occurred: 99 sys.stdout = old_stdout 100 sys.stderr = old_stderr 101 102 103if sys.platform == 'win32': 104 import ctypes 105 106 def _has_hidden_attribute(filepath): 107 """ 108 Returns True if the given filepath has the hidden attribute on 109 MS-Windows. Based on a post here: 110 http://stackoverflow.com/questions/284115/cross-platform-hidden-file-detection 111 """ 112 if isinstance(filepath, bytes): 113 filepath = filepath.decode(sys.getfilesystemencoding()) 114 try: 115 attrs = ctypes.windll.kernel32.GetFileAttributesW(filepath) 116 assert attrs != -1 117 result = bool(attrs & 2) 118 except (AttributeError, AssertionError): 119 result = False 120 return result 121else: 122 def _has_hidden_attribute(filepath): 123 return False 124 125 126def is_path_hidden(filepath): 127 """ 128 Determines if a given file or directory is hidden. 129 130 Parameters 131 ---------- 132 filepath : str 133 The path to a file or directory 134 135 Returns 136 ------- 137 hidden : bool 138 Returns `True` if the file is hidden 139 """ 140 141 name = os.path.basename(os.path.abspath(filepath)) 142 if isinstance(name, bytes): 143 is_dotted = name.startswith(b'.') 144 else: 145 is_dotted = name.startswith('.') 146 return is_dotted or _has_hidden_attribute(filepath) 147 148 149def walk_skip_hidden(top, onerror=None, followlinks=False): 150 """ 151 A wrapper for `os.walk` that skips hidden files and directories. 152 153 This function does not have the parameter `topdown` from 154 `os.walk`: the directories must always be recursed top-down when 155 using this function. 156 157 See also 158 -------- 159 os.walk : For a description of the parameters 160 """ 161 162 for root, dirs, files in os.walk( 163 top, topdown=True, onerror=onerror, 164 followlinks=followlinks): 165 # These lists must be updated in-place so os.walk will skip 166 # hidden directories 167 dirs[:] = [d for d in dirs if not is_path_hidden(d)] 168 files[:] = [f for f in files if not is_path_hidden(f)] 169 yield root, dirs, files 170 171 172def write_if_different(filename, data): 173 """Write `data` to `filename`, if the content of the file is different. 174 175 Parameters 176 ---------- 177 filename : str 178 The file name to be written to. 179 data : bytes 180 The data to be written to `filename`. 181 """ 182 183 assert isinstance(data, bytes) 184 185 if os.path.exists(filename): 186 with open(filename, 'rb') as fd: 187 original_data = fd.read() 188 else: 189 original_data = None 190 191 if original_data != data: 192 with open(filename, 'wb') as fd: 193 fd.write(data) 194 195 196def import_file(filename, name=None): 197 """ 198 Imports a module from a single file as if it doesn't belong to a 199 particular package. 200 201 The returned module will have the optional ``name`` if given, or else 202 a name generated from the filename. 203 """ 204 # Specifying a traditional dot-separated fully qualified name here 205 # results in a number of "Parent module 'astropy' not found while 206 # handling absolute import" warnings. Using the same name, the 207 # namespaces of the modules get merged together. So, this 208 # generates an underscore-separated name which is more likely to 209 # be unique, and it doesn't really matter because the name isn't 210 # used directly here anyway. 211 mode = 'r' 212 213 if name is None: 214 basename = os.path.splitext(filename)[0] 215 name = '_'.join(os.path.relpath(basename).split(os.sep)[1:]) 216 217 if not os.path.exists(filename): 218 raise ImportError('Could not import file {0}'.format(filename)) 219 220 if import_machinery: 221 loader = import_machinery.SourceFileLoader(name, filename) 222 mod = loader.load_module() 223 else: 224 with open(filename, mode) as fd: 225 mod = imp.load_module(name, fd, filename, ('.py', mode, 1)) 226 227 return mod 228 229 230def resolve_name(name): 231 """Resolve a name like ``module.object`` to an object and return it. 232 233 Raise `ImportError` if the module or name is not found. 234 """ 235 236 parts = name.split('.') 237 cursor = len(parts) - 1 238 module_name = parts[:cursor] 239 attr_name = parts[-1] 240 241 while cursor > 0: 242 try: 243 ret = __import__('.'.join(module_name), fromlist=[attr_name]) 244 break 245 except ImportError: 246 if cursor == 0: 247 raise 248 cursor -= 1 249 module_name = parts[:cursor] 250 attr_name = parts[cursor] 251 ret = '' 252 253 for part in parts[cursor:]: 254 try: 255 ret = getattr(ret, part) 256 except AttributeError: 257 raise ImportError(name) 258 259 return ret 260 261 262def extends_doc(extended_func): 263 """ 264 A function decorator for use when wrapping an existing function but adding 265 additional functionality. This copies the docstring from the original 266 function, and appends to it (along with a newline) the docstring of the 267 wrapper function. 268 269 Examples 270 -------- 271 272 >>> def foo(): 273 ... '''Hello.''' 274 ... 275 >>> @extends_doc(foo) 276 ... def bar(): 277 ... '''Goodbye.''' 278 ... 279 >>> print(bar.__doc__) 280 Hello. 281 282 Goodbye. 283 284 """ 285 286 def decorator(func): 287 if not (extended_func.__doc__ is None or func.__doc__ is None): 288 func.__doc__ = '\n\n'.join([extended_func.__doc__.rstrip('\n'), 289 func.__doc__.lstrip('\n')]) 290 return func 291 292 return decorator 293 294 295def find_data_files(package, pattern): 296 """ 297 Include files matching ``pattern`` inside ``package``. 298 299 Parameters 300 ---------- 301 package : str 302 The package inside which to look for data files 303 pattern : str 304 Pattern (glob-style) to match for the data files (e.g. ``*.dat``). 305 This supports the``**``recursive syntax. For example, ``**/*.fits`` 306 matches all files ending with ``.fits`` recursively. Only one 307 instance of ``**`` can be included in the pattern. 308 """ 309 310 return glob.glob(os.path.join(package, pattern), recursive=True) 311