1
# Licensed under a 3-clause BSD style license - see LICENSE.rst
2

3 20
import contextlib
4 20
import imp
5 20
import os
6 20
import sys
7 20
import glob
8

9 20
from 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.
14 20
class 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

22 20
class AstropyDeprecationWarning(AstropyWarning):
23
    """
24
    A warning class to indicate a deprecated feature.
25
    """
26

27

28 20
class AstropyPendingDeprecationWarning(PendingDeprecationWarning,
29
                                       AstropyWarning):
30
    """
31
    A warning class to indicate a soon-to-be deprecated feature.
32
    """
33

34

35 20
def _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 0
    plat_specifier = '.{0}-{1}'.format(cmd.plat_name, sys.version[0:3])
42 0
    return os.path.join(cmd.build_base, 'lib' + plat_specifier)
43

44

45 20
def 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 20
    import builtins
55 20
    if hasattr(builtins, '__NUMPY_SETUP__'):
56 0
        del builtins.__NUMPY_SETUP__
57 20
    import imp
58 20
    import numpy
59 20
    imp.reload(numpy)
60

61 20
    try:
62 20
        numpy_include = numpy.get_include()
63 0
    except AttributeError:
64 0
        numpy_include = numpy.get_numpy_include()
65 20
    return numpy_include
66

67

68 20
class _DummyFile(object):
69
    """A noop writeable object."""
70

71 20
    errors = ''
72

73 20
    def write(self, s):
74 20
        pass
75

76 20
    def flush(self):
77 0
        pass
78

79

80 20
@contextlib.contextmanager
81 3
def silence():
82
    """A context manager that silences sys.stdout and sys.stderr."""
83

84 20
    old_stdout = sys.stdout
85 20
    old_stderr = sys.stderr
86 20
    sys.stdout = _DummyFile()
87 20
    sys.stderr = _DummyFile()
88 20
    exception_occurred = False
89 20
    try:
90 20
        yield
91 0
    except:
92 0
        exception_occurred = True
93
        # Go ahead and clean up so that exception handling can work normally
94 0
        sys.stdout = old_stdout
95 0
        sys.stderr = old_stderr
96 0
        raise
97

98 20
    if not exception_occurred:
99 20
        sys.stdout = old_stdout
100 20
        sys.stderr = old_stderr
101

102

103 20
if sys.platform == 'win32':
104 2
    import ctypes
105

106 2
    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 2
        if isinstance(filepath, bytes):
113 0
            filepath = filepath.decode(sys.getfilesystemencoding())
114 2
        try:
115 2
            attrs = ctypes.windll.kernel32.GetFileAttributesW(filepath)
116 2
            assert attrs != -1
117 0
            result = bool(attrs & 2)
118 2
        except (AttributeError, AssertionError):
119 2
            result = False
120 2
        return result
121
else:
122 18
    def _has_hidden_attribute(filepath):
123 18
        return False
124

125

126 20
def 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 20
    name = os.path.basename(os.path.abspath(filepath))
142 20
    if isinstance(name, bytes):
143 0
        is_dotted = name.startswith(b'.')
144
    else:
145 20
        is_dotted = name.startswith('.')
146 20
    return is_dotted or _has_hidden_attribute(filepath)
147

148

149 20
def 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 20
    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 20
        dirs[:] = [d for d in dirs if not is_path_hidden(d)]
168 20
        files[:] = [f for f in files if not is_path_hidden(f)]
169 20
        yield root, dirs, files
170

171

172 20
def 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 0
    assert isinstance(data, bytes)
184

185 0
    if os.path.exists(filename):
186 0
        with open(filename, 'rb') as fd:
187 0
            original_data = fd.read()
188
    else:
189 0
        original_data = None
190

191 0
    if original_data != data:
192 0
        with open(filename, 'wb') as fd:
193 0
            fd.write(data)
194

195

196 20
def 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 20
    mode = 'r'
212

213 20
    if name is None:
214 0
        basename = os.path.splitext(filename)[0]
215 0
        name = '_'.join(os.path.relpath(basename).split(os.sep)[1:])
216

217 20
    if not os.path.exists(filename):
218 20
        raise ImportError('Could not import file {0}'.format(filename))
219

220 20
    if import_machinery:
221 20
        loader = import_machinery.SourceFileLoader(name, filename)
222 20
        mod = loader.load_module()
223
    else:
224 0
        with open(filename, mode) as fd:
225 0
            mod = imp.load_module(name, fd, filename, ('.py', mode, 1))
226

227 20
    return mod
228

229

230 20
def 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 0
    parts = name.split('.')
237 0
    cursor = len(parts) - 1
238 0
    module_name = parts[:cursor]
239 0
    attr_name = parts[-1]
240

241 0
    while cursor > 0:
242 0
        try:
243 0
            ret = __import__('.'.join(module_name), fromlist=[attr_name])
244 0
            break
245 0
        except ImportError:
246 0
            if cursor == 0:
247 0
                raise
248 0
            cursor -= 1
249 0
            module_name = parts[:cursor]
250 0
            attr_name = parts[cursor]
251 0
            ret = ''
252

253 0
    for part in parts[cursor:]:
254 0
        try:
255 0
            ret = getattr(ret, part)
256 0
        except AttributeError:
257 0
            raise ImportError(name)
258

259 0
    return ret
260

261

262 20
def 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 20
    def decorator(func):
287 20
        if not (extended_func.__doc__ is None or func.__doc__ is None):
288 20
            func.__doc__ = '\n\n'.join([extended_func.__doc__.rstrip('\n'),
289
                                        func.__doc__.lstrip('\n')])
290 20
        return func
291

292 20
    return decorator
293

294

295 20
def 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 0
    return glob.glob(os.path.join(package, pattern), recursive=True)

Read our documentation on viewing source code .

Loading