Frak
Frak

Reputation: 865

How do I re-write this python code to work within a pyinstaller generated exe?

PyInstaller 3.4
Python 3.7
Win 7-64 bit

I made a .exe from python code using PyInstaller that has the following logic. The goal is to launch the http server in a new console window on Windows. This is to avoid locking up/blocking the main application and the user can just close the newly launched console window when they are finished with the http server.

p = subprocess.call('start "" python  -m http.server --directory {} {} --bind {}'.format(web_server_path, web_server_port, web_bind_ip), shell=True)

This works on my windows PC that has Python installed but naturally does not work on a PC without Python.

Since the goal is to distribute this .exe to people without a python installation, can someone recommend the correct method to accomplish the above behavior using Python within PyInstaller? I know that a PyInstaller generated .exe is essentially running a Python interpreter but I don't know how to tap into it with my code.

UPDATE
It is a requirement that the user be able to set the directory, port, and address. It should also be easy to stop ideally by just closing a window (hence the original command line method) but that is open to change.

FURTHER  UPDATE
Is it possible to load the python3.dll and use its function directly somehow? ctypes.WinDLL('Path:\to\my.dll')

Upvotes: 1

Views: 233

Answers (1)

Grismar
Grismar

Reputation: 31379

Since your example code would presumably start the web server without blocking the further execution of your script, you'd want to run the web server on a separate thread.

from sys import version_info
import http.server
import socketserver
from threading import Thread


class WebServer(Thread):
    @staticmethod
    def get_directory_simple_http_request_handler(directory):
        _current_only = version_info < (3, 7)

        class _DirectorySimpleHTTPRequestHandler(http.server.SimpleHTTPRequestHandler):
            _current_only = version_info < (3, 7)

            def __init__(self, *args, **kwargs):
                if not self._current_only:
                    kwargs['directory'] = directory
                super(_DirectorySimpleHTTPRequestHandler, self).__init__(*args, **kwargs)

            @property
            def current_only(self):
                return self._current_only

        return _DirectorySimpleHTTPRequestHandler

    def __init__(self, address='127.0.0.1', port=8000, directory=''):
        self.handler = self.get_directory_simple_http_request_handler(directory)
        self.httpd = socketserver.TCPServer((address, port), self.handler)
        Thread.__init__(self)

    def run(self):
        self.httpd.serve_forever()

    def join(self, *args, **kwargs):
        self.httpd.shutdown()
        super().join(*args, **kwargs)


web_server = None
while True:
    if web_server is None:
        print('Currently not running (type start or exit).')
    else:
        print('Running on:', web_server.httpd.server_address, '(type stop or exit)')
        if web_server.handler.current_only:
            print('Serving current directory, Python <3.7 has no `directory` on SimpleHTTPRequestHandler.')

    command = input('>')

    if command == 'start':
        if web_server is None:
            web_server = WebServer(directory=r'C:\Temp')
            web_server.start()
    if command == 'stop':
        if web_server is not None:
            web_server.join()
            web_server = None
    if command == 'exit':
        exit(0)

Note that Python 3.7 is the first version to support the directory parameter, earlier versions of Python will only serve the current directory for the process.

The factory static method is there to set up a separate instance of the SimpleHTTPRequestHandler which gets initialised with the desired directory.

If you prefer to actually just launch a console window, running the Python (test) web server (or another external server, if Python is not available):

import time
from subprocess import Popen, CREATE_NEW_CONSOLE

proc = Popen(['python', '-m', 'http.server', '8000'],
             cwd=r'C:\temp',
             creationflags=CREATE_NEW_CONSOLE, close_fds=True)

while True:
    print('Running')
    time.sleep(2)

Similarly, if you like it quiet, but don't need access to the web server object, this also works:

import time
from subprocess import Popen

proc = Popen(['python', '-m', 'http.server', '8000'], close_fds=True, cwd=r'C:\temp')

while True:
    print('Running')
    time.sleep(2)

Upvotes: 1

Related Questions