1"""Abstract base classes related to import.""" 2from . import _bootstrap_external 3from . import machinery 4try: 5 import _frozen_importlib 6except ImportError as exc: 7 if exc.name != '_frozen_importlib': 8 raise 9 _frozen_importlib = None 10try: 11 import _frozen_importlib_external 12except ImportError: 13 _frozen_importlib_external = _bootstrap_external 14from ._abc import Loader 15import abc 16import warnings 17from typing import BinaryIO, Iterable, Text 18from typing import Protocol, runtime_checkable 19 20 21def _register(abstract_cls, *classes): 22 for cls in classes: 23 abstract_cls.register(cls) 24 if _frozen_importlib is not None: 25 try: 26 frozen_cls = getattr(_frozen_importlib, cls.__name__) 27 except AttributeError: 28 frozen_cls = getattr(_frozen_importlib_external, cls.__name__) 29 abstract_cls.register(frozen_cls) 30 31 32class Finder(metaclass=abc.ABCMeta): 33 34 """Legacy abstract base class for import finders. 35 36 It may be subclassed for compatibility with legacy third party 37 reimplementations of the import system. Otherwise, finder 38 implementations should derive from the more specific MetaPathFinder 39 or PathEntryFinder ABCs. 40 41 Deprecated since Python 3.3 42 """ 43 44 def __init__(self): 45 warnings.warn("the Finder ABC is deprecated and " 46 "slated for removal in Python 3.12; use MetaPathFinder " 47 "or PathEntryFinder instead", 48 DeprecationWarning) 49 50 @abc.abstractmethod 51 def find_module(self, fullname, path=None): 52 """An abstract method that should find a module. 53 The fullname is a str and the optional path is a str or None. 54 Returns a Loader object or None. 55 """ 56 warnings.warn("importlib.abc.Finder along with its find_module() " 57 "method are deprecated and " 58 "slated for removal in Python 3.12; use " 59 "MetaPathFinder.find_spec() or " 60 "PathEntryFinder.find_spec() instead", 61 DeprecationWarning) 62 63 64class MetaPathFinder(metaclass=abc.ABCMeta): 65 66 """Abstract base class for import finders on sys.meta_path.""" 67 68 # We don't define find_spec() here since that would break 69 # hasattr checks we do to support backward compatibility. 70 71 def find_module(self, fullname, path): 72 """Return a loader for the module. 73 74 If no module is found, return None. The fullname is a str and 75 the path is a list of strings or None. 76 77 This method is deprecated since Python 3.4 in favor of 78 finder.find_spec(). If find_spec() exists then backwards-compatible 79 functionality is provided for this method. 80 81 """ 82 warnings.warn("MetaPathFinder.find_module() is deprecated since Python " 83 "3.4 in favor of MetaPathFinder.find_spec() and is " 84 "slated for removal in Python 3.12", 85 DeprecationWarning, 86 stacklevel=2) 87 if not hasattr(self, 'find_spec'): 88 return None 89 found = self.find_spec(fullname, path) 90 return found.loader if found is not None else None 91 92 def invalidate_caches(self): 93 """An optional method for clearing the finder's cache, if any. 94 This method is used by importlib.invalidate_caches(). 95 """ 96 97_register(MetaPathFinder, machinery.BuiltinImporter, machinery.FrozenImporter, 98 machinery.PathFinder, machinery.WindowsRegistryFinder) 99 100 101class PathEntryFinder(metaclass=abc.ABCMeta): 102 103 """Abstract base class for path entry finders used by PathFinder.""" 104 105 # We don't define find_spec() here since that would break 106 # hasattr checks we do to support backward compatibility. 107 108 def find_loader(self, fullname): 109 """Return (loader, namespace portion) for the path entry. 110 111 The fullname is a str. The namespace portion is a sequence of 112 path entries contributing to part of a namespace package. The 113 sequence may be empty. If loader is not None, the portion will 114 be ignored. 115 116 The portion will be discarded if another path entry finder 117 locates the module as a normal module or package. 118 119 This method is deprecated since Python 3.4 in favor of 120 finder.find_spec(). If find_spec() is provided than backwards-compatible 121 functionality is provided. 122 """ 123 warnings.warn("PathEntryFinder.find_loader() is deprecated since Python " 124 "3.4 in favor of PathEntryFinder.find_spec() " 125 "(available since 3.4)", 126 DeprecationWarning, 127 stacklevel=2) 128 if not hasattr(self, 'find_spec'): 129 return None, [] 130 found = self.find_spec(fullname) 131 if found is not None: 132 if not found.submodule_search_locations: 133 portions = [] 134 else: 135 portions = found.submodule_search_locations 136 return found.loader, portions 137 else: 138 return None, [] 139 140 find_module = _bootstrap_external._find_module_shim 141 142 def invalidate_caches(self): 143 """An optional method for clearing the finder's cache, if any. 144 This method is used by PathFinder.invalidate_caches(). 145 """ 146 147_register(PathEntryFinder, machinery.FileFinder) 148 149 150class ResourceLoader(Loader): 151 152 """Abstract base class for loaders which can return data from their 153 back-end storage. 154 155 This ABC represents one of the optional protocols specified by PEP 302. 156 157 """ 158 159 @abc.abstractmethod 160 def get_data(self, path): 161 """Abstract method which when implemented should return the bytes for 162 the specified path. The path must be a str.""" 163 raise OSError 164 165 166class InspectLoader(Loader): 167 168 """Abstract base class for loaders which support inspection about the 169 modules they can load. 170 171 This ABC represents one of the optional protocols specified by PEP 302. 172 173 """ 174 175 def is_package(self, fullname): 176 """Optional method which when implemented should return whether the 177 module is a package. The fullname is a str. Returns a bool. 178 179 Raises ImportError if the module cannot be found. 180 """ 181 raise ImportError 182 183 def get_code(self, fullname): 184 """Method which returns the code object for the module. 185 186 The fullname is a str. Returns a types.CodeType if possible, else 187 returns None if a code object does not make sense 188 (e.g. built-in module). Raises ImportError if the module cannot be 189 found. 190 """ 191 source = self.get_source(fullname) 192 if source is None: 193 return None 194 return self.source_to_code(source) 195 196 @abc.abstractmethod 197 def get_source(self, fullname): 198 """Abstract method which should return the source code for the 199 module. The fullname is a str. Returns a str. 200 201 Raises ImportError if the module cannot be found. 202 """ 203 raise ImportError 204 205 @staticmethod 206 def source_to_code(data, path='<string>'): 207 """Compile 'data' into a code object. 208 209 The 'data' argument can be anything that compile() can handle. The'path' 210 argument should be where the data was retrieved (when applicable).""" 211 return compile(data, path, 'exec', dont_inherit=True) 212 213 exec_module = _bootstrap_external._LoaderBasics.exec_module 214 load_module = _bootstrap_external._LoaderBasics.load_module 215 216_register(InspectLoader, machinery.BuiltinImporter, machinery.FrozenImporter) 217 218 219class ExecutionLoader(InspectLoader): 220 221 """Abstract base class for loaders that wish to support the execution of 222 modules as scripts. 223 224 This ABC represents one of the optional protocols specified in PEP 302. 225 226 """ 227 228 @abc.abstractmethod 229 def get_filename(self, fullname): 230 """Abstract method which should return the value that __file__ is to be 231 set to. 232 233 Raises ImportError if the module cannot be found. 234 """ 235 raise ImportError 236 237 def get_code(self, fullname): 238 """Method to return the code object for fullname. 239 240 Should return None if not applicable (e.g. built-in module). 241 Raise ImportError if the module cannot be found. 242 """ 243 source = self.get_source(fullname) 244 if source is None: 245 return None 246 try: 247 path = self.get_filename(fullname) 248 except ImportError: 249 return self.source_to_code(source) 250 else: 251 return self.source_to_code(source, path) 252 253_register(ExecutionLoader, machinery.ExtensionFileLoader) 254 255 256class FileLoader(_bootstrap_external.FileLoader, ResourceLoader, ExecutionLoader): 257 258 """Abstract base class partially implementing the ResourceLoader and 259 ExecutionLoader ABCs.""" 260 261_register(FileLoader, machinery.SourceFileLoader, 262 machinery.SourcelessFileLoader) 263 264 265class SourceLoader(_bootstrap_external.SourceLoader, ResourceLoader, ExecutionLoader): 266 267 """Abstract base class for loading source code (and optionally any 268 corresponding bytecode). 269 270 To support loading from source code, the abstractmethods inherited from 271 ResourceLoader and ExecutionLoader need to be implemented. To also support 272 loading from bytecode, the optional methods specified directly by this ABC 273 is required. 274 275 Inherited abstractmethods not implemented in this ABC: 276 277 * ResourceLoader.get_data 278 * ExecutionLoader.get_filename 279 280 """ 281 282 def path_mtime(self, path): 283 """Return the (int) modification time for the path (str).""" 284 if self.path_stats.__func__ is SourceLoader.path_stats: 285 raise OSError 286 return int(self.path_stats(path)['mtime']) 287 288 def path_stats(self, path): 289 """Return a metadata dict for the source pointed to by the path (str). 290 Possible keys: 291 - 'mtime' (mandatory) is the numeric timestamp of last source 292 code modification; 293 - 'size' (optional) is the size in bytes of the source code. 294 """ 295 if self.path_mtime.__func__ is SourceLoader.path_mtime: 296 raise OSError 297 return {'mtime': self.path_mtime(path)} 298 299 def set_data(self, path, data): 300 """Write the bytes to the path (if possible). 301 302 Accepts a str path and data as bytes. 303 304 Any needed intermediary directories are to be created. If for some 305 reason the file cannot be written because of permissions, fail 306 silently. 307 """ 308 309_register(SourceLoader, machinery.SourceFileLoader) 310 311 312class ResourceReader(metaclass=abc.ABCMeta): 313 """Abstract base class for loaders to provide resource reading support.""" 314 315 @abc.abstractmethod 316 def open_resource(self, resource: Text) -> BinaryIO: 317 """Return an opened, file-like object for binary reading. 318 319 The 'resource' argument is expected to represent only a file name. 320 If the resource cannot be found, FileNotFoundError is raised. 321 """ 322 # This deliberately raises FileNotFoundError instead of 323 # NotImplementedError so that if this method is accidentally called, 324 # it'll still do the right thing. 325 raise FileNotFoundError 326 327 @abc.abstractmethod 328 def resource_path(self, resource: Text) -> Text: 329 """Return the file system path to the specified resource. 330 331 The 'resource' argument is expected to represent only a file name. 332 If the resource does not exist on the file system, raise 333 FileNotFoundError. 334 """ 335 # This deliberately raises FileNotFoundError instead of 336 # NotImplementedError so that if this method is accidentally called, 337 # it'll still do the right thing. 338 raise FileNotFoundError 339 340 @abc.abstractmethod 341 def is_resource(self, path: Text) -> bool: 342 """Return True if the named 'path' is a resource. 343 344 Files are resources, directories are not. 345 """ 346 raise FileNotFoundError 347 348 @abc.abstractmethod 349 def contents(self) -> Iterable[str]: 350 """Return an iterable of entries in `package`.""" 351 raise FileNotFoundError 352 353 354@runtime_checkable 355class Traversable(Protocol): 356 """ 357 An object with a subset of pathlib.Path methods suitable for 358 traversing directories and opening files. 359 """ 360 361 @abc.abstractmethod 362 def iterdir(self): 363 """ 364 Yield Traversable objects in self 365 """ 366 367 def read_bytes(self): 368 """ 369 Read contents of self as bytes 370 """ 371 with self.open('rb') as strm: 372 return strm.read() 373 374 def read_text(self, encoding=None): 375 """ 376 Read contents of self as text 377 """ 378 with self.open(encoding=encoding) as strm: 379 return strm.read() 380 381 @abc.abstractmethod 382 def is_dir(self) -> bool: 383 """ 384 Return True if self is a dir 385 """ 386 387 @abc.abstractmethod 388 def is_file(self) -> bool: 389 """ 390 Return True if self is a file 391 """ 392 393 @abc.abstractmethod 394 def joinpath(self, child): 395 """ 396 Return Traversable child in self 397 """ 398 399 def __truediv__(self, child): 400 """ 401 Return Traversable child in self 402 """ 403 return self.joinpath(child) 404 405 @abc.abstractmethod 406 def open(self, mode='r', *args, **kwargs): 407 """ 408 mode may be 'r' or 'rb' to open as text or binary. Return a handle 409 suitable for reading (same as pathlib.Path.open). 410 411 When opening as text, accepts encoding parameters such as those 412 accepted by io.TextIOWrapper. 413 """ 414 415 @abc.abstractproperty 416 def name(self) -> str: 417 """ 418 The base name of this object without any parent references. 419 """ 420 421 422class TraversableResources(ResourceReader): 423 """ 424 The required interface for providing traversable 425 resources. 426 """ 427 428 @abc.abstractmethod 429 def files(self): 430 """Return a Traversable object for the loaded package.""" 431 432 def open_resource(self, resource): 433 return self.files().joinpath(resource).open('rb') 434 435 def resource_path(self, resource): 436 raise FileNotFoundError(resource) 437 438 def is_resource(self, path): 439 return self.files().joinpath(path).is_file() 440 441 def contents(self): 442 return (item.name for item in self.files().iterdir()) 443