# Utiliser une API REST

## Introduction 

Pour découvrir Python, nous allons d'abord apprendre à manipuler des données.

Pour cela nous nous appuierons sur un jeu de données accessible via une API REST.

Une APi (Application Programming Interface) est un programme qui permet à deux programmes de communiquer ensemble.

Ici nous voulons accéder aux données proposées par un site web.

L'API utilisée, appellée REST API communique avec le site web par des requêtes http.

Nous allons utiliser l'APi de Gitlab qui contient énormément d'informations sur les projets hébergés.


---



## Import des librairies utiles

En plus des fonctions intégrées à python (comme la fonction `print()` que nous avons utilisée), 
nous allons importer des fonctions provenant d'autres librairies.

Il nous faut donc:  
1. installer les librairies sur notre système  
2. importer les fonctions utiles dans notre projet



### Installer les librairies

Pour installer des librairies python supplémentaires, nous allons utiliser `pip`

On peut utiliser pip pour installer directement une librairie:
```
pip install pytest
```
**[Pytest](https://docs.pytest.org/en/stable/){target=_blank}** est une librairie pour réaliser des tests sur le code. 


Pour notre projet, il y a deux librairies dont nous aurons absolument besoin: `requests` et `json`  
- **requests** permet d'envoyer et de recevoir des requêtes html. C'est un peu l'équivalent de `curl`en bash.   
- **json** permet de traiter des données au format **[json](https://www.json.org/json-fr.html){target=_blank}**  

Comme ces deux librairies sont nécessaires au fonctionnement du projet, nous allons créer un fichier requirements.txt à la racine de notre projet.

!!! note "requirements.txt"
    requests  
    json

Pour les installer, un simple:
```
pip install -r requirements.txt
```


### Importation dans le projet

Dans notre fichier `main.py` nous devons maintenant importer les libraires

!!! note "main.py"
    ```
    #!/usr/bin/env python3

    import requests
    import json

    api_url = "https://gitlab.com/api/v4"
    dataset = "/groups"
    endpoint = api_url + dataset
    ```

---

## Accès à l'API

Nous allons maintenant éxecuter ce code en mode interactif, et accéder à l'API dans la console python:

!!! bug ""
    ```
    user@host:~$ python -i main.py
    >>> r = requests.get(endpoint)
    ```
    Nous avons placé dans la variable `r`le résultat de la fonction `get`, prise de la bibliothèque `requests`, avec comme 1er argument `endpoint`, c'est à dire `https://gitlab.com/api/v4/groups`

!!! bug ""
    ```
    >>> print(r)
    <Response [200]>
    ```
    Le résultat ne semble pas encourageant, et pourtant ! Le code **200** signifie qu'une URL a bien été trouvée, ce qu'on préfère à une erreur **404**!

!!! bug ""
    ```
    >>> type(r)
    <class 'requests.models.Response'>
    ```
    Le type de `r` est une classe, c'est un **objet Python**, qui contient d'autres données que ce simple `**Status Code 200**`


!!! bug ""
    ```
    >>> help(r)
    >>> dir(r)
    ['__attrs__', '__bool__', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__enter__', '__eq__', '__exit__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__nonzero__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setstate__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_content', '_content_consumed', '_next', 'apparent_encoding', 'close', 'connection', 'content', 'cookies', 'elapsed', 'encoding', 'headers', 'history', 'is_permanent_redirect', 'is_redirect', 'iter_content', 'iter_lines', 'json', 'links', 'next', 'ok', 'raise_for_status', 'raw', 'reason', 'request', 'status_code', 'text', 'url']
    ```
    Les fonctions `help()` et `dir()` nous aident à manipuler cet objet.


!!! bug ""
    ```
    >>> r.__attrs__
    ['_content', 'status_code', 'headers', 'url', 'history', 'encoding', 'reason', 'cookies', 'elapsed', 'request'] 
    ```
    La **méthode** `__attrs` nous donne une liste d'attributs de notre objet `r`


!!! bug ""
    ```
    >>> r.url
    'https://gitlab.com/api/v4/groups'
    ```
    On peut ainsi vérifier l'URL sollicitée.


!!! bug ""
    ```
    >>> r.content
    ```
    Avant d'accéder au contenu, il va falloir le mettre en forme!


--- 

## Formuler un requête à l'API

Comme il y a beaucoup de données (ici, tous les groupes créés sur Gitlab, il est nécessaire de filtrer notre requête.

Nous allons pour cela utiliser des paramètres, écrits au format **json** et stockés dans une variable nommée `payload`

!!! bug ""
    ```
    >>> payload = {"search": "garage"}
    >>> r = requests.get(endpoint,params=payload)
    ```
    nous chercherons ainsi les groupes qui contiennent le mot **garage**  
    La fonction get prend comme deuxième argument les données json.  
    On place ces données dans la variable `params` afin de préserver `payload`  
    
!!! bug ""
    ```
    >>> r.headers
    {'Date': 'Wed, 05 Aug 2020 18:42:47 GMT', 'Content-Type': 'application/json', 'Transfer-Encoding': 'chunked', 
    'Connection': 'keep-alive', 'Set-Cookie': '__cfduid=dcbe4c6164f5e175674b0abff0fb011c71596652967; expires=Fri, 04-Sep-20 18:42:47 GMT; 
    path=/; domain=.gitlab.com; HttpOnly; SameSite=Lax; Secure', 'Vary': 'Accept-Encoding, Origin', 'Cache-Control': 'max-age=0, 
    private, must-revalidate', 'Etag': 'W/"44cfa1c513193c8d78cdfc584f6dea72"', 'Link': '<https://gitlab.com/api/v4/groups?order_by=name&owned=false&page=2&per_page=20&search=garage&sort=asc&statistics=false&with_custom_attributes=false>; 
    rel="next", <https://gitlab.com/api/v4/groups?order_by=name&owned=false&page=1&per_page=20&search=garage&sort=asc&statistics=false&with_custom_attributes=false>; 
    rel="first", <https://gitlab.com/api/v4/groups?order_by=name&owned=false&page=2&per_page=20&search=garage&sort=asc&statistics=false&with_custom_attributes=false>; 
    rel="last"', 'X-Content-Type-Options': 'nosniff', 'X-Frame-Options': 'SAMEORIGIN', 'X-Next-Page': '2', 'X-Page': '1', 'X-Per-Page': '20', 'X-Prev-Page': '', 
    'X-Request-Id': 'wo9iLfsTax', 'X-Runtime': '0.116039', 'X-Total': '37', 'X-Total-Pages': '2', 'Strict-Transport-Security': 'max-age=31536000', 
    'Referrer-Policy': 'strict-origin-when-cross-origin', 'Content-Encoding': 'gzip', 'GitLab-LB': 'fe-12-lb-gprd', 'GitLab-SV': 'api-18-sv-gprd', 
    'CF-Cache-Status': 'DYNAMIC', 'cf-request-id': '046187edf40000edab20af4200000001', 'Expect-CT': 'max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"', 
    'Server': 'cloudflare', 'CF-RAY': '5be2a8f65905edab-CDG'}
    >>>     
    >>> r.headers['X-Total']
    '37'
    ```
    Les headers nous donnent des informations intéressantes, et notamment ce `X-Total`, qui est le nombre total de résultats obtenus.  
    **Rappel : ** Les headers sont encadrés par des accolades ( `curly brackets : { }` ). C'est donc un objet de la classe `dic`. On accède à ses éléments en mettant entre crochets et guillemets la clé dont on veut voir la valeur, ici ` r.headers['X-Total']`


---

## Accéder au contenu de la requête

Il nous faut maintenant accéder aux données contenues dans la requête

!!! bug ""
    ```
    >>> data = r.json()
    >>>
    >>> type(data)
    <class 'list'>
    ```
    On créé la variable data, dans lequelle on met le contenu `r` sur lequel on a appliqué la fonction `json()`

!!! bug ""
    ```
    >>> data[0].keys()
    dict_keys(['id', 'web_url', 'name', 'path', 'description', 'visibility', 'share_with_group_lock', 
    'require_two_factor_authentication', 'two_factor_grace_period', 'project_creation_level', 'auto_devops_enabled', 
    'subgroup_creation_level', 'emails_disabled', 'mentions_disabled', 'lfs_enabled', 'default_branch_protection', 
    'avatar_url', 'request_access_enabled', 'full_name', 'full_path', 'created_at', 'parent_id', 'ldap_cn', 'ldap_access'])
    ```
    Prenons le 1er élément de la liste `data` (numéroté 0), et consultons la liste de ses `keys()`.
    Chaque élément de la liste `data`correspond à un résultat de la recherche, et est constitué d'un dictionnaire dans lequel nous allons pouvoir `itérer`
    
--- 

## Itération dans la liste de résultats

### `for`

Nous allons utiliser la boucle `for` pour **répéter une action sur chaque élément de la liste** (=itérer)

!!! bug ""
    ```
    >>> for i in data:
    ...     print(i['name'])  # Appuyez 2 fois Entrée pour créer un 1ère ligne non indentée 
    ...                       # et marquer la fin des instructions de for
    AppGarage
    Atheed-Garage
    Code Garage
    [..]
    Garagenum
    >>> 
    ```
    Retenez bien la syntaxe simple de la boucle `for`dans Python: les `:` pour marquer le début des instructions, et c'est l'indentation ( `4`  ++space++ en début de ligne) seulement qui marque la liste des instructions.
    
### `if`

Nous pouvons maintenant utiliser if pour afficher les détails d'un des résultats obtenus, que nous stockerons dans une variable nommée result:

!!! bug ""
    ```
    >>> for i in data:
    ...     if i['name'] == "Garagenum":
    ...             result = i
    ... 
    >>> print(result)
    {'id': 4160307, 'web_url': 'https://gitlab.com/groups/garagenum', 'name': 'Garagenum', 'path': 'garagenum', 'description': '', 'visibility': 'public', 'share_with_group_lock': False, 'require_two_factor_authentication': False, 'two_factor_grace_period': 48, 'project_creation_level': 'developer', 'auto_devops_enabled': None, 'subgroup_creation_level': 'owner', 'emails_disabled': None, 'mentions_disabled': None, 'lfs_enabled': True, 'default_branch_protection': 2, 'avatar_url': None, 'request_access_enabled': False, 'full_name': 'Garagenum', 'full_path': 'garagenum', 'created_at': '2018-11-30T14:35:23.504Z', 'parent_id': None, 'ldap_cn': None, 'ldap_access': None}
    ```
    Syntaxe similaire que pour le `for`, ici le `if`est imbriqué dans le `for`
    
### Affichage

Nous utiliserons aussi la boucle `for`pour afficher les résultats de manière lisible:

!!! bug ""
    ```
    >>> for i in result:
    ...     print(i + " : " + str(result[i]))
    ... 
    id : 4160307
    web_url : https://gitlab.com/groups/garagenum
    name : Garagenum
    [...]
    ```
    
    Le `str(result[i])`permet de transformer chaque valeur de `result`en **chaîne de caractères (str)**, pour permettre l'affichage par `print()`



---



## Authentification

Certaines données peuvent demander une authentification pour en autoriser l'accès.

Cherchons par exemple, dans les sous-groupes du garage, un groupe privé nommé `interventions`:

!!! bug ""
    ```
    >>> r = requests.get(endpoint,{"search":"garage"})
    >>>
    >>> for i in r.json():
    ...     if i['name'] == "Garagenum":
    ...             gn_id = i['id']
    ... 
    >>> print("L'ID du groupe Garagenum est " + gn_id)
    L'ID du groupe Garagenum est 4160307
    >>>
    >>>
    >>> gn_endpoint = endpoint + "/" + gn_id + "/subgroups"
    >>>
    >>> payload = {'search': 'interventions'}
    >>>
    >>> r = requests.get(gn_endpoint, params = payload)
    >>> for i in r.json():
    ...     print(i['name'])
    ... 
    >>>
    ```
    
Aucun résultat, et pourtant, ce groupe existe!

Pour vous authentifier, il vous faut d'abord créer un jeton d'authentification dans gitlab

## Générer un jeton d'authentification

Pour accéder aux données présentées par l'API (par exemple, la liste des 'issues' qui me sont attribuées),
Gitlab a besoin de m'identifier.

Pour cela, à partir de notre interface web utilisateur de Gitlab, nous allons générer un **jeton d'authentification**.

Vous pouvez utiliser ce [tutoriel pour générer votre jeton d'API](https://screenful.com/gitlab/gitlab-apikey){target=_blank}


### Accès authentifié

Il vous suffit maintenant de modifier la variable `payload` , qui contient la liste des options pour la requête.

!!! bug ""
    ```
    >>> payload['private_token'] = "G9ajiTNzqGMSNxajgQx2"
    >>>
    >>> r = requests.get(gn_endpoint, payload)
    >>>
    >>> for i in r.json():
    ...     print(i['name'])
    ... 
    interventions
    ```
    **Notez bien** la manière dont une nouvelle clé `private-token` a été ajoutée au dictonnaire `payload`

---

## Pour les étudiants

??? important "Conditions de validation"
    Pour valider cette unité, il faut que vous fassiez une merge-request vers la branche `start`
    Votre code doit permettre d'obtenir le résultat 
