<commit_before>import errno
import os
import sys
import time
import traceback

from eventlet.green import urllib
from eventlet.green import socket
from eventlet.green import BaseHTTPServer
from eventlet.pool import Pool

import greenio

DEFAULT_MAX_SIMULTANEOUS_REQUESTS = 1024
DEFAULT_MAX_HTTP_VERSION = 'HTTP/1.1'
MAX_REQUEST_LINE = 8192
MINIMUM_CHUNK_SIZE = 4096

# Weekday and month names for HTTP date/time formatting; always English!
_weekdayname = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
_monthname = [None, # Dummy so we can use 1-based month numbers
              "Jan", "Feb", "Mar", "Apr", "May", "Jun",
              "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]

def format_date_time(timestamp):
    year, month, day, hh, mm, ss, wd, y, z = time.gmtime(timestamp)
    return "%s, %02d %3s %4d %02d:%02d:%02d GMT" % (
        _weekdayname[wd], day, _monthname[month], year, hh, mm, ss
    )

class Input(object):
    def __init__(self,
                 rfile,
                 content_length,
                 wfile=None,
                 wfile_line=None,
                 chunked_input=False):

        self.rfile = rfile
        if content_length is not None:
            content_length = int(content_length)
        self.content_length = content_length

        self.wfile = wfile
        self.wfile_line = wfile_line

        self.position = 0
        self.chunked_input = chunked_input
        self.chunk_length = -1

    def _do_read(self, reader, length=None):
        if self.wfile is not None:
            ## 100 Continue
            self.wfile.write(self.wfile_line)
            self.wfile = None
            self.wfile_line = None

        if length is None and self.content_length is not None:
            length = self.content_length - self.position
        if length and length > self.content_length - self.position:
            length = self.content_length - self.position
        if not length:
            return ''
        try:
            read = reader(length)
        except greenio.SSL.ZeroReturnError:
            read = ''
        self.position += len(read)
        return read

    def _chunked_read(self, rfile, length=None):
        if self.wfile is not None:
            ## 100 Continue
            self.wfile.write(self.wfile_line)
            self.wfile = None
            self.wfile_line = None

        response = []
        try:
            if length is None:
                if self.chunk_length > self.position:
                    response.append(rfile.read(self.chunk_length - self.position))
                while self.chunk_length != 0:
                    self.chunk_length = int(rfile.readline(), 16)
                    response.append(rfile.read(self.chunk_length))
                    rfile.readline()
            else:
                while length > 0 and self.chunk_length != 0:
                    if self.chunk_length > self.position:
                        response.append(rfile.read(
                                min(self.chunk_length - self.position, length)))
                        length -= len(response[-1])
                        self.position += len(response[-1])
                        if self.chunk_length == self.position:
                            rfile.readline()
                    else:
                        self.chunk_length = int(rfile.readline(), 16)
                        self.position = 0
        except greenio.SSL.ZeroReturnError:
            pass
        return ''.join(response)

    def read(self, length=None):
        if self.chunked_input:
            return self._chunked_read(self.rfile, length)
        return self._do_read(self.rfile.read, length)

    def readline(self, size=None):
        return self._do_read(self.rfile.readline)

    def readlines(self, hint=None):
        return self._do_read(self.rfile.readlines, hint)

    def __iter__(self):
        return iter(self.read())

class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler):
    protocol_version = 'HTTP/1.1'
    minimum_chunk_size = MINIMUM_CHUNK_SIZE

    def setup(self):
        # overriding SocketServer.setup to correctly handle SSL.Connection objects
        conn = self.connection = self.request
        try:
            self.rfile = conn.makefile('rb', self.rbufsize)
            self.wfile = conn.makefile('wb', self.wbufsize)
        except (AttributeError, NotImplementedError):
            if hasattr(conn, 'send') and hasattr(conn, 'recv'):
                # it's an SSL.Connection
                self.rfile = socket._fileobject(conn, "rb", self.rbufsize)
                self.wfile = socket._fileobject(conn, "wb", self.wbufsize)
            else:
                # it's a SSLObject, or a martian
                raise NotImplementedError("wsgi.py doesn't support sockets "\
                                          "of type %s" % type(conn))

    def handle_one_request(self):
        if self.server.max_http_version:
            self.protocol_version = self.server.max_http_version

        if self.rfile.closed:
            self.close_connection = 1
            return

        try:
            self.raw_requestline = self.rfile.readline(MAX_REQUEST_LINE)
            if len(self.raw_requestline) == MAX_REQUEST_LINE:
                self.wfile.write(
                    "HTTP/1.0 414 Request URI Too Long\r\nConnection: close\r\nContent-length: 0\r\n\r\n")
                self.close_connection = 1
                return
        except greenio.SSL.ZeroReturnError:
            self.raw_requestline = ''
        except socket.error, e:
            if e[0] != errno.EBADF:
                raise
            self.raw_requestline = ''

        if not self.raw_requestline:
            self.close_connection = 1
            return

        if not self.parse_request():
            return

        self.environ = self.get_environ()
        self.application = self.server.app
        try:
            self.server.outstanding_requests += 1
            try:
                self.handle_one_response()
            except socket.error, e:
                # Broken pipe, connection reset by peer
                if e[0] in (32, 54):
                    pass
                else:
                    raise
        finally:
            self.server.outstanding_requests -= 1

    def handle_one_response(self):
        start = time.time()
        headers_set = []
        headers_sent = []

        wfile = self.wfile
        result = None
        use_chunked = [False]
        length = [0]
        status_code = [200]

        def write(data, _writelines=wfile.writelines):
            towrite = []
            if not headers_set:
                raise AssertionError("write() before start_response()")
            elif not headers_sent:
                status, response_headers = headers_set
                headers_sent.append(1)
                header_list = [header[0].lower() for header in response_headers]
                towrite.append('%s %s\r\n' % (self.protocol_version, status))
                for header in response_headers:
                    towrite.append('%s: %s\r\n' % header)

                # send Date header?
                if 'date' not in header_list:
                    towrite.append('Date: %s\r\n' % (format_date_time(time.time()),))
                if self.request_version == 'HTTP/1.0':
                    if self.headers.get('Connection', "").lower() == 'keep-alive':
                        towrite.append('Connection: keep-alive\r\n')
                        self.close_connection = 0
                    else:
                        towrite.append('Connection: close\r\n')
                        self.close_connection = 1
                elif 'content-length' not in header_list:
                    use_chunked[0] = True
                    towrite.append('Transfer-Encoding: chunked\r\n')
                towrite.append('\r\n')

            if use_chunked[0]:
                ## Write the chunked encoding
                towrite.append("%x\r\n%s\r\n" % (len(data), data))
            else:
                towrite.append(data)
            try:
                _writelines(towrite)
                length[0] = length[0] + sum(map(len, towrite))
            except UnicodeEncodeError:
                print "Encountered unicode while attempting to write wsgi response: ", [x for x in towrite if isinstance(x, unicode)]
                traceback.print_exc()
                _writelines(
                    ["HTTP/1.0 500 Internal Server Error\r\n",
                    "Connection: close\r\n",
                    "Content-type: text/plain\r\n",
                    "Content-length: 98\r\n",
                    "\r\n",
                    "Internal Server Error: wsgi application passed a unicode object to the server instead of a string."])

        def start_response(status, response_headers, exc_info=None):
            status_code[0] = status.split()[0]
            if exc_info:
                try:
                    if headers_sent:
                        # Re-raise original exception if headers sent
                        raise exc_info[0], exc_info[1], exc_info[2]
                finally:
                    # Avoid dangling circular ref
                    exc_info = None

            capitalized_headers = [('-'.join([x.capitalize() for x in key.split('-')]), value)
                                   for key, value in response_headers]

            headers_set[:] = [status, capitalized_headers]
            return write

        try:
            try:
                result = self.application(self.environ, start_response)
                if not headers_sent and hasattr(result, '__len__') and \
                        'Content-Length' not in [h for h, v in headers_set[1]]:
                    headers_set[1].append(('Content-Length', str(sum(map(len, result)))))
                towrite = []
                for data in result:
                    towrite.append(data)
                    if sum(map(len, towrite)) >= self.minimum_chunk_size:
                        write(''.join(towrite))
                        towrite = []
                if towrite:
                    write(''.join(towrite))
                if not headers_sent or use_chunked[0]:
                    write('')
            except Exception, e:
                self.close_connection = 1
                exc = traceback.format_exc()
                print exc
                if not headers_set:
                    start_response("500 Internal Server Error", [('Content-type', 'text/plain')])
                    write(exc)
        finally:
            if hasattr(result, 'close'):
                result.close()
            if self.environ['eventlet.input'].position < self.environ.get('CONTENT_LENGTH', 0):
                ## Read and discard body
                self.environ['eventlet.input'].read()
            finish = time.time()

            self.server.log_message('%s - - [%s] "%s" %s %s %.6f' % (
                self.client_address[0],
                self.log_date_time_string(),
                self.requestline,
                status_code[0],
                length[0],
                finish - start))

    def get_environ(self):
        env = self.server.get_environ()
        env['REQUEST_METHOD'] = self.command
        env['SCRIPT_NAME'] = ''

        if '?' in self.path:
            path, query = self.path.split('?', 1)
        else:
            path, query = self.path, ''
        env['PATH_INFO'] = urllib.unquote(path)
        env['QUERY_STRING'] = query

        if self.headers.typeheader is None:
            env['CONTENT_TYPE'] = self.headers.type
        else:
            env['CONTENT_TYPE'] = self.headers.typeheader

        length = self.headers.getheader('content-length')
        if length:
            env['CONTENT_LENGTH'] = length
        env['SERVER_PROTOCOL'] = 'HTTP/1.0'

        host, port = self.request.getsockname()
        env['SERVER_NAME'] = host
        env['SERVER_PORT'] = str(port)
        env['REMOTE_ADDR'] = self.client_address[0]
        env['GATEWAY_INTERFACE'] = 'CGI/1.1'

        for h in self.headers.headers:
            k, v = h.split(':', 1)
            k = k.replace('-', '_').upper()
            v = v.strip()
            if k in env:
                continue
            envk = 'HTTP_' + k
            if envk in env:
                env[envk] += ',' + v
            else:
                env[envk] = v

        if env.get('HTTP_EXPECT') == '100-continue':
            wfile = self.wfile
            wfile_line = 'HTTP/1.1 100 Continue\r\n\r\n'
        else:
            wfile = None
            wfile_line = None
        chunked = env.get('HTTP_TRANSFER_ENCODING', '').lower() == 'chunked'
        env['wsgi.input'] = env['eventlet.input'] = Input(
            self.rfile, length, wfile=wfile, wfile_line=wfile_line,
            chunked_input=chunked)

        return env

    def finish(self):
        BaseHTTPServer.BaseHTTPRequestHandler.finish(self)
        if self.connection.is_secure:
            self.connection.shutdown()
        self.connection.close()

class Server(BaseHTTPServer.HTTPServer):
    def __init__(self,
                 socket,
                 address,
                 app,
                 log=None,
                 environ=None,
                 max_http_version=None,
                 protocol=HttpProtocol,
                 minimum_chunk_size=None):

        self.outstanding_requests = 0
        self.socket = socket
        self.address = address
        if log:
            self.log = log
        else:
            self.log = sys.stderr
        self.app = app
        self.environ = environ
        self.max_http_version = max_http_version
        self.protocol = protocol
        self.pid = os.getpid()
        if minimum_chunk_size is not None:
            protocol.minimum_chunk_size = minimum_chunk_size

    def get_environ(self):
        socket = self.socket
        d = {
            'wsgi.errors': sys.stderr,
            'wsgi.version': (1, 0),
            'wsgi.multithread': True,
            'wsgi.multiprocess': False,
            'wsgi.run_once': False,
            'wsgi.url_scheme': 'http',
        }
        if self.environ is not None:
            d.update(self.environ)
        return d

    def process_request(self, (socket, address)):
        proto = self.protocol(socket, address, self)
        proto.handle()

    def log_message(self, message):
        self.log.write(message + '\n')

def server(sock, site,
           log=None,
           environ=None,
           max_size=None,
           max_http_version=DEFAULT_MAX_HTTP_VERSION,
           protocol=HttpProtocol,
           server_event=None,
           minimum_chunk_size=None):
    """  Start up a wsgi server handling requests from the supplied server socket.

    This function loops forever.
    """

    serv = Server(sock, sock.getsockname(),
                  site, log,
                  environ=None,
                  max_http_version=max_http_version,
                  protocol=protocol,
                  minimum_chunk_size=minimum_chunk_size)
    if server_event is not None:
        server_event.send(serv)
    if max_size is None:
        max_size = DEFAULT_MAX_SIMULTANEOUS_REQUESTS
    pool = Pool(max_size=max_size)
    try:
        host, port = sock.getsockname()
        port = ':%s' % (port, )
        if sock.is_secure:
            scheme = 'https'
            if port == ':443':
                port = ''
        else:
            scheme = 'http'
            if port == ':80':
                port = ''

        print "(%s) wsgi starting up on %s://%s%s/" % (os.getpid(), scheme, host, port)
        while True:
            try:
                try:
                    client_socket = sock.accept()
                except socket.error, e:
                    if e[0] != errno.EPIPE and e[0] != errno.EBADF:
                        raise
                pool.execute_async(serv.process_request, client_socket)
            except (KeyboardInterrupt, SystemExit):
                print "wsgi exiting"
                break
    finally:
        try:
            if sock.is_secure:
                sock.shutdown()
            sock.close()
        except socket.error, e:
            if e[0] != errno.EPIPE:
                traceback.print_exc()

<commit_msg>Use serv.log attribute rather than print.  Fixes #4.<commit_after>import errno
import os
import sys
import time
import traceback

from eventlet.green import urllib
from eventlet.green import socket
from eventlet.green import BaseHTTPServer
from eventlet.pool import Pool

import greenio

DEFAULT_MAX_SIMULTANEOUS_REQUESTS = 1024
DEFAULT_MAX_HTTP_VERSION = 'HTTP/1.1'
MAX_REQUEST_LINE = 8192
MINIMUM_CHUNK_SIZE = 4096

# Weekday and month names for HTTP date/time formatting; always English!
_weekdayname = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
_monthname = [None, # Dummy so we can use 1-based month numbers
              "Jan", "Feb", "Mar", "Apr", "May", "Jun",
              "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]

def format_date_time(timestamp):
    year, month, day, hh, mm, ss, wd, y, z = time.gmtime(timestamp)
    return "%s, %02d %3s %4d %02d:%02d:%02d GMT" % (
        _weekdayname[wd], day, _monthname[month], year, hh, mm, ss
    )

class Input(object):
    def __init__(self,
                 rfile,
                 content_length,
                 wfile=None,
                 wfile_line=None,
                 chunked_input=False):

        self.rfile = rfile
        if content_length is not None:
            content_length = int(content_length)
        self.content_length = content_length

        self.wfile = wfile
        self.wfile_line = wfile_line

        self.position = 0
        self.chunked_input = chunked_input
        self.chunk_length = -1

    def _do_read(self, reader, length=None):
        if self.wfile is not None:
            ## 100 Continue
            self.wfile.write(self.wfile_line)
            self.wfile = None
            self.wfile_line = None

        if length is None and self.content_length is not None:
            length = self.content_length - self.position
        if length and length > self.content_length - self.position:
            length = self.content_length - self.position
        if not length:
            return ''
        try:
            read = reader(length)
        except greenio.SSL.ZeroReturnError:
            read = ''
        self.position += len(read)
        return read

    def _chunked_read(self, rfile, length=None):
        if self.wfile is not None:
            ## 100 Continue
            self.wfile.write(self.wfile_line)
            self.wfile = None
            self.wfile_line = None

        response = []
        try:
            if length is None:
                if self.chunk_length > self.position:
                    response.append(rfile.read(self.chunk_length - self.position))
                while self.chunk_length != 0:
                    self.chunk_length = int(rfile.readline(), 16)
                    response.append(rfile.read(self.chunk_length))
                    rfile.readline()
            else:
                while length > 0 and self.chunk_length != 0:
                    if self.chunk_length > self.position:
                        response.append(rfile.read(
                                min(self.chunk_length - self.position, length)))
                        length -= len(response[-1])
                        self.position += len(response[-1])
                        if self.chunk_length == self.position:
                            rfile.readline()
                    else:
                        self.chunk_length = int(rfile.readline(), 16)
                        self.position = 0
        except greenio.SSL.ZeroReturnError:
            pass
        return ''.join(response)

    def read(self, length=None):
        if self.chunked_input:
            return self._chunked_read(self.rfile, length)
        return self._do_read(self.rfile.read, length)

    def readline(self, size=None):
        return self._do_read(self.rfile.readline)

    def readlines(self, hint=None):
        return self._do_read(self.rfile.readlines, hint)

    def __iter__(self):
        return iter(self.read())

class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler):
    protocol_version = 'HTTP/1.1'
    minimum_chunk_size = MINIMUM_CHUNK_SIZE

    def setup(self):
        # overriding SocketServer.setup to correctly handle SSL.Connection objects
        conn = self.connection = self.request
        try:
            self.rfile = conn.makefile('rb', self.rbufsize)
            self.wfile = conn.makefile('wb', self.wbufsize)
        except (AttributeError, NotImplementedError):
            if hasattr(conn, 'send') and hasattr(conn, 'recv'):
                # it's an SSL.Connection
                self.rfile = socket._fileobject(conn, "rb", self.rbufsize)
                self.wfile = socket._fileobject(conn, "wb", self.wbufsize)
            else:
                # it's a SSLObject, or a martian
                raise NotImplementedError("wsgi.py doesn't support sockets "\
                                          "of type %s" % type(conn))

    def handle_one_request(self):
        if self.server.max_http_version:
            self.protocol_version = self.server.max_http_version

        if self.rfile.closed:
            self.close_connection = 1
            return

        try:
            self.raw_requestline = self.rfile.readline(MAX_REQUEST_LINE)
            if len(self.raw_requestline) == MAX_REQUEST_LINE:
                self.wfile.write(
                    "HTTP/1.0 414 Request URI Too Long\r\nConnection: close\r\nContent-length: 0\r\n\r\n")
                self.close_connection = 1
                return
        except greenio.SSL.ZeroReturnError:
            self.raw_requestline = ''
        except socket.error, e:
            if e[0] != errno.EBADF:
                raise
            self.raw_requestline = ''

        if not self.raw_requestline:
            self.close_connection = 1
            return

        if not self.parse_request():
            return

        self.environ = self.get_environ()
        self.application = self.server.app
        try:
            self.server.outstanding_requests += 1
            try:
                self.handle_one_response()
            except socket.error, e:
                # Broken pipe, connection reset by peer
                if e[0] in (32, 54):
                    pass
                else:
                    raise
        finally:
            self.server.outstanding_requests -= 1

    def handle_one_response(self):
        start = time.time()
        headers_set = []
        headers_sent = []

        wfile = self.wfile
        result = None
        use_chunked = [False]
        length = [0]
        status_code = [200]

        def write(data, _writelines=wfile.writelines):
            towrite = []
            if not headers_set:
                raise AssertionError("write() before start_response()")
            elif not headers_sent:
                status, response_headers = headers_set
                headers_sent.append(1)
                header_list = [header[0].lower() for header in response_headers]
                towrite.append('%s %s\r\n' % (self.protocol_version, status))
                for header in response_headers:
                    towrite.append('%s: %s\r\n' % header)

                # send Date header?
                if 'date' not in header_list:
                    towrite.append('Date: %s\r\n' % (format_date_time(time.time()),))
                if self.request_version == 'HTTP/1.0':
                    if self.headers.get('Connection', "").lower() == 'keep-alive':
                        towrite.append('Connection: keep-alive\r\n')
                        self.close_connection = 0
                    else:
                        towrite.append('Connection: close\r\n')
                        self.close_connection = 1
                elif 'content-length' not in header_list:
                    use_chunked[0] = True
                    towrite.append('Transfer-Encoding: chunked\r\n')
                towrite.append('\r\n')

            if use_chunked[0]:
                ## Write the chunked encoding
                towrite.append("%x\r\n%s\r\n" % (len(data), data))
            else:
                towrite.append(data)
            try:
                _writelines(towrite)
                length[0] = length[0] + sum(map(len, towrite))
            except UnicodeEncodeError:
                print "Encountered unicode while attempting to write wsgi response: ", [x for x in towrite if isinstance(x, unicode)]
                traceback.print_exc()
                _writelines(
                    ["HTTP/1.0 500 Internal Server Error\r\n",
                    "Connection: close\r\n",
                    "Content-type: text/plain\r\n",
                    "Content-length: 98\r\n",
                    "\r\n",
                    "Internal Server Error: wsgi application passed a unicode object to the server instead of a string."])

        def start_response(status, response_headers, exc_info=None):
            status_code[0] = status.split()[0]
            if exc_info:
                try:
                    if headers_sent:
                        # Re-raise original exception if headers sent
                        raise exc_info[0], exc_info[1], exc_info[2]
                finally:
                    # Avoid dangling circular ref
                    exc_info = None

            capitalized_headers = [('-'.join([x.capitalize() for x in key.split('-')]), value)
                                   for key, value in response_headers]

            headers_set[:] = [status, capitalized_headers]
            return write

        try:
            try:
                result = self.application(self.environ, start_response)
                if not headers_sent and hasattr(result, '__len__') and \
                        'Content-Length' not in [h for h, v in headers_set[1]]:
                    headers_set[1].append(('Content-Length', str(sum(map(len, result)))))
                towrite = []
                for data in result:
                    towrite.append(data)
                    if sum(map(len, towrite)) >= self.minimum_chunk_size:
                        write(''.join(towrite))
                        towrite = []
                if towrite:
                    write(''.join(towrite))
                if not headers_sent or use_chunked[0]:
                    write('')
            except Exception, e:
                self.close_connection = 1
                exc = traceback.format_exc()
                print exc
                if not headers_set:
                    start_response("500 Internal Server Error", [('Content-type', 'text/plain')])
                    write(exc)
        finally:
            if hasattr(result, 'close'):
                result.close()
            if self.environ['eventlet.input'].position < self.environ.get('CONTENT_LENGTH', 0):
                ## Read and discard body
                self.environ['eventlet.input'].read()
            finish = time.time()

            self.server.log_message('%s - - [%s] "%s" %s %s %.6f' % (
                self.client_address[0],
                self.log_date_time_string(),
                self.requestline,
                status_code[0],
                length[0],
                finish - start))

    def get_environ(self):
        env = self.server.get_environ()
        env['REQUEST_METHOD'] = self.command
        env['SCRIPT_NAME'] = ''

        if '?' in self.path:
            path, query = self.path.split('?', 1)
        else:
            path, query = self.path, ''
        env['PATH_INFO'] = urllib.unquote(path)
        env['QUERY_STRING'] = query

        if self.headers.typeheader is None:
            env['CONTENT_TYPE'] = self.headers.type
        else:
            env['CONTENT_TYPE'] = self.headers.typeheader

        length = self.headers.getheader('content-length')
        if length:
            env['CONTENT_LENGTH'] = length
        env['SERVER_PROTOCOL'] = 'HTTP/1.0'

        host, port = self.request.getsockname()
        env['SERVER_NAME'] = host
        env['SERVER_PORT'] = str(port)
        env['REMOTE_ADDR'] = self.client_address[0]
        env['GATEWAY_INTERFACE'] = 'CGI/1.1'

        for h in self.headers.headers:
            k, v = h.split(':', 1)
            k = k.replace('-', '_').upper()
            v = v.strip()
            if k in env:
                continue
            envk = 'HTTP_' + k
            if envk in env:
                env[envk] += ',' + v
            else:
                env[envk] = v

        if env.get('HTTP_EXPECT') == '100-continue':
            wfile = self.wfile
            wfile_line = 'HTTP/1.1 100 Continue\r\n\r\n'
        else:
            wfile = None
            wfile_line = None
        chunked = env.get('HTTP_TRANSFER_ENCODING', '').lower() == 'chunked'
        env['wsgi.input'] = env['eventlet.input'] = Input(
            self.rfile, length, wfile=wfile, wfile_line=wfile_line,
            chunked_input=chunked)

        return env

    def finish(self):
        BaseHTTPServer.BaseHTTPRequestHandler.finish(self)
        if self.connection.is_secure:
            self.connection.shutdown()
        self.connection.close()

class Server(BaseHTTPServer.HTTPServer):
    def __init__(self,
                 socket,
                 address,
                 app,
                 log=None,
                 environ=None,
                 max_http_version=None,
                 protocol=HttpProtocol,
                 minimum_chunk_size=None):

        self.outstanding_requests = 0
        self.socket = socket
        self.address = address
        if log:
            self.log = log
        else:
            self.log = sys.stderr
        self.app = app
        self.environ = environ
        self.max_http_version = max_http_version
        self.protocol = protocol
        self.pid = os.getpid()
        if minimum_chunk_size is not None:
            protocol.minimum_chunk_size = minimum_chunk_size

    def get_environ(self):
        socket = self.socket
        d = {
            'wsgi.errors': sys.stderr,
            'wsgi.version': (1, 0),
            'wsgi.multithread': True,
            'wsgi.multiprocess': False,
            'wsgi.run_once': False,
            'wsgi.url_scheme': 'http',
        }
        if self.environ is not None:
            d.update(self.environ)
        return d

    def process_request(self, (socket, address)):
        proto = self.protocol(socket, address, self)
        proto.handle()

    def log_message(self, message):
        self.log.write(message + '\n')

def server(sock, site,
           log=None,
           environ=None,
           max_size=None,
           max_http_version=DEFAULT_MAX_HTTP_VERSION,
           protocol=HttpProtocol,
           server_event=None,
           minimum_chunk_size=None):
    """  Start up a wsgi server handling requests from the supplied server socket.

    This function loops forever.
    """

    serv = Server(sock, sock.getsockname(),
                  site, log,
                  environ=None,
                  max_http_version=max_http_version,
                  protocol=protocol,
                  minimum_chunk_size=minimum_chunk_size)
    if server_event is not None:
        server_event.send(serv)
    if max_size is None:
        max_size = DEFAULT_MAX_SIMULTANEOUS_REQUESTS
    pool = Pool(max_size=max_size)
    try:
        host, port = sock.getsockname()
        port = ':%s' % (port, )
        if sock.is_secure:
            scheme = 'https'
            if port == ':443':
                port = ''
        else:
            scheme = 'http'
            if port == ':80':
                port = ''

        serv.log.write("(%s) wsgi starting up on %s://%s%s/\n" % (os.getpid(), scheme, host, port))
        while True:
            try:
                try:
                    client_socket = sock.accept()
                except socket.error, e:
                    if e[0] != errno.EPIPE and e[0] != errno.EBADF:
                        raise
                pool.execute_async(serv.process_request, client_socket)
            except (KeyboardInterrupt, SystemExit):
                serv.log.write("wsgi exiting\n")
                break
    finally:
        try:
            if sock.is_secure:
                sock.shutdown()
            sock.close()
        except socket.error, e:
            if e[0] != errno.EPIPE:
                traceback.print_exc()

<|endoftext|>


<commit_before>from document import *
import objcog_db
import os

from optparse import OptionParser

def insert_session(sessions, training_path, meta_info):
    session = {"type":"session", "tags":["images"], "path_origin": training_path, "meta": meta_info}
    doc_id, doc_rev = sessions.save(session)
    doc = sessions[doc_id]
    return (doc, doc_id)

def create_session_frame(documents, session_id):
    doc_meta = {"type":"frame", "session_id":session_id}
    doc = Document(documents,doc_meta)
    return doc

if __name__ == '__main__':
    parser = OptionParser(description='Insert all images into the db from a specific folder.')
    parser.add_option("-p", "--training_path", dest="training_path",
                  help="The path to all the training bags", metavar="PATH_TO_IMAGES",
                   default='./')
    parser.add_option("-e", "--extension", dest="extension",
                  help="The extension", metavar="IMAGE_TYPE_EXTENSION",
                   default='.png')
    parser.add_option("-m", "--meta_info", dest="meta_info",
                  help="The meta info for the given object.", metavar="META_INFO_JSON_STRING",
                   default="Images from training path.")

    (options, args) = parser.parse_args()
    print "Training directory is %s" % options.training_path
    print "Meta info is %s" % options.meta_info
    idx = 0

    couch = couchdb.Server()

    sessions = create_db("objcog/sessions", couch)

    documents = create_db("objcog/documents", couch)

    (session, session_id) = insert_session(sessions,options.training_path,options.meta_info)

    for file in sorted(os.listdir(options.training_path)):
        if(file.endswith(options.extension)):
          print "putting %s" % file
          frame = create_session_frame(documents,session_id)
          objcog_db.insert_image(os.path.join(options.training_path,file),"image",frame)
          idx += 1

<commit_msg>- no more errors in the code<commit_after><|endoftext|>


<commit_before>#
# SConscript for himan-lib

import os
import platform

OS_NAME = platform.linux_distribution()[0]
OS_VERSION = float('.'.join(platform.linux_distribution()[1].split('.')[:2]))

IS_RHEL = False
IS_SLES = False

if OS_NAME == "Red Hat Enterprise Linux Server" or OS_NAME == "CentOS Linux":
	IS_RHEL=True
elif OS_NAME == "SUSE Linux Enterprise Server ":
	IS_SLES=True

# Should also get compiler version here but it seems to be rather
# complicated with python subprocess -module

env = Environment(ENV = {'PATH' : os.environ['PATH']})

# Get color output from gcc / clang
try:
    env['ENV']['TERM'] = os.environ['TERM']
except KeyError:
    pass

env['CC'] = 'gcc'
env['CXX'] = 'g++'

if os.environ.get('CC') != None:
        env['CC'] = os.environ.get('CC')

if os.environ.get('CXX') != None:
        env['CXX'] = os.environ.get('CXX')

IS_GCC = False
IS_CLANG = False

if env['CXX'] == 'g++':
	IS_GCC=True
elif env['CXX'] == 'clang++':
	IS_CLANG=True

AddOption(
    '--debug-build',
    dest='debug-build',
    action='store_true',
    help='debug build',
    default=False)

AddOption(
    '--no-cuda-build',
    dest='no-cuda-build',
    action='store_true',
    help='no cuda build',
    default=False)

# Check build

NOCUDA = GetOption('no-cuda-build')
DEBUG = GetOption('debug-build')
RELEASE = (not DEBUG)

env['DEBUG'] = DEBUG
env['RELEASE'] = RELEASE

# Workspace

env['WORKSPACE'] = "%s/../" % os.getcwd()
# rpm builds:
#env['WORKSPACE'] = "/home/dev/partio/himan"

# cuda toolkit path

if not NOCUDA:
	cuda_toolkit_path = '/usr/local/cuda'

	if os.environ.get('CUDA_TOOLKIT_PATH') is not None:
        	cuda_toolkit_path = os.environ['CUDA_TOOLKIT_PATH']

env['HAVE_CUDA'] = False

if not NOCUDA and os.path.isfile(cuda_toolkit_path + '/lib64/libcudart.so'):
        env['HAVE_CUDA'] = True

env['HAVE_S3'] = False

if os.path.isfile('/usr/include/libs3.h'):
	env['HAVE_S3'] = True

# Required for scan-build
env["ENV"].update(x for x in os.environ.items() if x[0].startswith("CCC_"))

# Includes

includes = []

try:
	includes.extend(os.environ['INCLUDEPATHS'].split(';'))
except:
	pass

includes.append(env['WORKSPACE'] + '/himan-lib/include')
includes.append(env['WORKSPACE'] + '/himan-plugins/include')

if env['HAVE_CUDA']:
        includes.append(cuda_toolkit_path + '/include')

env.Append(CPPPATH = includes)

# Library paths

librarypaths = []

try:
	librarypaths.extend(os.environ['LIBRARYPATHS'].split(';'))
except:
	pass

librarypaths.append('/usr/lib64/boost169')
env.Append(LIBPATH = librarypaths)

# Libraries

env.Append(LIBS = ['z', 'bz2'])

if env['HAVE_CUDA']:
        env.Append(LIBS=env.File(cuda_toolkit_path + '/lib64/libcudart_static.a'))

# CFLAGS

# "Normal" flags

cflags_normal = []
cflags_normal.append('-Wall')
cflags_normal.append('-W')
cflags_normal.append('-Wno-unused-parameter')
cflags_normal.append('-Werror')
cflags_normal.append('-Wno-narrowing')
cflags_normal.append('-Wpointer-arith')
cflags_normal.append('-Wcast-qual')

# Extra flags

cflags_extra = []
cflags_extra.append('-Wcast-align')
cflags_extra.append('-Wwrite-strings')
cflags_extra.append('-Wconversion')
# cflags_extra.append('-Winline')
cflags_extra.append('-Wnon-virtual-dtor')
cflags_extra.append('-Wsign-promo')
cflags_extra.append('-Wchar-subscripts')
cflags_extra.append('-Wold-style-cast')
cflags_extra.append('-Wunreachable-code')
cflags_extra.append('-Wshadow')

if IS_GCC:
       cflags_extra.append('-Wno-pmf-conversions')
elif IS_CLANG:
       cflags_extra.append('-Wno-int-conversions')

# Difficult flags

cflags_difficult = []
cflags_difficult.append('-pedantic')
cflags_difficult.append('-Weffc++')
cflags_difficult.append('-Wredundant-decls')
cflags_difficult.append('-Woverloaded-virtual')
cflags_difficult.append('-Wctor-dtor-privacy')

# Default flags (common for release/debug)

if IS_GCC:
	env.Append(CCFLAGS = '-std=c++11')
else:
	env.Append(CCFLAGS = '-std=c++11')

env.Append(CCFLAGS = '-fPIC')
env.Append(CCFLAGS = cflags_normal)
env.Append(CCFLAGS = cflags_extra)

env.AppendUnique(CCFLAGS=('-isystem', '/usr/include/boost169'))
env.AppendUnique(CCFLAGS=('-isystem', '/usr/include/gdal'))

if IS_CLANG:
	env.AppendUnique(CCFLAGS=('-isystem', '/usr/include/smartmet/newbase'))
	env.AppendUnique(CCFLAGS=('-isystem', '/usr/include/Eigen'))
	env.AppendUnique(CCFLAGS=('-isystem', '/opt/llvm-5.0.0/include'))
	env.AppendUnique(CCFLAGS=('-isystem', '/opt/llvm-5.0.0/include/c++/v1'))

# Linker flags

env.Append(LINKFLAGS = ['-rdynamic','-Wl,--as-needed'])

# Defines

env.Append(CPPDEFINES=['UNIX'])

if env['HAVE_CUDA']:
        env.Append(CPPDEFINES=['HAVE_CUDA'])
if env['HAVE_S3']:
        env.Append(CPPDEFINES=['HAVE_S3'])

env.Append(NVCCDEFINES=['HAVE_CUDA'])

env.Append(NVCCFLAGS = ['-m64', '-Xcompiler', '-fPIC'])
env.Append(NVCCFLAGS = ['-Wno-deprecated-declarations'])
env.Append(NVCCFLAGS = ['-gencode=arch=compute_35,code=sm_35'])
env.Append(NVCCFLAGS = ['-gencode=arch=compute_52,code=sm_52'])
env.Append(NVCCFLAGS = ['-gencode=arch=compute_60,code=sm_60'])
env.Append(NVCCFLAGS = ['-gencode=arch=compute_70,code=sm_70'])

#if IS_CLANG:
#	env.Append(NVCCFLAGS = ['-ccbin=clang++'])
#	env.Append(NVCCFLAGS = ['-std=c++14'])

#else:
env.AppendUnique(NVCCFLAGS = ['-std=c++11'])
env.AppendUnique(NVCCFLAGS = ('-isystem', '/usr/include/boost169'))

for flag in cflags_normal:
	if flag == '-Wcast-qual':
		continue
	env.Append(NVCCFLAGS = ['-Xcompiler', flag])

env.Append(NVCCPATH = [env['WORKSPACE'] + '/himan-lib/include']) # cuda-helper
env.Append(NVCCPATH = [env['WORKSPACE'] + '/himan-plugins/include'])
env.Append(NVCCPATH = ['/usr/include/smartmet/newbase'])

# Other

build_dir = ""

env.Append(NOCUDA = NOCUDA)

if RELEASE:
	env.Append(CPPDEFINES = ['NDEBUG'])
	env.Append(CCFLAGS = ['-O2'])

	env.Append(NVCCFLAGS = ['-O2'])
	env.Append(NVCCDEFINES = ['NDEBUG'])

	build_dir = 'build/release'

if DEBUG:
	env.Append(CCFLAGS = ['-O0'])
	env.Append(CCFLAGS = ['-ggdb', '-g3'])
	#env.Append(CCFLAGS = cflags_difficult)
	env.Append(CPPDEFINES = ['DEBUG'])

	# Cuda
	env.Append(NVCCFLAGS = ['-O0','-g','-G'])
	env.Append(NVCCDEFINES = ['DEBUG'])

	build_dir = 'build/debug'

#
# https://bitbucket.org/scons/scons/wiki/PhonyTargets
#
def PhonyTargets(env = None, **kw):
        if not env: env = DefaultEnvironment()
        for target,action in kw.items():
                env.AlwaysBuild(env.Alias(target, [], action))

PhonyTargets(CPPCHECK = 'cppcheck --std=c++11 --enable=all -I ./include -I ../himan-lib/include ./')
PhonyTargets(SCANBUILD = 'scan-build make debug')

Export('env build_dir')
<commit_msg>Add gdal include directory to nvcc compilation<commit_after>#
# SConscript for himan-lib

import os
import platform

OS_NAME = platform.linux_distribution()[0]
OS_VERSION = float('.'.join(platform.linux_distribution()[1].split('.')[:2]))

IS_RHEL = False
IS_SLES = False

if OS_NAME == "Red Hat Enterprise Linux Server" or OS_NAME == "CentOS Linux":
	IS_RHEL=True
elif OS_NAME == "SUSE Linux Enterprise Server ":
	IS_SLES=True

# Should also get compiler version here but it seems to be rather
# complicated with python subprocess -module

env = Environment(ENV = {'PATH' : os.environ['PATH']})

# Get color output from gcc / clang
try:
    env['ENV']['TERM'] = os.environ['TERM']
except KeyError:
    pass

env['CC'] = 'gcc'
env['CXX'] = 'g++'

if os.environ.get('CC') != None:
        env['CC'] = os.environ.get('CC')

if os.environ.get('CXX') != None:
        env['CXX'] = os.environ.get('CXX')

IS_GCC = False
IS_CLANG = False

if env['CXX'] == 'g++':
	IS_GCC=True
elif env['CXX'] == 'clang++':
	IS_CLANG=True

AddOption(
    '--debug-build',
    dest='debug-build',
    action='store_true',
    help='debug build',
    default=False)

AddOption(
    '--no-cuda-build',
    dest='no-cuda-build',
    action='store_true',
    help='no cuda build',
    default=False)

# Check build

NOCUDA = GetOption('no-cuda-build')
DEBUG = GetOption('debug-build')
RELEASE = (not DEBUG)

env['DEBUG'] = DEBUG
env['RELEASE'] = RELEASE

# Workspace

env['WORKSPACE'] = "%s/../" % os.getcwd()
# rpm builds:
#env['WORKSPACE'] = "/home/dev/partio/himan"

# cuda toolkit path

if not NOCUDA:
	cuda_toolkit_path = '/usr/local/cuda'

	if os.environ.get('CUDA_TOOLKIT_PATH') is not None:
        	cuda_toolkit_path = os.environ['CUDA_TOOLKIT_PATH']

env['HAVE_CUDA'] = False

if not NOCUDA and os.path.isfile(cuda_toolkit_path + '/lib64/libcudart.so'):
        env['HAVE_CUDA'] = True

env['HAVE_S3'] = False

if os.path.isfile('/usr/include/libs3.h'):
	env['HAVE_S3'] = True

# Required for scan-build
env["ENV"].update(x for x in os.environ.items() if x[0].startswith("CCC_"))

# Includes

includes = []

try:
	includes.extend(os.environ['INCLUDEPATHS'].split(';'))
except:
	pass

includes.append(env['WORKSPACE'] + '/himan-lib/include')
includes.append(env['WORKSPACE'] + '/himan-plugins/include')

if env['HAVE_CUDA']:
        includes.append(cuda_toolkit_path + '/include')

env.Append(CPPPATH = includes)

# Library paths

librarypaths = []

try:
	librarypaths.extend(os.environ['LIBRARYPATHS'].split(';'))
except:
	pass

librarypaths.append('/usr/lib64/boost169')
env.Append(LIBPATH = librarypaths)

# Libraries

env.Append(LIBS = ['z', 'bz2'])

if env['HAVE_CUDA']:
        env.Append(LIBS=env.File(cuda_toolkit_path + '/lib64/libcudart_static.a'))

# CFLAGS

# "Normal" flags

cflags_normal = []
cflags_normal.append('-Wall')
cflags_normal.append('-W')
cflags_normal.append('-Wno-unused-parameter')
cflags_normal.append('-Werror')
cflags_normal.append('-Wno-narrowing')
cflags_normal.append('-Wpointer-arith')
cflags_normal.append('-Wcast-qual')

# Extra flags

cflags_extra = []
cflags_extra.append('-Wcast-align')
cflags_extra.append('-Wwrite-strings')
cflags_extra.append('-Wconversion')
# cflags_extra.append('-Winline')
cflags_extra.append('-Wnon-virtual-dtor')
cflags_extra.append('-Wsign-promo')
cflags_extra.append('-Wchar-subscripts')
cflags_extra.append('-Wold-style-cast')
cflags_extra.append('-Wunreachable-code')
cflags_extra.append('-Wshadow')

if IS_GCC:
       cflags_extra.append('-Wno-pmf-conversions')
elif IS_CLANG:
       cflags_extra.append('-Wno-int-conversions')

# Difficult flags

cflags_difficult = []
cflags_difficult.append('-pedantic')
cflags_difficult.append('-Weffc++')
cflags_difficult.append('-Wredundant-decls')
cflags_difficult.append('-Woverloaded-virtual')
cflags_difficult.append('-Wctor-dtor-privacy')

# Default flags (common for release/debug)

if IS_GCC:
	env.Append(CCFLAGS = '-std=c++11')
else:
	env.Append(CCFLAGS = '-std=c++11')

env.Append(CCFLAGS = '-fPIC')
env.Append(CCFLAGS = cflags_normal)
env.Append(CCFLAGS = cflags_extra)

env.AppendUnique(CCFLAGS=('-isystem', '/usr/include/boost169'))
env.AppendUnique(CCFLAGS=('-isystem', '/usr/include/gdal'))

if IS_CLANG:
	env.AppendUnique(CCFLAGS=('-isystem', '/usr/include/smartmet/newbase'))
	env.AppendUnique(CCFLAGS=('-isystem', '/usr/include/Eigen'))
	env.AppendUnique(CCFLAGS=('-isystem', '/opt/llvm-5.0.0/include'))
	env.AppendUnique(CCFLAGS=('-isystem', '/opt/llvm-5.0.0/include/c++/v1'))

# Linker flags

env.Append(LINKFLAGS = ['-rdynamic','-Wl,--as-needed'])

# Defines

env.Append(CPPDEFINES=['UNIX'])

if env['HAVE_CUDA']:
        env.Append(CPPDEFINES=['HAVE_CUDA'])
if env['HAVE_S3']:
        env.Append(CPPDEFINES=['HAVE_S3'])

env.Append(NVCCDEFINES=['HAVE_CUDA'])

env.Append(NVCCFLAGS = ['-m64', '-Xcompiler', '-fPIC'])
env.Append(NVCCFLAGS = ['-Wno-deprecated-declarations'])
env.Append(NVCCFLAGS = ['-gencode=arch=compute_35,code=sm_35'])
env.Append(NVCCFLAGS = ['-gencode=arch=compute_52,code=sm_52'])
env.Append(NVCCFLAGS = ['-gencode=arch=compute_60,code=sm_60'])
env.Append(NVCCFLAGS = ['-gencode=arch=compute_70,code=sm_70'])

#if IS_CLANG:
#	env.Append(NVCCFLAGS = ['-ccbin=clang++'])
#	env.Append(NVCCFLAGS = ['-std=c++14'])

#else:
env.AppendUnique(NVCCFLAGS = ['-std=c++11'])
env.AppendUnique(NVCCFLAGS = ('-isystem', '/usr/include/boost169'))
env.AppendUnique(NVCCFLAGS = ('-isystem', '/usr/include/gdal'))

for flag in cflags_normal:
	if flag == '-Wcast-qual':
		continue
	env.Append(NVCCFLAGS = ['-Xcompiler', flag])

env.Append(NVCCPATH = [env['WORKSPACE'] + '/himan-lib/include']) # cuda-helper
env.Append(NVCCPATH = [env['WORKSPACE'] + '/himan-plugins/include'])
env.Append(NVCCPATH = ['/usr/include/smartmet/newbase'])

# Other

build_dir = ""

env.Append(NOCUDA = NOCUDA)

if RELEASE:
	env.Append(CPPDEFINES = ['NDEBUG'])
	env.Append(CCFLAGS = ['-O2'])

	env.Append(NVCCFLAGS = ['-O2'])
	env.Append(NVCCDEFINES = ['NDEBUG'])

	build_dir = 'build/release'

if DEBUG:
	env.Append(CCFLAGS = ['-O0'])
	env.Append(CCFLAGS = ['-ggdb', '-g3'])
	#env.Append(CCFLAGS = cflags_difficult)
	env.Append(CPPDEFINES = ['DEBUG'])

	# Cuda
	env.Append(NVCCFLAGS = ['-O0','-g','-G'])
	env.Append(NVCCDEFINES = ['DEBUG'])

	build_dir = 'build/debug'

#
# https://bitbucket.org/scons/scons/wiki/PhonyTargets
#
def PhonyTargets(env = None, **kw):
        if not env: env = DefaultEnvironment()
        for target,action in kw.items():
                env.AlwaysBuild(env.Alias(target, [], action))

PhonyTargets(CPPCHECK = 'cppcheck --std=c++11 --enable=all -I ./include -I ../himan-lib/include ./')
PhonyTargets(SCANBUILD = 'scan-build make debug')

Export('env build_dir')
<|endoftext|>


<commit_before>from colorclass.toggles import disable_all_colors
from loguru import logger

from flexget import options
from flexget.event import event
from flexget.plugin import get_plugins
from flexget.terminal import TerminalTable, TerminalTableError, colorize, console, table_parser

logger = logger.bind(name='plugins')

def plugins_summary(manager, options):
    if options.table_type == 'porcelain':
        disable_all_colors()
    header = ['Keyword', 'Interfaces', 'Phases', 'Flags']
    table_data = [header]
    for plugin in sorted(get_plugins(phase=options.phase, interface=options.interface)):
        if options.builtins and not plugin.builtin:
            continue

        flags = []
        if plugin.instance.__doc__:
            flags.append('doc')
        if plugin.builtin:
            flags.append('builtin')
        if plugin.debug:
            if not options.debug:
                continue
            flags.append('developers')

        handlers = plugin.phase_handlers
        roles = []
        for phase in handlers:
            priority = handlers[phase].priority
            roles.append('{0}({1})'.format(phase, priority))

        name = colorize('green', plugin.name) if 'builtin' in flags else plugin.name
        table_data.append([name, ', '.join(plugin.interfaces), ', '.join(roles), ', '.join(flags)])

    try:
        table = TerminalTable(options.table_type, table_data, wrap_columns=[1, 2])
        console(table.output)
    except TerminalTableError as e:
        console('ERROR: %s' % str(e))
        return
    console(colorize('green', ' Built-in plugins'))

@event('options.register')
def register_parser_arguments():
    parser = options.register_command(
        'plugins',
        plugins_summary,
        help='Print registered plugin summaries',
        parents=[table_parser],
    )
    parser.add_argument('--interface', help='Show plugins belonging to this interface')
    parser.add_argument('--phase', help='Show plugins that act on this phase')
    parser.add_argument('--builtins', action='store_true', help='Show just builtin plugins')
<commit_msg>Convert plugins cli to new terminal tables<commit_after>from colorclass.toggles import disable_all_colors
from loguru import logger

from flexget import options
from flexget.event import event
from flexget.plugin import get_plugins
from flexget.terminal import TerminalTable, colorize, console, table_parser

logger = logger.bind(name='plugins')

def plugins_summary(manager, options):
    if options.table_type == 'porcelain':
        disable_all_colors()
    header = ['Keyword', 'Interfaces', 'Phases', 'Flags']
    table = TerminalTable(*header, table_type=options.table_type)
    for plugin in sorted(get_plugins(phase=options.phase, interface=options.interface)):
        if options.builtins and not plugin.builtin:
            continue

        flags = []
        if plugin.instance.__doc__:
            flags.append('doc')
        if plugin.builtin:
            flags.append('builtin')
        if plugin.debug:
            if not options.debug:
                continue
            flags.append('developers')

        handlers = plugin.phase_handlers
        roles = []
        for phase in handlers:
            priority = handlers[phase].priority
            roles.append('{0}({1})'.format(phase, priority))

        name = colorize('green', plugin.name) if 'builtin' in flags else plugin.name
        table.add_row(name, ', '.join(plugin.interfaces), ', '.join(roles), ', '.join(flags))

    table.caption = colorize('green', ' Built-in plugins')
    table.caption_justify = 'left'
    console(table)

@event('options.register')
def register_parser_arguments():
    parser = options.register_command(
        'plugins',
        plugins_summary,
        help='Print registered plugin summaries',
        parents=[table_parser],
    )
    parser.add_argument('--interface', help='Show plugins belonging to this interface')
    parser.add_argument('--phase', help='Show plugins that act on this phase')
    parser.add_argument('--builtins', action='store_true', help='Show just builtin plugins')
<|endoftext|>


<commit_before><commit_msg>Adding new table definitions for 'api-router' deployable. It shouldn't really be in there, but it's a temporary solution. This is all untested at the moment.<commit_after><|endoftext|>


<commit_before>'''Schema class and related code.'''
import keyword
import textwrap
from collections import OrderedDict

from lima import abc
from lima import exc
from lima import registry
from lima import util

# Helper functions ############################################################

def _fields_from_bases(bases):
    '''Return fields determined from a list of base classes'''
    fields = OrderedDict()

    # determine base classes that are actually Schemas by checking if they
    # inherit from abc.SchemaABC
    schema_bases = [b for b in bases if (issubclass(b, abc.SchemaABC) and
                                         b != abc.SchemaABC)]

    # Add fields of base schemas. Bases listed first have precedence (to
    # reflect how python inherits class attributes). Their items are also
    # placed first in the fields OrderedDict (to reflect the order in which the
    # bases are written down in the class definition).
    for base in schema_bases:
        for k, v in base.__fields__.items():
            if k not in fields:
                fields[k] = v

    return fields

def _fields_include(fields, include):
    '''Return a copy of fields with fields in include included.'''
    util.ensure_mapping(include)
    util.ensure_only_instances_of(include, str)
    util.ensure_only_instances_of(include.values(), abc.FieldABC)
    result = fields.copy()
    result.update(include)
    return result

def _fields_exclude(fields, remove):
    '''Return a copy of fields with fields mentioned in exclude missing.'''
    util.ensure_only_instances_of(remove, str)
    util.ensure_subset_of(remove, fields)
    result = OrderedDict()
    for k, v in fields.items():
        if k not in remove:
            result[k] = v
    return result

def _fields_only(fields, only):
    '''Return a copy of fields containing only fields mentioned in only.'''
    util.ensure_only_instances_of(only, str)
    util.ensure_subset_of(only, fields)
    result = OrderedDict()
    for k, v in fields.items():
        if k in only:
            result[k] = v
    return result

def _mangle_name(name):
    '''Return mangled field name.

    Mangled field names have some name prefixes replaced with others (see
    mapping in code). This is to allow some field names with special chars in
    them to be defined via Schema class attributes.

    '''
    mapping = dict(at='@', dash='-', dot='.', hash='#', plus='+', nil='')
    if '__' not in name:
        return name
    before, after = name.split('__', 1)
    if before not in mapping:
        return name
    return mapping[before] + after

# Schema Metaclass ############################################################

class SchemaMeta(type):
    '''Metaclass of Schema.

    .. note::
       The metaclass :class:`SchemaMeta` is used internally to simplify the
       configuration of new :class:`Schema` classes. For users of the library
       there should be no need to use :class:`SchemaMeta` directly.

    When defining a new :class:`Schema` (sub)class, :class:`SchemaMeta` makes
    sure that the new class has a class attribute :attr:`__fields__` of type
    :class:`collections.OrderedDict` containing the fields for the new
    Schema.

    :attr:`__fields__` is determined like this:

    - The :attr:`__fields__` of all base classes are copied (with base classes
      specified first having precedence).

      Note that the fields themselves are *not* copied - changing an inherited
      field would change this field for all base classes referencing this field
      as well. In general it is *strongly* suggested to treat fields as
      immutable.

    - Fields (Class variables of type :class:`lima.abc.FieldABC`) are moved out
      of the class namespace and into :attr:`__fields__`, overriding any fields
      of the same name therein.

    - If present, the class attribute :attr:`__lima_args__` is removed from the
      class namespace and evaluated as follows:

      - Fields specified via ``__lima_args__['include']`` (an optional mapping
        of field names to fields) are inserted into :attr:`__fields__`.
        overriding any fields of the same name therein.

        If the order of your fields is important, make sure that
        ``__lima_args__['include']`` is of type
        :class:`collections.OrderedDict` or similar.

        New fields in ``__lima_args__['include']__`` are inserted at the
        position where ``__lima_args__`` is specified in the class.

      - Fields named in an optional sequence ``__lima_args__['exclude']`` are
        removed from :attr:`__fields__`. If only one field is to be removed,
        it's ok to supply a simple string instead of a list containing only one
        string. ``__lima_args__['exclude']`` may not be specified together with
        ``__lima_args__['only']``.

      - If in an optional sequence ``__lima_args__['only']`` is provided, *all
        but* the fields mentioned therein are removed from :attr:`__fields__`.
        If only one field is to remain, it's ok to supply a simple string
        instead of a list containing only one string. ``__lima_args__['only']``
        may not be specified together with ``__lima_args__['exclude']``.

        Think twice before using ``__lima_args__['only']`` - most of the time
        it's better to rethink your Schema than to remove a lot of fields that
        maybe shouldn't be there in the first place.

    .. versionadded:: 0.3
        Support for ``__lima_args__['only']``.

    :class:`SchemaMeta` also makes sure the new Schema class is registered with
    the lima class registry :mod:`lima.registry` (at least if the Schema isn't
    defined inside a local namespace, where we wouldn't find it later on).

    '''
    def __new__(metacls, name, bases, namespace):

        # aggregate fields from base classes
        fields = _fields_from_bases(bases)

        # pop/verify __lima_args__
        args = namespace.get('__lima_args__', {})
        with util.complain_about('__lima_args__'):
            util.ensure_mapping(args)
            util.ensure_subset_of(args, {'include', 'exclude', 'only'})
            util.ensure_only_one_of(args, {'exclude', 'only'})

        # determine individual args (include, exclude, only)
        include = args.get('include', {})
        exclude = util.vector_context(args.get('exclude', []))
        only = util.vector_context(args.get('only', []))

        # loop over copy of namespace items (we mutate namespace in this loop)
        for k, v in list(namespace.items()):
            if k == '__lima_args__':
                # at position of __lima_args__: insert include (if specified)
                if include:
                    with util.complain_about("__lima_args__['include']"):
                        fields = _fields_include(fields, include)
            elif isinstance(v, abc.FieldABC):
                # if a field was found: move it from namespace into fields
                # (also, mangle its name to allow some special field names)
                fields[_mangle_name(k)] = namespace.pop(k)

        if exclude:
            with util.complain_about('__lima_args__["exclude"]'):
                fields = _fields_exclude(fields, exclude)
        elif only:
            with util.complain_about('__lima_args__["only"]'):
                fields = _fields_only(fields, only)

        # add __fields__ to namespace
        namespace['__fields__'] = fields

        # remove __lima_args__ from namespace (if present)
        namespace.pop('__lima_args__', None)

        # Create the new class. Note that the superclass gets the altered
        # namespace as a common dict explicitly - we don't need an OrderedDict
        # namespace any more at this point.
        cls = super().__new__(metacls, name, bases, dict(namespace))

        # Try to register the new class. Classes defined in local namespaces
        # cannot be registerd. We're ok with this.
        with util.suppress(exc.RegisterLocalClassError):
            registry.global_registry.register(cls)

        # return class
        return cls

    @classmethod
    def __prepare__(metacls, name, bases):
        '''Return an OrderedDict as the class namespace.'''
        return OrderedDict()

# Schema ######################################################################

class Schema(abc.SchemaABC, metaclass=SchemaMeta):
    '''Base class for Schemas.

    Args:
        exclude: An optional sequence of field names to be removed from the
            fields of the new :class:`Schema` instance. If only one field is to
            be removed, it's ok to supply a simple string instead of a list
            containing only one string for ``exclude``. ``exclude`` may not be
            specified together with ``only``.

        only: An optional sequence of the names of the only fields that shall
            remain for the new :class:`Schema` instance.  If just one field is
            to remain, it's ok to supply a simple string instead of a list
            containing only one string for ``only``. ``only`` may not be
            specified together with ``exclude``.

        include: An optional mapping of field names to fields to additionally
            include in the new Schema instance. Think twice before using this
            option - most of the time it's better to include fields at class
            level rather than at instance level.

        ordered: An optional boolean indicating if the :meth:`Schema.dump`
            method should output :class:`collections.OrderedDict` objects
            instead of simple :class:`dict` objects.  Defaults to ``False``.
            This does not influence how nested fields are serialized.

        many: An optional boolean indicating if the new Schema will be
            serializing single objects (``many=False``) or collections of
            objects (``many=True``) per default. This can later be overridden
            in the :meth:`dump` Method.

    .. versionadded:: 0.3
        The ``include`` parameter.

    .. versionadded:: 0.3
        The ``ordered`` parameter.

    Upon creation, each Schema object gets an internal mapping of field names
    to fields. This mapping starts out as a copy of the class's
    :attr:`__fields__` attribute.  (For an explanation on how this
    :attr:`__fields__` attribute is determined, see :class:`SchemaMeta`.)

    Note that the fields themselves are not copied - changing the field of an
    instance would change this field for the other instances and classes
    referencing this field as well. In general it is *strongly* suggested to
    treat fields as immutable.

    The internal field mapping is then modified as follows:

    - If ``include`` was provided, fields specified therein are added
      (overriding any fields of the same name already present)

      If the order of your fields is important, make sure that ``include`` is
      of type :class:`collections.OrderedDict` or similar.

    - If ``exclude`` was provided, fields specified therein are removed.

    - If ``only`` was provided, *all but* the fields specified therein are
      removed (unless ``exclude`` was provided as well, in which case a
      :exc:`ValueError` is raised.)

    Also upon creation, each Schema object gets an individually created dump
    function that aims to unroll most of the loops and to minimize the number
    of attribute lookups, resulting in a little speed gain on serialization.

    :class:`Schema` classes defined outside of local namespaces can be
    referenced by name (used by :class:`lima.fields.Nested`).

    '''
    def __init__(self,
                 *,
                 exclude=None,
                 only=None,
                 include=None,
                 ordered=False,
                 many=False):
        fields = self.__class__.__fields__.copy()
        if exclude and only:
            msg = "Can't specify exclude and only at the same time."
            raise ValueError(msg)

        if include:
            with util.complain_about('include'):
                fields = _fields_include(fields, include)

        if exclude:
            with util.complain_about('exclude'):
                fields = _fields_exclude(fields, util.vector_context(exclude))
        elif only:
            with util.complain_about('only'):
                fields = _fields_only(fields, util.vector_context(only))

        self._fields = fields
        self._ordered = ordered
        self.many = many

        # get code/namespace for the customized dump function
        code, namespace = self._dump_function_code_ns(fields, ordered)

        # prepare namespace: provide OrderedDict and nothing else
        namespace['OrderedDict'] = OrderedDict
        namespace['__builtins__'] = {}

        # this defines _dump_function inside namespace
        exec(code, namespace)

        # and set _dump_function attr to the new function
        self._dump_function = namespace['_dump_function']

    @staticmethod
    def _field_value_code_ns(field, field_name, field_num):
        '''Get code/namespace-dict to determine a field's serialized value.

        Args:
            field: A :class:`lima.fields.Field` instance.

            field_name: The name (key) of the field instance.

            field_num: A schema-wide unique number for the field.

        Returns: A tuple consisting of: a) a fragment of Python code to
            determine the field's value and b) a namespace-dict containing
            shortcuts to necessary values/functions for this code fragment to
            work.

        '''
        namespace = {}
        if hasattr(field, 'val'):
            # add constant-field-value-shortcut to namespace
            name = 'val{}'.format(field_num)
            namespace[name] = field.val

            # later, get value using this shortcut
            val_code = name

        elif hasattr(field, 'get'):
            # add getter-shortcut to namespace
            name = 'get{}'.format(field_num)
            namespace[name] = field.get

            # later, get value by calling this shortcut
            val_code = '{}(obj)'.format(name)

        else:
            # neither constant val nor getter: try to get value via attr
            # (if attr is not specified, use field name as attr)
            obj_attr = getattr(field, 'attr', field_name)

            if not str.isidentifier(obj_attr) or keyword.iskeyword(obj_attr):
                msg = 'Not a valid attribute name: {!r}'
                raise ValueError(msg.format(obj_attr))

            # later, get value using obj_attr
            val_code = 'obj.{}'.format(obj_attr)

        if hasattr(field, 'pack'):
            # add pack-shortcut to attributes
            name = 'pack{}'.format(field_num)
            namespace[name] = field.pack

            # later, pass result field value to this shortcut
            val_code = '{}({})'.format(name, val_code)

        return val_code, namespace

    @staticmethod
    def _dump_function_code_ns(fields, ordered):
        '''Get code/namespace-dict for a customized dump function

        Args:
            fields: An ordered mapping of field names to fields

            ordered: If True, make the resulting function return OrderedDict
                objects, else make it return ordinary dict objects

        Returns: A tuple consisting of: a) a fragment of Python code to
            define a dump function for a schema instance and b) a
            namespace-dict containing shortcuts to necessary values/functions
            for this code fragment to work.

        '''
        namespace = {}
        # get correct templates
        if ordered:
            func_tpl = textwrap.dedent(
                '''\
                def _dump_function(obj, many):
                    if many:
                        return [OrderedDict([{contents}]) for obj in obj]
                    return OrderedDict([{contents}])
                '''
            )
            entry_tpl = '("{key}", {get_val})'
        else:
            func_tpl = textwrap.dedent(
                '''\
                def _dump_function(obj, many):
                    if many:
                        return [{{{contents}}} for obj in obj]
                    return {{{contents}}}
                '''
            )
            entry_tpl = '"{key}": {get_val}'

        # one entry per field
        entries = []

        # iterate over fields to fill up entries
        for field_num, (field_name, field) in enumerate(fields.items()):
            val_code, val_namespace = Schema._field_value_code_ns(
                field, field_name, field_num
            )
            namespace.update(val_namespace)

            # try to guard against code injection via quotes in key
            key = str(field_name)
            if '"' in key or "'" in key:
                msg = 'Quotes are not allowed in field names: {}'
                raise ValueError(msg.format(key))

            # add entry
            entries.append(entry_tpl.format(key=key, get_val=val_code))
        code = func_tpl.format(contents=', '.join(entries))
        return code, namespace

    def dump(self, obj, *, many=None):
        '''Return a marshalled representation of obj.

        Args:
            obj: The object (or collection of objects) to marshall.

            many: Wether obj is a single object or a collection of objects. If
                ``many`` is ``None``, the value of the instance's
                :attr:`many` attribute is used.

        Returns:
            A representation of ``obj`` in the form of a JSON-serializable dict
            (or :class:`collections.OrderedDict` if the Schema was created with
            ``ordered==True``), with each entry corresponding to one of the
            :class:`Schema`'s fields. (Or a list of such dicts in case a
            collection of objects was marshalled)

        '''
        dump_function = self._dump_function
        if many is None:
            many = self.many
        return dump_function(obj, many)
<commit_msg>Change some comments.<commit_after>'''Schema class and related code.'''
import keyword
import textwrap
from collections import OrderedDict

from lima import abc
from lima import exc
from lima import registry
from lima import util

# Helper functions ############################################################

def _fields_from_bases(bases):
    '''Return fields determined from a list of base classes'''
    fields = OrderedDict()

    # determine base classes that are actually Schemas by checking if they
    # inherit from abc.SchemaABC
    schema_bases = [b for b in bases if (issubclass(b, abc.SchemaABC) and
                                         b != abc.SchemaABC)]

    # Add fields of base schemas. Bases listed first have precedence (to
    # reflect how python inherits class attributes). Their items are also
    # placed first in the fields OrderedDict (to reflect the order in which the
    # bases are written down in the class definition).
    for base in schema_bases:
        for k, v in base.__fields__.items():
            if k not in fields:
                fields[k] = v

    return fields

def _fields_include(fields, include):
    '''Return a copy of fields with fields in include included.'''
    util.ensure_mapping(include)
    util.ensure_only_instances_of(include, str)
    util.ensure_only_instances_of(include.values(), abc.FieldABC)
    result = fields.copy()
    result.update(include)
    return result

def _fields_exclude(fields, remove):
    '''Return a copy of fields with fields mentioned in exclude missing.'''
    util.ensure_only_instances_of(remove, str)
    util.ensure_subset_of(remove, fields)
    result = OrderedDict()
    for k, v in fields.items():
        if k not in remove:
            result[k] = v
    return result

def _fields_only(fields, only):
    '''Return a copy of fields containing only fields mentioned in only.'''
    util.ensure_only_instances_of(only, str)
    util.ensure_subset_of(only, fields)
    result = OrderedDict()
    for k, v in fields.items():
        if k in only:
            result[k] = v
    return result

def _mangle_name(name):
    '''Return mangled field name.

    Mangled field names have some name prefixes replaced with others (see
    mapping in code). This is to allow some field names with special chars in
    them to be defined via Schema class attributes.

    '''
    mapping = dict(at='@', dash='-', dot='.', hash='#', plus='+', nil='')
    if '__' not in name:
        return name
    before, after = name.split('__', 1)
    if before not in mapping:
        return name
    return mapping[before] + after

# Schema Metaclass ############################################################

class SchemaMeta(type):
    '''Metaclass of Schema.

    .. note::
       The metaclass :class:`SchemaMeta` is used internally to simplify the
       configuration of new :class:`Schema` classes. For users of the library
       there should be no need to use :class:`SchemaMeta` directly.

    When defining a new :class:`Schema` (sub)class, :class:`SchemaMeta` makes
    sure that the new class has a class attribute :attr:`__fields__` of type
    :class:`collections.OrderedDict` containing the fields for the new
    Schema.

    :attr:`__fields__` is determined like this:

    - The :attr:`__fields__` of all base classes are copied (with base classes
      specified first having precedence).

      Note that the fields themselves are *not* copied - changing an inherited
      field would change this field for all base classes referencing this field
      as well. In general it is *strongly* suggested to treat fields as
      immutable.

    - Fields (Class variables of type :class:`lima.abc.FieldABC`) are moved out
      of the class namespace and into :attr:`__fields__`, overriding any fields
      of the same name therein.

    - If present, the class attribute :attr:`__lima_args__` is removed from the
      class namespace and evaluated as follows:

      - Fields specified via ``__lima_args__['include']`` (an optional mapping
        of field names to fields) are inserted into :attr:`__fields__`.
        overriding any fields of the same name therein.

        If the order of your fields is important, make sure that
        ``__lima_args__['include']`` is of type
        :class:`collections.OrderedDict` or similar.

        New fields in ``__lima_args__['include']__`` are inserted at the
        position where ``__lima_args__`` is specified in the class.

      - Fields named in an optional sequence ``__lima_args__['exclude']`` are
        removed from :attr:`__fields__`. If only one field is to be removed,
        it's ok to supply a simple string instead of a list containing only one
        string. ``__lima_args__['exclude']`` may not be specified together with
        ``__lima_args__['only']``.

      - If in an optional sequence ``__lima_args__['only']`` is provided, *all
        but* the fields mentioned therein are removed from :attr:`__fields__`.
        If only one field is to remain, it's ok to supply a simple string
        instead of a list containing only one string. ``__lima_args__['only']``
        may not be specified together with ``__lima_args__['exclude']``.

        Think twice before using ``__lima_args__['only']`` - most of the time
        it's better to rethink your Schema than to remove a lot of fields that
        maybe shouldn't be there in the first place.

    .. versionadded:: 0.3
        Support for ``__lima_args__['only']``.

    :class:`SchemaMeta` also makes sure the new Schema class is registered with
    the lima class registry :mod:`lima.registry` (at least if the Schema isn't
    defined inside a local namespace, where we wouldn't find it later on).

    '''
    def __new__(metacls, name, bases, namespace):

        # aggregate fields from base classes
        fields = _fields_from_bases(bases)

        # pop/verify __lima_args__
        args = namespace.get('__lima_args__', {})
        with util.complain_about('__lima_args__'):
            util.ensure_mapping(args)
            util.ensure_subset_of(args, {'include', 'exclude', 'only'})
            util.ensure_only_one_of(args, {'exclude', 'only'})

        # determine individual args (include, exclude, only)
        include = args.get('include', {})
        exclude = util.vector_context(args.get('exclude', []))
        only = util.vector_context(args.get('only', []))

        # loop over copy of namespace items (we mutate namespace in this loop)
        for k, v in list(namespace.items()):
            if k == '__lima_args__':
                # at position of __lima_args__: insert include (if specified)
                if include:
                    with util.complain_about("__lima_args__['include']"):
                        fields = _fields_include(fields, include)
            elif isinstance(v, abc.FieldABC):
                # if a field was found: move it from namespace into fields
                # (also, mangle its name to allow some special field names)
                fields[_mangle_name(k)] = namespace.pop(k)

        if exclude:
            with util.complain_about('__lima_args__["exclude"]'):
                fields = _fields_exclude(fields, exclude)
        elif only:
            with util.complain_about('__lima_args__["only"]'):
                fields = _fields_only(fields, only)

        # add __fields__ to namespace
        namespace['__fields__'] = fields

        # remove __lima_args__ from namespace (if present)
        namespace.pop('__lima_args__', None)

        # Create the new class. Note that the superclass gets the altered
        # namespace as a common dict explicitly - we don't need an OrderedDict
        # namespace any more at this point.
        cls = super().__new__(metacls, name, bases, dict(namespace))

        # Try to register the new class. Classes defined in local namespaces
        # cannot be registerd. We're ok with this.
        with util.suppress(exc.RegisterLocalClassError):
            registry.global_registry.register(cls)

        # return class
        return cls

    @classmethod
    def __prepare__(metacls, name, bases):
        '''Return an OrderedDict as the class namespace.'''
        return OrderedDict()

# Schema ######################################################################

class Schema(abc.SchemaABC, metaclass=SchemaMeta):
    '''Base class for Schemas.

    Args:
        exclude: An optional sequence of field names to be removed from the
            fields of the new :class:`Schema` instance. If only one field is to
            be removed, it's ok to supply a simple string instead of a list
            containing only one string for ``exclude``. ``exclude`` may not be
            specified together with ``only``.

        only: An optional sequence of the names of the only fields that shall
            remain for the new :class:`Schema` instance.  If just one field is
            to remain, it's ok to supply a simple string instead of a list
            containing only one string for ``only``. ``only`` may not be
            specified together with ``exclude``.

        include: An optional mapping of field names to fields to additionally
            include in the new Schema instance. Think twice before using this
            option - most of the time it's better to include fields at class
            level rather than at instance level.

        ordered: An optional boolean indicating if the :meth:`Schema.dump`
            method should output :class:`collections.OrderedDict` objects
            instead of simple :class:`dict` objects.  Defaults to ``False``.
            This does not influence how nested fields are serialized.

        many: An optional boolean indicating if the new Schema will be
            serializing single objects (``many=False``) or collections of
            objects (``many=True``) per default. This can later be overridden
            in the :meth:`dump` Method.

    .. versionadded:: 0.3
        The ``include`` parameter.

    .. versionadded:: 0.3
        The ``ordered`` parameter.

    Upon creation, each Schema object gets an internal mapping of field names
    to fields. This mapping starts out as a copy of the class's
    :attr:`__fields__` attribute.  (For an explanation on how this
    :attr:`__fields__` attribute is determined, see :class:`SchemaMeta`.)

    Note that the fields themselves are not copied - changing the field of an
    instance would change this field for the other instances and classes
    referencing this field as well. In general it is *strongly* suggested to
    treat fields as immutable.

    The internal field mapping is then modified as follows:

    - If ``include`` was provided, fields specified therein are added
      (overriding any fields of the same name already present)

      If the order of your fields is important, make sure that ``include`` is
      of type :class:`collections.OrderedDict` or similar.

    - If ``exclude`` was provided, fields specified therein are removed.

    - If ``only`` was provided, *all but* the fields specified therein are
      removed (unless ``exclude`` was provided as well, in which case a
      :exc:`ValueError` is raised.)

    Also upon creation, each Schema object gets an individually created dump
    function that aims to unroll most of the loops and to minimize the number
    of attribute lookups, resulting in a little speed gain on serialization.

    :class:`Schema` classes defined outside of local namespaces can be
    referenced by name (used by :class:`lima.fields.Nested`).

    '''
    def __init__(self,
                 *,
                 exclude=None,
                 only=None,
                 include=None,
                 ordered=False,
                 many=False):
        fields = self.__class__.__fields__.copy()
        if exclude and only:
            msg = "Can't specify exclude and only at the same time."
            raise ValueError(msg)

        if include:
            with util.complain_about('include'):
                fields = _fields_include(fields, include)

        if exclude:
            with util.complain_about('exclude'):
                fields = _fields_exclude(fields, util.vector_context(exclude))
        elif only:
            with util.complain_about('only'):
                fields = _fields_only(fields, util.vector_context(only))

        self._fields = fields
        self._ordered = ordered
        self.many = many

        # get code/namespace for the customized dump function
        code, namespace = self._dump_function_code_ns(fields, ordered)

        # namespace for dump function: provide OrderedDict and nothing else
        namespace['OrderedDict'] = OrderedDict
        namespace['__builtins__'] = {}

        # define _dump_function inside namespace, then set _dump_function attr
        exec(code, namespace)
        self._dump_function = namespace['_dump_function']

    @staticmethod
    def _field_value_code_ns(field, field_name, field_num):
        '''Get code/namespace-dict to determine a field's serialized value.

        Args:
            field: A :class:`lima.fields.Field` instance.

            field_name: The name (key) of the field instance.

            field_num: A schema-wide unique number for the field.

        Returns: A tuple consisting of: a) a fragment of Python code to
            determine the field's value and b) a namespace-dict containing
            shortcuts to necessary values/functions for this code fragment to
            work.

        '''
        namespace = {}
        if hasattr(field, 'val'):
            # add constant-field-value-shortcut to namespace
            name = 'val{}'.format(field_num)
            namespace[name] = field.val

            # later, get value using this shortcut
            val_code = name

        elif hasattr(field, 'get'):
            # add getter-shortcut to namespace
            name = 'get{}'.format(field_num)
            namespace[name] = field.get

            # later, get value by calling this shortcut
            val_code = '{}(obj)'.format(name)

        else:
            # neither constant val nor getter: try to get value via attr
            # (if attr is not specified, use field name as attr)
            obj_attr = getattr(field, 'attr', field_name)

            if not str.isidentifier(obj_attr) or keyword.iskeyword(obj_attr):
                msg = 'Not a valid attribute name: {!r}'
                raise ValueError(msg.format(obj_attr))

            # later, get value using obj_attr
            val_code = 'obj.{}'.format(obj_attr)

        if hasattr(field, 'pack'):
            # add pack-shortcut to attributes
            name = 'pack{}'.format(field_num)
            namespace[name] = field.pack

            # later, pass result field value to this shortcut
            val_code = '{}({})'.format(name, val_code)

        return val_code, namespace

    @staticmethod
    def _dump_function_code_ns(fields, ordered):
        '''Get code/namespace-dict for a customized dump function

        Args:
            fields: An ordered mapping of field names to fields

            ordered: If True, make the resulting function return OrderedDict
                objects, else make it return ordinary dict objects

        Returns: A tuple consisting of: a) a fragment of Python code to
            define a dump function for a schema instance and b) a
            namespace-dict containing shortcuts to necessary values/functions
            for this code fragment to work.

        '''
        namespace = {}
        # get correct templates
        if ordered:
            func_tpl = textwrap.dedent(
                '''\
                def _dump_function(obj, many):
                    if many:
                        return [OrderedDict([{contents}]) for obj in obj]
                    return OrderedDict([{contents}])
                '''
            )
            entry_tpl = '("{key}", {get_val})'
        else:
            func_tpl = textwrap.dedent(
                '''\
                def _dump_function(obj, many):
                    if many:
                        return [{{{contents}}} for obj in obj]
                    return {{{contents}}}
                '''
            )
            entry_tpl = '"{key}": {get_val}'

        # one entry per field
        entries = []

        # iterate over fields to fill up entries
        for field_num, (field_name, field) in enumerate(fields.items()):
            val_code, val_namespace = Schema._field_value_code_ns(
                field, field_name, field_num
            )
            namespace.update(val_namespace)

            # try to guard against code injection via quotes in key
            key = str(field_name)
            if '"' in key or "'" in key:
                msg = 'Quotes are not allowed in field names: {}'
                raise ValueError(msg.format(key))

            # add entry
            entries.append(entry_tpl.format(key=key, get_val=val_code))
        code = func_tpl.format(contents=', '.join(entries))
        return code, namespace

    def dump(self, obj, *, many=None):
        '''Return a marshalled representation of obj.

        Args:
            obj: The object (or collection of objects) to marshall.

            many: Wether obj is a single object or a collection of objects. If
                ``many`` is ``None``, the value of the instance's
                :attr:`many` attribute is used.

        Returns:
            A representation of ``obj`` in the form of a JSON-serializable dict
            (or :class:`collections.OrderedDict` if the Schema was created with
            ``ordered==True``), with each entry corresponding to one of the
            :class:`Schema`'s fields. (Or a list of such dicts in case a
            collection of objects was marshalled)

        '''
        dump_function = self._dump_function
        if many is None:
            many = self.many
        return dump_function(obj, many)
<|endoftext|>