Source code for b3j0f.utils.path

# -*- coding: utf-8 -*-

# --------------------------------------------------------------------
# The MIT License (MIT)
#
# Copyright (c) 2014 Jonathan Labéjof <jonathan.labejof@gmail.com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
# --------------------------------------------------------------------

"""Tools for managing path resolution of python objects."""

# ensure str are unicodes
from __future__ import unicode_literals, absolute_import

from inspect import ismodule, currentframe

from random import random

from .version import PY26
from .runtime import safe_eval

if PY26:
    import_module = __import__

else:
    from importlib import import_module

__all__ = ['clearcache', 'incache', 'lookup', 'getpath', 'alias']

#: lookup cache
__LOOKUP_CACHE = {}


[docs]def clearcache(path=None): """Clear cache memory for input path. :param str path: element path to remove from cache. If None clear all cache :param dict cache: cache to clear. Default is __LOOKUP_CACHE. :Example: >>> incache('b3j0f.utils') False >>> lookup('b3j0f.utils') >>> incache('b3j0f.utils') True >>> clearcache('b3j0f.path') >>> incache('b3j0f.utils') False >>> lookup('b3j0f.utils') >>> incache('b3j0f.utils') True >>> clearcache() >>> incache('b3j0f.utils') False """ if path is None: __LOOKUP_CACHE.clear() else: __LOOKUP_CACHE.pop(path, None)
[docs]def incache(path): """Check if input path is in cache. :return: True if path is in cache :rtype: bool :Example: >>> incache('b3j0f.utils') False >>> lookup('b3j0f.utils') >>> incache('b3j0f.utils') True """ return path in __LOOKUP_CACHE
[docs]def lookup(path, cache=True, scope=None, safe=False): """Get element reference from input element. The element can be a builtin/globals/scope object or is resolved from the current execution stack. :limitations: it does not resolve class methods or static values such as True, False, numbers, string and keywords. :param str path: full path to a python element. :param bool cache: if True (default), permits to reduce time complexity for lookup resolution in using cache memory to save resolved elements. :param dict scope: object scrope from where find path. For example, this scope can be locals(). Default is globals(). :param bool safe: use lookup in a safe context. A safe context avoid to reach builtins function with I/O consequences. :return: python object which is accessible through input path or raise an exception if the path is wrong. :rtype: object :raises ImportError: if path is wrong """ result = None found = path and cache and path in __LOOKUP_CACHE if found: result = __LOOKUP_CACHE[path] elif path: _eval = safe_eval if safe else eval try: # search among scope result = _eval(path, scope) except (NameError, SyntaxError): # we generate a result in order to accept the result such as a None generated_result = random() result = generated_result components = path.split('.') index = 0 components_len = len(components) module_name = components[0] # try to resolve an absolute path try: result = import_module(module_name) except ImportError: # resolve element globals or locals of the from previous frame previous_frame = currentframe().f_back if module_name in previous_frame.f_locals: result = previous_frame.f_locals[module_name] elif module_name in previous_frame.f_globals: result = previous_frame.f_globals[module_name] found = result is not generated_result if found: if components_len > 1: index = 1 # try to import all sub-modules/packages try: # check if name is defined from an external module # find the right module while index < components_len: module_name = '{0}.{1}'.format( module_name, components[index] ) result = import_module(module_name) index += 1 except ImportError: # path sub-module content try: if PY26: # when __import__ is used index = 1 # restart count of pathing while index < components_len: result = getattr(result, components[index]) index += 1 except AttributeError: raise ImportError( 'Wrong path {0} at {1}'.format( path, components[:index] ) ) else: # in case of PY26 if PY26: index = 1 while index < components_len: result = getattr(result, components[index]) index += 1 else: found = True if found: if cache: # save in cache if found __LOOKUP_CACHE[path] = result else: raise ImportError('Wrong path {0}'.format(path)) return result
[docs]def getpath(element): """Get full path of a given element such as the opposite of the resolve_path behaviour. :param element: must be directly defined into a module or a package and has the attribute '__name__'. :return: element absolute path. :rtype: str :raises AttributeError: if element has not the attribute __name__. :Example: >>> getpath(getpath) b3j0f.utils.path.getpath """ if not hasattr(element, '__name__'): raise AttributeError( 'element {0} must have the attribute __name__'.format(element) ) result = element.__name__ if ismodule(element) else \ '{0}.{1}'.format(element.__module__, element.__name__) return result
[docs]def alias(_id): """Decorator dedicated to make an alias of a decorated element in order to register it in the lookup cache. :param str _id: alias identifier. :Example: >>> alias('halfsonofzeus', 'hercules') 'hercules' >>> lookup('halfsonofzeus') 'hercules' >>> @alias('cube') >>> def cube(value): return value ** value ** value >>> lookpath('cube')(2) 8 """ def _register_elt(elt): """Register a specific element in the lookup cache.""" __LOOKUP_CACHE[_id] = elt return elt return _register_elt