Source code for msl.loadlib.start_server32

"""
This module is built in to a 32-bit executable by running :mod:`.freeze_server32`.

The executable is used to host a 32-bit library, :class:`~.server32.Server32`,
so that a module running in a 64-bit Python interpreter, :class:`~.client64.Client64`,
can communicate with the library. This client-server exchange of information is a
form of `inter-process communication <ipc_>`_.

.. _ipc: https://en.wikipedia.org/wiki/Inter-process_communication
"""
from __future__ import print_function

import argparse
import code
import importlib
import inspect
import os
import sys
import tempfile
import traceback

from msl.loadlib import SERVER_FILENAME
from msl.loadlib import Server32


[docs]def main(): """Starts a 32-bit server (which is a subclass of :class:`~.server32.Server32`). Parses the command-line arguments to run a Python module on a 32-bit server to host a 32-bit library. To see the list of command-line arguments that are allowed, run the executable with the ``--help`` flag (or click here_ to view the source code of the :class:`argparse.ArgumentParser` implementation). .. _here: https://msl-loadlib.readthedocs.io/en/stable/_modules/msl/loadlib/start_server32.html#main """ parser = argparse.ArgumentParser( description='Starts a 32-bit Python interpreter which allows for inter-process communication' ' via a client-server protocol -- i.e., calling a 32-bit process (server) from a' ' 64-bit process (client).', ) parser.add_argument('-m', '--module', default=None, help='the name of the Python module to run on the 32-bit ' 'server (the module must contain a class that is a ' 'subclass of msl.loadlib.Server32)') parser.add_argument('-s', '--append-sys-path', default=None, help=r'append path(s) to the sys.path variable on the 32-bit ' r'server, e.g., -s D:\path\to\my_scripts, or for ' r'multiple paths separate each path by a semi-colon, ' r'e.g., -s D:\path\to\my_scripts;D:\lib') parser.add_argument('-e', '--append-environ-path', default=None, help=r"append path(s) to the os.environ['PATH'] variable on " r"the 32-bit server, e.g., -e D:\code\bin, or for " r"multiple paths separate each path by a semi-colon, " r"e.g., -e D:\code\bin;D:\lib") parser.add_argument('-H', '--host', default='127.0.0.1', help='the IP address of the host [default: 127.0.0.1]') parser.add_argument('-p', '--port', default=8080, help='the port to open on the host [default: 8080]') parser.add_argument('-v', '--version', action='store_true', help='show the Python version that the server is running on ' 'and exit') parser.add_argument('-i', '--interactive', action='store_true', help='start a 32-bit interactive Python console and exit') parser.add_argument('-k', '--kwargs', default=None, help='keyword arguments that are passed to the constructor ' 'of the msl.loadlib.Server32 subclass as "key=value;" ' 'pairs, e.g., -k a=-2;b=3.14;c=whatever;d=[1,2,3]') parser.add_argument('-q', '--quiet', action='store_true', help='ignored and will be removed in a future release') args = parser.parse_args() if args.version: print('Python ' + sys.version) return 0 # include directories in sys.path sys.path.append(os.path.abspath('.')) if args.module is not None and os.path.dirname(args.module): sys.path.append(os.path.dirname(args.module)) if args.append_sys_path is not None: for path in args.append_sys_path.split(';'): if path: sys.path.append(os.path.abspath(path)) # include directories in os.environ['PATH'] if args.append_environ_path is not None: for path in args.append_environ_path.split(';'): if path: os.environ['PATH'] += os.pathsep + os.path.abspath(path) if args.interactive: globals().update({'exit': sys.exit, 'quit': sys.exit}) console = code.InteractiveConsole(locals=dict(globals(), **locals())) banner = 'Python ' + sys.version banner += '\nType exit() or quit() or <CTRL+Z then Enter> to terminate the console.' console.interact(banner=banner) return 0 # build the keyword-argument dictionary kwargs = {} if args.kwargs is not None: for item in args.kwargs.split(';'): item_split = item.split('=') if len(item_split) == 1: key = item_split[0].strip() value = '' else: key = item_split[0].strip() value = item_split[1].strip() if len(key) > 0: kwargs[key] = value if args.quiet: print('DeprecationWarning: the --quiet flag is ignored and will be removed in a future release') # if you get to this point in the script that means you want to start a server for # inter-process communication and therefore args.module must have a value if not args.module: err = 'You must specify a Python module to run on the 32-bit server.\n' \ 'For example: {} -m my_module\n' \ 'Cannot start the 32-bit server.'.format(SERVER_FILENAME) print(err, file=sys.stderr) return -1 args.module = os.path.basename(args.module) if args.module.endswith('.py'): args.module = args.module[:-3] if args.module.startswith('.'): err = 'ImportError: {}\n' \ 'Cannot perform relative imports.\n' \ 'Cannot start the 32-bit server.'.format(args.module) print(err, file=sys.stderr) return -1 f = os.path.join(tempfile.gettempdir(), 'msl-loadlib-{}-{}.txt'.format(args.host, args.port)) with open(f, mode='wt') as fp: fp.write('{}\n{}'.format(os.getpid(), sys._MEIPASS)) try: mod = importlib.import_module(args.module) except ImportError as e: # ignore the folders from the unfrozen application paths = '\n '.join(item for item in sys.path if not item.startswith(sys._MEIPASS)) err = 'ImportError: {}\n' \ 'The missing module must be in sys.path (see the --append-sys-path option)\n' \ 'The paths in sys.path are:\n {}\n\n' \ 'Cannot start the 32-bit server.'.format(e, paths) print(err, file=sys.stderr) return -1 except Exception: err = 'Importing {!r} on the 32-bit server raised the following exception:\n\n' \ '{}\nCannot start the 32-bit server.'.format(args.module, traceback.format_exc()) print(err, file=sys.stderr) return -1 # ensure that there is a subclass of Server32 in the module cls = None for name, obj in inspect.getmembers(mod, inspect.isclass): if name != 'Server32' and issubclass(obj, Server32): cls = obj break if cls is None: err = 'AttributeError: module {}.py\n' \ 'Module does not contain a class that is a subclass of Server32.\n' \ 'Cannot start the 32-bit server.'.format(args.module) print(err, file=sys.stderr) return -1 server, error, tb = None, None, None try: server = cls(args.host, args.port, **kwargs) except Exception as e: # support the old syntax where the Server32 required a 'quiet' argument if "missing 1 required positional argument: 'quiet'" in str(e): try: server = cls(args.host, args.port, True, **kwargs) except Exception as e: error = e tb = traceback.format_exc() else: error = e tb = traceback.format_exc() if error is not None: err = 'Instantiating {!r} raised the following exception:\n\n{}\n'.format(cls.__name__, tb) if error.__class__.__name__ == 'TypeError' and '__init__' in str(error): err += 'Check that the {0!r} class is defined with the following syntax\n\n' \ 'class {0}(Server32):\n' \ ' def __init__(self, host, port, **kwargs):\n' \ ' super({0}, self).__init__(path, libtype, host, port, **kwargs)\n\n'.format(cls.__name__) err += 'Cannot start the 32-bit server.' print(err, file=sys.stderr) return -1 if not hasattr(server, '_library'): err = 'The super() function was never called in the Server32 subclass.\n' \ 'Check that the {0!r} class is defined with the following syntax\n\n' \ 'class {0}(Server32):\n' \ ' def __init__(self, host, port, **kwargs):\n' \ ' super({0}, self).__init__(path, libtype, host, port, **kwargs)\n\n' \ 'Cannot start the 32-bit server.'.format(cls.__name__) print(err, file=sys.stderr) return -1 try: server.server_bind() server.server_activate() server.serve_forever() except (SystemExit, KeyboardInterrupt): pass except Exception: # Can only get here if starting the HTTPServer raised an exception. # Error handling for a request is handled by the RequestHandler class. print('Binding, activating and starting the HTTPServer raised the following exception\n' '{}'.format(traceback.format_exc()), file=sys.stderr) return -1 finally: server.server_close()
if __name__ == '__main__': sys.exit(main())