Pris par la curiosité de savoir comment peut fonctionner un programme CLI,
autant se lancer dans la réalisation d’un projet simple histoire de voir
ce qu’il y a derrière !
Pour cela, je vais utiliser le package python
Click
et simplement connecter une API météo pour afficher le temps qu’il fait dans
les jolies villes de France.
Son petit nom: Morning
C’est parti !
mkdir morning
cd morning
python3 -m venv .venv
pip freeze > requirements.txt
source .venv/bin/activate
vim morning.py
Je crée le bon dossier à l’endroit qui va bien suivi d’un environnement virtuel.
morning.py
import click
@click.command()
@click.option('--city', '-c', help='Le nom de votre ville.')
def now(city):
"""La météo de votre ville, tout simplement."""
click.echo('Coucou de ' + city)
if __name__ == '__main__':
now()
Le décorateur @click.option
permet d’ajouter des arguments à notre commande:
$ python morning.py now --city Paris
Coucou de Paris
L’argument help
permet de renseigner les infos qui seront visibles si l’on tape:
$ python morning.py now --help
Usage: morning now [OPTIONS]
La météo de votre ville, tout simplement.
Options:
-c, --city TEXT Le nom de votre ville
--help Show this message and exit.
L’idée est là, maintenant on démarre l’écriture du programme.
Première étape: on va setup l’api.
- J’ai choisi d’utiliser OpenWeatherMap
Ils ont un accès gratuit qui suffira amplement.
Tu t’inscris et tu vas recevoir ta clé API par email.
import json
import requests
import click
API_KEY = 'ta_clé_super_secrète'
OPENWEATHER_API_URL = 'http://api.openweathermap.org/data/2.5/weather?id={}&appid={}'
class RequestedWeather():
def __init__(self, city_name):
self.country = 'FR'
self.city_name = city_name.capitalize()
def api(self, id=None):
if not id:
id = self.city_id()
url = OPENWEATHER_API_URL.format(id, API_KEY)
response = requests.get(url)
if response:
return response.json()
def city_id(self):
with open('city.list.json', 'r') as f:
data = json.load(f)
city = next(x for x in data
if x['name'] == self.city_name and x['country'] == self.country)
if city:
return city['id']
else:
click.echo("La ville {} n'existe pas".format(self.city_name))
@click.command()
@click.option('--city', '-c', help='Le nom de votre ville.')
def now(city):
"""La météo de votre ville, tout simplement."""
requested_weather = RequestedWeather(city).api()
click.echo('Coucou de ' + city)
click.echo(requested_weather)
if __name__ == '__main__':
now()
Alors, du haut en bas:
J’importe la libraire requests
pour gérer l’api et json pour
gérer le json, Captain Obvious.
Ensuite deux jolies constantes pour stocker la clé et le call api.
J’ai utilisé ce format d’url car la doc d’OpenWeatherMap conseil de prendre l’id
des villes à la place des noms directement.
__init__()
, l’idée c’est de seulement avoir les villes françaises alors
hop on met FR
en dur et on garde city_name
en dynamique.
api()
effectue le call en fonction de l’id renseigné et retourne un beau json
qui contient toutes les infos utilisables par la suite.
city_id()
va s’occuper d’aller chercher l’id correspondant à la ville entrée
en argument par l’utilisateur dans la liste fournie par
OpenWeatherMap.
now(city)
, on instancie notre objet RequestedWeather()
avec la ville renseignée
par l’utilisateur et on renvoit ses informations avec click.echo(requested_weather)
On pourrait s’arrêter là en vrai parce qu’on a le comportement souhaité à la base, mais c’est pas jolie, pas lisible, c’est caca quoi. Donc je vais formatter un peu tout ce joyeux bordel.
ABSOLUTE_ZERO = 273.15
@click.command()
@click.option('--city', '-c', help='Le nom de votre ville.')
def now(city):
"""La météo de votre ville, tout simplement."""
# Init objet
requested_weather = RequestedWeather(city).api()
# Retrieve informations
if requested_weather:
weather = requested_weather['weather'][0]['main']
temp = requested_weather['main']['temp'] - ABSOLUTE_ZERO
else:
click.echo('Tu dois renseigner une API_KEY')
# Return
click.echo('Actuellement dans votre jolie ville de {}:'.format(city))
click.echo('--------------')
click.echo('Temps: {}'.format(weather))
click.echo('--------------')
click.echo('Temperature: {}°C'.format(int(temp)))
click.echo('Bonne journée !')
La constante ABSOLUTE_ZERO
fait son entrée pour convertir la température donnée
en Kelvin dans le json en Celsius.
Ok, le truc est sympa. Mais le nom des villes est sensible à la casse…
Je vais pas trop m’embêter et simplement ajouter une commande en plus de now
pour faire une recherche des villes disponibles.
import json
import requests
import click
ABSOLUTE_ZERO = 273.15
API_KEY = 'ta_clé_super_secrète'
CITIES_DATA = None
OPENWEATHER_API_URL = 'http://api.openweathermap.org/data/2.5/weather?id={}&appid={}'
class RequestedWeather():
def __init__(self, city_name):
self.country = 'FR'
self.city_name = city_name.capitalize()
def api(self, id=None):
if not id:
id = self.city_id()
url = OPENWEATHER_API_URL.format(id, API_KEY)
response = requests.get(url)
if response:
return response.json()
def city_id(self):
city = next(x for x in CITIES_DATA
if x['name'] == self.city_name and x['country'] == self.country)
if city:
return city['id']
else:
click.echo("La ville {} n'existe pas".format(self.city_name))
@click.group()
def cli():
global CITIES_DATA
with open('city.list.json', 'r') as f:
CITIES_DATA = json.load(f)
@cli.command()
def all():
"""Liste des villes disponibles."""
user = input('Le nom de votre ville ? \n')
for city in CITIES_DATA:
my_city = city['name'].lower()
if my_city.find(user.lower()) >= 0 and city['country'] == 'FR':
click.echo(my_city.capitalize())
@cli.command()
@click.option('--city', '-c', help='Le nom de votre ville.')
def now(city):
"""La météo de votre ville, tout simplement."""
# Init objet
requested_weather = RequestedWeather(city).api()
# Retrieve informations
if requested_weather:
weather = requested_weather['weather'][0]['main']
temp = requested_weather['main']['temp'] - ABSOLUTE_ZERO
else:
click.echo('Tu dois renseigner une API_KEY')
# Return
click.echo('Actuellement dans votre jolie ville de {}:'.format(city))
click.echo('--------------')
click.echo('Temps: {}'.format(weather))
click.echo('--------------')
click.echo('Temperature: {}°C'.format(int(temp)))
click.echo('Bonne journée !')
if __name__ == '__main__':
cli()
Alors que pasa a la izquierda…
Click permet la création de groupe de commandes, d’où l’arrivé de cli()
.
Maintenant les décorateurs de nos commandes changent pour @cli.command()
et on
appelle à la toute fin cli()
.
Les commandes python morning.py now
et python morning.py all
sont dispo !
$ python morning.py now -c Nice
Actuellement dans votre jolie ville de Nice:
------------------
Temps: Clouds
------------------
Temperature: 13°C
------------------
Bonne journée !
$ python morning.py all
Le nom de votre ville ?
Bordeau
Arrondissement de bordeaux
Bordeaux
Bordeaux
Saint-caprais-de-bordeaux
Saint-caprais-de-bordeaux
Artigues-près-bordeaux
Artigues-pres-bordeaux
Lignan-de-bordeaux
Lignan-de-bordeaux
Carignan-de-bordeaux
Afin d’eviter de laisser la clé api en dur dans le fichier, tu
peux setup une variable d’environnement et utiliser os.environ
pour la récupérer.
$ export API_KEY='votre_clé_magique'
et
import os
API_KEY = os.environ.get('API_KEY')
Pour avoir une commande qui claque du genre morning now -c Lille
, regarde du
côté de la création d’un package avec
setuptools.
Voili voilou pour la découverte de click, si t’as une remarque ou besoin d’aide n’hésites pas à venir sur le bon twitter des familles.
Le projet complet est diposnible sur mon github.