1
1
import os
2
2
import errno
3
- import tempfile
4
3
4
+ from . import trees
5
5
from ._compat import FileNotFoundError
6
- from contextlib import contextmanager
7
6
from importlib import import_module
8
7
from io import BytesIO , TextIOWrapper , open as io_open
9
- from pathlib2 import Path
10
- from zipfile import ZipFile
11
8
12
9
13
10
def _resolve (name ):
@@ -96,7 +93,10 @@ def read_text(package, resource, encoding='utf-8', errors='strict'):
96
93
return fp .read ()
97
94
98
95
99
- @contextmanager
96
+ def files (package ):
97
+ return trees .from_package (_get_package (package ))
98
+
99
+
100
100
def path (package , resource ):
101
101
"""A context manager providing a file path object to the resource.
102
102
@@ -106,33 +106,10 @@ def path(package, resource):
106
106
raised if the file was deleted prior to the context manager
107
107
exiting).
108
108
"""
109
- resource = _normalize_path (resource )
110
- package = _get_package (package )
111
- package_directory = Path (package .__file__ ).parent
112
- file_path = package_directory / resource
113
- # If the file actually exists on the file system, just return it.
114
- if file_path .exists ():
115
- yield file_path
116
- return
117
-
118
- # Otherwise, it's probably in a zip file, so we need to create a temporary
119
- # file and copy the contents into that file, hence the contextmanager to
120
- # clean up the temp file resource.
121
- with open_binary (package , resource ) as fp :
122
- data = fp .read ()
123
- # Not using tempfile.NamedTemporaryFile as it leads to deeper 'try'
124
- # blocks due to the need to close the temporary file to work on Windows
125
- # properly.
126
- fd , raw_path = tempfile .mkstemp ()
127
- try :
128
- os .write (fd , data )
129
- os .close (fd )
130
- yield Path (raw_path )
131
- finally :
132
- try :
133
- os .remove (raw_path )
134
- except FileNotFoundError :
135
- pass
109
+ path = files (package ).joinpath (_normalize_path (resource ))
110
+ if not path .is_file ():
111
+ raise FileNotFoundError (path )
112
+ return trees .as_file (path )
136
113
137
114
138
115
def is_resource (package , name ):
@@ -155,42 +132,7 @@ def is_resource(package, name):
155
132
return False
156
133
if name not in package_contents :
157
134
return False
158
- # Just because the given file_name lives as an entry in the package's
159
- # contents doesn't necessarily mean it's a resource. Directories are not
160
- # resources, so let's try to find out if it's a directory or not.
161
- path = Path (package .__file__ ).parent / name
162
- if path .is_file ():
163
- return True
164
- if path .is_dir ():
165
- return False
166
- # If it's not a file and it's not a directory, what is it? Well, this
167
- # means the file doesn't exist on the file system, so it probably lives
168
- # inside a zip file. We have to crack open the zip, look at its table of
169
- # contents, and make sure that this entry doesn't have sub-entries.
170
- archive_path = package .__loader__ .archive # type: ignore
171
- package_directory = Path (package .__file__ ).parent
172
- with ZipFile (archive_path ) as zf :
173
- toc = zf .namelist ()
174
- relpath = package_directory .relative_to (archive_path )
175
- candidate_path = relpath / name
176
- for entry in toc : # pragma: nobranch
177
- try :
178
- relative_to_candidate = Path (entry ).relative_to (candidate_path )
179
- except ValueError :
180
- # The two paths aren't relative to each other so we can ignore it.
181
- continue
182
- # Since directories aren't explicitly listed in the zip file, we must
183
- # infer their 'directory-ness' by looking at the number of path
184
- # components in the path relative to the package resource we're
185
- # looking up. If there are zero additional parts, it's a file, i.e. a
186
- # resource. If there are more than zero it's a directory, i.e. not a
187
- # resource. It has to be one of these two cases.
188
- return len (relative_to_candidate .parts ) == 0
189
- # I think it's impossible to get here. It would mean that we are looking
190
- # for a resource in a zip file, there's an entry matching it in the return
191
- # value of contents(), but we never actually found it in the zip's table of
192
- # contents.
193
- raise AssertionError ('Impossible situation' )
135
+ return (trees .from_package (package ) / name ).is_file ()
194
136
195
137
196
138
def contents (package ):
@@ -201,48 +143,4 @@ def contents(package):
201
143
to check if it is a resource or not.
202
144
"""
203
145
package = _get_package (package )
204
- package_directory = Path (package .__file__ ).parent
205
- try :
206
- return os .listdir (str (package_directory ))
207
- except OSError as error :
208
- if error .errno not in (errno .ENOENT , errno .ENOTDIR ):
209
- # We won't hit this in the Python 2 tests, so it'll appear
210
- # uncovered. We could mock os.listdir() to return a non-ENOENT or
211
- # ENOTDIR, but then we'd have to depend on another external
212
- # library since Python 2 doesn't have unittest.mock. It's not
213
- # worth it.
214
- raise # pragma: nocover
215
- # The package is probably in a zip file.
216
- archive_path = getattr (package .__loader__ , 'archive' , None )
217
- if archive_path is None :
218
- raise
219
- relpath = package_directory .relative_to (archive_path )
220
- with ZipFile (archive_path ) as zf :
221
- toc = zf .namelist ()
222
- subdirs_seen = set ()
223
- subdirs_returned = []
224
- for filename in toc :
225
- path = Path (filename )
226
- # Strip off any path component parts that are in common with the
227
- # package directory, relative to the zip archive's file system
228
- # path. This gives us all the parts that live under the named
229
- # package inside the zip file. If the length of these subparts is
230
- # exactly 1, then it is situated inside the package. The resulting
231
- # length will be 0 if it's above the package, and it will be
232
- # greater than 1 if it lives in a subdirectory of the package
233
- # directory.
234
- #
235
- # However, since directories themselves don't appear in the zip
236
- # archive as a separate entry, we need to return the first path
237
- # component for any case that has > 1 subparts -- but only once!
238
- if path .parts [:len (relpath .parts )] != relpath .parts :
239
- continue
240
- subparts = path .parts [len (relpath .parts ):]
241
- if len (subparts ) == 1 :
242
- subdirs_returned .append (subparts [0 ])
243
- elif len (subparts ) > 1 : # pragma: nobranch
244
- subdir = subparts [0 ]
245
- if subdir not in subdirs_seen :
246
- subdirs_seen .add (subdir )
247
- subdirs_returned .append (subdir )
248
- return subdirs_returned
146
+ return list (item .name for item in trees .from_package (package ).iterdir ())
0 commit comments