Search code examples
pythonpython-requestscx-freeze

python requests and cx_freeze


I am trying to freeze a python app that depends on requests, but I am getting the following error:

Traceback (most recent call last):
  File "c:\Python33\lib\site-packages\requests\packages\urllib3\util.py", line 630, in ssl_wrap_socket
    context.load_verify_locations(ca_certs)
FileNotFoundError: [Errno 2] No such file or directory

Looks like it is having trouble finding the ssl certificate with the executable. I found this which seems to be the same problem, but I am not able to figure out how they got it to work. The main problem seems to be that the certificate bundled by requests is not copied over to the compressed library. So it seems that I will have to force cx_freeze to bundle the certificates and then point to it from my script.

Starting with this simple script everything works fine:

import requests
r = requests.get("https://yourapihere.com")
print(r.json())

Then if I add the certificate file I stat getting errors:

import requests
r = requests.get("https://yourapihere.com", cert=requests.certs.where())
print(r.json())

-

Traceback (most recent call last):
  File "c:\Python33\lib\site-packages\requests\packages\urllib3\connectionpool.py", line 480, in urlopen
    body=body, headers=headers)
  File "c:\Python33\lib\site-packages\requests\packages\urllib3\connectionpool.py", line 285, in _make_request
    conn.request(method, url, **httplib_request_kw)
  File "c:\Python33\lib\http\client.py", line 1065, in request
    self._send_request(method, url, body, headers)
  File "c:\Python33\lib\http\client.py", line 1103, in _send_request
    self.endheaders(body)
  File "c:\Python33\lib\http\client.py", line 1061, in endheaders
    self._send_output(message_body)
  File "c:\Python33\lib\http\client.py", line 906, in _send_output
    self.send(msg)
  File "c:\Python33\lib\http\client.py", line 844, in send
    self.connect()
  File "c:\Python33\lib\site-packages\requests\packages\urllib3\connection.py", line 164, in connect
    ssl_version=resolved_ssl_version)
  File "c:\Python33\lib\site-packages\requests\packages\urllib3\util.py", line 637, in ssl_wrap_socket
    context.load_cert_chain(certfile, keyfile)
ssl.SSLError: [SSL] PEM lib (_ssl.c:2155)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "c:\Python33\lib\site-packages\requests\adapters.py", line 330, in send
    timeout=timeout
  File "c:\Python33\lib\site-packages\requests\packages\urllib3\connectionpool.py", line 504, in urlopen
    raise SSLError(e)
requests.packages.urllib3.exceptions.SSLError: [SSL] PEM lib (_ssl.c:2155)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "example.py", line 10, in <module>
    r = requests.get("https://yourapihere.com", cert=requests.certs.where())
  File "c:\Python33\lib\site-packages\requests\api.py", line 55, in get
    return request('get', url, **kwargs)
  File "c:\Python33\lib\site-packages\requests\api.py", line 44, in request
    return session.request(method=method, url=url, **kwargs)
  File "c:\Python33\lib\site-packages\requests\sessions.py", line 383, in request
    resp = self.send(prep, **send_kwargs)
  File "c:\Python33\lib\site-packages\requests\sessions.py", line 486, in send
    r = adapter.send(request, **kwargs)
  File "c:\Python33\lib\site-packages\requests\adapters.py", line 385, in send
    raise SSLError(e)
requests.exceptions.SSLError: [SSL] PEM lib (_ssl.c:2155)

I guess I am using it correctly, but cant really figure out why it is not working. I guess that after fixing this I can continue and add the certificate to the cx_freeze bundle, something like:

example.py:

import os
import requests

cert = os.path.join(os.path.dirname(requests.__file__),'cacert.pem')
r = requests.get("https://yourapihere.com", cert=cert)
print(r.json())

setup.py:

from cx_Freeze import setup, Executable

import requests.certs
build_exe_options = {"zip_includes":[(requests.certs.where(),'requests/cacert.pem')]}

executables = [
    Executable('example.py')
]

setup(
      executables=executables
      )

if someone could give me a tip it would be much appreciated.


Solution

  • Steps to get this to work:

    • Explicitly tell requests where the certificate are
    • Tell cx_freeze to also get the certificate file when "building"
    • have the code to use the proper certificate file when frozen

    test.py:

    import os
    import sys
    
    import requests
    
    # sets the path to the certificate file
    if getattr(sys, 'frozen', False):
        # if frozen, get embeded file
        cacert = os.path.join(os.path.dirname(sys.executable), 'cacert.pem')
    else:
        # else just get the default file
        cacert = requests.certs.where()
    
    # remember to use the verify to set the certificate to be used
    # I guess it could also work with REQUESTS_CA_BUNDLE, but I have not tried
    r = requests.get('https://www.google.com', verify=cacert)
    
    print(r)
    

    setup.py:

    from cx_Freeze import setup, Executable
    import requests
    import sys
    
    executable = Executable( script = "test.py" )
    
    # Add certificate to the build
    options = {
        "build_exe": {
            'include_files' : [(requests.certs.where(), 'cacert.pem')]
        }
    }
    
    setup(
        version = "0",
        requires = ["requests"],
        options = options,
        executables = [executable]
    )
    

    to build it, just:

    $ python setup.py build
    

    If successful you should see:

    $ test
    <Response [200]>