airquality: update to new Atmo Auvergne-Rhône-Alpes API (#49654)

This commit is contained in:
Frédéric Péters 2020-12-21 13:51:29 +01:00 committed by Thomas NOEL
parent 42bfc161ab
commit 58d340c18b
3 changed files with 139 additions and 61 deletions

View File

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.29 on 2020-12-21 12:54
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('airquality', '0003_remove_airquality_log_level'),
]
operations = [
migrations.AddField(
model_name='airquality',
name='atmo_aura_api_token',
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='ATMO AURA API token'),
),
]

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
# passerelle - uniform access to multiple data sources and services
# Copyright (C) 2017 Entr'ouvert
# Copyright (C) 2017-2020 Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
@ -16,7 +16,6 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import datetime
import xml.etree.ElementTree as ET
from django.db import models
from django.http import Http404
@ -24,6 +23,8 @@ from django.utils.translation import ugettext_lazy as _
from passerelle.base.models import BaseResource
from passerelle.utils.api import endpoint
from passerelle.utils.jsonresponse import APIError
class AirQuality(BaseResource):
category = _('Misc')
@ -32,6 +33,10 @@ class AirQuality(BaseResource):
(But only supports the Rhône-Alpes region for now).
''')
atmo_aura_api_token = models.CharField(max_length=100,
verbose_name=_('ATMO AURA API token'),
blank=True, null=True)
class Meta:
verbose_name = _('Air Quality')
@ -61,26 +66,53 @@ class AirQuality(BaseResource):
return getattr(self, local_method)(request, country, city, **kwargs)
def air_rhonealpes(self, request, country, city, **kwargs):
response = self.requests.get('https://www.atmo-auvergnerhonealpes.fr/site/indice/XML/ATMO/now/%s' % city)
atmo = ET.fromstring(response.content)
data = {
'latest': {
'date': datetime.datetime.strptime(
atmo.find('JOUR_ATMO/MESURE/INDICE/JOUR_INDICE').attrib['jour'],
'%d/%m/%Y').strftime('%Y-%m-%d'),
'value': atmo.findtext('JOUR_ATMO/MESURE/INDICE/VALEUR'),
},
'comment': atmo.findtext('JOUR_ATMO/COMMENTAIRE').strip(),
if not self.atmo_aura_api_token:
raise APIError('missing access token for ATMO AURA API')
insee_codes = {
'albertville': '73011',
'annecy': '74010',
'bourg-en-bresse': '01053',
'chambery': '73065',
'chamonix': '74056',
'grenoble': '38185',
'lyon': '69381',
'roanne': '42187',
'saint-etienne': '42218',
'valence': '26362',
'vienne': '38544',
}
if atmo.findtext('JOUR_ATMO/PREVISION_J1/INDICE/VALEUR') != '-':
data.update({
'forecast': {
insee_code = insee_codes.get(city.lower())
response = self.requests.get('https://api.atmo-aura.fr/communes/%s/indices' % insee_code,
params={'api_token': self.atmo_aura_api_token},
)
json_response = response.json()
today = datetime.datetime.today().strftime('%Y-%m-%d')
tomorrow = (datetime.datetime.today() + datetime.timedelta(days=1)).strftime('%Y-%m-%d')
response_data = {}
for indice in json_response['indices']['data']:
if indice['date'] == today:
response_data['latest'] = {
'date': today,
'value': indice['valeur'],
}
elif indice['date'] == tomorrow:
response_data['forecast'] = {
'j1': {
'date': datetime.datetime.strptime(
atmo.findtext('JOUR_ATMO/PREVISION_J1/INDICE/JOUR_INDICE'),
'%d/%m/%Y').strftime('%Y-%m-%d'),
'value': atmo.findtext('JOUR_ATMO/PREVISION_J1/INDICE/VALEUR'),
},
},
})
return {'data': data, 'err': 0}
'date': today,
'value': indice['valeur'],
}
}
if 'latest' in response_data and 'forecast' in response_data:
break
if 'latest' in response_data:
comment_response = self.requests.get('https://api.atmo-aura.fr/commentaire',
params={
'date': response_data['latest']['date'],
'api_token': self.atmo_aura_api_token,
}
)
if comment_response.ok:
response_data['comment'] = comment_response.json().get('commentaire')
return {'data': response_data, 'err': 0}

View File

@ -1,56 +1,82 @@
# -*- coding: utf-8 -*-
import pytest
import mock
import utils
import json
import freezegun
from httmock import HTTMock, response
from passerelle.apps.airquality.models import AirQuality
SAMPLE_RESPONSE = '''<?xml version='1.0' ?>
<ATMO>
<JOUR_ATMO>
12/05/2017 <VILLE>Indice ATMO de l'agglomération de Lyon</VILLE>
<TYPE_INDICE>PARTIEL</TYPE_INDICE>
<COMMENTAIRE>
<![CDATA[
Jeudi 11 mai, le temps perturbé a permis davoir une bonne qualité de lair sur la zone de surveillance.
Vendredi 12, la succession dépisodes pluvio-orageux favorise le maintien de la qualité de lair qui restera bonne.
Samedi 13, les conditions météorologiques proches de celles de la veille devraient conduire à une bonne qualité de lair sur lensemble du territoire.
]]>
</COMMENTAIRE>
<MESURE>
<INDICE>
<JOUR_INDICE jour="12/05/2017" />
<VALEUR>4</VALEUR>
<SOUS_INDICES>
<SOUSINDICE NOM="POUSSIERE">2</SOUSINDICE>
<SOUSINDICE NOM="DIOXYDE D'AZOTE">3</SOUSINDICE>
<SOUSINDICE NOM="OZONE">4</SOUSINDICE>
</SOUS_INDICES>
</INDICE>
</MESURE>
<PREVISION_J1>
<INDICE>
<JOUR_INDICE>13/05/2017</JOUR_INDICE>
<VALEUR>4</VALEUR>
</INDICE>
</PREVISION_J1>
</JOUR_ATMO>
</ATMO>
'''
SAMPLE_RESPONSE = {
"licence": "https://opendatacommons.org/licenses/odbl/",
"commune": "LYON-1ER-ARRONDISSEMENT",
"code_insee": "69381",
"indices": {
"current_page": 1,
"data": [
{
"date": "2020-12-22",
"valeur": "26.6503231768126",
"couleur_html": "#5CCB60",
"qualificatif": "Bon",
"type_valeur": "prévision",
},
{
"date": "2020-12-21",
"valeur": "21.6876695818178",
"couleur_html": "#5CCB60",
"qualificatif": "Bon",
"type_valeur": "prévision",
},
{
"date": "2020-12-20",
"valeur": "26.1405508214683",
"couleur_html": "#5CCB60",
"qualificatif": "Bon",
"type_valeur": "prévision",
},
],
},
"first_page_url": "https://api.atmo-aura.fr/communes/69381/indices?api_token=XXX&page=1",
"from": 1,
"last_page": 23,
"last_page_url": "https://api.atmo-aura.fr/communes/69381/indices?api_token=XXX&page=23",
"next_page_url": "https://api.atmo-aura.fr/communes/69381/indices?api_token=XXX&page=2",
"path": "https://api.atmo-aura.fr/communes/69381/indices",
"per_page": 50,
"prev_page_url": None,
"to": 50,
"total": 1137,
}
SAMPLE_COMMENT_RESPONSE = {
"licence": "https://opendatacommons.org/licenses/odbl/",
"commentaire": "Jeudi 11 mai, le temps perturbé a permis davoir une bonne qualité de lair sur la zone de surveillance.",
}
@pytest.fixture
def airquality(db):
return AirQuality.objects.create(slug='atmo')
return AirQuality.objects.create(slug='atmo', atmo_aura_api_token='XXX')
def mocked_http(url, request):
if url.path.startswith('/commune'):
return response(200, SAMPLE_RESPONSE, request=request)
if url.path.startswith('/commentaire'):
return response(200, SAMPLE_COMMENT_RESPONSE, request=request)
@freezegun.freeze_time('2020-12-21')
def test_airquality_details(app, airquality):
endpoint = utils.generic_endpoint_url('airquality', 'details', slug=airquality.slug)
assert endpoint == '/airquality/atmo/details'
with mock.patch('passerelle.utils.Request.get') as requests_get:
requests_get.return_value = mock.Mock(content=SAMPLE_RESPONSE, status_code=200)
with HTTMock(mocked_http):
resp = app.get(endpoint + '/fr/lyon/', status=200)
assert resp.json['data']['latest']['value'] == '4'
assert resp.json['data']['latest']['value'] == '21.6876695818178'
assert 'Jeudi 11 mai, le temps' in resp.json['data']['comment']
def test_airquality_details_unknown_city(app, airquality):
endpoint = utils.generic_endpoint_url('airquality', 'details', slug=airquality.slug)