"""
Tools to obtain resource files needed for PISA, whether the resource is located
in the filesystem or with the installed PISA package.
"""
from __future__ import absolute_import
from os import environ
from os.path import exists, expanduser, expandvars, join
import sys
import pkg_resources
__all__ = ['RESOURCES_SUBDIRS', 'find_resource', 'open_resource', 'find_path']
__author__ = 'S. Boeser, J.L. Lanfranchi'
__license__ = '''Copyright (c) 2014-2017, The IceCube Collaboration
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.'''
RESOURCES_SUBDIRS = ['data', 'scripts', 'settings']
[docs]
def find_resource(resource, fail=True):
"""Try to find a resource (file or directory).
First check if `resource` is an absolute path, then check relative to
the paths specified by the PISA_RESOURCES environment variable (if it is
defined). Otherwise, look in the resources directory of the PISA
installation.
Note that the PISA_RESOURCES environment variable can contain multiple
paths, each separated by a colon. Due to using colons as separators,
however, the paths themselves can *not* contain colons.
Note also if PISA is packaged as archive distribution (e.g. zipped egg),
this method extracts the resource (if directory, the entire contents are
extracted) to a temporary cache directory. Therefore, it is preferable to
use the `open_resource` method directly, and avoid this method if possible.
Parameters
----------
resource : str
Resource path; can be path relative to CWD, path relative to
PISA_RESOURCES environment variable (if defined), or a package resource
location relative to the `pisa_examples/resources` sub-directory. Within
each path specified in PISA_RESOURCES and within the
`pisa_examples/resources` dir, the sub-directories 'data', 'scripts',
and 'settings' are checked for `resource` _before_ the base directories
are checked. Note that the **first** result found is returned.
fail : bool
If True, raise IOError if resource not found
If False, return None if resource not found
Returns
-------
String if `resource` is found (relative path to the file or directory); if
not found and `fail` is False, returns None.
Raises
------
IOError if `resource` is not found and `fail` is True.
"""
# NOTE: this import needs to be here -- and not at top -- to avoid circular
# imports
import pisa.utils.log as log
log.logging.trace('Attempting to find resource "%s"', resource)
# 1) Check for file in filesystem at absolute path or relative to
# PISA_RESOURCES environment var
resource_path = find_path(resource, fail=False)
if resource_path is not None:
return resource_path
# TODO: use resource_string or resource_stream instead, so that this works
# with egg distributions
# 2) Look inside the installed pisa package
log.logging.trace('Searching package resources...')
resource_spec = ('pisa_examples', 'resources/' + resource)
if pkg_resources.resource_exists(*resource_spec):
resource_path = pkg_resources.resource_filename(*resource_spec)
log.logging.debug('Found resource "%s" in PISA package at "%s"',
resource, resource_path)
return resource_path
for subdir in RESOURCES_SUBDIRS + [None]:
if subdir is None:
augmented_path = resource
else:
augmented_path = '/'.join([subdir, resource])
resource_spec = ('pisa_examples', 'resources/' + augmented_path)
if pkg_resources.resource_exists(*resource_spec):
resource_path = pkg_resources.resource_filename(*resource_spec)
log.logging.debug('Found resource "%s" in PISA package at "%s"',
resource, resource_path)
return resource_path
# 3) If you get here, the resource is nowhere to be found
msg = ('Could not find resource "%s" in filesystem OR in PISA package.'
% resource)
if fail:
raise IOError(msg)
log.logging.debug(msg)
[docs]
def open_resource(resource, mode='r'):
"""Find the resource file (see find_resource), open it, and return a file
handle.
Parameters
----------
resource : str
Resource path; can be path relative to CWD, path relative to
PISA_RESOURCES environment variable (if defined), or a package resource
location relative to PISA's `pisa_examples/resources` sub-directory.
Within each path specified in PISA_RESOURCES and within the
`pisa_examples/resources` dir, the sub-directories 'data', 'scripts',
and 'settings' are checked for `resource` _before_ the base directories
are checked. Note that the **first** result found is returned.
mode : str
'r', 'w', or 'rw'; only 'r' is valid for package resources (as these
cannot be written)
Returns
-------
binary stream object (which behaves identically to a file object)
See Also
--------
find_resource
Locate a file or directory (in fileystem) or a package
resource
find_path
Locate a file or directory in the filesystem
Open a (file) package resource and return stream object.
Notes
-----
See help for pkg_resources module / resource_stream method for more details
on handling of package resources.
"""
# NOTE: this import needs to be here -- and not at top -- to avoid circular
# imports
import pisa.utils.log as log
log.logging.trace('Attempting to open resource "%s"', resource)
# 1) Check for file in filesystem at absolute path or relative to
# PISA_RESOURCES environment var
fs_exc_info = None
try:
resource_path = find_path(resource, fail=True)
except IOError:
fs_exc_info = sys.exc_info()
else:
log.logging.debug('Opening resource "%s" from filesystem at "%s"',
resource, resource_path)
return open(resource_path, mode=mode)
# 2) Look inside the installed pisa package; this should error out if not
# found
log.logging.trace('Searching package resources...')
pkg_exc_info = None
for subdir in RESOURCES_SUBDIRS + [None]:
if subdir is None:
augmented_path = resource
else:
augmented_path = '/'.join([subdir, resource])
try:
resource_spec = ('pisa_examples', 'resources/' + augmented_path)
stream = pkg_resources.resource_stream(*resource_spec)
# TODO: better way to check if read mode (i.e. will 'r' miss
# anything that can be specified to also mean "read mode")?
if mode.strip().lower() != 'r':
del stream
raise IOError(
'Illegal mode "%s" specified. Cannot open a PISA package'
' resource in anything besides "r" (read-only) mode.' %mode
)
except IOError:
pkg_exc_info = sys.exc_info()
else:
log.logging.debug('Opening resource "%s" from PISA package.',
resource)
return stream
if fs_exc_info is not None:
if pkg_exc_info is not None:
msg = ('Could not locate resource "%s" in filesystem OR in'
' installed PISA package.' %resource)
raise IOError(msg)
raise fs_exc_info[0](fs_exc_info[1]).with_traceback(fs_exc_info[2])
raise pkg_exc_info[0](pkg_exc_info[1]).with_traceback(pkg_exc_info[2])
[docs]
def find_path(pathspec, fail=True):
"""Find a file or directory in the filesystem (i.e., something that the
operating system can locate, as opposed to Python package resources, which
can be located within a package and therefore hidden from the filesystem).
Parameters
----------
pathspec : string
fail : bool
Returns
-------
None (if not found) or string (absolute path to file or dir if found)
"""
# NOTE: this import needs to be here -- and not at top -- to avoid circular
# imports
import pisa.utils.log as log
# 1) Check for absolute path or path relative to current working
# directory
log.logging.trace('Checking absolute or path relative to cwd...')
resource_path = expandvars(expanduser(pathspec))
if exists(resource_path):
log.logging.debug('Found "%s" at "%s"', pathspec, resource_path)
return resource_path
# 2) Check if $PISA_RESOURCES is set in environment; if so, look relative
# to that
log.logging.trace('Checking environment for $PISA_RESOURCES...')
if 'PISA_RESOURCES' in environ:
pisa_resources = environ['PISA_RESOURCES']
log.logging.trace('Searching resource path PISA_RESOURCES=%s',
pisa_resources)
resource_paths = pisa_resources.split(':')
for resource_path in resource_paths:
if not resource_path:
continue
resource_path = expandvars(expanduser(resource_path))
# Look in all default sub-dirs for the pathspec
augmented_paths = [join(resource_path, subdir, pathspec)
for subdir in RESOURCES_SUBDIRS]
# Also look in the base dir specified for the pathspec
augmented_paths.append(join(resource_path, pathspec))
for augmented_path in augmented_paths:
if exists(augmented_path):
log.logging.debug('Found path "%s" at %s',
pathspec, augmented_path)
return augmented_path
# 3) If you get here, the file is nowhere to be found
msg = 'Could not find path "%s"' % pathspec
if fail:
raise IOError(msg)
log.logging.trace(msg)
return None