L'ensemble de l'application peut tenir dans un seul fichier Python, mais plus l'application sera grosse, plus ce fichier sera long et illisible. C'est pourquoi Flask propose de découper l'application en sous-modules, ce qui la rend plus lisible.
mon_application/
├── app/
| ├── __init__.py
| ├── app.py
| ├── models/
| | ├── __init__.py
| | ├── model1.py
| | └── model2.py
| ├── routes/
| | ├── __init__.py
| | ├── routes1.py
| | └── routes2.py
| ├── templates/
| | ├── container.html
| | ├── partials/
| | | └── menu.html
| | └── pages/
| | | ├── page1.html
| | | └── page2.html
| ├── statics/
| | ├── css/
| | | └── styles.css
| | ├── js/
| | | └── script.js
| | ├── img/
| | | └── img.png
| | ├── data/
| | | └── data.json
| └── config.py
├── tests/
├── env/
├── .gitignore
├── README.md
├── requirements.txt
└── .env
Passons en revue chaque dossier et chaque fichier:
mon_application
est le répertoire racine qui contient tout le code source de l'applicationtests/
: une application n'est pas seulement le code qui tourne sur le serveur, c'est aussi un ensemble de tests qui doivent être joués avant de passer en production. Ces tests sont écrits, dans le meilleur des cas, avant même de débuter le développement dans l'application: ils permettent d'avoir une direction et de s'assurer que les autres fonctionnalités déjà présentes ne sont pas altérées par la modification en coursenv/
contient l'ensemble de l'environnement virtuel dans lequel l'application tournera. Ce dossier, en cas d'utilisation de Git, doit être mis dans le .gitignore, il est propre à chaque machine.requirements.txt
: parce que env/
est propre à chaque machine, le fichier requirements.txt
liste toutes les dépendances de l'application. Ce fichier est utilisé pour télécharger et installer, via pip install requirements.txt
, les dépendances requises, quand on récupère le dépôt. Ce fichier sera nécessaire lors du rendu de l'évaluation..gitignore
est un fichier de configuration pour Git, il permet de ne pas suivre (et donc de ne pas mettre dans le dépôt) certains fichiers/dossiers qui pourraient être sensibles ou étant propres aux environnements propres à chacunREADME.md
est une rapide documentation qu'il convient de rédiger quand on créé un dépôt Git. Ce fichier sera obligatoire lors du rendu de l'évaluation afin d'indiquer les étapes de l'installation de l'application.env
est un fichier ultra-confidentiel, il ne faut surtout pas le mettre sur Git ou le partager avec le code source. Il contient des mots de passe, les secrets, des urls, etc. : c'est un fichier de variables. Ces variables seront appelées et stockées dans app/config.py
app/
est le dossier du code source. C'est un paquet presque indépendant du reste et qui fonctionne grâce au .env
et aux dépendances installées.__init__.py
: parce que app/
est un paquet en lui-même, il comporte ce fichier d'initialisation, qui peut rester vide, mais qu'il est nécessaire d'ajouter. On ajoutera un __init__.py
à chaque fois que l'on fait d'un dossier un module.app.py
contient le code de base de l'application. C'est ce fichier qu'il faut exécuter pour lancer l'application.routes/
est le dossier qui contient l'ensemble des routes (i.e. le code qui permet de générer les URLS et les traitements associés)models/
contient les classes d'objets nécessaires pour les ORM (Object-Relational Mapping)templates/
contient les structures HTML des pagesstatics/
contient les ressources nécessaires aux templates (CSS, JS, etc) ou aux routesconfig.py
instancie la classe Config() qui contient les variables que l'on a défini dans .env
Avant de commencer le développement du projet Flask (ou de tout autre projet), il convient d'installer un environnement virtuel spécifique à ce projet (un vase clos dans lequel seul le projet s'exécutera). Il est courant d'avoir plusieurs projets en cours de développement sur sa machine, or chaque projet peut utiliser des versions différentes des packages Python. Pour éviter les erreurs sur les packages, l'environnement virtuel est essentiel.
Hors d'un environnement, la commande which python
indiquera l'installation globale de Python sur la machine.
which python
L'installation d'un environnement virtuel pour un projet Python se fait en plusieurs étapes, et dans le dossier racine de notre projet (voir dans l'arbre ci-dessus où se trouve le dossier env/
)/
# dans un terminal, effectuer les étapes suivantes
# installation du package virtualenv, si ce n'est pas déjà fait, dans le Python global de la machine
pip install virtualenv
# on s'assure ensuite que l'on est dans le dossier racine de l'application
pwd
# si ce n'est pas le cas, y aller
cd dossier_racine/
# installation de l'environnement
virtualenv env -p python3
Les étapes d'installation ne sont à réaliser qu'une seule fois. Une fois l'environnement installé, il suffit de l'activer/désactiver au début et à la fin de chaque séance de développement. Les packagaes installés quand on est dans l'environnement resteront dans cet environnement et n'auront pas d'interférence avec le reste de la machine.
# pour activer l'environnement:
# Ubuntu
source env/bin/activate
# Windows
source env/Scripts/activate
# Mac OS
source env/bin/activate
Une fois activé, (env)
apparaît au début de chaque ligne du terminal: c'est l'assurance que l'on travaille dans cet environnement. Pour s'assurer définitivement que l'on a bien un Python dédié au projet, on peut relancer un which python
.
which python
Pour désactiver l'environnement, il suffit d'exécuter la commande deactivate
.
Une fois l'environnement activé, nous pouvons installer toutes les librairies dont l'application va avoir besoin, à commencer par Flask.
pip install Flask
La commande vient d'installer Flask et toutes les librairies dont Flask avait besoin:
Successfully installed Flask-2.2.2 Jinja2-3.1.2 MarkupSafe-2.1.1 Werkzeug-2.2.2 click-8.1.3 colorama-0.4.5 importlib-metadata-5.0.0 itsdangerous-2.1.2 zipp-3.9.0
Il est temps de voir ce que contient notre environnement virtuel:
pip freeze
Comme c'est un vase clos, il contient bien uniquement ce que l'on vient d'installer. Cette comande pip freeze
est à exécuter régulièrement à la racine de notre application sous la forme pip freeze > requirements.txt
de manière à indiquer aux futurs utilisateurs du code quelles dépendances (et quelles versions) sont nécessaires pour faire fonctionner l'application.
Avant d'entamer le développement d'une première application, il faut avoir conscience de la modularisation requise pour une application. Il s'agit du découpage de l'application en de multiples fichiers, pour rendre le code plus lisible et plus cohérent (c'est le principe des templates, des routes, des models, etc. présentés plus haut). Le développement de l'application est en réalité le développement de multiples modules qui, réunis, forment des packages.
Flask
est un package, tout comme colorama
ou Jinja2
que l'on a importé juste au-dessus avec l'installation de Flask.
Quand nous ferons plus tard from flask import Flask
, nous importerons le module principal Flask
du package flask
. Chaque module contient des variables, des classes et des fonctions, utilisables par conséquent à de multiples endroits dans l'application. Pratique !
Ecrire un package est très facile, essayons.
# dès lors qu'un fichier __init__.py se trouve dans un dossier, ce dossier devient package
vi exemples/package_simple/un_package/__init__.py
# dans un fichier run.py, situé au-dessus du dossier deveu package, je peux importer mon package et sa variable d'initialisation "une_variable"
vi exemples/package_simple/run.py
# exécutons run.py
python exemples/package_simple/run.py
Pour résumer, un package se compose toujours d'un fichier __init__.py
à la racine d'un dossier. Le nom de ce dossier devient le nom de mon package. Ce package peut être importé depuis du code Python situé autre part dans notre application.
| nom_package/
| |-- __init__.py
| code_appelant_package.py
Nous l'avons dit plus haut, un package peut avoir plusieurs modules. Ces modules sont un peu comme des tiroirs, où l'on va trier nos fonctions et nos classes.
# on initie toujours notre fichier __init__.py
vi exemples/package_modules/package/__init__.py
# puis on peut créer notre premier module, module1.py, à côté de ce fichier __init__.py
# à l'intérieur de ce module, on définit une classe Module1() qui contient une seule fonction, une_fonction()
vi exemples/package_modules/package/module1.py
# dans le fichier run.py, on peut alors appeler des variables du package, des modules, des classes et des fonctions
python exemples/package_modules/run.py
Décrivons les imports et l'utilisation des classes:
from package.module1 import Module1
, package
est le nom de mon package, et module1
le module qui l'on appele. Module1
est la classe du module module1
from package import ma_variable
, package
est de nouveau le package, et ma_variable
est la variable initialisée dans __init__.py
Module1().une_fonction(ma_variable)
est exécuté. Module1()
est la classe du module module1
qui a été importée au-dessus. On appele la fonction une_fonction
de cette classe en lui donnant comme paramètre la variable d'initialisation ma_variable
python exemples/package_modules/run.py
La création et l'utilisation de packages et de modules n'est pas plus compliquée que cela. Pour résumer:
| nom_package/
| |-- __init__.py
| |-- module.py
| code_appelant_package_et_module.py
Les appels de packages se font par des chemins puisque ce sont des fichiers. Il pourrait être tentant de donner un chemin depuis la racine de la machine (ce que l'on appele un chemin absolu), mais cela irait à l'enconytre du principe-même de package qui veut que ce package soit partageable et réutilisable sur différentes machines.
Pour éviter de tels problèmes, il conviendra de toujours utiliser les imports relatifs. Pour naviguer dans l'arborescence de l'application, on utilise des .
:
.
indique le dossier courant..
indique le dossier père...
indique le dossier père du dossier pèrePrenons pour exemple le dossier exemples/chemins/
. Il comporte deux packages package1
et package2
, comprenant chacun deux modules module1
et module2
.
| package1/
| |-- __init__.py
| |-- module1.py
| |-- module2.py
| package2/
| |-- __init__.py
| |-- module1.py
| |-- module2.py
| run.py
module2
de package2
depuis run.py
, il suffit de mettre from package2 import module2
.module2
de package2
, pour accéder à Module1()
de module1
de package2
, il faut indiquer que l'on lit dans le répertoire courant, soit from .module1 import Module1
module2
de package2
, pour accéder à module1
de package1
, il faut d'abord remonter au répertoire père pour redescendre ensuite dans le package1
: from ..package1 import module1
Il est temps de créer une première application et de la faire tourner sur notre serveur de développement dans l'environnement virtuel. Pour créer une application fonctionnelle avec Flask, il suffit de quelques
lignes dans le fichier run.py
.
# Code/Seance1/base/run.py
from flask import Flask
app = Flask(__name__)
app.run()
python run.py
app
.__name__
, l'application prend le nom du module courantCes deux lignes suffisent à créer une instance de Flask. Lancer app.run()
permet d'exécuter l'application et de la faire tourner sur notre serveur local.
Cette application est très basique, et il vaut mieux la structurer correctement dès le début, en préparant modules et packages. C'est le package Code/seance1/structure
| app/
| |-- __init__.py
| |-- app.py
|nom_application.py
app.py
, dans notre package.nom_application.py
est un script Python de haut-niveau (il est le seul à ce niveau) qui permet d'indiquer où se trouve l'instance de Flask pour notre application, et de lancer l'application. if __name__ == "__main__"
'assure que la méthode run()
est appelée uniquement quand app.py
est exécuté comme le programme principal. Cette méthode ne sera pas appelée si app.py
est appelé dans un autre module.# pour lancer l'application, il faut désormais exécuter la commande suivante dans le terminal
python mon_application.py
Le serveur doit se lancer correctement, en indiquant son port d'exécution sur un hôte local. Mais il n'y a pas encore d'URL sur notre application. Ce sera le but de la Séance2.
Quand on lance le serveur, on remarque une ligne Debug mode: on
. Si l'on souhaite changer cette variable, il nous faut modifier une variable d'environnement (DEBUG) sur laquelle s'appuie Flask. Le mode DEBUG se définit lors du lancement de app.run()
. Il serait donc tentant de mettre app.run(debug=True)
. Seulement, cette solution n'est pas idéale quand on partage son code, et surtout, quand nous définirons plus tard des mots de passe, il nous faudra variabiliser notre application. Autant entamer la variabilisation dès maintenant.
Pour cela, il nous faut plusieurs fichiers:
.env
à la racine qui va contenir toutes les valeurs de nos variables. Ce fichier ne doit jamais être présent dans un dépôt Git, ni transmis, il est propre à chacun et à chaque machine.config.py
dans app/
, qui contiendra une classe Config()
qui ira chercher les variables dans le fichier .env
Commençons par .env
, ce sont des paires clé=valeur, où la clé est le nom de notre varibale.
# .env
DEBUG=True
Passons ensuite à config.py
. Il est nécessaire d'installer
python-dotenv
avec pip install python-dotenv
.
# Code/Seance1/structure/app/config.py
import dotenv
import os
# on récupère le chemin actuel de l'application
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# on charge les varibales de .env
dotenv.load_dotenv(os.path.join(BASE_DIR, '.env'))
class Config():
DEBUG = os.environ.get("DEBUG")
# Code/Seance1/structure/app/app.py
from .config import Config
...
app.config.from_object(Config)
Dorénavant, les variables d'environnement sont accessibles dans un dictionnaire via app.config["nom_variable"]
. On peut donc maintenant lancer l'application avec app.run(app.config["DEBUG"])