Re: Loading Resources From a Python Module/Package
Paul Moore <[email protected]> Sat, 31 Jan 2015 09:34:45 +0000
| Newsgroups | gmane.comp.python.import |
|---|---|
| Message-ID | <CACac1F8ZJw_Bqov2VO5CZAcrV_dPvKEqv-J5No5_MLFkUMGskA@mail.gmail.com> |
On 31 January 2015 at 00:52, Donald Stufft <[email protected]> wrote: >> Sounds reasonable. It's a relatively rare, but useful use case. One >> possible issue, though, would people assume that if they get a >> filename it'd be writeable? For the filesystem loader it would be, but >> that would break subtly (writes work but would get discarded) for >> loaders that don't have a native get_data_filename. > > I don’t think you can assume it’s writeable since that’ll break in a lot > of common cases even with the filesystem loader since often times things > in the filesystem will be installed in the system and users won’t have > permissions to write to them anyways. Agreed, It's just that it could happen (either deliberately or by accident). One example I found was pytz, which downloads and builds the timezone data by doing dirname(__file__) in an "update the DB" API call - it'd be an "obvious" case for using resource data. (That was from a long time ago - checking the code now they seem to have tidied this up so it's no longer that way). But yes, documenting it as "don't do that" is probably fine. >> Related question - how would the temp files be cleaned up? At exit? > > My patch registers an atexit handler that cleans up the temporary files yea. Great. >>> B. What do people think about modifying Loader.get_data so it can support >>> relative filenames instead of the calling code needing to handle that? >> >> I'd have to think about that one, but in principle it seems reasonable. >> >> While we're extending the loaders, a far more commonly requested >> feature would be to list available data files. At the moment, code can >> only load data from known paths, which is not ideal. While it's >> unrelated to the original proposal, it makes sense if we're changing >> the spec of loaders to do it in one go, rather than having multiple >> iterations. > > Well both pkgutil.get_data and pkgutil.get_data_filename have parallels in the > pkg_resources library for similar reasons. If we want to extend this to more > things it might make sense to take a look at what all exists there currently: +1 on following pkg_resources. > resource_exists(package_or_requirement, resource_name) > Does the named resource exist? Return True or False accordingly. > > resource_stream(package_or_requirement, resource_name) > Return a readable file-like object for the specified resource; it may be an > actual file, a StringIO, or some similar object. The stream is in > “binary mode”, in the sense that whatever bytes are in the resource will be > read as-is. > > resource_string(package_or_requirement, resource_name) > Return the specified resource as a string. The resource is read in binary > fashion, such that the returned string contains exactly the bytes that are > stored in the resource. > > resource_isdir(package_or_requirement, resource_name) > Is the named resource a directory? Return True or False accordingly. > > resource_listdir(package_or_requirement, resource_name) > List the contents of the named resource directory, just like os.listdir > except that it works even if the resource is in a zipfile. > > resource_filename(package_or_requirement, resource_name) > Sometimes, it is not sufficient to access a resource in string or stream > form, and a true filesystem filename is needed. In such cases, you can use > this method (or module-level function) to obtain a filename for a resource. > If the resource is in an archive distribution (such as a zipped egg), it > will be extracted to a cache directory, and the filename within the cache > will be returned. If the named resource is a directory, then all resources > within that directory (including subdirectories) are also extracted. If the > named resource is a C extension or “eager resource” (see the setuptools > documentation for details), then all C extensions and eager resources are > extracted at the same time. > > See https://pythonhosted.org/setuptools/pkg_resources.html#basic-resource-access > and https://pythonhosted.org/setuptools/pkg_resources.html#resource-extraction > > Obviously the similar functions here are: > > * pkgutil.get_data is pkg_resources.resource_string > * pkgutil.get_data_filename is pkg_resources.resource_filename > > The major difference being that pkg_resource.resource_filename will extract to > a cache directory (controllable with an environment variable or > programatically) and won't clean up the extracted files. This means that they > are (by default) extracted once per user and reused between extractions. I felt > like it made more sense to just extract to a temporary location (even though > this is less performant) in the stdlib. > > That leaves: > > * resource_exists > * resource_stream > * resource_isdir > * resource_listdir > > Which can be done via pkg_resources but not via the standard library, I don't > have a major opinion on whether or not the standard library should do all of > them but I don't think it would hurt if it did. > > Another interesting question if we're going to add more methods is where they > should all live. As far as I know pkgutil.get_data predates the importlib > module. Perhaps deprecating pkgutil.get_data and adding a importlib.resources > module which supports functions like: > > * get_bytes(package, resource) > * get_stream(package, resource) > * get_filename(package, resource) > * exists(package, resource) > * isdir(package, resource) > * listdir(package, resource) > > Changing the names (particular get_data -> get_bytes) could also provide the > mechanism for allowing relative files and deprecating the "you must pass in > a full file path to the Loader()" behavior since the get_data method could be > left alone and a new get_bytes method could be added. > > This would mean people can do things like: > > import importlib.resources > import socket > import ssl > > context = ssl.SSLContext(ssl.PROTOCOL_SSLv23) > context.verify_mode = ssl.CERT_REQUIRED > context.check_hostname = True > context.load_verify_locations( > cafile=importlib.resources.get_filename("certifi", "cacert.pem"), > ) > > s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) > ssl_sock = context.wrap_socket(s, server_hostname='www.verisign.com') > ssl_sock.connect(('www.verisign.com', 443)) +1 on all of the above. Obviously, a lot of the support methods in loaders would need to be optional, but that's fine - and the vast majority of use cases are the filesystem and zipfiles, both of which support these methods, and can be handled in the stdlib. Paul _______________________________________________ Import-SIG mailing list [email protected] https://mail.python.org/mailman/listinfo/import-sig