<commit_before>import re

from requests.auth import HTTPBasicAuth
import requests

from flask import current_app

from labonneboite.conf import settings
from ..exceptions import BackendUnreachable


TIMEOUT_SECONDS = 8


def isochrone(origin, duration):
    # Documentation: https://geoservices.ign.fr/documentation/geoservices/isochrones.html
    endpoint = 'isochrone/isochrone.json'
    params = {
        'location': '{:.7f},{:.7f}'.format(origin[1], origin[0]),
        'time': int(duration * 60),
        'smoothing': 'true',
        # Settings holes=true yields really beautiful maps, but very hard to
        # debug. Also, requires more data transfer.
        # 'holes': 'true',
    }
    data = request_json_api(endpoint, params)

    # geometry is a string of the form: 'POLYGON ((3.504869 45.910195, ...), (...))'
    geometry = data['wktGeometry']
    polygons = re.findall(r'\([0-9. ,]+\)', geometry)
    isochrones = []
    for polygon in polygons:
        coordinates = polygon.strip(')').strip('(').split(', ')
        lon_lat = [coords.split(' ') for coords in coordinates]
        isochrones.append([
            (float(lat), float(lon)) for lon, lat in lon_lat
        ])
    return isochrones


def durations(origin, destinations):
    result = []
    for destination in destinations:
        data = get_journey(origin, destination)
        result.append(float(data['durationSeconds']))
    return result


def get_journey(origin, destination):
    """
    As a rough estimate, the IGN API replies in 1-2s.

    Documentation: https://geoservices.ign.fr/documentation/geoservices/itineraires.html

    Args:
        origin (coordinates)
        destination (coordinates)

    Return: the JSON data returned by the IGN "itineraire" API.
    """
    endpoint = 'itineraire/rest/route.json'
    params = {
        'origin': '{:.7f},{:.7f}'.format(origin[1], origin[0]),
        'destination': '{:.7f},{:.7f}'.format(destination[1], destination[0]),
        'graphName': 'Voiture'
    }
    data = request_json_api(endpoint, params)
    return data


def request_json_api(endpoint, params):
    ign_credentials = settings.IGN_CREDENTIALS
    url = 'https://wxs.ign.fr/{}/{}'.format(ign_credentials['key'], endpoint)
    auth = HTTPBasicAuth(
        ign_credentials['username'], ign_credentials['password']
    ) if 'username' in ign_credentials else None
    headers = {
        "Referer": ign_credentials['referer']
    } if 'referer' in ign_credentials else None

    try:
        response = requests.get(url, params=params, auth=auth, timeout=TIMEOUT_SECONDS, headers=headers)
    except requests.exceptions.Timeout:
        # This occurs frequently so we don't trigger a timeout
        current_app.logger.warning('IGN API timeout')
        raise BackendUnreachable

    if response.status_code == 200:
        return response.json()
    if response.status_code == 401:
        current_app.logger.error('Invalid IGN API user/password')
    elif response.status_code == 403:<commit_msg>Catch and silently ignore IGN API timeouts<commit_after>import re

from requests.auth import HTTPBasicAuth
import requests
from requests.exceptions import ConnectionError, Timeout

from flask import current_app

from labonneboite.conf import settings
from ..exceptions import BackendUnreachable


TIMEOUT_SECONDS = 8


def isochrone(origin, duration):
    # Documentation: https://geoservices.ign.fr/documentation/geoservices/isochrones.html
    endpoint = 'isochrone/isochrone.json'
    params = {
        'location': '{:.7f},{:.7f}'.format(origin[1], origin[0]),
        'time': int(duration * 60),
        'smoothing': 'true',
        # Settings holes=true yields really beautiful maps, but very hard to
        # debug. Also, requires more data transfer.
        # 'holes': 'true',
    }
    data = request_json_api(endpoint, params)

    # geometry is a string of the form: 'POLYGON ((3.504869 45.910195, ...), (...))'
    geometry = data['wktGeometry']
    polygons = re.findall(r'\([0-9. ,]+\)', geometry)
    isochrones = []
    for polygon in polygons:
        coordinates = polygon.strip(')').strip('(').split(', ')
        lon_lat = [coords.split(' ') for coords in coordinates]
        isochrones.append([
            (float(lat), float(lon)) for lon, lat in lon_lat
        ])
    return isochrones


def durations(origin, destinations):
    result = []
    for destination in destinations:
        data = get_journey(origin, destination)
        result.append(float(data['durationSeconds']))
    return result


def get_journey(origin, destination):
    """
    As a rough estimate, the IGN API replies in 1-2s.

    Documentation: https://geoservices.ign.fr/documentation/geoservices/itineraires.html

    Args:
        origin (coordinates)
        destination (coordinates)

    Return: the JSON data returned by the IGN "itineraire" API.
    """
    endpoint = 'itineraire/rest/route.json'
    params = {
        'origin': '{:.7f},{:.7f}'.format(origin[1], origin[0]),
        'destination': '{:.7f},{:.7f}'.format(destination[1], destination[0]),
        'graphName': 'Voiture'
    }
    data = request_json_api(endpoint, params)
    return data


def request_json_api(endpoint, params):
    ign_credentials = settings.IGN_CREDENTIALS
    url = 'https://wxs.ign.fr/{}/{}'.format(ign_credentials['key'], endpoint)
    auth = HTTPBasicAuth(
        ign_credentials['username'], ign_credentials['password']
    ) if 'username' in ign_credentials else None
    headers = {
        "Referer": ign_credentials['referer']
    } if 'referer' in ign_credentials else None

    try:
        response = requests.get(url, params=params, auth=auth, timeout=TIMEOUT_SECONDS, headers=headers)
    except Timeout:
        # This occurs frequently so we don't trigger a timeout error
        current_app.logger.warning('IGN API timeout')
        raise BackendUnreachable
    except ConnectionError as e:
        if str(e) == "HTTPSConnectionPool(host='wxs.ign.fr', port=443): Read timed out.":
            # This occurs frequently so we don't trigger a timeout error
            raise BackendUnreachable
        else:
            raise e

    if response.status_code == 200:
        return response.json()
    if response.status_code == 401:
        current_app.logger.error('Invalid IGN API user/password')
    elif response.status_code == 403:
{'fraction_non_alphanumeric': 0.1256099480560365, 'fraction_numerical': 0.014009129545096805, 'mean_word_length': 3.128654970760234, 'pattern_counts': {'":': 3, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 6, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2124826', 'n_tokens_mistral': 1997, 'n_tokens_neox': 1894, 'n_words': 481}