1from __future__ import absolute_import 2 3import abc 4 5from ._compat import ABC, FileNotFoundError, runtime_checkable, Protocol 6 7# Use mypy's comment syntax for Python 2 compatibility 8try: 9 from typing import BinaryIO, Iterable, Text 10except ImportError: 11 pass 12 13 14class ResourceReader(ABC): 15 """Abstract base class for loaders to provide resource reading support.""" 16 17 @abc.abstractmethod 18 def open_resource(self, resource): 19 # type: (Text) -> BinaryIO 20 """Return an opened, file-like object for binary reading. 21 22 The 'resource' argument is expected to represent only a file name. 23 If the resource cannot be found, FileNotFoundError is raised. 24 """ 25 # This deliberately raises FileNotFoundError instead of 26 # NotImplementedError so that if this method is accidentally called, 27 # it'll still do the right thing. 28 raise FileNotFoundError 29 30 @abc.abstractmethod 31 def resource_path(self, resource): 32 # type: (Text) -> Text 33 """Return the file system path to the specified resource. 34 35 The 'resource' argument is expected to represent only a file name. 36 If the resource does not exist on the file system, raise 37 FileNotFoundError. 38 """ 39 # This deliberately raises FileNotFoundError instead of 40 # NotImplementedError so that if this method is accidentally called, 41 # it'll still do the right thing. 42 raise FileNotFoundError 43 44 @abc.abstractmethod 45 def is_resource(self, path): 46 # type: (Text) -> bool 47 """Return True if the named 'path' is a resource. 48 49 Files are resources, directories are not. 50 """ 51 raise FileNotFoundError 52 53 @abc.abstractmethod 54 def contents(self): 55 # type: () -> Iterable[str] 56 """Return an iterable of entries in `package`.""" 57 raise FileNotFoundError 58 59 60@runtime_checkable 61class Traversable(Protocol): 62 """ 63 An object with a subset of pathlib.Path methods suitable for 64 traversing directories and opening files. 65 """ 66 67 @abc.abstractmethod 68 def iterdir(self): 69 """ 70 Yield Traversable objects in self 71 """ 72 73 @abc.abstractmethod 74 def read_bytes(self): 75 """ 76 Read contents of self as bytes 77 """ 78 79 @abc.abstractmethod 80 def read_text(self, encoding=None): 81 """ 82 Read contents of self as bytes 83 """ 84 85 @abc.abstractmethod 86 def is_dir(self): 87 """ 88 Return True if self is a dir 89 """ 90 91 @abc.abstractmethod 92 def is_file(self): 93 """ 94 Return True if self is a file 95 """ 96 97 @abc.abstractmethod 98 def joinpath(self, child): 99 """ 100 Return Traversable child in self 101 """ 102 103 @abc.abstractmethod 104 def __truediv__(self, child): 105 """ 106 Return Traversable child in self 107 """ 108 109 @abc.abstractmethod 110 def open(self, mode='r', *args, **kwargs): 111 """ 112 mode may be 'r' or 'rb' to open as text or binary. Return a handle 113 suitable for reading (same as pathlib.Path.open). 114 115 When opening as text, accepts encoding parameters such as those 116 accepted by io.TextIOWrapper. 117 """ 118 119 @abc.abstractproperty 120 def name(self): 121 # type: () -> str 122 """ 123 The base name of this object without any parent references. 124 """ 125 126 127class TraversableResources(ResourceReader): 128 @abc.abstractmethod 129 def files(self): 130 """Return a Traversable object for the loaded package.""" 131 132 def open_resource(self, resource): 133 return self.files().joinpath(resource).open('rb') 134 135 def resource_path(self, resource): 136 raise FileNotFoundError(resource) 137 138 def is_resource(self, path): 139 return self.files().joinpath(path).is_file() 140 141 def contents(self): 142 return (item.name for item in self.files().iterdir()) 143