[Import-SIG] Loading Resources From a Python Module/Package

Donald Stufft donald at stufft.io
Sat Jan 31 17:43:52 CET 2015


> On Jan 31, 2015, at 11:31 AM, Brett Cannon <brett at python.org> wrote:
> 
> 
> 
> On Sat Jan 31 2015 at 10:54:22 AM Paul Moore <p.f.moore at gmail.com <mailto:p.f.moore at gmail.com>> wrote:
> On 31 January 2015 at 15:47, Donald Stufft <donald at stufft.io <mailto:donald at stufft.io>> wrote:
> >> It's certainly possible to add a new API that loads resources based on
> >> a relative name, but you'd have to specify relative to *what*.
> >> get_data explicitly ducks out of making that decision.
> >
> > data = __loader__.get_bytes(__name__, “logo.gif”)
> 
> Quite possibly. It needs a bit of fleshing out to make sure it doesn't
> prohibit sharing of loaders, etc, in the way Brett mentions.
> 
> By specifying the package anchor point I don't think it does.
>  
> Also, the
> fact that it needs __name__ in there feels wrong - a bit like the old
> version of super() needing to be told which class it was being called
> from.
> 
> You can't avoid that. This is the entire reason why loader reuse is a pain; you **have** to specify what to work off of, else its ambiguous and a specific feature of a specific loader.
> 
> But this is only an issue when you are trying to access a file relative to the package/module you're in. Otherwise you're going to be specifying a string constant like 'foo.bar'.
>  
> But in principle I don't object to finding a suitable form of
> this.
> 
> And I like the name get_bytes - much more explicit in these Python 3
> days of explicit str/bytes distinctions :-)
> 
> One unfortunate side-effect from having a new method to return bytes from a data file is that it makes get_data() somewhat redundant. If we make it get_data_filename(package_name, path) then it can return an absolute path which can then be passed to get_data() to read the actual bytes. If we create importlib.resources as Donald has suggested then all of this can be hidden  behind a function and users don't have to care about any of this, e.g. importlib.resources.read_data(module_anchor, path).

I think we actually have to go the other way, because only some Loaders will be able to actually return a filename (returning a filename is basically an optimization to prevent needing to call get_data and write that out to a temporary directory) but pretty much any loader should theoretically be able to support get_data.

I think it is redundant but given that it’s a new API (passing module and a “resource path”) I think it makes sense. The old get_data API can be deprecated but left in for compatibility reasons if we want (sort of like Loader().load_module() -> Loader().exec_module()).

> 
> One thing to consider is do we want to allow anything other than filenames for the path part? Thanks to namespace packages every directory is essentially a package, so we could say that the package anchor has to encapsulate the directory and the path bit can only be a filename. That gets us even farther away from having the concept of file paths being manipulated in relation to import-related APIs.

I think we do want to allow directories, it’s not unusual to have something like:

warehouse
├── __init__.py
├── templates
│   ├── accounts
│   │   └── profile.html
│   └── hello.html
├── utils
│   └── mapper.py
└── wsgi.py

Conceptually templates isn’t a package (even though with namespace packages it kinda is) and I’d want to load profile.html by doing something like:

importlib.resources.get_bytes(“warehouse”, “templates/accounts/profile.html”)

In pkg_resources the second argument to that function is a “resource path” which is defined as a relative to the given module/package and it must use / to denote them. It explicitly says it’s not a file system path but a resource path. It may translate to a file system path (as is the case with the FileLoader) but it also may not (as is the case with a theoretical S3Loader or PostgreSQLLoader). How you turn a warehouse + a resource path into some data (or whatever other function we support) is an implementation detail of the Loader.

> 
> And just so I don't forget it, I keep wanting to pass an actual module in so the code can extract the name that way, but that prevents the __name__ trick as you would have to import yourself or grab the module from sys.modules.

Is an actual module what gets passed into Loader().exec_module()? If so I think it’s fine to pass that into the new Loader() functions and a new top level API in importlib.resources can do the things needed to turn a string into a module object. So instead of doing __loader__.get_bytes(__name__, “logo.gif”) you’d do importlib.resources.get_bytes(__name__, “logo.gif”).

---
Donald Stufft
PGP: 7C6B 7C5D 5E2B 6356 A926 F04F 6E3C BCE9 3372 DCFA

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/import-sig/attachments/20150131/7e7d5034/attachment-0001.html>


More information about the Import-SIG mailing list