3
3
import tempfile
4
4
5
5
from . import abc as resources_abc
6
- from ._util import _wrap_file
7
6
from builtins import open as builtins_open
8
7
from contextlib import contextmanager
9
8
from importlib import import_module
13
12
from types import ModuleType
14
13
from typing import Iterator , Optional , Set , Union # noqa: F401
15
14
from typing import cast
16
- from typing .io import IO
15
+ from typing .io import BinaryIO , TextIO
17
16
from zipfile import ZipFile
18
17
19
18
@@ -60,27 +59,54 @@ def _get_resource_reader(
60
59
return None
61
60
62
61
63
- def open (package : Package ,
64
- resource : Resource ,
65
- encoding : str = None ,
66
- errors : str = None ) -> IO :
67
- """Return a file-like object opened for reading of the resource."""
62
+ def open_binary (package : Package , resource : Resource ) -> BinaryIO :
63
+ """Return a file-like object opened for binary reading of the resource."""
68
64
resource = _normalize_path (resource )
69
65
package = _get_package (package )
70
66
reader = _get_resource_reader (package )
71
67
if reader is not None :
72
- return _wrap_file (reader .open_resource (resource ), encoding , errors )
68
+ return reader .open_resource (resource )
69
+ # Using pathlib doesn't work well here due to the lack of 'strict'
70
+ # argument for pathlib.Path.resolve() prior to Python 3.6.
71
+ absolute_package_path = os .path .abspath (package .__spec__ .origin )
72
+ package_path = os .path .dirname (absolute_package_path )
73
+ full_path = os .path .join (package_path , resource )
74
+ try :
75
+ return builtins_open (full_path , mode = 'rb' )
76
+ except IOError :
77
+ # Just assume the loader is a resource loader; all the relevant
78
+ # importlib.machinery loaders are and an AttributeError for
79
+ # get_data() will make it clear what is needed from the loader.
80
+ loader = cast (ResourceLoader , package .__spec__ .loader )
81
+ try :
82
+ data = loader .get_data (full_path )
83
+ except IOError :
84
+ package_name = package .__spec__ .name
85
+ message = '{!r} resource not found in {!r}' .format (
86
+ resource , package_name )
87
+ raise FileNotFoundError (message )
88
+ else :
89
+ return BytesIO (data )
90
+
91
+
92
+ def open_text (package : Package ,
93
+ resource : Resource ,
94
+ encoding : str = 'utf-8' ,
95
+ errors : str = 'strict' ) -> TextIO :
96
+ """Return a file-like object opened for text reading of the resource."""
97
+ resource = _normalize_path (resource )
98
+ package = _get_package (package )
99
+ reader = _get_resource_reader (package )
100
+ if reader is not None :
101
+ return TextIOWrapper (reader .open_resource (resource ), encoding , errors )
73
102
# Using pathlib doesn't work well here due to the lack of 'strict'
74
103
# argument for pathlib.Path.resolve() prior to Python 3.6.
75
104
absolute_package_path = os .path .abspath (package .__spec__ .origin )
76
105
package_path = os .path .dirname (absolute_package_path )
77
106
full_path = os .path .join (package_path , resource )
78
- if encoding is None :
79
- args = dict (mode = 'rb' )
80
- else :
81
- args = dict (mode = 'r' , encoding = encoding , errors = errors )
82
107
try :
83
- return builtins_open (full_path , ** args ) # type: ignore
108
+ return builtins_open (
109
+ full_path , mode = 'r' , encoding = encoding , errors = errors )
84
110
except IOError :
85
111
# Just assume the loader is a resource loader; all the relevant
86
112
# importlib.machinery loaders are and an AttributeError for
@@ -94,29 +120,30 @@ def open(package: Package,
94
120
resource , package_name )
95
121
raise FileNotFoundError (message )
96
122
else :
97
- return _wrap_file (BytesIO (data ), encoding , errors )
123
+ return TextIOWrapper (BytesIO (data ), encoding , errors )
124
+
125
+
126
+ def read_binary (package : Package , resource : Resource ) -> bytes :
127
+ """Return the binary contents of the resource."""
128
+ resource = _normalize_path (resource )
129
+ package = _get_package (package )
130
+ with open_binary (package , resource ) as fp :
131
+ return fp .read ()
98
132
99
133
100
- def read (package : Package ,
101
- resource : Resource ,
102
- encoding : str = 'utf-8' ,
103
- errors : str = 'strict' ) -> Union [ str , bytes ] :
134
+ def read_text (package : Package ,
135
+ resource : Resource ,
136
+ encoding : str = 'utf-8' ,
137
+ errors : str = 'strict' ) -> str :
104
138
"""Return the decoded string of the resource.
105
139
106
140
The decoding-related arguments have the same semantics as those of
107
141
bytes.decode().
108
142
"""
109
143
resource = _normalize_path (resource )
110
144
package = _get_package (package )
111
- # Note this is **not** builtins.open()!
112
- with open (package , resource ) as binary_file :
113
- if encoding is None :
114
- return binary_file .read ()
115
- # Decoding from io.TextIOWrapper() instead of str.decode() in hopes
116
- # that the former will be smarter about memory usage.
117
- text_file = TextIOWrapper (
118
- binary_file , encoding = encoding , errors = errors )
119
- return text_file .read ()
145
+ with open_text (package , resource , encoding , errors ) as fp :
146
+ return fp .read ()
120
147
121
148
122
149
@contextmanager
@@ -145,8 +172,8 @@ def path(package: Package, resource: Resource) -> Iterator[Path]:
145
172
if file_path .exists ():
146
173
yield file_path
147
174
else :
148
- with open (package , resource ) as file :
149
- data = file .read ()
175
+ with open_binary (package , resource ) as fp :
176
+ data = fp .read ()
150
177
# Not using tempfile.NamedTemporaryFile as it leads to deeper 'try'
151
178
# blocks due to the need to close the temporary file to work on
152
179
# Windows properly.
0 commit comments