22
22
import marshal # for loads
23
23
import sys # for modules
24
24
import time # for mktime
25
+ import _warnings # For warn()
25
26
26
27
__all__ = ['ZipImportError' , 'zipimporter' ]
27
28
@@ -42,7 +43,7 @@ class ZipImportError(ImportError):
42
43
STRING_END_ARCHIVE = b'PK\x05 \x06 '
43
44
MAX_COMMENT_LEN = (1 << 16 ) - 1
44
45
45
- class zipimporter :
46
+ class zipimporter ( _bootstrap_external . _LoaderBasics ) :
46
47
"""zipimporter(archivepath) -> zipimporter object
47
48
48
49
Create a new zipimporter instance. 'archivepath' must be a path to
@@ -115,7 +116,12 @@ def find_loader(self, fullname, path=None):
115
116
full path name if it's possibly a portion of a namespace package,
116
117
or None otherwise. The optional 'path' argument is ignored -- it's
117
118
there for compatibility with the importer protocol.
119
+
120
+ Deprecated since Python 3.10. Use find_spec() instead.
118
121
"""
122
+ _warnings .warn ("zipimporter.find_loader() is deprecated and slated for "
123
+ "removal in Python 3.12; use find_spec() instead" ,
124
+ DeprecationWarning )
119
125
mi = _get_module_info (self , fullname )
120
126
if mi is not None :
121
127
# This is a module or package.
@@ -146,15 +152,46 @@ def find_module(self, fullname, path=None):
146
152
instance itself if the module was found, or None if it wasn't.
147
153
The optional 'path' argument is ignored -- it's there for compatibility
148
154
with the importer protocol.
155
+
156
+ Deprecated since Python 3.10. Use find_spec() instead.
149
157
"""
158
+ _warnings .warn ("zipimporter.find_module() is deprecated and slated for "
159
+ "removal in Python 3.12; use find_spec() instead" ,
160
+ DeprecationWarning )
150
161
return self .find_loader (fullname , path )[0 ]
151
162
163
+ def find_spec (self , fullname , target = None ):
164
+ """Create a ModuleSpec for the specified module.
165
+
166
+ Returns None if the module cannot be found.
167
+ """
168
+ module_info = _get_module_info (self , fullname )
169
+ if module_info is not None :
170
+ return _bootstrap .spec_from_loader (fullname , self , is_package = module_info )
171
+ else :
172
+ # Not a module or regular package. See if this is a directory, and
173
+ # therefore possibly a portion of a namespace package.
174
+
175
+ # We're only interested in the last path component of fullname
176
+ # earlier components are recorded in self.prefix.
177
+ modpath = _get_module_path (self , fullname )
178
+ if _is_dir (self , modpath ):
179
+ # This is possibly a portion of a namespace
180
+ # package. Return the string representing its path,
181
+ # without a trailing separator.
182
+ path = f'{ self .archive } { path_sep } { modpath } '
183
+ spec = _bootstrap .ModuleSpec (name = fullname , loader = None ,
184
+ is_package = True )
185
+ spec .submodule_search_locations .append (path )
186
+ return spec
187
+ else :
188
+ return None
152
189
153
190
def get_code (self , fullname ):
154
191
"""get_code(fullname) -> code object.
155
192
156
193
Return the code object for the specified module. Raise ZipImportError
157
- if the module couldn't be found .
194
+ if the module couldn't be imported .
158
195
"""
159
196
code , ispackage , modpath = _get_module_code (self , fullname )
160
197
return code
@@ -184,7 +221,8 @@ def get_data(self, pathname):
184
221
def get_filename (self , fullname ):
185
222
"""get_filename(fullname) -> filename string.
186
223
187
- Return the filename for the specified module.
224
+ Return the filename for the specified module or raise ZipImportError
225
+ if it couldn't be imported.
188
226
"""
189
227
# Deciding the filename requires working out where the code
190
228
# would come from if the module was actually loaded
@@ -236,8 +274,13 @@ def load_module(self, fullname):
236
274
237
275
Load the module specified by 'fullname'. 'fullname' must be the
238
276
fully qualified (dotted) module name. It returns the imported
239
- module, or raises ZipImportError if it wasn't found.
277
+ module, or raises ZipImportError if it could not be imported.
278
+
279
+ Deprecated since Python 3.10. Use exec_module() instead.
240
280
"""
281
+ msg = ("zipimport.zipimporter.load_module() is deprecated and slated for "
282
+ "removal in Python 3.12; use exec_module() instead" )
283
+ _warnings .warn (msg , DeprecationWarning )
241
284
code , ispackage , modpath = _get_module_code (self , fullname )
242
285
mod = sys .modules .get (fullname )
243
286
if mod is None or not isinstance (mod , _module_type ):
@@ -280,11 +323,18 @@ def get_resource_reader(self, fullname):
280
323
return None
281
324
except ZipImportError :
282
325
return None
283
- if not _ZipImportResourceReader ._registered :
284
- from importlib .abc import ResourceReader
285
- ResourceReader .register (_ZipImportResourceReader )
286
- _ZipImportResourceReader ._registered = True
287
- return _ZipImportResourceReader (self , fullname )
326
+ from importlib .readers import ZipReader
327
+ return ZipReader (self , fullname )
328
+
329
+
330
+ def invalidate_caches (self ):
331
+ """Reload the file data of the archive path."""
332
+ try :
333
+ self ._files = _read_directory (self .archive )
334
+ _zip_directory_cache [self .archive ] = self ._files
335
+ except ZipImportError :
336
+ _zip_directory_cache .pop (self .archive , None )
337
+ self ._files = {}
288
338
289
339
290
340
def __repr__ (self ):
@@ -580,20 +630,15 @@ def _eq_mtime(t1, t2):
580
630
581
631
582
632
# Given the contents of a .py[co] file, unmarshal the data
583
- # and return the code object. Return None if it the magic word doesn't
584
- # match, or if the recorded .py[co] metadata does not match the source,
585
- # (we do this instead of raising an exception as we fall back
586
- # to .py if available and we don't want to mask other errors).
633
+ # and return the code object. Raises ImportError it the magic word doesn't
634
+ # match, or if the recorded .py[co] metadata does not match the source.
587
635
def _unmarshal_code (self , pathname , fullpath , fullname , data ):
588
636
exc_details = {
589
637
'name' : fullname ,
590
638
'path' : fullpath ,
591
639
}
592
640
593
- try :
594
- flags = _bootstrap_external ._classify_pyc (data , fullname , exc_details )
595
- except ImportError :
596
- return None
641
+ flags = _bootstrap_external ._classify_pyc (data , fullname , exc_details )
597
642
598
643
hash_based = flags & 0b1 != 0
599
644
if hash_based :
@@ -607,11 +652,8 @@ def _unmarshal_code(self, pathname, fullpath, fullname, data):
607
652
source_bytes ,
608
653
)
609
654
610
- try :
611
- _bootstrap_external ._validate_hash_pyc (
612
- data , source_hash , fullname , exc_details )
613
- except ImportError :
614
- return None
655
+ _bootstrap_external ._validate_hash_pyc (
656
+ data , source_hash , fullname , exc_details )
615
657
else :
616
658
source_mtime , source_size = \
617
659
_get_mtime_and_size_of_source (self , fullpath )
@@ -697,6 +739,7 @@ def _get_pyc_source(self, path):
697
739
# 'fullname'.
698
740
def _get_module_code (self , fullname ):
699
741
path = _get_module_path (self , fullname )
742
+ import_error = None
700
743
for suffix , isbytecode , ispackage in _zip_searchorder :
701
744
fullpath = path + suffix
702
745
_bootstrap ._verbose_message ('trying {}{}{}' , self .archive , path_sep , fullpath , verbosity = 2 )
@@ -707,8 +750,12 @@ def _get_module_code(self, fullname):
707
750
else :
708
751
modpath = toc_entry [0 ]
709
752
data = _get_data (self .archive , toc_entry )
753
+ code = None
710
754
if isbytecode :
711
- code = _unmarshal_code (self , modpath , fullpath , fullname , data )
755
+ try :
756
+ code = _unmarshal_code (self , modpath , fullpath , fullname , data )
757
+ except ImportError as exc :
758
+ import_error = exc
712
759
else :
713
760
code = _compile_source (modpath , data )
714
761
if code is None :
@@ -718,75 +765,8 @@ def _get_module_code(self, fullname):
718
765
modpath = toc_entry [0 ]
719
766
return code , ispackage , modpath
720
767
else :
721
- raise ZipImportError (f"can't find module { fullname !r} " , name = fullname )
722
-
723
-
724
- class _ZipImportResourceReader :
725
- """Private class used to support ZipImport.get_resource_reader().
726
-
727
- This class is allowed to reference all the innards and private parts of
728
- the zipimporter.
729
- """
730
- _registered = False
731
-
732
- def __init__ (self , zipimporter , fullname ):
733
- self .zipimporter = zipimporter
734
- self .fullname = fullname
735
-
736
- def open_resource (self , resource ):
737
- fullname_as_path = self .fullname .replace ('.' , '/' )
738
- path = f'{ fullname_as_path } /{ resource } '
739
- from io import BytesIO
740
- try :
741
- return BytesIO (self .zipimporter .get_data (path ))
742
- except OSError :
743
- raise FileNotFoundError (path )
744
-
745
- def resource_path (self , resource ):
746
- # All resources are in the zip file, so there is no path to the file.
747
- # Raising FileNotFoundError tells the higher level API to extract the
748
- # binary data and create a temporary file.
749
- raise FileNotFoundError
750
-
751
- def is_resource (self , name ):
752
- # Maybe we could do better, but if we can get the data, it's a
753
- # resource. Otherwise it isn't.
754
- fullname_as_path = self .fullname .replace ('.' , '/' )
755
- path = f'{ fullname_as_path } /{ name } '
756
- try :
757
- self .zipimporter .get_data (path )
758
- except OSError :
759
- return False
760
- return True
761
-
762
- def contents (self ):
763
- # This is a bit convoluted, because fullname will be a module path,
764
- # but _files is a list of file names relative to the top of the
765
- # archive's namespace. We want to compare file paths to find all the
766
- # names of things inside the module represented by fullname. So we
767
- # turn the module path of fullname into a file path relative to the
768
- # top of the archive, and then we iterate through _files looking for
769
- # names inside that "directory".
770
- from pathlib import Path
771
- fullname_path = Path (self .zipimporter .get_filename (self .fullname ))
772
- relative_path = fullname_path .relative_to (self .zipimporter .archive )
773
- # Don't forget that fullname names a package, so its path will include
774
- # __init__.py, which we want to ignore.
775
- assert relative_path .name == '__init__.py'
776
- package_path = relative_path .parent
777
- subdirs_seen = set ()
778
- for filename in self .zipimporter ._files :
779
- try :
780
- relative = Path (filename ).relative_to (package_path )
781
- except ValueError :
782
- continue
783
- # If the path of the file (which is relative to the top of the zip
784
- # namespace), relative to the package given when the resource
785
- # reader was created, has a parent, then it's a name in a
786
- # subdirectory and thus we skip it.
787
- parent_name = relative .parent .name
788
- if len (parent_name ) == 0 :
789
- yield relative .name
790
- elif parent_name not in subdirs_seen :
791
- subdirs_seen .add (parent_name )
792
- yield parent_name
768
+ if import_error :
769
+ msg = f"module load failed: { import_error } "
770
+ raise ZipImportError (msg , name = fullname ) from import_error
771
+ else :
772
+ raise ZipImportError (f"can't find module { fullname !r} " , name = fullname )
0 commit comments