16
16
17
17
import imp
18
18
import marshal
19
+ import struct
19
20
import sys
20
- from os .path import exists , isdir , join
21
+ from importlib ._bootstrap_external import _get_supported_file_loaders
22
+ from importlib .machinery import FileFinder , SourceFileLoader , SOURCE_SUFFIXES
23
+ from . import _pyconcrete # noqa: E402
24
+
21
25
22
- EXT_PY = '.py'
23
- EXT_PYC = '.pyc'
24
- EXT_PYD = '.pyd'
25
26
EXT_PYE = '.pye'
26
27
27
28
__all__ = ["info" ]
28
29
29
- from . import _pyconcrete # noqa: E402
30
30
31
31
info = _pyconcrete .info
32
32
encrypt_file = _pyconcrete .encrypt_file
33
33
decrypt_file = _pyconcrete .decrypt_file
34
34
decrypt_buffer = _pyconcrete .decrypt_buffer
35
35
36
+ # We need to modify SOURCE_SUFFIXES, because it used in importlib.machinery.all_suffixes function which
37
+ # called by inspect.getmodulename and we need to be able to detect the module name relative to .pye files
38
+ # because .py can be deleted by us
39
+ SOURCE_SUFFIXES .append (EXT_PYE )
36
40
37
- class PyeLoader (object ):
38
- def __init__ (self , is_pkg , pkg_path , full_path ):
39
- self .is_pkg = is_pkg
40
- self .pkg_path = pkg_path
41
- self .full_path = full_path
42
- with open (full_path , 'rb' ) as f :
43
- self .data = f .read ()
44
-
45
- def new_module (self , fullname , path , package_path ):
46
- m = imp .new_module (fullname )
47
- m .__file__ = path
48
- m .__loader__ = self
49
- if self .is_pkg :
50
- m .__path__ = [package_path ]
51
-
52
- if "__name__" not in m .__dict__ :
53
- m .__name__ = fullname
54
-
55
- return m
56
-
57
- def load_module (self , fullname ):
58
- if fullname in sys .modules : # skip reload by now ...
59
- return sys .modules [fullname ]
60
-
61
- data = decrypt_buffer (self .data ) # decrypt pye
62
-
63
- self ._validate_version (data )
64
41
42
+ class PyeLoader (SourceFileLoader ):
43
+ @property
44
+ def magic (self ):
65
45
if sys .version_info >= (3 , 7 ):
66
46
# reference python source code
67
47
# python/Lib/importlib/_bootstrap_external.py _code_to_timestamp_pyc() & _code_to_hash_pyc()
@@ -77,47 +57,39 @@ def load_module(self, fullname):
77
57
# reference http://stackoverflow.com/questions/1830727/how-to-load-compiled-python-modules-from-memory
78
58
# MAGIC + TIMESTAMP
79
59
magic = 8
80
-
81
- code = marshal .loads (data [magic :])
82
-
83
- m = self .new_module (fullname , self .full_path , self .pkg_path )
84
- sys .modules [fullname ] = m
85
- exec (code , m .__dict__ )
86
- return m
87
-
88
- def is_package (self , fullname ):
89
- return self .is_pkg
60
+ return magic
90
61
91
62
@staticmethod
92
63
def _validate_version (data ):
93
64
magic = imp .get_magic ()
94
65
ml = len (magic )
95
66
if data [:ml ] != magic :
96
- import struct
97
-
98
67
# convert little-endian byte string to unsigned short
99
68
py_magic = struct .unpack ('<H' , magic [:2 ])[0 ]
100
69
pye_magic = struct .unpack ('<H' , data [:2 ])[0 ]
101
70
raise ValueError ("Python version doesn't match with magic: python(%d) != pye(%d)" % (py_magic , pye_magic ))
102
71
72
+ def get_code (self , fullname ):
73
+ if not self .path .endswith (EXT_PYE ):
74
+ return super ().get_code (fullname )
75
+
76
+ path = self .get_filename (fullname )
77
+ data = decrypt_buffer (self .get_data (path ))
78
+ self ._validate_version (data )
79
+ return marshal .loads (data [self .magic :])
80
+
81
+ def get_source (self , fullname ):
82
+ if self .path .endswith (EXT_PYE ):
83
+ return None
84
+ return super ().get_source (fullname )
85
+
103
86
104
- class PyeMetaPathFinder (object ):
105
- def find_module (self , fullname , path = None ):
106
- mod_name = fullname .split ('.' )[- 1 ]
107
- paths = path if path else sys .path
87
+ loader_details = [(PyeLoader , SOURCE_SUFFIXES )] + _get_supported_file_loaders ()
108
88
109
- for trypath in paths :
110
- mod_path = join (trypath , mod_name )
111
- is_pkg = isdir (mod_path )
112
- if is_pkg :
113
- full_path = join (mod_path , '__init__' + EXT_PYE )
114
- pkg_path = mod_path
115
- else :
116
- full_path = mod_path + EXT_PYE
117
- pkg_path = trypath
118
89
119
- if exists (full_path ):
120
- return PyeLoader (is_pkg , pkg_path , full_path )
90
+ def install ():
91
+ sys .path_importer_cache .clear ()
92
+ sys .path_hooks .insert (0 , FileFinder .path_hook (* loader_details ))
121
93
122
94
123
- sys . meta_path . insert ( 0 , PyeMetaPathFinder () )
95
+ install ( )
0 commit comments