Source code for kabaret.app.resources

'''
Generic file resouces repository.

This package provide a way to register some folders under a given name
(:meth:`add_folder`) and later retreive files by their basename and
registered folder name (:meth:`get`).

When registering a folder with the same name as another one, the lookup will
favorize the last one and fall back to the first one.

This way you can extend and/or override the resources available under a given
folder name.

You can deal with any kind of files, but in the case of images there are a couple
helper functions: :meth:`get_pixmap()` and :meth:`get_icon()`

Every search result is cached so disk access is minimized. You don't need to
cache your resources next to their usage code, just can keep calling
``resources.get()`` as much as you want.

You can inspect the available resources with :meth:`get_folder_names()`,
:meth:`list_folder()` and :meth:`list_folder_paths()`

.. note::

    Files starting with an underscore ``_`` are ignored.

'''

import logging
import os
import glob


_PATHS = {}
_CACHE = {}
_PIXCACHE = {}
_ICONCACHE = {}


[docs]class ResourcesError(Exception): ''' Raised when the ``folder_name`` is unknown. '''
[docs]class NotFoundError(ResourcesError): ''' Raised when the the resource was not found. Note that this is a :class:`ResourcesError` too. '''
[docs]def add_folder(name, path): ''' Register a folder to the search path. The ``name`` will be used to access files. You can use :meth:`get_folder_names` to get a list of all declared names. The ``path`` can be a file inside the folder to register, or the path of the folder itself. If a folder with the same name was previously added, the files from the last one overrides the previous one but the previous files not available in the last one are still accessible. In other words: the last call overrides and extends the previous ones. Typical resource declaration: .. code-block:: python :caption: my_studio/icons/emoji/__init__.py # We're in a package folder containing some png files # Every file will be accessible, and overrides previous # declarations of 'icons.emoji' files import kabaret.app.resources kabaret.app.resources.add_folder('icons.emoji', __file__) .. code-block:: python :caption: my_studio/gui.py # Be sure to have the resource modules imported at least once # This is typically done in the module defining your session from my_studio.icons import emoji Typical resource access: .. code-block:: python :caption: my_studio/flows/my_dope_flow/__init__.py # Use anywhere from kabaret.app import resources icon = resources.get('icons.emoji', 'sunglasses') .. seealso:: :meth:`get_icon()` :meth:`get_pixmap()` ''' global _PATHS, _CACHE if not os.path.isdir(path): path = os.path.dirname(path) if name not in _PATHS: _PATHS[name] = [] _PATHS[name].insert(0, path) # Remove cache for this folder name: if name in _CACHE: _CACHE.pop(name)
[docs]def get_folder_names(): ''' Returns a list of available folder names. ''' global _PATHS return list(_PATHS)
[docs]def list_folder(folder_name): ''' Returns a list of resource names available in ``folder_name``. ''' global _PATHS EXCLUDE_LIST = ('Thumbs.db',) folder_name = folder_name.lower() ret = set() for path in _PATHS[folder_name]: ret.update( [ name.split('.', 1)[0] for name in os.listdir(path) if not name.startswith('_') if not name.startswith('.') and name not in EXCLUDE_LIST ] ) return sorted(ret)
[docs]def list_folder_paths(folder_name): ''' Return a list of list of file_name for ``folder_name``. (overrides come first) ''' global _PATHS folder_name = folder_name.lower() ret = [] for path in _PATHS[folder_name]: ret.append(sorted( [ name.split('.', 1)[0] for name in os.listdir(path) if not name.startswith('_') ] )) return ret
[docs]def get(folder_name, file_name): ''' Returns the file named ``file_name`` in the folder registered as ``folder_name``. ''' global _PATHS, _CACHE # avoid case sensitive look-up in order to have the same # behavior in win$ and Linux: folder_name = folder_name.lower() file_name = file_name.lower() cache_key = (folder_name, file_name) if cache_key in _CACHE: if _CACHE[cache_key]: return _CACHE[cache_key] else: raise NotFoundError("Resource not found: " + repr(cache_key)) try: path_list = _PATHS[folder_name] except KeyError: raise ResourcesError( "Unknown resource folder " + repr(folder_name) + ". (Did you install it first?)" ) files = None for path in path_list: files = glob.glob( os.path.join( path, file_name + ('.' not in file_name and ".*" or "") ) ) if files: break if not files: _CACHE[cache_key] = None raise NotFoundError("Resource not found: " + repr(cache_key)) if len(files) > 1: logging.getLogger('kabaret.resources').warn( "Several resources found for: " + repr(cache_key)) _CACHE[cache_key] = files[0] return files[0]
[docs]def get_pixmap(folder_name, pixmap_name): ''' Same as :meth:`get()`, but returns a QPixmap ''' global _PIXCACHE path = get(folder_name, pixmap_name) if path in _PIXCACHE: return _PIXCACHE[path] from qtpy import QtGui ret = QtGui.QPixmap(path) _PIXCACHE[path] = ret return ret
[docs]def get_icon(icon_ref, for_widget=None, disabled_ref=None): ''' Same as :meth:`get_pixmap()`, but returns a QIcon If ``icon_ref`` is an **int**, ``for_widget`` must not be **None** and its current QStyle will be used to return the QIcon pointed by icon_ref. If icon_ref is a 2D tuple, it is used to call :meth:`get_pixmap(*icon_ref)` If icon_ref is a file path, an icon is created from this file. if disable_ref is not None, it must be a 2D tuple suitable for :meth:`get_pixmap(*disabled_ref)` and will be used as the "disabled" state of the icon. Warning: calls with different disabled_ref for the same icon_ref has undefined behavior. ''' if isinstance(icon_ref, int): style = for_widget.style() return style.standardIcon(icon_ref) global _ICONCACHE if icon_ref in _ICONCACHE: return _ICONCACHE[icon_ref] from qtpy import QtGui pix = None try: pix = get_pixmap(*icon_ref) except Exception: try: if os.path.isfile(icon_ref): pix = QtGui.QPixmap(icon_ref) except TypeError: pass if pix is None: raise NotFoundError('Cannot find icon for %r' % (icon_ref,)) icon = QtGui.QIcon(pix) if disabled_ref is not None: pix = get_pixmap(*disabled_ref) icon.addPixmap(pix, mode=icon.Disabled) _ICONCACHE[icon_ref] = icon return icon