source: sasmodels/sasmodels/custom/__init__.py @ d321747

core_shell_microgelsmagnetic_modelticket-1257-vesicle-productticket_1156ticket_1265_superballticket_822_more_unit_tests
Last change on this file since d321747 was d321747, checked in by Paul Kienzle <pkienzle@…>, 5 years ago

sasview only updates models when code changes; now detects changes to c files

  • Property mode set to 100644
File size: 5.0 KB
Line 
1"""
2Custom Models
3-------------
4
5This is a place holder for the custom models namespace.  When models are
6loaded from a file by :func:`generate.load_kernel_module` they are loaded
7as if they exist in *sasmodels.custom*.  This package needs to exist for this
8to occur without error.
9"""
10from __future__ import division, print_function
11
12import sys
13import os
14from os.path import basename, splitext, join as joinpath, exists, dirname
15
16try:
17    # Python 3.5 and up
18    from importlib.util import spec_from_file_location, module_from_spec  # type: ignore
19    def load_module_from_path(fullname, path):
20        # type: (str, str) -> "module"
21        """load module from *path* as *fullname*"""
22        spec = spec_from_file_location(fullname, os.path.expanduser(path))
23        module = module_from_spec(spec)
24        spec.loader.exec_module(module)
25        return module
26except ImportError:
27    # CRUFT: python 2
28    import imp
29    def load_module_from_path(fullname, path):
30        # type: (str, str) -> "module"
31        """load module from *path* as *fullname*"""
32        # Clear out old definitions, if any
33        if fullname in sys.modules:
34            del sys.modules[fullname]
35        if path.endswith(".py") and os.path.exists(path) and os.path.exists(path+"c"):
36            # remove automatic pyc file before loading a py file
37            os.unlink(path+"c")
38        module = imp.load_source(fullname, os.path.expanduser(path))
39        return module
40
41_MODULE_CACHE = {} # type: Dict[str, Tuple("module", int)]
42_MODULE_DEPENDS = {} # type: Dict[str, List[str]]
43_MODULE_DEPENDS_STACK = [] # type: List[str]
44def load_custom_kernel_module(path):
45    # type: str -> "module"
46    """load SAS kernel from *path* as *sasmodels.custom.modelname*"""
47    # Pull off the last .ext if it exists; there may be others
48    name = basename(splitext(path)[0])
49    path = os.path.expanduser(path)
50
51    # Reload module if necessary.
52    if need_reload(path):
53        # Assume the module file is the only dependency
54        _MODULE_DEPENDS[path] = set([path])
55
56        # Load the module while pushing it onto the dependency stack.  If
57        # this triggers any submodules, then they will add their dependencies
58        # to this module as the "working_on" parent.  Pop the stack when the
59        # module is loaded.
60        _MODULE_DEPENDS_STACK.append(path)
61        module = load_module_from_path('sasmodels.custom.'+name, path)
62        _MODULE_DEPENDS_STACK.pop()
63
64        # Include external C code in the dependencies.  We are looking
65        # for module.source and assuming that it is a list of C source files
66        # relative to the module itself.  Any files that do not exist,
67        # such as those in the standard libraries, will be ignored.
68        # TODO: look in builtin module path for standard c sources
69        # TODO: share code with generate.model_sources
70        c_sources = getattr(module, 'source', None)
71        if isinstance(c_sources, (list, tuple)):
72            _MODULE_DEPENDS[path].update(_find_sources(path, c_sources))
73
74        # Cache the module, and tag it with the newest timestamp
75        timestamp = max(os.path.getmtime(f) for f in _MODULE_DEPENDS[path])
76        _MODULE_CACHE[path] = module, timestamp
77
78        #print("loading", os.path.basename(path), _MODULE_CACHE[path][1],
79        #    [os.path.basename(p) for p in _MODULE_DEPENDS[path]])
80
81    # Add path and all its dependence to the parent module, if there is one.
82    if _MODULE_DEPENDS_STACK:
83        working_on = _MODULE_DEPENDS_STACK[-1]
84        _MODULE_DEPENDS[working_on].update(_MODULE_DEPENDS[path])
85
86    return _MODULE_CACHE[path][0]
87
88def need_reload(path):
89    # type: str -> bool
90    """
91    Return True if any path dependencies have a timestamp newer than the time
92    when the path was most recently loaded.
93    """
94    # TODO: fails if a dependency has a modification time in the future
95    # If the newest dependency has a time stamp in the future, then this
96    # will be recorded as the cached time.  When a second dependency
97    # is updated to the current time stamp, it will still be considered
98    # older than the current build and the reload will not be triggered.
99    # Could instead treat all future times as 0 here and in the code above
100    # which records the newest timestamp.  This will force a reload when
101    # the future time is reached, but other than that should perform
102    # correctly.  Probably not worth the extra code...
103    _, cache_time = _MODULE_CACHE.get(path, (None, -1))
104    depends = _MODULE_DEPENDS.get(path, [path])
105    #print("reload", any(cache_time < os.path.getmtime(p) for p in depends))
106    #for f in depends: print(">>>  ", f, os.path.getmtime(f))
107    return any(cache_time < os.path.getmtime(p) for p in depends)
108
109def _find_sources(path, source_list):
110    # type: (str, List[str]) -> List[str]
111    """
112    Return a list of the sources relative to base file; ignore any that
113    are not found.
114    """
115    root = dirname(path)
116    found = []
117    for source_name in source_list:
118        source_path = joinpath(root, source_name)
119        if exists(source_path):
120            found.append(source_path)
121    return found
Note: See TracBrowser for help on using the repository browser.