コード例 #1
0
ファイル: background.py プロジェクト: denis-ryzhkov/apiphant
def main():

    #### become cooperative

    import gevent.monkey
    gevent.monkey.patch_all()

    #### import

    from apiphant.paths import init_paths
    from gevent import sleep
    import logging, sys, time
    from traceback import format_exc

    #### command-line config

    usage = 'Usage: apiphant-background path/to/myproduct'

    try:
        _, product_path = sys.argv

    except ValueError:
        exit(usage)

    #### paths

    api_path, product_name = init_paths(product_path)

    #### logging

    logging.basicConfig(level=logging.DEBUG, format='%(levelname)s at %(module)s.%(funcName)s:%(lineno)d [%(asctime)s] %(message)s') # To sys.stderr by default.

    #### import myproduct.api.background to init tasks[] with @seconds

    product_module_name = '{product_name}.api.background'.format(product_name=product_name)
    product_module = __import__(product_module_name, globals(), locals())
    on_error = getattr(product_module.api.background, 'on_error', None)

    #### loop

    logging.info('\nApiphant is scheduling {product_module_name}\n'.format(product_module_name=product_module_name))

    while True:

        #### Select tasks ready to run.

        now = time.time()

        ready_tasks = [
            task
            for task in tasks
            if now - task.last >= task.seconds
        ]

        #### Sort to respect desired "seconds" as possible with non-overlapping mode.

        ready_tasks.sort(key=lambda task: task.seconds)

        #### Run them one by one, non-overlapping, safe, logging.

        for task in ready_tasks:
            task.last = now # Even if it fails, we don't want to retry before the next time it should run.

            try:
                task.task()

            except:
                error = 'Task {task} failed:\n{traceback}'.format(task=task.name, traceback=format_exc())
                logging.error(error)

                #### on_error
 
                if on_error: # E.g. send_email_message(to=email_config['user'], subject='Error', text=error, **email_config)
                    try:
                        on_error(error)
 
                    except:
                        logging.error('on_error failed:\n{traceback}'.format(traceback=format_exc()))

                    else:
                        logging.info('on_error: OK.')

            else:
                logging.info('Task {task}: OK.'.format(task=task.name))

        #### Minimal 1-second sleep to not make the loop too tight.

        sleep(1)
コード例 #2
0
ファイル: server.py プロジェクト: denis-ryzhkov/apiphant
def serve(product_path, host, port):

    #### become cooperative

    import gevent.monkey
    gevent.monkey.patch_all()

    #### import

    from adict import adict
    from apiphant.paths import init_paths
    from apiphant.validation import ApiError, status_by_code
    import gevent.wsgi
    import json, logging, os, re, sys
    from traceback import print_exc

    #### paths

    api_path, product_name = init_paths(product_path)

    #### routes

    def routes():

        '''Creates map of routes from PATH_INFO to action().'''

        routes = dict()
        version_re = re.compile('^v\d+$')
        py_extension = '.py'

        for version in os.listdir(api_path):
            version_path = os.path.join(api_path, version)
            if not os.path.isdir(version_path) or not version_re.match(version):
                continue

            for target_file_name in os.listdir(version_path):
                if not target_file_name.endswith(py_extension):
                    continue

                target_name = target_file_name[:-len(py_extension)]
                # TODO: Add support for nested t/a/r/g/e/t-s.
                target_module = __import__(
                    '{product_name}.api.{version}.{target_name}'.format(
                        product_name=product_name,
                        version=version,
                        target_name=target_name,
                    ),
                    globals(), locals(),
                )

                for action_name in 'create', 'read', 'update', 'delete':
                    action = getattr(getattr(getattr(target_module.api, version), target_name), action_name, None)
                    if not action:
                        continue

                    routes['/api/{version}/{target_name}/{action_name}'.format(**locals())] = action

        return routes

    routes = routes()

    #### app

    def app(environ, start_response):

        status = response = raw_response = ''
        try:

            action = routes.get(environ['PATH_INFO'])
            if not action:
                raise ApiError(404)

            raw_response = hasattr(action, 'raw_response')

            #### raw environ

            if hasattr(action, 'raw_environ'):

                if raw_response:
                    response = action(environ, start_response)
                else:
                    response = action(environ)

            #### normal request

            else:

                if environ['REQUEST_METHOD'] != 'POST': # See README.md.
                    raise ApiError(501)

                request = environ['wsgi.input'].read()

                try:
                    request = json.loads(request) if request else {}
                    if not isinstance(request, dict):
                        raise ValueError

                except ValueError:
                    raise ApiError(400, 'Request content should be JSON Object') # See README.md.

                request = adict(request)

                if raw_response:
                    response = action(request, start_response)
                else:
                    response = action(request)

            #### normal response

            if not raw_response:

                if response is None:
                    response = {}

                if not isinstance(response, dict):
                    raise Exception('Response content should be JSON Object') # See README.md.

                status = status_by_code[200]

        #### Expected error, no logging.

        except ApiError as e:
            status = status_by_code[e.status_code]
            response = dict(error=(e.error or status))

        #### Unexpected error, traceback is logged.

        except:
            print_exc() # To sys.stderr.
            status = status_by_code[500]
            response = dict(error=status) # Server error details are saved to log and not disclosed to a client.

        #### Response.

        finally:

            #### raw response

            if raw_response:
                return response

            #### normal response

            start_response(status, headers=[
                ('Content-Type', 'application/json'),
            ])
            return [
                json.dumps(response),
            ]

    #### logging

    logging.basicConfig(level=logging.DEBUG, format='%(levelname)s at %(module)s.%(funcName)s:%(lineno)d [%(asctime)s] %(message)s') # To sys.stderr by default.
    logging.info('\nApiphant is serving {api_path} at http://{host}:{port}/api\n'.format(**locals()))

    #### gevent.serve

    gevent.wsgi.WSGIServer((host, port), app).serve_forever()