Skip to content
This repository has been archived by the owner on Jun 22, 2020. It is now read-only.

Commit

Permalink
Make parameters node accessible through the Web API
Browse files Browse the repository at this point in the history
  • Loading branch information
fpagnoux committed Aug 3, 2018
2 parents a044a30 + 608d561 commit ae9ffdb
Show file tree
Hide file tree
Showing 17 changed files with 242 additions and 164 deletions.
48 changes: 47 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,51 @@
# Changelog

## 23.4.0 [#694](https://github.com/openfisca/openfisca-core/pull/694)

* Use `/` rather than `.` in the path to access a parameter:
- For instance `/parameter/benefits.basic_income` becomes `/parameter/benefits/basic_income`
- Using `.` is for now still supported, but is considered deprecated and will be turned to a 301 redirection in the next major version.

* Expose parameters `metadata` and `source` in the Web API and:

For instance, `/parameter/benefits/basic_income` contains:

```JSON
{
"description": "Amount of the basic income",
"id": "benefits.basic_income",
"metadata": {
"reference": "https://law.gov.example/basic-income/amount",
"unit": "currency-EUR"
},
"source": "https://github.com/openfisca/country-template/blob/3.2.2/openfisca_country_template/parameters/benefits/basic_income.yaml",
"values": {
"2015-12-01": 600.0
}
}
```

* Expose parameters nodes in the Web API
- For instance, `/parameter/benefits` now exists and contains:

```JSON
{
"description": "Social benefits",
"id": "benefits",
"metadata": {},
"source": "https://github.com/openfisca/country-template/blob/3.2.2/openfisca_country_template/parameters/benefits",
"subparams": {
"basic_income": {
"description": "Amount of the basic income"
},
"housing_allowance": {
"description": "Housing allowance amount (as a fraction of the rent)"
}
}
}
```

Note that this route doesn't _recursively_ explore the node, and only exposes its direct children name and description.

### 23.3.2 [#702](https://github.com/openfisca/openfisca-core/pull/702)

Expand All @@ -13,7 +59,7 @@ Minor Change without any impact for country package developers and users:

* Send reference of the country-package and its version to the tracker so it will appear in the tracking statistics.

### 23.3.0 [#681](https://github.com/openfisca/openfisca-core/pull/681)
## 23.3.0 [#681](https://github.com/openfisca/openfisca-core/pull/681)

* Change the way metadata are declared for Parameter.

Expand Down
10 changes: 7 additions & 3 deletions openfisca_web_api_preview/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,13 @@ def create_app(tax_benefit_system,
def get_parameters():
return jsonify(data['parameters_description'])

@app.route('/parameter/<id>')
def get_parameter(id):
parameter = data['parameters'].get(id)
@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)
Expand Down
2 changes: 1 addition & 1 deletion openfisca_web_api_preview/loader/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def extract_description(items):

def build_data(tax_benefit_system):
country_package_metadata = tax_benefit_system.get_package_metadata()
parameters = build_parameters(tax_benefit_system)
parameters = build_parameters(tax_benefit_system, country_package_metadata)
variables = build_variables(tax_benefit_system, country_package_metadata)
openAPI_spec = build_openAPI_specification(tax_benefit_system, country_package_metadata)
return {
Expand Down
74 changes: 46 additions & 28 deletions openfisca_web_api_preview/loader/parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@
from openfisca_core.parameters import Parameter, ParameterNode, Scale


def transform_values_history(values_history):
values_history_transformed = {}
def build_api_values_history(values_history):
api_values_history = {}
for value_at_instant in values_history.values_list:
values_history_transformed[value_at_instant.instant_str] = value_at_instant.value
api_values_history[value_at_instant.instant_str] = value_at_instant.value

return values_history_transformed
return api_values_history


def get_value(date, values):
Expand All @@ -25,61 +25,79 @@ def get_value(date, values):
return None


def transform_scale(scale):
def build_api_scale(scale):
# preprocess brackets
brackets = [{
'thresholds': transform_values_history(bracket.threshold),
'rates': transform_values_history(bracket.rate),
'thresholds': build_api_values_history(bracket.threshold),
'rates': build_api_values_history(bracket.rate),
} for bracket in scale.brackets]

dates = set(sum(
[list(bracket['thresholds'].keys()) + list(bracket['rates'].keys()) for bracket in brackets],
[])) # flatten the dates and remove duplicates

# We iterate on all dates as we need to build the whole scale for each of them
brackets_transformed = {}
api_scale = {}
for date in dates:
for bracket in brackets:
threshold_value = get_value(date, bracket['thresholds'])
if threshold_value is not None:
rate_value = get_value(date, bracket['rates'])
brackets_transformed[date] = brackets_transformed.get(date) or {}
brackets_transformed[date][threshold_value] = rate_value
api_scale[date] = api_scale.get(date) or {}
api_scale[date][threshold_value] = rate_value

# Handle stopped parameters: a parameter is stopped if its first bracket is stopped
latest_date_first_threshold = max(brackets[0]['thresholds'].keys())
latest_value_first_threshold = brackets[0]['thresholds'][latest_date_first_threshold]
if latest_value_first_threshold is None:
brackets_transformed[latest_date_first_threshold] = None
api_scale[latest_date_first_threshold] = None

return brackets_transformed
return api_scale


def walk_node(node, parameters, path_fragments):
def build_source_url(absolute_file_path, country_package_metadata):
relative_path = absolute_file_path.replace(country_package_metadata['location'], '')
return '{}/blob/{}{}'.format(
country_package_metadata['repository_url'],
country_package_metadata['version'],
relative_path
)


def walk_node(node, parameters, path_fragments, country_package_metadata):
children = node.children

for child_name, child in children.items():
if isinstance(child, ParameterNode):
walk_node(child, parameters, path_fragments + [child_name])
else:
object_transformed = {
'description': getattr(child, "description", None),
'id': '.'.join(path_fragments + [child_name]),
api_parameter = {
'description': getattr(child, "description", None),
'id': '.'.join(path_fragments + [child_name]),
'metadata': child.metadata
}
if child.file_path:
api_parameter['source'] = build_source_url(child.file_path, country_package_metadata)
if isinstance(child, Parameter):
api_parameter['values'] = build_api_values_history(child)
elif isinstance(child, Scale):
api_parameter['brackets'] = build_api_scale(child)
elif isinstance(child, ParameterNode):
api_parameter['subparams'] = {
grandchild_name: {
'description': grandchild.description,
}
for grandchild_name, grandchild in child.children.items()
}
if isinstance(child, Scale):
object_transformed['brackets'] = transform_scale(child)
elif isinstance(child, Parameter):
object_transformed['values'] = transform_values_history(child)
parameters.append(object_transformed)
walk_node(child, parameters, path_fragments + [child_name], country_package_metadata)
parameters.append(api_parameter)


def build_parameters(tax_benefit_system):
def build_parameters(tax_benefit_system, country_package_metadata):
original_parameters = tax_benefit_system.parameters
transformed_parameters = []
api_parameters = []
walk_node(
original_parameters,
parameters = transformed_parameters,
parameters = api_parameters,
path_fragments = [],
country_package_metadata = country_package_metadata,
)

return {parameter['id']: parameter for parameter in transformed_parameters}
return {parameter['id'].replace('.', '/'): parameter for parameter in api_parameters}
14 changes: 14 additions & 0 deletions openfisca_web_api_preview/openAPI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -194,11 +194,22 @@ definitions:
type: "object"
additionalProperties:
$ref: "#/definitions/Brackets"
subparams:
type: "object"
additionalProperties:
type: "object"
properties:
definition:
type: 'string'
metadata:
type: "object"
description:
type: "string"
id:
type: "integer"
format: "string"
source:
type: "string"
example:
id: "cotsoc.gen.smic_h_b"
description: "SMIC horaire brut"
Expand All @@ -222,6 +233,9 @@ definitions:
"2015-01-01": 9.61,
"2016-01-01": 9.67,
"2017-01-01": 9.76}
source: https://github.com/openfisca/openfisca-france/blob/22.2.1/openfisca_france/parameters/cotsoc/gen/smic_h_b.yaml
metadata:
unit: "currency-EUR"

Parameters:
type: "object"
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

setup(
name = 'OpenFisca-Core',
version = '23.3.2',
version = '23.4.0',
author = 'OpenFisca Team',
author_email = 'contact@openfisca.fr',
classifiers = [
Expand Down
11 changes: 11 additions & 0 deletions tests/web_api/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# -*- coding: utf-8 -*-

from __future__ import unicode_literals, print_function, division, absolute_import
import pkg_resources
from openfisca_web_api_preview.app import create_app
from openfisca_core.scripts import build_tax_benefit_system

TEST_COUNTRY_PACKAGE_NAME = 'openfisca_country_template'
distribution = pkg_resources.get_distribution(TEST_COUNTRY_PACKAGE_NAME)
tax_benefit_system = build_tax_benefit_system(TEST_COUNTRY_PACKAGE_NAME, extensions = None, reforms = None)
subject = create_app(tax_benefit_system).test_client()
125 changes: 0 additions & 125 deletions tests/web_api/basic_case/test_parameters.py

This file was deleted.

File renamed without changes.

0 comments on commit ae9ffdb

Please sign in to comment.