<NME> _cpmodpy.py
<BEF> """Native adapter for serving CherryPy via mod_python

Basic usage:

##########################################
# Application in a module called myapp.py
##########################################

import cherrypy

class Root:
    @cherrypy.expose
    def index(self):
        return 'Hi there, Ho there, Hey there'


# We will use this method from the mod_python configuration
# as the entry point to our application
def setup_server():
    cherrypy.tree.mount(Root())
    cherrypy.config.update({'environment': 'production',
                            'log.screen': False,
                            'show_tracebacks': False})

##########################################
# mod_python settings for apache2
# This should reside in your httpd.conf
# or a file that will be loaded at
# apache startup
##########################################

# Start
DocumentRoot "/"
Listen 8080
LoadModule python_module /usr/lib/apache2/modules/mod_python.so

<Location "/">
    PythonPath "sys.path+['/path/to/my/application']"
    SetHandler python-program
    PythonHandler cherrypy._cpmodpy::handler
    PythonOption cherrypy.setup myapp::setup_server
    PythonDebug On
</Location>
# End

The actual path to your mod_python.so is dependent on your
environment. In this case we suppose a global mod_python
installation on a Linux distribution such as Ubuntu.

We do set the PythonPath configuration setting so that
your application can be found by from the user running
the apache2 instance. Of course if your application
resides in the global site-package this won't be needed.

Then restart apache2 and access http://127.0.0.1:8080
"""

import logging
import sys
import io

import cherrypy
from cherrypy._cpcompat import copyitems, ntob
import cherrypy
from cherrypy._cperror import format_exc, bare_error
from cherrypy.lib import httputil


# ------------------------------ Request-handling


def setup(req):
    from mod_python import apache

    # Run any setup functions defined by a "PythonOption cherrypy.setup"
    # directive.
    options = req.get_options()
    if 'cherrypy.setup' in options:
        for function in options['cherrypy.setup'].split():
            atoms = function.split('::', 1)
            if len(atoms) == 1:
                mod = __import__(atoms[0], globals(), locals())
            else:
                modname, fname = atoms
                mod = __import__(modname, globals(), locals(), [fname])
                func = getattr(mod, fname)
                func()

    cherrypy.config.update({'log.screen': False,
                            'tools.ignore_headers.on': True,
                            'tools.ignore_headers.headers': ['Range'],
                            })

    engine = cherrypy.engine
    if hasattr(engine, 'signal_handler'):
        engine.signal_handler.unsubscribe()
    if hasattr(engine, 'console_control_handler'):
        engine.console_control_handler.unsubscribe()
    engine.autoreload.unsubscribe()
    cherrypy.server.unsubscribe()

    @engine.subscribe('log')
    def _log(msg, level):
        newlevel = apache.APLOG_ERR
        if logging.DEBUG >= level:
            newlevel = apache.APLOG_DEBUG
        elif logging.INFO >= level:
            newlevel = apache.APLOG_INFO
        elif logging.WARNING >= level:
            newlevel = apache.APLOG_WARNING
        # On Windows, req.server is required or the msg will vanish. See
        # http://www.modpython.org/pipermail/mod_python/2003-October/014291.html
        # Also, "When server is not specified...LogLevel does not apply..."
        apache.log_error(msg, newlevel, req.server)

    engine.start()

    def cherrypy_cleanup(data):
        engine.exit()
    try:
        # apache.register_cleanup wasn't available until 3.1.4.
        apache.register_cleanup(cherrypy_cleanup)
    except AttributeError:
        req.server.register_cleanup(req, cherrypy_cleanup)


class _ReadOnlyRequest:
    expose = ('read', 'readline', 'readlines')

    def __init__(self, req):
        for method in self.expose:
            self.__dict__[method] = getattr(req, method)


recursive = False

_isSetUp = False


def handler(req):
    from mod_python import apache
    try:
        global _isSetUp
        if not _isSetUp:
            setup(req)
            _isSetUp = True

        # Obtain a Request object from CherryPy
        local = req.connection.local_addr
        local = httputil.Host(
            local[0], local[1], req.connection.local_host or '')
        remote = req.connection.remote_addr
        remote = httputil.Host(
            remote[0], remote[1], req.connection.remote_host or '')

        scheme = req.parsed_uri[0] or 'http'
        req.get_basic_auth_pw()

        try:
            # apache.mpm_query only became available in mod_python 3.1
            q = apache.mpm_query
            threaded = q(apache.AP_MPMQ_IS_THREADED)
            forked = q(apache.AP_MPMQ_IS_FORKED)
        except AttributeError:
            bad_value = ("You must provide a PythonOption '%s', "
                         "either 'on' or 'off', when running a version "
                         'of mod_python < 3.1')

            options = req.get_options()

            threaded = options.get('multithread', '').lower()
            if threaded == 'on':
                threaded = True
            elif threaded == 'off':
                threaded = False
            else:
                raise ValueError(bad_value % 'multithread')

            forked = options.get('multiprocess', '').lower()
            if forked == 'on':
                forked = True
            elif forked == 'off':
                forked = False
            else:
                raise ValueError(bad_value % 'multiprocess')

        sn = cherrypy.tree.script_name(req.uri or '/')
        if sn is None:
            send_response(req, '404 Not Found', [], '')
        else:
            app = cherrypy.tree.apps[sn]
            method = req.method
            path = req.uri
            qs = req.args or ''
            reqproto = req.protocol
            headers = list(req.headers_in.copy().items())
            rfile = _ReadOnlyRequest(req)
            prev = None

            try:
                redirections = []
                while True:
                    request, response = app.get_serving(local, remote, scheme,
                                                        'HTTP/1.1')
                    request.login = req.user
                    request.multithread = bool(threaded)
                    request.multiprocess = bool(forked)
                    request.app = app
                    request.prev = prev

                    # Run the CherryPy Request object and obtain the response
                    try:
                        request.run(method, path, qs, reqproto, headers, rfile)
                        break
                    except cherrypy.InternalRedirect:
                        ir = sys.exc_info()[1]
                        app.release_serving()
                        prev = request

                        if not recursive:
                            if ir.path in redirections:
                                raise RuntimeError(
                                    'InternalRedirector visited the same URL '
                                    'twice: %r' % ir.path)
                            else:
                                # Add the *previous* path_info + qs to
                                # redirections.
                                if qs:
                                    qs = '?' + qs
                                redirections.append(sn + path + qs)

                        # Munge environment and try again.
                        method = 'GET'
                        path = ir.path
                        qs = ir.query_string
                        rfile = io.BytesIO()

                send_response(
                    req, response.output_status, response.header_list,
                    response.body, response.stream)
            finally:
                app.release_serving()
    except Exception:
        tb = format_exc()
        cherrypy.log(tb, 'MOD_PYTHON', severity=logging.ERROR)
        s, h, b = bare_error()
        send_response(req, s, h, b)
    return apache.OK


def send_response(req, status, headers, body, stream=False):
    # Set response status
    req.status = int(status[:3])

    # Set response headers
    req.content_type = 'text/plain'
    for header, value in headers:
        if header.lower() == 'content-type':
            req.content_type = value
            continue
        req.headers_out.add(header, value)

    if stream:
        # Flush now so the status and headers are sent immediately.
        req.flush()

    # Set response body
    for seg in always_iterable(body):
        req.write(seg)


# --------------- Startup tools for CherryPy + mod_python --------------- #
import os
import re
try:
    import subprocess

    def popen(fullcmd):
        p = subprocess.Popen(fullcmd, shell=True,
                             stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
                             close_fds=True)
        return p.stdout
except ImportError:
    def popen(fullcmd):
        pipein, pipeout = os.popen4(fullcmd)
        return pipeout


def read_process(cmd, args=''):
    fullcmd = '%s %s' % (cmd, args)
    pipeout = popen(fullcmd)
    try:
        firstline = pipeout.readline()
        cmd_not_found = re.search(
            b'(not recognized|No such file|not found)',
            firstline,
            re.IGNORECASE
        )
        if cmd_not_found:
            raise IOError('%s must be on your system path.' % cmd)
        output = firstline + pipeout.read()
    finally:
        pipeout.close()
    return output


class ModPythonServer(object):

    template = """
# Apache2 server configuration file for running CherryPy with mod_python.

DocumentRoot "/"
Listen %(port)s
LoadModule python_module modules/mod_python.so

<Location %(loc)s>
    SetHandler python-program
    PythonHandler %(handler)s
    PythonDebug On
%(opts)s
</Location>
"""

    def __init__(self, loc='/', port=80, opts=None, apache_path='apache',
                 handler='cherrypy._cpmodpy::handler'):
        self.loc = loc
        self.port = port
        self.opts = opts
        self.apache_path = apache_path
        self.handler = handler

    def start(self):
        opts = ''.join(['    PythonOption %s %s\n' % (k, v)
                        for k, v in self.opts])
        conf_data = self.template % {'port': self.port,
                                     'loc': self.loc,
                                     'opts': opts,
                                     'handler': self.handler,
                                     }

        mpconf = os.path.join(os.path.dirname(__file__), 'cpmodpy.conf')
        with open(mpconf, 'wb') as f:
            f.write(conf_data)

        response = read_process(self.apache_path, '-k start -f %s' % mpconf)
        self.ready = True
        return response

    def stop(self):
        os.popen('apache -k stop')
        self.ready = False

<MSG> Fix E402 in _cpmodpy

<DFF> @@ -55,9 +55,11 @@ resides in the global site-package this won't be needed.
 Then restart apache2 and access http://127.0.0.1:8080
 """
 
+import io
 import logging
+import os
+import re
 import sys
-import io
 
 import cherrypy
 from cherrypy._cpcompat import copyitems, ntob
@@ -270,8 +272,6 @@ def send_response(req, status, headers, body, stream=False):
 
 
 # --------------- Startup tools for CherryPy + mod_python --------------- #
-import os
-import re
 try:
     import subprocess
 

{'commit': 'diff --git a/cherrypy/_cpmodpy.py b/cherrypy/_cpmodpy.py\nindex 1f80093f..b5c121ed 100644\n--- a/cherrypy/_cpmodpy.py\n+++ b/cherrypy/_cpmodpy.py\n@@ -55,9 +55,11 @@ resides in the global site-package this won\'t be needed.\n Then restart apache2 and access http://127.0.0.1:8080\n """\n \n+import io\n import logging\n+import os\n+import re\n import sys\n-import io\n \n import cherrypy\n from cherrypy._cpcompat import copyitems, ntob\n@@ -270,8 +272,6 @@ def send_response(req, status, headers, body, stream=False):\n \n \n # --------------- Startup tools for CherryPy + mod_python --------------- #\n-import os\n-import re\n try:\n     import subprocess\n \n', 'commit_subject': 'Fix E402 in _cpmodpy', 'repo_name': 'cherrypy/cherrypy', 'committer': {'name': 'Sviatoslav Sydorenko', 'email': '13bd1fc5137f503b0a8355ad400e7a87ebb45b25@sydorenko.org.ua', 'time_sec': '1473325714', 'tz_offset': '180', 'date': {'seconds': '1473325714'}}, 'commit_tree': '1e3a72ce3b19044338224f0fb333ec953a2f44df', 'commit_message': 'Fix E402 in _cpmodpy\n', 'language_name': 'HTML', 'license': 'bsd-3-clause', 'hunks': '@@ -55,9 +55,11 @@ resides in the global site-package this won\'t be needed.\n Then restart apache2 and access http://127.0.0.1:8080\n """\n \n+import io\n import logging\n+import os\n+import re\n import sys\n-import io\n \n import cherrypy\n from cherrypy._cpcompat import copyitems, ntob\n@@ -270,8 +272,6 @@ def send_response(req, status, headers, body, stream=False):\n \n \n # --------------- Startup tools for CherryPy + mod_python --------------- #\n-import os\n-import re\n try:\n     import subprocess\n \n', 'addition_count': 3, 'deletion_count': 3, 'src_file': 'a/cherrypy/_cpmodpy.py', 'tgt_file': 'b/cherrypy/_cpmodpy.py', 'file_extension': '.py', 'before_file': '"""Native adapter for serving CherryPy via mod_python\n\nBasic usage:\n\n##########################################\n# Application in a module called myapp.py\n##########################################\n\nimport cherrypy\n\nclass Root:\n    @cherrypy.expose\n    def index(self):\n        return \'Hi there, Ho there, Hey there\'\n\n\n# We will use this method from the mod_python configuration\n# as the entry point to our application\ndef setup_server():\n    cherrypy.tree.mount(Root())\n    cherrypy.config.update({\'environment\': \'production\',\n                            \'log.screen\': False,\n                            \'show_tracebacks\': False})\n\n##########################################\n# mod_python settings for apache2\n# This should reside in your httpd.conf\n# or a file that will be loaded at\n# apache startup\n##########################################\n\n# Start\nDocumentRoot "/"\nListen 8080\nLoadModule python_module /usr/lib/apache2/modules/mod_python.so\n\n<Location "/">\n    PythonPath "sys.path+[\'/path/to/my/application\']"\n    SetHandler python-program\n    PythonHandler cherrypy._cpmodpy::handler\n    PythonOption cherrypy.setup myapp::setup_server\n    PythonDebug On\n</Location>\n# End\n\nThe actual path to your mod_python.so is dependent on your\nenvironment. In this case we suppose a global mod_python\ninstallation on a Linux distribution such as Ubuntu.\n\nWe do set the PythonPath configuration setting so that\nyour application can be found by from the user running\nthe apache2 instance. Of course if your application\nresides in the global site-package this won\'t be needed.\n\nThen restart apache2 and access http://127.0.0.1:8080\n"""\n\nimport logging\nimport sys\nimport io\n\nimport cherrypy\nfrom cherrypy._cpcompat import copyitems, ntob\nimport cherrypy\nfrom cherrypy._cperror import format_exc, bare_error\nfrom cherrypy.lib import httputil\n\n\n# ------------------------------ Request-handling\n\n\ndef setup(req):\n    from mod_python import apache\n\n    # Run any setup functions defined by a "PythonOption cherrypy.setup"\n    # directive.\n    options = req.get_options()\n    if \'cherrypy.setup\' in options:\n        for function in options[\'cherrypy.setup\'].split():\n            atoms = function.split(\'::\', 1)\n            if len(atoms) == 1:\n                mod = __import__(atoms[0], globals(), locals())\n            else:\n                modname, fname = atoms\n                mod = __import__(modname, globals(), locals(), [fname])\n                func = getattr(mod, fname)\n                func()\n\n    cherrypy.config.update({\'log.screen\': False,\n                            \'tools.ignore_headers.on\': True,\n                            \'tools.ignore_headers.headers\': [\'Range\'],\n                            })\n\n    engine = cherrypy.engine\n    if hasattr(engine, \'signal_handler\'):\n        engine.signal_handler.unsubscribe()\n    if hasattr(engine, \'console_control_handler\'):\n        engine.console_control_handler.unsubscribe()\n    engine.autoreload.unsubscribe()\n    cherrypy.server.unsubscribe()\n\n    @engine.subscribe(\'log\')\n    def _log(msg, level):\n        newlevel = apache.APLOG_ERR\n        if logging.DEBUG >= level:\n            newlevel = apache.APLOG_DEBUG\n        elif logging.INFO >= level:\n            newlevel = apache.APLOG_INFO\n        elif logging.WARNING >= level:\n            newlevel = apache.APLOG_WARNING\n        # On Windows, req.server is required or the msg will vanish. See\n        # http://www.modpython.org/pipermail/mod_python/2003-October/014291.html\n        # Also, "When server is not specified...LogLevel does not apply..."\n        apache.log_error(msg, newlevel, req.server)\n\n    engine.start()\n\n    def cherrypy_cleanup(data):\n        engine.exit()\n    try:\n        # apache.register_cleanup wasn\'t available until 3.1.4.\n        apache.register_cleanup(cherrypy_cleanup)\n    except AttributeError:\n        req.server.register_cleanup(req, cherrypy_cleanup)\n\n\nclass _ReadOnlyRequest:\n    expose = (\'read\', \'readline\', \'readlines\')\n\n    def __init__(self, req):\n        for method in self.expose:\n            self.__dict__[method] = getattr(req, method)\n\n\nrecursive = False\n\n_isSetUp = False\n\n\ndef handler(req):\n    from mod_python import apache\n    try:\n        global _isSetUp\n        if not _isSetUp:\n            setup(req)\n            _isSetUp = True\n\n        # Obtain a Request object from CherryPy\n        local = req.connection.local_addr\n        local = httputil.Host(\n            local[0], local[1], req.connection.local_host or \'\')\n        remote = req.connection.remote_addr\n        remote = httputil.Host(\n            remote[0], remote[1], req.connection.remote_host or \'\')\n\n        scheme = req.parsed_uri[0] or \'http\'\n        req.get_basic_auth_pw()\n\n        try:\n            # apache.mpm_query only became available in mod_python 3.1\n            q = apache.mpm_query\n            threaded = q(apache.AP_MPMQ_IS_THREADED)\n            forked = q(apache.AP_MPMQ_IS_FORKED)\n        except AttributeError:\n            bad_value = ("You must provide a PythonOption \'%s\', "\n                         "either \'on\' or \'off\', when running a version "\n                         \'of mod_python < 3.1\')\n\n            options = req.get_options()\n\n            threaded = options.get(\'multithread\', \'\').lower()\n            if threaded == \'on\':\n                threaded = True\n            elif threaded == \'off\':\n                threaded = False\n            else:\n                raise ValueError(bad_value % \'multithread\')\n\n            forked = options.get(\'multiprocess\', \'\').lower()\n            if forked == \'on\':\n                forked = True\n            elif forked == \'off\':\n                forked = False\n            else:\n                raise ValueError(bad_value % \'multiprocess\')\n\n        sn = cherrypy.tree.script_name(req.uri or \'/\')\n        if sn is None:\n            send_response(req, \'404 Not Found\', [], \'\')\n        else:\n            app = cherrypy.tree.apps[sn]\n            method = req.method\n            path = req.uri\n            qs = req.args or \'\'\n            reqproto = req.protocol\n            headers = list(req.headers_in.copy().items())\n            rfile = _ReadOnlyRequest(req)\n            prev = None\n\n            try:\n                redirections = []\n                while True:\n                    request, response = app.get_serving(local, remote, scheme,\n                                                        \'HTTP/1.1\')\n                    request.login = req.user\n                    request.multithread = bool(threaded)\n                    request.multiprocess = bool(forked)\n                    request.app = app\n                    request.prev = prev\n\n                    # Run the CherryPy Request object and obtain the response\n                    try:\n                        request.run(method, path, qs, reqproto, headers, rfile)\n                        break\n                    except cherrypy.InternalRedirect:\n                        ir = sys.exc_info()[1]\n                        app.release_serving()\n                        prev = request\n\n                        if not recursive:\n                            if ir.path in redirections:\n                                raise RuntimeError(\n                                    \'InternalRedirector visited the same URL \'\n                                    \'twice: %r\' % ir.path)\n                            else:\n                                # Add the *previous* path_info + qs to\n                                # redirections.\n                                if qs:\n                                    qs = \'?\' + qs\n                                redirections.append(sn + path + qs)\n\n                        # Munge environment and try again.\n                        method = \'GET\'\n                        path = ir.path\n                        qs = ir.query_string\n                        rfile = io.BytesIO()\n\n                send_response(\n                    req, response.output_status, response.header_list,\n                    response.body, response.stream)\n            finally:\n                app.release_serving()\n    except Exception:\n        tb = format_exc()\n        cherrypy.log(tb, \'MOD_PYTHON\', severity=logging.ERROR)\n        s, h, b = bare_error()\n        send_response(req, s, h, b)\n    return apache.OK\n\n\ndef send_response(req, status, headers, body, stream=False):\n    # Set response status\n    req.status = int(status[:3])\n\n    # Set response headers\n    req.content_type = \'text/plain\'\n    for header, value in headers:\n        if header.lower() == \'content-type\':\n            req.content_type = value\n            continue\n        req.headers_out.add(header, value)\n\n    if stream:\n        # Flush now so the status and headers are sent immediately.\n        req.flush()\n\n    # Set response body\n    for seg in always_iterable(body):\n        req.write(seg)\n\n\n# --------------- Startup tools for CherryPy + mod_python --------------- #\nimport os\nimport re\ntry:\n    import subprocess\n\n    def popen(fullcmd):\n        p = subprocess.Popen(fullcmd, shell=True,\n                             stdout=subprocess.PIPE, stderr=subprocess.STDOUT,\n                             close_fds=True)\n        return p.stdout\nexcept ImportError:\n    def popen(fullcmd):\n        pipein, pipeout = os.popen4(fullcmd)\n        return pipeout\n\n\ndef read_process(cmd, args=\'\'):\n    fullcmd = \'%s %s\' % (cmd, args)\n    pipeout = popen(fullcmd)\n    try:\n        firstline = pipeout.readline()\n        cmd_not_found = re.search(\n            b\'(not recognized|No such file|not found)\',\n            firstline,\n            re.IGNORECASE\n        )\n        if cmd_not_found:\n            raise IOError(\'%s must be on your system path.\' % cmd)\n        output = firstline + pipeout.read()\n    finally:\n        pipeout.close()\n    return output\n\n\nclass ModPythonServer(object):\n\n    template = """\n# Apache2 server configuration file for running CherryPy with mod_python.\n\nDocumentRoot "/"\nListen %(port)s\nLoadModule python_module modules/mod_python.so\n\n<Location %(loc)s>\n    SetHandler python-program\n    PythonHandler %(handler)s\n    PythonDebug On\n%(opts)s\n</Location>\n"""\n\n    def __init__(self, loc=\'/\', port=80, opts=None, apache_path=\'apache\',\n                 handler=\'cherrypy._cpmodpy::handler\'):\n        self.loc = loc\n        self.port = port\n        self.opts = opts\n        self.apache_path = apache_path\n        self.handler = handler\n\n    def start(self):\n        opts = \'\'.join([\'    PythonOption %s %s\\n\' % (k, v)\n                        for k, v in self.opts])\n        conf_data = self.template % {\'port\': self.port,\n                                     \'loc\': self.loc,\n                                     \'opts\': opts,\n                                     \'handler\': self.handler,\n                                     }\n\n        mpconf = os.path.join(os.path.dirname(__file__), \'cpmodpy.conf\')\n        with open(mpconf, \'wb\') as f:\n            f.write(conf_data)\n\n        response = read_process(self.apache_path, \'-k start -f %s\' % mpconf)\n        self.ready = True\n        return response\n\n    def stop(self):\n        os.popen(\'apache -k stop\')\n        self.ready = False\n', 'message': 'Fix E402 in _cpmodpy\n', 'author_name': 'Sviatoslav Sydorenko', 'lang': 'py'}