Example #1
0
def create_app(tax_benefit_system,
               tracker_url = None,
               tracker_idsite = None,
               tracker_token = None,
               welcome_message = None,
               ):

    if not tracker_url or not tracker_idsite:
        tracker = None
    else:
        tracker = init_tracker(tracker_url, tracker_idsite, tracker_token)

    app = Flask(__name__)
    # Fix request.remote_addr to get the real client IP address
    app.wsgi_app = ProxyFix(app.wsgi_app, x_for = 1, x_host = 1)
    CORS(app, origins = '*')

    app.config['JSON_AS_ASCII'] = False  # When False, lets jsonify encode to utf-8
    app.url_map.strict_slashes = False  # Accept url like /parameters/
    app.url_map.merge_slashes = False  # Do not eliminate // in paths
    app.config['JSON_SORT_KEYS'] = False  # Don't sort JSON keys in the Web API

    data = build_data(tax_benefit_system)

    DEFAULT_WELCOME_MESSAGE = "This is the root of an OpenFisca Web API. To learn how to use it, check the general documentation (https://openfisca.org/doc/) and the OpenAPI specification of this instance ({}spec)."

    @app.route('/')
    def get_root():
        return jsonify({
            'welcome': welcome_message or DEFAULT_WELCOME_MESSAGE.format(request.host_url)
            }), 300

    @app.route('/parameters')
    def get_parameters():
        parameters = {
            parameter['id']: {
                'description': parameter['description'],
                'href': '{}parameter/{}'.format(request.host_url, name)
                }
            for name, parameter in data['parameters'].items()
            if parameter.get('subparams') is None  # For now and for backward compat, don't show nodes in overview
            }

        return jsonify(parameters)

    @app.route('/parameter/<path:parameter_id>')
    def get_parameter(parameter_id):
        parameter = data['parameters'].get(parameter_id)
        if parameter is None:
            # Try legacy route
            parameter_new_id = parameter_id.replace('.', '/')
            parameter = data['parameters'].get(parameter_new_id)
        if parameter is None:
            raise abort(404)
        return jsonify(parameter)

    @app.route('/variables')
    def get_variables():
        variables = {
            name: {
                'description': variable['description'],
                'href': '{}variable/{}'.format(request.host_url, name)
                }
            for name, variable in data['variables'].items()
            }
        return jsonify(variables)

    @app.route('/variable/<id>')
    def get_variable(id):
        variable = data['variables'].get(id)
        if variable is None:
            raise abort(404)
        return jsonify(variable)

    @app.route('/entities')
    def get_entities():
        return jsonify(data['entities'])

    @app.route('/spec')
    def get_spec():
        return jsonify({
            **data['openAPI_spec'],
            **{'host': request.host},
            **{'schemes': [request.environ['wsgi.url_scheme']]}
            })

    def handle_invalid_json(error):
        json_response = jsonify({
            'error': 'Invalid JSON: {}'.format(error.args[0]),
            })

        abort(make_response(json_response, 400))

    @app.route('/calculate', methods=['POST'])
    def calculate():
        tax_benefit_system = data['tax_benefit_system']
        request.on_json_loading_failed = handle_invalid_json
        input_data = request.get_json()
        try:
            result = handlers.calculate(tax_benefit_system, input_data)
        except SituationParsingError as e:
            abort(make_response(jsonify(e.error), e.code or 400))
        except UnicodeEncodeError as e:
            abort(make_response(jsonify({"error": "'" + e[1] + "' is not a valid ASCII value."}), 400))
        return jsonify(result)

    @app.route('/trace', methods=['POST'])
    def trace():
        tax_benefit_system = data['tax_benefit_system']
        request.on_json_loading_failed = handle_invalid_json
        input_data = request.get_json()
        try:
            result = handlers.trace(tax_benefit_system, input_data)
        except SituationParsingError as e:
            abort(make_response(jsonify(e.error), e.code or 400))
        return jsonify(result)

    @app.after_request
    def apply_headers(response):
        response.headers.extend({
            'Country-Package': data['country_package_metadata']['name'],
            'Country-Package-Version': data['country_package_metadata']['version']
            })
        return response

    @app.after_request
    def track_requests(response):

        if tracker:
            if request.headers.get('dnt'):
                source_ip = ""
            elif request.headers.get('X-Forwarded-For'):
                source_ip = request.headers['X-Forwarded-For'].split(', ')[0]
            else:
                source_ip = request.remote_addr

            api_version = "{}-{}".format(data['country_package_metadata']['name'],
                                         data['country_package_metadata']['version'])

            tracker.track(request.url, source_ip, api_version, request.path)
        return response

    @app.errorhandler(500)
    def internal_server_error(e):
        message = str(e.args[0])
        if type(e) == UnicodeEncodeError or type(e) == UnicodeDecodeError:
            response = jsonify({"error": "Internal server error: '" + e[1] + "' is not a valid ASCII value."})
        elif message:
            response = jsonify({"error": "Internal server error: " + message.strip(os.linesep).replace(os.linesep, ' ')})
        else:
            response = jsonify({"error": "Internal server error: " + str(e)})
        response.status_code = 500
        return response

    return app
Example #2
0
def create_app(
    tax_benefit_system,
    tracker_url=None,
    tracker_idsite=None,
    tracker_token=None,
    welcome_message=None,
):

    if not tracker_url or not tracker_idsite:
        tracker = None
    else:
        tracker = init_tracker(tracker_url, tracker_idsite, tracker_token)

    app = Flask(__name__)
    app.wsgi_app = ProxyFix(
        app.wsgi_app, num_proxies=1
    )  # Fix request.remote_addr to get the real client IP address
    CORS(app, origins='*')

    app.config[
        'JSON_AS_ASCII'] = False  # When False, lets jsonify encode to utf-8
    app.url_map.strict_slashes = False  # Accept url like /parameters/

    data = build_data(tax_benefit_system)

    DEFAULT_WELCOME_MESSAGE = "This is the root of an OpenFisca Web API. To learn how to use it, check the general documentation (https://openfisca.org/doc/api) and the OpenAPI specification of this instance ({}spec)."

    @app.route('/')
    def get_root():
        return jsonify({
            'welcome':
            welcome_message or DEFAULT_WELCOME_MESSAGE.format(request.host_url)
        }), 300

    @app.route('/parameters')
    def get_parameters():
        parameters = {
            parameter['id']: {
                'description': parameter['description'],
                'href': '{}parameter/{}'.format(request.host_url, name)
            }
            for name, parameter in data['parameters'].items()
            if parameter.get('subparams') is
            None  # For now and for backward compat, don't show nodes in overview
        }

        return jsonify(parameters)

    @app.route('/parameter/<path:parameter_id>')
    def get_parameter(parameter_id):
        parameter = data['parameters'].get(parameter_id)
        if parameter is None:
            # Try legacy route
            parameter_new_id = parameter_id.replace('.', '/')
            parameter = data['parameters'].get(parameter_new_id)
        if parameter is None:
            raise abort(404)
        return jsonify(parameter)

    @app.route('/variables')
    def get_variables():
        variables = {
            name: {
                'description': variable['description'],
                'href': '{}variable/{}'.format(request.host_url, name)
            }
            for name, variable in data['variables'].items()
        }
        return jsonify(variables)

    @app.route('/variable/<id>')
    def get_variable(id):
        variable = data['variables'].get(id)
        if variable is None:
            raise abort(404)
        return jsonify(variable)

    @app.route('/entities')
    def get_entities():
        return jsonify(data['entities'])

    @app.route('/spec')
    def get_spec():
        # Ugly Python2-compatible way
        response = {}
        response.update(data['openAPI_spec'])
        response.update({'host': request.host_url})
        return jsonify(response)

        # Nice Python3 syntax, but doesn't work in Python 2
        # return jsonify({**data['openAPI_spec'], **{'host': request.host_url}})

    def handle_invalid_json(error):
        json_response = jsonify({
            'error':
            'Invalid JSON: {}'.format(error.args[0]),
        })

        abort(make_response(json_response, 400))

    @app.route('/calculate', methods=['POST'])
    def calculate():
        tax_benefit_system = data['tax_benefit_system']
        request.on_json_loading_failed = handle_invalid_json
        input_data = request.get_json()
        try:
            simulation = Simulation(tax_benefit_system=tax_benefit_system,
                                    simulation_json=input_data)
        except SituationParsingError as e:
            abort(make_response(jsonify(e.error), e.code or 400))
        except UnicodeEncodeError as e:
            abort(
                make_response(
                    jsonify({
                        "error":
                        "'" + e[1] + "' is not a valid ASCII value."
                    }), 400))

        requested_computations = dpath.util.search(input_data,
                                                   '*/*/*/*',
                                                   afilter=lambda t: t is None,
                                                   yielded=True)
        results = {}

        try:
            for computation in requested_computations:
                path = computation[0]
                entity_plural, entity_id, variable_name, period = path.split(
                    '/')
                variable = tax_benefit_system.get_variable(variable_name)
                result = simulation.calculate(variable_name, period)
                entity = simulation.get_entity(plural=entity_plural)
                entity_index = entity.ids.index(entity_id)

                if variable.value_type == Enum:
                    entity_result = result.decode()[entity_index].name
                elif variable.value_type == float:
                    entity_result = float(
                        str(result[entity_index])
                    )  # To turn the float32 into a regular float without adding confusing extra decimals. There must be a better way.
                elif variable.value_type == str:
                    entity_result = to_unicode(
                        result[entity_index])  # From bytes to unicode
                else:
                    entity_result = result.tolist()[entity_index]

                dpath.util.new(results, path, entity_result)

        except UnicodeEncodeError as e:
            abort(
                make_response(
                    jsonify({
                        "error":
                        "'" + e[1] + "' is not a valid ASCII value."
                    }), 400))

        dpath.util.merge(input_data, results)

        return jsonify(input_data)

    @app.route('/trace', methods=['POST'])
    def trace():
        tax_benefit_system = data['tax_benefit_system']
        request.on_json_loading_failed = handle_invalid_json
        input_data = request.get_json()
        try:
            simulation = Simulation(tax_benefit_system=tax_benefit_system,
                                    simulation_json=input_data,
                                    trace=True)
        except SituationParsingError as e:
            abort(make_response(jsonify(e.error), e.code or 400))

        requested_computations = dpath.util.search(input_data,
                                                   '*/*/*/*',
                                                   afilter=lambda t: t is None,
                                                   yielded=True)
        for computation in requested_computations:
            path = computation[0]
            entity_plural, entity_id, variable_name, period = path.split('/')
            simulation.calculate(variable_name, period)

        trace = deepcopy(simulation.tracer.trace)
        for vector_key, vector_trace in trace.items():
            value = vector_trace['value'].tolist()
            if isinstance(value[0], Enum):
                value = [item.name for item in value]
            if isinstance(value[0], bytes):
                value = [to_unicode(item) for item in value]
            vector_trace['value'] = value

        return jsonify({
            "trace":
            trace,
            "entitiesDescription": {
                entity.plural: entity.ids
                for entity in simulation.entities.values()
            },
            "requestedCalculations":
            list(simulation.tracer.requested_calculations)
        })

    @app.after_request
    def apply_headers(response):
        response.headers.extend({
            'Country-Package':
            data['country_package_metadata']['name'],
            'Country-Package-Version':
            data['country_package_metadata']['version']
        })
        return response

    @app.after_request
    def track_requests(response):

        if tracker:
            if request.headers.get('dnt'):
                source_ip = ""
            elif request.headers.get('X-Forwarded-For'):
                source_ip = request.headers['X-Forwarded-For'].split(', ')[0]
            else:
                source_ip = request.remote_addr

            api_version = "{}-{}".format(
                data['country_package_metadata']['name'],
                data['country_package_metadata']['version'])

            tracker.track(request.url, source_ip, api_version, request.path)
        return response

    @app.errorhandler(500)
    def internal_server_error(e):
        message = to_unicode(e.args[0])
        if type(e) == UnicodeEncodeError or type(e) == UnicodeDecodeError:
            response = jsonify({
                "error":
                "Internal server error: '" + e[1] +
                "' is not a valid ASCII value."
            })
        elif message:
            response = jsonify({
                "error":
                "Internal server error: " +
                message.strip(os.linesep).replace(os.linesep, ' ')
            })
        else:
            response = jsonify({"error": "Internal server error: " + str(e)})
        response.status_code = 500
        return response

    return app