Search code examples
pythonembedded-resource

embedding resources in python scripts


I'd like to figure out how to embed binary content in a python script. For instance, I don't want to have any external files around (images, sound, ... ), I want all this content living inside of my python scripts.

Little example to clarify, let's say I got this small snippet:

from StringIO import StringIO
from PIL import Image, ImageFilter

embedded_resource = StringIO(open("Lenna.png", "rb").read())
im = Image.open(embedded_resource)
im.show()

im_sharp = im.filter(ImageFilter.SHARPEN)
im_sharp.show()

As you can see, the example is reading the external file 'Lenna.png'

enter image description here

Question

How to proceed to embed "Lenna.png" as a resource (variable) into my python script. What's the fastest way to achieve this simple task using python?


Solution

  • You might find the following class rather useful for embedding resources in your program. To use it, call the package method with paths to the files that you want to embed. The class will print out a DATA attribute that should be used to replace the one already found in the class. If you want to add files to your pre-built data, use the add method instead. To use the class in your program, make calls to the load method using context manager syntax. The returned value is a Path object that can be used as a filename argument to other functions or for the purpose of directly loading the reconstituted file. See this SMTP Client for example usage.

    import base64
    import contextlib
    import pathlib
    import pickle
    import pickletools
    import sys
    import zlib
    
    
    class Resource:
    
        """Manager for resources that would normally be held externally."""
    
        WIDTH = 76
        __CACHE = None
        DATA = b''
    
        @classmethod
        def package(cls, *paths):
            """Creates a resource string to be copied into the class."""
            cls.__generate_data(paths, {})
    
        @classmethod
        def add(cls, *paths):
            """Include paths in the pre-generated DATA block up above."""
            cls.__preload()
            cls.__generate_data(paths, cls.__CACHE.copy())
    
        @classmethod
        def __generate_data(cls, paths, buffer):
            """Load paths into buffer and output DATA code for the class."""
            for path in map(pathlib.Path, paths):
                if not path.is_file():
                    raise ValueError('{!r} is not a file'.format(path))
                key = path.name
                if key in buffer:
                    raise KeyError('{!r} has already been included'.format(key))
                with path.open('rb') as file:
                    buffer[key] = file.read()
            pickled = pickle.dumps(buffer, pickle.HIGHEST_PROTOCOL)
            optimized = pickletools.optimize(pickled)
            compressed = zlib.compress(optimized, zlib.Z_BEST_COMPRESSION)
            encoded = base64.b85encode(compressed)
            cls.__print("    DATA = b'''")
            for offset in range(0, len(encoded), cls.WIDTH):
                cls.__print("\\\n" + encoded[
                    slice(offset, offset + cls.WIDTH)].decode('ascii'))
            cls.__print("'''")
    
        @staticmethod
        def __print(line):
            """Provides alternative printing interface for simplicity."""
            sys.stdout.write(line)
            sys.stdout.flush()
    
        @classmethod
        @contextlib.contextmanager
        def load(cls, name, delete=True):
            """Dynamically loads resources and makes them usable while needed."""
            cls.__preload()
            if name not in cls.__CACHE:
                raise KeyError('{!r} cannot be found'.format(name))
            path = pathlib.Path(name)
            with path.open('wb') as file:
                file.write(cls.__CACHE[name])
            yield path
            if delete:
                path.unlink()
    
        @classmethod
        def __preload(cls):
            """Warm up the cache if it does not exist in a ready state yet."""
            if cls.__CACHE is None:
                decoded = base64.b85decode(cls.DATA)
                decompressed = zlib.decompress(decoded)
                cls.__CACHE = pickle.loads(decompressed)
    
        def __init__(self):
            """Creates an error explaining class was used improperly."""
            raise NotImplementedError('class was not designed for instantiation')