api.py 10.1 KB
Newer Older
1
import datetime
David Foucher's avatar
David Foucher committed
2
import os
David Foucher's avatar
David Foucher committed
3
import re
Yohan Boniface's avatar
Yohan Boniface committed
4 5
from http import HTTPStatus

6 7
import gitlab

8
from roll import HttpError, Roll
Yohan Boniface's avatar
Yohan Boniface committed
9
from roll.extensions import cors
David Foucher's avatar
David Foucher committed
10

David Foucher's avatar
David Foucher committed
11
import ujson as json
Yohan Boniface's avatar
Yohan Boniface committed
12

David Foucher's avatar
David Foucher committed
13 14
from . import VERSION, get_financements, get_remunerations, simulate
from . import routine
David Foucher's avatar
David Foucher committed
15
from .config import AUTHORIZED, FINANCEMENTS, COMMIT_AUTHORIZED, GLOSSARY, IDCC, NAF, CERTIFINFO, RAW_RULES, SCHEMA, GITLAB_TOKEN
David Foucher's avatar
David Foucher committed
16 17
from .context import Context
from .debugging import SCENARIOS, data_from_lbf_url, make_scenario
David Foucher's avatar
David Foucher committed
18
from .exceptions import DataError, NotModifiedError, UnauthorizedAccess
19
from .helpers import flatten, fold_name
David Foucher's avatar
David Foucher committed
20
from .legacy import simulate_legacy
21
from .loggers import log_simulate, logger
Yohan Boniface's avatar
Yohan Boniface committed
22
from .openapis import OPENAPI
23
from .routine import get_formation_json, search_term
24
from .source import submit_modification
Yohan Boniface's avatar
Yohan Boniface committed
25 26

app = Roll()
Yohan Boniface's avatar
Yohan Boniface committed
27
cors(app)
Yohan Boniface's avatar
Yohan Boniface committed
28 29


David Foucher's avatar
Black  
David Foucher committed
30
@app.listen("response")
Yohan Boniface's avatar
Yohan Boniface committed
31
async def expose_version(request, response):
David Foucher's avatar
Black  
David Foucher committed
32
    response.headers["Version"] = VERSION
Yohan Boniface's avatar
Yohan Boniface committed
33 34


David Foucher's avatar
Black  
David Foucher committed
35
@app.listen("error")
36
async def json_error_response(request, response, error):
Régis Behmo's avatar
Régis Behmo committed
37
    if isinstance(error.message, (str, bytes)):
David Foucher's avatar
Black  
David Foucher committed
38
        error.message = {"error": error.message}
Régis Behmo's avatar
Régis Behmo committed
39
    response.json = error.message
Yohan Boniface's avatar
Yohan Boniface committed
40
    if error.status != HTTPStatus.NOT_FOUND:
David Foucher's avatar
Black  
David Foucher committed
41 42
        logger.debug(
            f"HttpError: status={error.status}, version={VERSION}, "
43
            f"message={response.body}, request={request.body}, url={request.url}"
David Foucher's avatar
Black  
David Foucher committed
44
        )
45 46


47 48 49 50 51 52 53 54 55 56
@app.route("/healthcheck")
async def healthcheck(request, response):
    response.json = {
        "status": "running",
        "version": f"{VERSION}",
        "last version": f"{os.environ.get('VERSION', 'NaN')}",
        "back version": f"{os.environ.get('OLD_VERSION', 'NaN')}",
    }


David Foucher's avatar
Black  
David Foucher committed
57
@app.route("/financement", methods=["POST"])
58
async def simulate_(request, response):
59
    context = request.json
David Foucher's avatar
Black  
David Foucher committed
60
    financements = get_financements(tags=request.query.list("tags", []))
Yohan Boniface's avatar
Yohan Boniface committed
61
    try:
62
        await simulate(context, financements)
Yohan Boniface's avatar
Yohan Boniface committed
63 64 65 66
    except DataError as err:
        error = {err.key: err.error}
        log_simulate(context, errors=error)
        raise HttpError(HTTPStatus.UNPROCESSABLE_ENTITY, error)
Régis Behmo's avatar
Régis Behmo committed
67

David Foucher's avatar
David Foucher committed
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
    eligible = request.query.bool("eligible", None)
    if eligible is not None:
        financements = [f for f in financements if f["eligible"] == eligible]
    else:
        financements = sorted(
            financements, key=lambda value: value["eligible"], reverse=True
        )

    explain = request.query.bool("explain", False)
    for financement in financements:
        financement["explain"] = (
            [s.json for s in financement["explain"]] if explain else None
        )
    body = {"financements": financements}
    if request.query.bool("context", False):
        body["context"] = {
            k: v for k, v in context.items()
            if k in SCHEMA and "label" in SCHEMA[k]}
    if request.query.bool("scenario", False):
        body["scenario"] = make_scenario(context, financements)
    response.json = body

    log_simulate(context, financements=financements)


David Foucher's avatar
David Foucher committed
93
# TODO : add pointer error for bad region number + test
David Foucher's avatar
David Foucher committed
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131
@app.route("/remuneration", methods=["POST"])
async def remuneration_(request, response):
    data = request.json
    remunerations = get_remunerations(tags=request.query.list("tags", []))
    try:
        flatten(data)
        context = Context(data.copy())
        routine.extrapolate_context(context)
        routine.preprocess(context)
        for remuneration in remunerations:
            copy = context.copy()
            routine.check_remuneration(context, remuneration)
            data.update(copy.cleaned_data)

        # FIXME (limits of the single-store-all context object)
        # Clean keys not meant to be exposed
        for key in list(data.keys()):
            if key.startswith("remuneration"):
                del data[key]

    except DataError as err:
        error = {err.key: err.error}
        log_simulate(context, errors=error)
        raise HttpError(HTTPStatus.UNPROCESSABLE_ENTITY, error)

    # TODO: explain only for financement see routine.py check_remuneration
    # explain = request.query.bool("explain", False)
    # for remuneration in remunerations:
    #     remuneration["explain"] = (
    #         [s.json for s in remunerations["explain"]] if explain else None
    #     )

    body = {"remunerations": remunerations}
    # if request.query.bool("context", False):
    #     body["context"] = {
    #         k: v for k, v in context.items()
    #         if k in SCHEMA and "label" in SCHEMA[k]}
    response.json = body
Régis Behmo's avatar
Régis Behmo committed
132

Yohan Boniface's avatar
Yohan Boniface committed
133

David Foucher's avatar
Black  
David Foucher committed
134
app.route("/legacy", methods=["POST"])(simulate_legacy)
David Foucher's avatar
David Foucher committed
135 136


David Foucher's avatar
Black  
David Foucher committed
137
@app.route("/schema")
Yohan Boniface's avatar
Yohan Boniface committed
138
async def schema(request, response):
Yohan Boniface's avatar
Yohan Boniface committed
139
    response.json = OPENAPI
Yohan Boniface's avatar
Yohan Boniface committed
140 141


David Foucher's avatar
Black  
David Foucher committed
142
@app.route("/naf")
Yohan Boniface's avatar
Yohan Boniface committed
143
async def naf(request, response):
144
    response.json = search_term(NAF, request.query.get("q"))
Yohan Boniface's avatar
Yohan Boniface committed
145 146


David Foucher's avatar
David Foucher committed
147 148
@app.route("/idcc")
async def idcc(request, response):
149
    response.json = search_term(IDCC, request.query.get("q"))
David Foucher's avatar
David Foucher committed
150 151 152 153 154


@app.route("/certifinfo")
async def certifinfo(request, response):
    response.json = search_term(CERTIFINFO, request.query.get("q"))
David Foucher's avatar
David Foucher committed
155 156


David Foucher's avatar
Black  
David Foucher committed
157
@app.route("/explore/schema")
Yohan Boniface's avatar
Yohan Boniface committed
158 159 160 161
async def explore_schema(request, response):
    response.json = SCHEMA


David Foucher's avatar
Black  
David Foucher committed
162
@app.route("/explore/rules")
Yohan Boniface's avatar
Yohan Boniface committed
163 164
async def explore_rules(request, response):
    response.json = RAW_RULES
165 166


David Foucher's avatar
Black  
David Foucher committed
167
@app.route("/explore/glossary")
Yohan Boniface's avatar
Yohan Boniface committed
168 169 170 171
async def explore_glossary(request, response):
    response.json = GLOSSARY


David Foucher's avatar
Black  
David Foucher committed
172
@app.route("/explore/financements")
173 174 175 176
async def explore_financements(request, response):
    response.json = FINANCEMENTS


David Foucher's avatar
Black  
David Foucher committed
177
@app.route("/explore/scenarios")
178 179
async def explore_scenarios(request, response):
    response.json = SCENARIOS
Yohan Boniface's avatar
Yohan Boniface committed
180 181


David Foucher's avatar
Black  
David Foucher committed
182
@app.route("/explore/catalog")
183
async def explore_catalog(request, response):
David Foucher's avatar
Black  
David Foucher committed
184
    data = await get_formation_json(request.query.get("id"))
David Foucher's avatar
David Foucher committed
185
    response.body = json.dumps(data, indent=2, ensure_ascii=False)
186 187


David Foucher's avatar
Black  
David Foucher committed
188
@app.route("/explore/decode-lbf-url")
189
async def decode_lbf_url(request, response):
David Foucher's avatar
Black  
David Foucher committed
190
    response.json = data_from_lbf_url(request.query.get("url"))