Source code for yapyutils.modules.loader

# -*- coding: utf-8 -*-
"""*yapyutils.modules.loader* provides module location and load as file.
"""
import sys
import os
import re

from yapyutils.modules import YapyUtilsModulesError
from pythonids import PYVxyz, PYV27X, PYV3X, PYV35Plus, decode_pysyntax_16bit_to_str


__author__ = 'Arno-Can Uestuensoez'
__license__ = "Artistic-License-2.0 + Forced-Fairplay-Constraints"
__copyright__ = "Copyright (C) 2019 Arno-Can Uestuensoez" \
                " @Ingenieurbuero Arno-Can Uestuensoez"
__version__ = '0.1.1'
__uuid__ = "60cac28d-efe6-4a8d-802f-fa4fc94fa741"

__docformat__ = "restructuredtext en"


[docs]class YapyUtilsLoaderError(YapyUtilsModulesError): """Module load error.""" pass
_debug = 0 _verbose = 0
[docs]def get_modulelocation(mname, mbase=None, mpaths=None, **kargs): """"A very basic function for the detection of the absolute path and the relative module search-path-name for a given path of a module. The values are the same as would be present in *sys.modules*. Supports source modules as input only. The *platformids* is a low-level library within the software stack. The generic functions for the allocation of module sources and binaries are provided by *sourceinfo* [sourceinfo]_, which itself depends on the *platformids*. Thus *sourceinfo* could not be used in order to avoid circular dependencies. So implemented this function to keep *platformids* on lowest possible software-stack level only. Args: mname: The relative path of the module in dotted *Python* notation, without file suffix. :: mname := ( <dotted-module-name-str> | <dotted-module-name-path-name-str> ) mbase: Base for module search paths, filepath name with a trailing separator. :: default := os.path.normpath(os.path.curdir + os.sep + '..' ) + os.sep The base path is used within the post-processing of the eventually matched path, thus has to be appropriate for all items in *mpaths*. mpaths: List of module search paths relative to *mbase*:: default := [ '', ] resulting in:: default := [ mbase, ] kargs: permitrel: Permit the return of relative module names within *mpath*. If *False* absolute only, which is actually relative to an existing search path entry in *sys.path*. :: permitrel := ( True, # returns a relative module name if within subtree False # returns in any case a module name relative to sys.path ) Sets relavive base to the default:: rbase = os.path.normpath(os.path.dirname(__file__) + os.sep + '..' + re.sub(r'[.]', os.sep, mname)) + os.sep Returns: Returns in case of a match the resulting entry within *sys.modules*:: match -> (<relative-module-name>, <module-file-path-name>,) The default when no match occured is to rely on the more versatile search mechanism of the import implementation of the concrete *Python* implementation for another final trial by the caller:: default -> (<mname>, None,) Raises: PlatformIDsError 'mbase' does not match 'mpaths' PlatformIDsPresentError missing 'mbase' pass-through """ if mpaths == None: mpaths = ('',) # if mbase == None: # mbase = os.path.normpath( # os.path.abspath(os.path.curdir) + os.sep + '..' ) + os.sep assert(isinstance(mpaths, (list, tuple))) _permitrel = kargs.get('permitrel', False) rbase = kargs.get('rbase') _res = re.split(r'[.]', mname, maxsplit=1) if len(_res) == 1: _package = _relpath = '' _relname = _module = _res[0] else: _package = _res[0] _relname = _res[1] _relpath = re.sub(r'[.]', os.sep, _relname) _relpath, _module = os.path.split(_relpath) if mbase == None: for s in sys.path: if os.path.exists(s + os.sep + _package): mbase = s + os.sep + _package break else: raise YapyUtilsLoaderError( "Cannot find package dir: '" + str(_package) + "'") mbase = os.path.normpath(mbase) + os.sep if _permitrel and rbase == None: rbase = mbase + _relpath if mpaths == None: # default _mpaths = [ mbase, ] elif mpaths and mpaths[0]: _mpaths = list(mpaths[:]) # permit relative to mbase only for mi in range(len(_mpaths)): if os.path.isabs(_mpaths[mi]): continue _mpaths[mi] = os.path.normpath(mbase + os.sep + _mpaths[mi]) + os.sep elif not mpaths: raise YapyUtilsLoaderError("missing 'mpaths'") else: _mpaths = mpaths if _mpaths and not _mpaths[0].startswith(mbase): raise YapyUtilsLoaderError( "'mbase' does not match 'mpaths'\nmbase = %s\nmpaths[0] = %s" %( mbase, _mpaths[0] ) ) modfpath = '' for _p in _mpaths: modfpath = os.path.join(_p, _package, _relpath, _module) + '.py' if os.path.exists(modfpath): if _permitrel and modfpath.startswith(rbase): modname = re.sub(r'[/\\\\]', r'.', os.path.normpath(modfpath[len(rbase):]))[:-3] break _largest_match = '' for spx in sys.path: if os.path.normpath(mbase).startswith(os.path.normpath(spx)): if len(_largest_match) < len(spx): _largest_match = os.path.normpath(spx) if os.path.normpath(mbase) == _largest_match: modname = re.sub(r'[/\\\\]', r'.', modfpath[len(mbase):])[:-3] elif os.path.normpath(os.path.dirname(mbase)) == _largest_match: modname = re.sub(r'[/\\\\]', r'.', modfpath[len(os.path.dirname(mbase)) + 1:])[:-3] elif os.path.normpath(os.path.dirname(mbase[:-1])) == _largest_match: modname = re.sub(r'[/\\\\]', r'.', modfpath[len(os.path.dirname(mbase[:-1])) + 1:])[:-3] else: modname = re.sub(r'[/\\\\]', r'.', os.path.normpath(modfpath[len(_largest_match) + 1:]))[:-3] break else: return(mname, None) return (modname, modfpath)
[docs]def load_module(importname, modfpath): """Loads the specified module by it's name and file system path. Provides a common interface for all supported platforms and Python implementations: *CPyhton*, *IPython*, *IronPython*, *Jython*, and *PyPy*. For the syntaxversions *Python2.7* and *Python3*. Args: importname: The import name of the module in dotted path notation. The *importname* is registered in *sys.modules*. modfpath: The full file system path name. The *modfpath* is registered in *sys.modules*. Returns: The module object on success, else *None*. Raises: PlatformIDsError pass-through """ _modx = None if sys.modules.get(importname): # already loaded - most likely a standard module #TODO: hopefully no counter for less common actual reload return sys.modules[importname] # in case of failure want to see the exception elif PYV35Plus: # PYVxyz >= PYV35: # Python 3.5+ import importlib.util # @UnresolvedImport spec = importlib.util.spec_from_file_location(importname, modfpath) # @UndefinedVariable if spec: _modx = importlib.util.module_from_spec(spec) # @UndefinedVariable spec.loader.exec_module(_modx) elif PYV3X: # PYVxyz >= PYV33: # Python 3.3 and 3.4 from importlib.machinery import SourceFileLoader # @UnresolvedImport _modx = SourceFileLoader(importname, modfpath).load_module() elif PYV27X: # Python 2 - verified and released for 2.7 only, but don't block import imp # @UnresolvedImport try: _modx = imp.load_source(importname, modfpath) except IOError: raise YapyUtilsLoaderError( "Missing platform module: %s: %s" %( str(importname), str(modfpath) ) ) else: raise YapyUtilsLoaderError( "Syntax release not supported: %s" %( str(decode_pysyntax_16bit_to_str(PYVxyz)) ) ) if _modx: sys.modules[importname] = _modx globals()[importname] = _modx return sys.modules[importname] # in case of failure want to see the exception
[docs]def search_modulelocation(mname, mbases=None, mpaths=None, **kargs): """Similar to *get_modulelocation()*, but searches multiple bases - *mbases* vs. *base*. Internally calls *get_modulelocation* for each base. Args: mname: see *get_modulelocation()* mbases: A list of bases for the search of relative module paths. :: default := sys.path + os.path.curdir mpaths: see *get_modulelocation()* kargs: permitrel: see *get_modulelocation()* Returns: see *get_modulelocation()* Raises: see *get_modulelocation()* """ if mbases == None: mbases = sys.path for _mbase in mbases: _f = get_modulelocation(mname, _mbase, mpaths) if _f: return _f