Package: resource-man
Resource manager to work with Python standards and executables.Standard Resource Functionsfiles - importlib.resources files this function is the standard for retrieving resources for Python 3.9+as_file - context manager for retrieving a true filepath for Python 3.9+.read_binary - Return the bytes found in the package with the given basename.read_text - Return the text found in the package with the given basename.contents - Return an iterable of basenames in the given package.is_resource - Return if the given package, basename exists.Custom Helpersclear - clear all registered resources.register - Register a package and basename.register_data - Register a plain data resource that does not have a file.register_directory - Register the contents of a package directory.unregister - Remove a resource from the registration.has_resource - Return if the given resource, package_path, or alias has a registered resource.get_resources - Return a list of Resources. If multiple Resources register the same alias the last one will be used.get_resource - Return the Resource object for the given alias, fallback alias, or default value.get_binary - Return the binary data read from the found resource.get_text - Return the text data read from the found resource.Notes:I added support for subdirectories which importlib.resources specifically does not support.
I believe this was a terrible mistake. If I am using any library with css, html, or js there will naturally be
a couple of subdirectories. If I include these projects as sub-repositories I do not want to add __init__.py files
to everything.# mylib/run.py# File Structure:#     mylib/#         __init__.py#         run.py#         subrepo/#             html/#                 index.html#             css/#                 my.css#             js/#                 my.js#             edit-cut.png#             README.mdimportresource_manRESOURCES=[resource_man.register('mylib','subrepo/html/index.html')resource_man.register('mylib','subrepo/css/my.css')resource_man.register('mylib','subrepo/js/my.js')resource_man.register('mylib','subrepo/edit-cut.png')]# RESOURCES = resource_man.register_directory('mylib', 'subrepo', recursive=True,#                                             exclude=['README.md'],  # exclude from directory ('subrepo')#                                             extensions=['.png', '.html', '.js', '.css'])Resource Man ExampleRegister resources. This example requires that the file exists.
If you use PyInstaller to make an executable “mylib/actions/edit-cut.png” needs to be included in datas.
This should also work if the resource is bundled in a zip file according to some of the Python documentation.# mylib/run.py# File Structure:#     mylib/#         __init__.py#         run.py#         actions/#             __init__.py#             edit-cut.pngimportresource_manasrscrsc.register('mylib.actions','edit-cut.png',alias='edit-cut')if__name__=='__main__':edit_cut_bin=resource_man.get_binary('edit-cut')Best practice is probably to register the resource file in the __init__.py that it exists with.
Then you have access to all resources on import.# mylib/actions/__init__.pyimportresource_manasrscEDIT_CUT=rsc.register('mylib.actions','edit-cut.png')if__name__=='__main__':edit_cut_bin=EDIT_CUT.read_bytes()# Use with `import mylib.actions`Register directories by registering all objectsimportresource_manasrscDIRECTORY=rsc.register_directory('check_lib.check_sub',extensions=['.txt','.png'])assertlen(DIRECTORY)>0assertisinstance(DIRECTORY[0],rsc.Resource)forresourceinDIRECTORY:print(resource)importlib.resources ExampleUsing filenames and paths.
As stated earlier Python recommends that you use importlib.resources to read the resource data.
Filenames still have some support with importlib.resources, but it must be used as a context manager.# my_interface.py# sdl2 with sld2.dll in package# File Structure:#     my_sdl/#         sdl2_dll_path/#             __init__.py  # Is probably still required. Was required for pkg_resources#             SDL2.dll#         __init__.py#         my_interface.pyimportosfromresource_manimportfiles,as_fileimportmy_sdl.sdl2_dll_path# Required for PyInstaller to include the package# ".sdl2_dll_path" would require __init__.pybinary=files('my_sdl.sdl2_dll_path').joinpath('SDL2.dll').read_binary()withas_file(files('my_sdl.sdl2_dll_path').joinpath('SDL2.dll'))assdl_path:os.environ.setdefault('PYSDL2_DLL_PATH',os.path.dirname(str(sdl_path)))importsdl2# Use sdl2assertsdl2isnotNonePyInstaller HelperThis library has a collect_datas helper function.
I believe this function to be more useful than PyInstallers built in tool.# hook-mylib.py## File Structure:#     mylib/#         __init__.py#         run.py#         edit-cut.png#     pyinstaller-hooks/#         hook-mylib.pyfromresource_man.pyinstallerimportfind_datas,registered_datas# datas = find_datas('mylib')  # Will also find resources in sub packagesdatas=registered_datas(use_dest_dirs=False)# Return a list of registered resourcesUse the pyinstaller helper with pylibimp to import all resources for your project.# build_exe.pyfromresource_man.pyinstallerimportregistered_datasfromPyInstallerimportconfigfrompylibimpimportimport_moduleimportsubprocessif__name__=='__main__':main_module='mylib/run.py'# Import the main module to register all of the data files.import_module(main_module,reset_modules=True)# Get registered datasdatas=registered_datas(use_dest_dirs=False)args=[]fordataindatas:args.extend(['--add-data',os.pathsep.join(data)])subprocess.run(['pyinstaller',main_module]+args)You could also make your own PyInstaller hook using these helper functions.Qt ExampleTheimportlib.resourceslibrary prefers reading data from a resource instead of using filename paths.
This is to speed up execution and support with zip files.
Qt Primarily uses filenames, but also has it’s own system of importing compiled resources.
I have created several utilities to help with this.Compiled ResourcesThe best way is probably to use compiled resources.Theresource_manlibrary helps with utilities for registering resources, create .qrc files, and compiling .qrc files.1. Register the ResourceUseresource_manto register resources when the file is imported.# main_qt.py# File Structure:#    main_qt.py#    check_lib/#        __init__.py#        check_sub/#            __init__.py#            edit-cut.pngimportresource_man.qtasrsc# Register on import outside of mainrsc.register('check_lib.check_sub','edit-cut.png',alias='edit-cut')DOCUMENT_NEW=rsc.register('check_lib.check_sub','document-new.png')# QFile name is ":/check_lib/check_sub/document-new.png"RSC2=rsc.register('check_lib.check_sub','rsc2.txt',...)# ... uses name as alias ("rsc2.txt")After registering,resource_mancan create the list of resources in a .qrc file.2. Create .qrc FileCreate the .qrc file that can compile all resources into a binary data file.python -m resource_man.qt create ./main_qt.pyThis creates a file that looks like.<!DOCTYPE RCC><RCC version="1.0">
<qresource>
    <file alias="edit-cut">check_lib\check_sub\edit-cut.png</file>
    <file>check_lib\check_sub\document-new.png</file>
    <file alias="rsc2.txt">check_lib\check_sub\rsc2.txt</file>
</qresource>
</RCC>3. Compile the .qrc fileCompile the .qrc file into an importable .py file. PySide can also make a C++ .rcc file that can be registered as well.python -m resource_man.qt compileThis creates a large .py file with the binary data.4. Load the compiled fileLoad the compiled .py file....if__name__=='__main__':app=QtWidgets.QApplication([])# Load the Qt RCC after QApplicationsuccess=load_resource()5. Use the qrc resourceUse the QIcon or QPixmap to use the registered resource.fromresource_man.qtimportQIcon,QPixmap...icon=QIcon('edit-cut')icon=QIcon(':/edit-cut')icon=QIcon(':/document-new.png')Use with importlib.resources.fromresource_man.qtimportread_binaryimportcheck_lib.check_sub...# Need to --add-datas with PyInstaller to use this in an executablebinary_img=read_binary('check_lib.check_sub','edit-cut.png')Full ExampleTheresource_manlibrary includes a QIcon and QPixmap class to use registered resources.
This QIcon and QPixmap can take in binary data as the first argument to create the icon.
This QIcon and QPixmap can also take the registered alias.
This library usesQtPyto support PySide or PyQt.# mylib/run.py# File Structure:#     check_lib/#         __init__.py#         run.py#         check_sub/#             __init__.py#             edit-cut.png#             document-new.png#             document-save-as.svgimportcheck_lib.check_subfromqtpyimportQtWidgets,QtCoreimportresource_man.qtasrsc# Register on import outside of mainEDIT_CUT=rsc.register('check_lib.check_sub','edit-cut.png',None)# None uses name no ext as alias ('edit-cut')rsc.register('check_lib.check_sub','document-save-as.svg',None)# None uses name no ext as alias ('document-save-as')RSC=rsc.register('check_lib','rsc.txt',...)# ... uses name as alias ("rsc.txt")RSC2=rsc.register('check_lib.check_sub','rsc2.txt',...)# ... uses name as alias ("rsc2.txt")DOCUMENT_NEW=rsc.register('check_lib.check_sub','document-new.png')# QFile ":/check_lib/check_sub/document-new.png"DOC_NEW_DATA=rsc.register_data(DOCUMENT_NEW.read_bytes(),'readme_qt','data_resource','data_resource')if__name__=='__main__':app=QtWidgets.QApplication([])# Load the Qt RCC after QApplicationsuccess=rsc.load_resource()widg=QtWidgets.QWidget()widg.setLayout(QtWidgets.QVBoxLayout())# Use the Resource as the Path. This is not recommended. Use 'as_file' or 'read_text'.withopen(DOCUMENT_NEW,'rb')asf:# Need str for some objects QtCore.QFile(str(DOCUMENT_NEW))assertlen(f.read())>0# Resource file (Must be compiled and loaded)file=QtCore.QFile(':/rsc2.txt')ifnotfile.open(QtCore.QIODevice.ReadOnly|QtCore.QIODevice.Text):text='File Not Available'else:text=file.readAll().data().decode('utf-8')file.close()msg='READ FILE\n'\'File Path ={}\nread_text ={}\nQFile :/rsc2.txt ={}'.format(str(RSC2),repr(RSC2.read_text()),repr(text))lbl=QtWidgets.QLabel(msg)widg.layout().addWidget(lbl)# Use resource_man register aliasbtn=QtWidgets.QPushButton(rsc.QIcon('edit-cut'),'resource_man alias "edit-cut"',None)widg.layout().addWidget(btn)# Use Qt QResource alias namebtn=QtWidgets.QPushButton(rsc.QIcon(':/edit-cut'),'QFile alias ":/edit-cut"',None)widg.layout().addWidget(btn)# Use Qt QResource File name - DOES NOT WORK! CAN ONLY USE QRC ALIAS IDENTIFIER!# btn = QtWidgets.QPushButton(QIcon(':\\check_lib\\check_sub\\edit-cut.png'),#                                   'QFile name ":\\check_lib\\check_sub\\edit-cut.png"', None)# widg.layout().addWidget(btn)# Use resource_man register aliasbtn=QtWidgets.QPushButton(rsc.QIcon(DOCUMENT_NEW),'resource_man object DOCUMENT_NEW',None)widg.layout().addWidget(btn)# Use Qt QResource File name aliasbtn=QtWidgets.QPushButton(rsc.QIcon(':/check_lib/check_sub/document-new.png'),'QFile alias ":/check_lib/check_sub/document-new.png"',None)widg.layout().addWidget(btn)# Use Qt QResource File name aliasbtn=QtWidgets.QPushButton(rsc.QIcon('data_resource'),'data_resource',None)widg.layout().addWidget(btn)# Use Qt QResource File name alias - DOES NOT WORK! CAN ONLY USE QRC ALIAS IDENTIFIER!# btn = QtWidgets.QPushButton(rsc.QIcon(':/check_lib/check_sub/document-new.png'),#                                   '":/check_lib/check_sub/document-new.png"', None)# widg.layout().addWidget(btn)# ===== The two methods below only work if the resource files exist in the executable =====# you need to include the .png files as data files in PyInstaller# you also need to import the package (`import check_lib.check_sub`) for PyInstaller to include the package.# resource_man binary (resource_man register alias support)try:btn_binary_resource_man=QtWidgets.QPushButton(rsc.QIcon(rsc.get_binary('edit-cut')),'resource_man get_binary("edit-cut")')widg.layout().addWidget(btn_binary_resource_man)except(rsc.ResourceNotAvailable,OSError,TypeError)aserr:pass# importlib.resources binarytry:btn_binary_importlib=QtWidgets.QPushButton(rsc.QIcon(rsc.read_binary('check_lib.check_sub','edit-cut.png')),'importlib.resources read_binary("check_lib.check_sub", "edit-cut.png")')widg.layout().addWidget(btn_binary_importlib)except(rsc.ResourceNotAvailable,OSError,TypeError)aserr:pass# Show Imageshlay=QtWidgets.QHBoxLayout()widg.layout().addLayout(hlay)try:lbl=QtWidgets.QLabel()lbl.setPixmap(rsc.QPixmap(rsc.files('check_lib.check_sub').joinpath('edit-cut.png')).scaledToHeight(32))hlay.addWidget(lbl)except(rsc.ResourceNotAvailable,OSError,TypeError)aserr:passtry:# QSvg Cannot load png images. This will be blankinvalid=rsc.QSvgWidget("check_lib/check_sub/document-new.png")w=QtWidgets.QWidget()w.setLayout(QtWidgets.QVBoxLayout())w.layout().addWidget(QtWidgets.QLabel('QSvgWidget NO PNG'))w.layout().addWidget(invalid)hlay.addWidget(w)except(rsc.ResourceNotAvailable,OSError,TypeError)aserr:passtry:lbl=QtWidgets.QLabel()lbl.setPixmap(rsc.QPixmap("document-save-as").scaledToHeight(32))hlay.addWidget(lbl)except(rsc.ResourceNotAvailable,OSError,TypeError)aserr:passtry:svg=rsc.QSvgWidget("document-save-as")svg.setFixedSize(32,32)hlay.addWidget(svg)except(rsc.ResourceNotAvailable,OSError,TypeError)aserr:passwidg.show()app.exec_()