# Copyright (C) 2012- Takafumi Arakaki # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . import sys import logging from .py3compat import SocketServer from .utils import autolog, deprecated from .core import EPCCore from .handler import EPCHandler, ThreadingEPCHandler @deprecated def setuplogfile(logger=None, filename='python-epc.log'): if logger is None: from .core import _logger as logger ch = logging.FileHandler(filename=filename, mode='w') ch.setLevel(logging.DEBUG) logger.addHandler(ch) class EPCClientManager: # This class will be mixed with `SocketServer.TCPServer`, # which is an old style class. def __init__(self): self.clients = [] """ A list of :class:`EPCHandler` object for connected clients. """ def add_client(self, handler): self.clients.append(handler) self.handle_client_connect(handler) def remove_client(self, handler): self.clients.remove(handler) self.handle_client_disconnect(handler) def handle_client_connect(self, handler): """ Handler which is called with a newly connected `client`. :type handler: :class:`EPCHandler` :arg handler: Object for handling request from the client. Default implementation does nothing. """ def handle_client_disconnect(self, handler): """ Handler which is called with a disconnected `client`. :type handler: :class:`EPCHandler` :arg handler: Object for handling request from the client. Default implementation does nothing. """ class EPCServer(SocketServer.TCPServer, EPCClientManager, EPCCore): """ A server class to publish functions and call functions via EPC protocol. To publish Python functions, all you need is :meth:`register_function`, :meth:`print_port` and :meth:`serve_forever() `. >>> server = EPCServer(('localhost', 0)) >>> def echo(*a): ... return a >>> server.register_function(echo) #doctest: +ELLIPSIS >>> server.print_port() #doctest: +SKIP 9999 >>> server.serve_forever() #doctest: +SKIP To call client's method, use :attr:`clients ` attribute to get client handler and use its :meth:`EPCHandler.call` and :meth:`EPCHandler.methods` methods to communicate with connected client. >>> handler = server.clients[0] #doctest: +SKIP >>> def callback(reply): ... print(reply) >>> handler.call('method_name', ['arg-1', 'arg-2', 'arg-3'], ... callback) #doctest: +SKIP See :class:`SocketServer.TCPServer` and :class:`SocketServer.BaseServer` for other usable methods. """ def __init__(self, server_address, RequestHandlerClass=EPCHandler, bind_and_activate=True, debugger=None, log_traceback=False): # `BaseServer` (super class of `SocketServer`) will set # `RequestHandlerClass` to the attribute `self.RequestHandlerClass`. # This class is initialize in `BaseServer.finish_request` by # `self.RequestHandlerClass(request, client_address, self)`. SocketServer.TCPServer.__init__( self, server_address, RequestHandlerClass, bind_and_activate) EPCClientManager.__init__(self) EPCCore.__init__(self, debugger, log_traceback) self.logger.debug('-' * 75) self.logger.debug( "EPCServer is initialized: server_address = %r", self.server_address) @autolog('debug') def handle_error(self, request, client_address): self.logger.error('handle_error: trying to get traceback.format_exc') try: import traceback self.logger.error('handle_error: \n%s', traceback.format_exc()) except: self.logger.error('handle_error: OOPS') def print_port(self, stream=sys.stdout): """ Print port this EPC server runs on. As Emacs client reads port number from STDOUT, you need to call this just before calling :meth:`serve_forever`. :type stream: text stream :arg stream: A stream object to write port on. Default is :data:`sys.stdout`. """ stream.write(str(self.server_address[1])) stream.write("\n") stream.flush() class ThreadingEPCServer(SocketServer.ThreadingMixIn, EPCServer): """ Class :class:`EPCServer` mixed with :class:`SocketServer.ThreadingMixIn`. Use this class when combining EPCServer with other Python module which has event loop, such as GUI modules. For example, see `examples/gtk/server.py`_ for how to use this class with GTK .. _examples/gtk/server.py: https://github.com/tkf/python-epc/blob/master/examples/gtk/server.py """ def __init__(self, *args, **kwds): kwds.update(RequestHandlerClass=ThreadingEPCHandler) EPCServer.__init__(self, *args, **kwds) def main(args=None): """ Quick CLI to serve Python functions in a module. Example usage:: python -m epc.server --allow-dotted-names os Note that only the functions which gets and returns simple built-in types (str, int, float, list, tuple, dict) works. """ import argparse from textwrap import dedent parser = argparse.ArgumentParser( formatter_class=type('EPCHelpFormatter', (argparse.ArgumentDefaultsHelpFormatter, argparse.RawDescriptionHelpFormatter), {}), description=dedent(main.__doc__)) parser.add_argument( 'module', help='Serve python functions in this module.') parser.add_argument( '--address', default='localhost', help='server address') parser.add_argument( '--port', default=0, type=int, help='server port. 0 means to pick up random port.') parser.add_argument( '--allow-dotted-names', default=False, action='store_true') parser.add_argument( '--pdb', dest='debugger', const='pdb', action='store_const', help='start pdb when error occurs.') parser.add_argument( '--ipdb', dest='debugger', const='ipdb', action='store_const', help='start ipdb when error occurs.') parser.add_argument( '--log-traceback', action='store_true', default=False) ns = parser.parse_args(args) server = EPCServer((ns.address, ns.port), debugger=ns.debugger, log_traceback=ns.log_traceback) server.register_instance( __import__(ns.module), allow_dotted_names=ns.allow_dotted_names) server.print_port() server.serve_forever() if __name__ == '__main__': main()