Search code examples
pythonqtpython-requestspysidecx-freeze

Cx_Freezing a PySide, praw, requests application stops working when frozen


I'm having troubles with praw, cx_freeze, pyside and requests, before freezing everything works fine, but when i freeze this happens, something with requests goes wrong i think: http://pastie.org/10614254

This is the project that i'm working with: https://github.com/MrCappuccino/WallDit-QT

This is my setup.py: https://gist.github.com/MrCappuccino/0f1b0571d29d47a95895

import sys
import cx_Freeze
import PySide
import praw
import requests.certs
from cx_Freeze import setup, Executable

exe = Executable(
      script="WallDit_QT.py",
      base="Win32GUI",
      targetName="WallDit_QT.exe"
     )

#includefiles = ['README.txt', 'CHANGELOG.txt', 'helpers\uncompress\unRAR.exe', , 'helpers\uncompress\unzip.exe']
#build_exe_options = {"packages": ["os"], "includefiles": ['README.txt', 'CHANGELOG.txt']}

setup(name = 'WallDit_QT',
  version = '1.0',
  author = 'Disco Dolan',
  description ='Set your wallpaper interactively!',
  executables = [exe],
  options = {'build.exe': {"include_files":['cacert.pem', 'praw.ini', 'README.md']}},
  requires = ['PySide', 'cx_Freeze', 'praw', 'shutil', 'requests']
)

could anybody help out?

I have tried adding cacert.pem, to no avail, at this point i have no more ideas


Solution

  • For some frozen applications, you have to set the the cacert (or external data in general) path inside the frozen applications.

    Setup.py Section

    You first need to include it in your build options and manually specify the installation directory. This is the only part that goes inside the setup.py:

    # notice how I say the folder the certificate is installed
    {"include_files":[(requests.certs.where(),'cacert.pem')]}
    

    In your case, this produces the following setup file:

    import requests
    import sys
    # more imports
    
    setup(name = 'WallDit_QT',
        version = '1.0',
        author = 'Disco Dolan',
        description ='Set your wallpaper interactively!',
        executables = [exe],
        options = {
            'build.exe': {
                "include_files": [
                    (requests.certs.where(),'cacert.pem'), 
                    'praw.ini', 
                    'README.md'
                ]
            }
        },
        requires = ['PySide', 'cx_Freeze', 'praw', 'shutil', 'requests']
    )
    

    Application Section

    You then need to get the certificate path at runtime inside the frozen application. For PyInstaller, a path is defined at runtime to the data directory called _MEIPASS (which can be gotten from sys._MEIPASS), allowing you to access all the data required for the application. In the case of cacert.pem, the path would be determined as follows:

    cacertpath = os.path.join(sys._MEIPASS, "cacert.pem")
    

    For cx_Freeze, the path can be determined from the path of the installation and joining it with the data desired. Here, we get the path as follows:

    cacertpath = os.path.join(datadir, 'cacert.pem')
    

    You can get the data directory easily for frozen applications with the following:

    datadir = os.path.dirname(sys.executable)
    

    (Please note that this won't work with a non-frozen application, so to ensure it works for both frozen and non-frozen applications, Cx_Freeze recommends you code it as follows):

    def find_data_file(filename):
        if getattr(sys, 'frozen', False):
            # The application is frozen
            datadir = os.path.dirname(sys.executable)
        else:
            # The application is not frozen
            # Change this bit to match where you store your data files:
            datadir = os.path.dirname(__file__)
    
        return os.path.join(datadir, filename)
    

    You then include this path all your requests module GET and POST requests as follows:

    request.get(url, headers=headers, verify=cacertpath)
    

    Example 1

    An example code snippet would be as follows:

    # load modules
    import os
    import sys
    
    import requests
    
    # define our path finder
    
    
    def find_data_file(filename):
        if getattr(sys, 'frozen', False):
            # The application is frozen
            datadir = os.path.dirname(sys.executable)
        else:
            # The application is not frozen
            # Change this bit to match where you store your data files:
            datadir = os.path.dirname(__file__)
    
        return os.path.join(datadir, filename)
    
    
    # get our cacert path and post our GET request
    cacertpath = find_data_file('cacert.pem')
    r = requests.get('https://api.github.com/events', verify=cacertpath)
    # print the text from the request
    print(r.text)
    

    Example 2

    You can also tell requests where to find the certificate in the future by doing the following:

    os.environ["REQUESTS_CA_BUNDLE"] = cacertpath
    

    In this case, we would do the following. The advantage here is that the cacertpath does not to be explicitly defined in every module (or imported from another module) and can be defined in the environment.

    import os
    import sys
    
    import requests
    
    def find_data_file(filename):
        if getattr(sys, 'frozen', False):
            # The application is frozen
            datadir = os.path.dirname(sys.executable)
        else:
            # The application is not frozen
            # Change this bit to match where you store your data files:
            datadir = os.path.dirname(__file__)
    
        return os.path.join(datadir, filename)
    
    
    cacertpath = find_data_file('cacert.pem')
    os.environ["REQUESTS_CA_BUNDLE"] = cacertpath
    
    r = requests.get('https://api.github.com/events')
    r.text